Files
aee-arcade/source/game/entities/entity_manager.hpp
T

151 lines
7.2 KiB
C++

#pragma once
#include <SDL3/SDL.h>
#include <cstddef>
#include <memory>
#include <string>
#include <vector>
#include "game/entities/event_bus.hpp"
class Entity;
class Sarcophagus;
class Door;
class Player;
// Contenidor i coordinador d'entitats d'un nivell. Responsabilitats:
// 1. `add` registra una entitat, la connecta al bus i manté propietat.
// 2. `update(dt)` propaga el tick a totes.
// 3. Comptador de sarcòfags oberts — quan iguala el total registrat,
// crida `door_->open()` automàticament.
// 4. `notifyPlayerTilePainted(col, row)` — fan-out del hook per a tots
// els sarcòfags (i qualsevol entitat futura que s'hi subscrigui via
// `notifyTilePainted`).
//
// NO és un singleton: es crea una instància per nivell. De moment no
// s'usa encara al GameScene (andamiatge pur).
class EntityManager {
public:
EntityManager();
~EntityManager();
EntityManager(const EntityManager&) = delete;
EntityManager(EntityManager&&) = delete;
auto operator=(const EntityManager&) -> EntityManager& = delete;
auto operator=(EntityManager&&) -> EntityManager& = delete;
// Registre d'una entitat. L'ownership és del manager; retorna
// l'observador per conveniència (no eximeix que el manager la destrueixi).
auto add(std::unique_ptr<Entity> entity) -> Entity*;
// Marca una entitat com a porta principal del nivell. N'hi ha només
// una. Si ja n'hi ha una assignada, la substitueix (útil a tests).
void setDoor(Door* door);
// Marca una entitat com a sarcòfag — si no es passa per `add` amb
// type=sarcophagus no es compta al total. Útil en combinació amb
// `add` quan es fa addSarcophagus(unique_ptr<Sarcophagus>).
auto addSarcophagus(std::unique_ptr<Sarcophagus> sarc) -> Sarcophagus*;
// Tick de totes les entitats.
void update(float delta_time);
// Dibuix de totes les entitats. Sense ordenació per z: la crida dura
// l'ordre d'inserció. GameScene fa el z-order interleaving amb els
// players directament via `renderEntity`; `renderAll` queda com a
// dreçera quan no hi ha players al render (editor, tests).
void renderAll();
// Render d'una única entitat (mateixa lògica de clipping per frame
// que renderAll). Exposat perquè GameScene pugui interleavar entitats
// i players per `getFeetY()` (REND-01). Estàtic perquè la lògica de
// render no depèn de cap estat del manager — ara com ara, l'entitat
// porta la seua surface, animació i pixel_rect.
static void renderEntity(const Entity& entity);
// Accés als punters observacionals a les entitats per a consumidors
// externs que necessiten construir una llista ordenada (z-order).
[[nodiscard]] auto getEntities() const -> std::vector<Entity*>;
// Comprova contacte del(s) jugador(s) amb entitats `collides()=true`.
// Si la caixa base del jugador solapa amb la caixa de col·lisió de
// l'entitat, crida `entity->onPlayerContact(player)`. No fa res si
// la llista està buida. Es crida des de GameScene cada frame, després
// de `update`, perquè la resposta (p.ex. `player_hit`, `level_complete`)
// reflecteixi l'estat del frame actual.
void checkPlayerCollisions(const std::vector<Player*>& players);
// Cerca la primera entitat que (a) sigui decal de sòl
// (`is_floor_decal_=true`), (b) admeti triggers d'àrea
// (`triggers_on_overlap_=true`), i (c) la seva caixa de col·lisió
// solape amb el rect donat. Pensat per a `Player::integrateJump`,
// que la consulta a l'aterratge per detectar caigudes sobre Hole
// (i en el futur pinchos plans, plaques de pressió, charcos...) i
// saltar-se el LANDING_STUN normal. Retorna `nullptr` si no hi ha
// cap match — el caller usa el camí d'aterratge habitual.
[[nodiscard]] auto findFloorTriggerAt(const SDL_FRect& rect) const -> const Entity*;
// Fan-out del hook de pintat: per a cada sarcòfag, cridar
// notifyTilePainted. Les entitats no-sarcòfag ho ignoren per defecte
// (veure Entity::notifyTilePainted). Invocat com a reacció a
// `tile_painted` al bus (re-publicat pel PaintLayer un cop filtrat).
void notifyPlayerTilePainted(int col, int row);
// Carrega els spawns d'un YAML de nivell i els afegeix al manager:
// 1. Llegeix el bloc `entities:` via EntityLoader.
// 2. Per cada spawn: crea via factory, carrega la seva surface al
// Resource::Cache, calcula pixel_rect_ segons el tipus
// (sarc: Map::getSarcRect; altres: col/row del tilemap) i l'afegeix.
// 3. Loguea `Entities: N loaded` per facilitar el smoke d'arrencada.
// Requereix que Map::loadFrom ja s'hagi cridat (necessita
// `Map::tile_size`, `tilemap_origin_*` i `getSarcRect`).
void loadFromLevel(const std::string& level_yaml_path);
// Reset complet del nivell: destrueix totes les entitats, reseteja
// els comptadors interns (sarcòfags oberts, total, exited, flag de
// level_complete) i torna a cridar `loadFromLevel` per regenerar les
// entitats des del YAML. El bus i les seues subscripcions
// sobreviuen — això inclou les subscripcions externes (paint_layer_,
// hud_, players, GameScene). Cridat des de GameScene a la transició
// ROOM_OUTRO → ROOM_INTRO. `setExpectedPlayerCount` cal cridar-lo de
// nou després si vols agregació de level_complete al nou cicle.
void reload(const std::string& level_yaml_path);
// Fixa quants jugadors actius es consideren al recompte d'EXITED.
// Cridat per GameScene un cop instanciats els jugadors. Si manca,
// `expected_players_ = 0` i mai s'emet `level_complete` automàticament.
// Cridar-lo de nou al respawn (futur reload) reseteja el comptador.
void setExpectedPlayerCount(int n);
[[nodiscard]] auto getBus() -> EventBus& { return bus_; }
[[nodiscard]] auto entityCount() const -> std::size_t { return entities_.size(); }
[[nodiscard]] auto sarcophagusCount() const -> std::size_t { return total_sarcs_; }
[[nodiscard]] auto openedSarcophagusCount() const -> std::size_t { return opened_sarcs_; }
[[nodiscard]] auto exitedPlayerCount() const -> std::size_t { return exited_players_; }
// Força l'obertura de la porta sense que els sarcòfags estiguin
// oberts. Pensat com a hook de debug per a la GameScene: el flux
// posterior (creuar la porta → `player_exited` → `level_complete`
// → OUTRO) segueix exactament igual perquè depèn de Door, no de
// sarcs. No-op si la porta ja està oberta o si el nivell no en té.
void forceOpenDoor();
private:
EventBus bus_;
std::vector<std::unique_ptr<Entity>> entities_;
Door* door_{nullptr};
std::size_t total_sarcs_{0};
std::size_t opened_sarcs_{0};
// Recompte d'`player_exited`: incrementat a la subscripció al bus,
// resetejat per `setExpectedPlayerCount`. Quan `exited_players_` arriba
// a `expected_players_` (i tots dos > 0), publica `level_complete`
// amb `player_id = -1` (event d'agregat, no atribuible a un jugador).
int expected_players_{0};
int exited_players_{0};
bool level_complete_emitted_{false};
EventBus::SubId sarc_sub_id_{0};
EventBus::SubId tile_sub_id_{0};
EventBus::SubId exit_sub_id_{0};
};