diff --git a/source/game/entities/entity_manager.cpp b/source/game/entities/entity_manager.cpp index 01125fd..8cf2371 100644 --- a/source/game/entities/entity_manager.cpp +++ b/source/game/entities/entity_manager.cpp @@ -32,11 +32,37 @@ EntityManager::EntityManager() { const auto* tile = std::get_if(&ev.payload); if (tile != nullptr) { notifyPlayerTilePainted(tile->col, tile->row); } }); + + // Subscripció a `player_exited` (publicat per Door en travessar la + // porta oberta amb un jugador): comptem una vegada per jugador i, + // quan tots els jugadors actius han eixit, emetem `level_complete` + // — l'event d'agregat que GameScene escolta per disparar la + // transició FSM a ROOM_OUTRO. La idempotència de Door (check + // `isExited`) garanteix que cada jugador suma com a màxim un cop. + exit_sub_id_ = bus_.subscribe("player_exited", [this](const Event& /*ev*/) { + ++exited_players_; + if (level_complete_emitted_) { return; } + if (expected_players_ > 0 && exited_players_ >= expected_players_) { + level_complete_emitted_ = true; + bus_.publish(Event{ + .name = "level_complete", + .payload = std::monostate{}, + .player_id = -1, + }); + } + }); } EntityManager::~EntityManager() { bus_.unsubscribe(sarc_sub_id_); bus_.unsubscribe(tile_sub_id_); + bus_.unsubscribe(exit_sub_id_); +} + +void EntityManager::setExpectedPlayerCount(int n) { + expected_players_ = n; + exited_players_ = 0; + level_complete_emitted_ = false; } auto EntityManager::add(std::unique_ptr entity) -> Entity* { diff --git a/source/game/entities/entity_manager.hpp b/source/game/entities/entity_manager.hpp index 000d6fc..da9192a 100644 --- a/source/game/entities/entity_manager.hpp +++ b/source/game/entities/entity_manager.hpp @@ -90,10 +90,17 @@ class EntityManager { // `Map::tile_size`, `tilemap_origin_*` i `getSarcRect`). void loadFromLevel(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_; @@ -101,6 +108,14 @@ class EntityManager { 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}; }; diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index cc9f4f4..179dee4 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -367,6 +367,12 @@ GameScene::GameScene(Config config) players_view_.reserve(players_.size()); std::ranges::transform(players_, std::back_inserter(players_view_), [](const auto& p) { return p.get(); }); for (auto& p : players_) { p->setOtherPlayers(&players_view_); } + + // Comptador d'EXITED a EntityManager: publica `level_complete` quan + // tots els jugadors actius han travessat la porta. Cap setExpectedPlayerCount + // ha d'inicialitzar-se abans del primer `player_exited`; ho fem aquí + // perquè ja sabem el nombre real d'instàncies. + entity_manager_.setExpectedPlayerCount(static_cast(players_.size())); // El snap també empeny lateralment si l'altre jugador ocupa el carril: // passem el mateix factor perquè vaja al mateix ritme que el resolver. for (auto& p : players_) { p->setPushFactor(push_factor_); }