#pragma once #include #include #include #include #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*; // 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). auto addSarcophagus(std::unique_ptr 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; // 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& players); // 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); [[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_; } private: EventBus bus_; std::vector> entities_; Door* door_{nullptr}; std::size_t total_sarcs_{0}; std::size_t opened_sarcs_{0}; EventBus::SubId sarc_sub_id_{0}; EventBus::SubId tile_sub_id_{0}; };