132 lines
6.1 KiB
C++
132 lines
6.1 KiB
C++
#pragma once
|
|
|
|
#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);
|
|
|
|
// 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_; }
|
|
|
|
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};
|
|
};
|