diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index 82c21c8..29b5250 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -263,11 +263,13 @@ GameScene::GameScene(Config config) // Les surfaces de les entitats es carreguen al Resource::Cache aquí. entity_manager_.loadFromLevel(config_.map_yaml); - // Subscripcions bàsiques al bus del nivell: de moment només logs, sense - // tocar ni la vida del jugador ni l'estat de la escena. Serà la base - // sobre la qual cablejar HUD i fi de nivell més endavant. - entity_manager_.getBus().subscribe("level_complete", [](const Event& ev) { + // Subscripcions bàsiques al bus del nivell. `level_complete` també + // dispara la transició a ROOM_OUTRO si encara estem a PLAYING; les + // re-emissions es filtren per estat per no re-entrar mai des d'una + // transició ja en marxa. + entity_manager_.getBus().subscribe("level_complete", [this](const Event& ev) { std::cout << "[LEVEL COMPLETE] player_id=" << ev.player_id << '\n'; + if (state_ == State::PLAYING) { setState(State::ROOM_OUTRO); } }); entity_manager_.getBus().subscribe("player_hit", [](const Event& ev) { const auto* dmg = std::get_if(&ev.payload); @@ -395,10 +397,64 @@ GameScene::GameScene(Config config) auto pause_surface = Resource::Cache::get().getSurface(PAUSE_FONT_KEY); pause_font_ = std::make_shared(pause_surface, UiConfig::get().placeholderFont().fnt); pause_font_->setDefaultKerning(UiConfig::get().placeholderFont().kerning); + + // Log d'estat inicial: la FSM arrenca a ROOM_INTRO. En aquesta fita + // les transicions són instantànies (sense fades ni text) i el primer + // tick d'`updateRoomTransition` ja saltarà a PLAYING. + std::cout << "[ROOM] state=ROOM_INTRO (initial)" << '\n'; } GameScene::~GameScene() = default; +namespace { + // Nom textual d'un State per als logs de transició. L'enum és privat + // a GameScene, així que la funció ha de viure aquí com a lambda + // local del setState — la deixem com a constexpr que rep l'enum + // intern via auto-deducció a partir del seu valor entrant. + constexpr auto stateName(GameScene::State s) -> const char* { + using S = GameScene::State; + switch (s) { + case S::ROOM_INTRO: + return "ROOM_INTRO"; + case S::PLAYING: + return "PLAYING"; + case S::ROOM_OUTRO: + return "ROOM_OUTRO"; + } + return "?"; + } +} // namespace + +void GameScene::setState(State new_state) { + if (new_state == state_) { return; } + std::cout << "[ROOM] " << stateName(state_) << " -> " << stateName(new_state) << '\n'; + state_ = new_state; + // Els acumuladors de la transició de sala (sub-fase, elapsed) viuran + // aquí als commits següents. De moment no en tenim cap. +} + +void GameScene::updateRoomTransition(int /*delta_ms*/) { + // Implementació mínima: no hi ha sub-fases ni fades encara, així que + // ROOM_INTRO i ROOM_OUTRO són efectivament instantanis. El bucle de + // sala queda demostrat (INTRO → PLAYING; al level_complete OUTRO → + // INTRO → PLAYING) sense efectes visuals. Els commits 3 i 4 + // afegiran timings i sub-fases reals. + if (state_ == State::ROOM_INTRO) { + setState(State::PLAYING); + return; + } + if (state_ == State::ROOM_OUTRO) { + // Placeholder fins a l'itinerari de sales: tornem a la mateixa + // sala. No es reinicia res — porta + sarcòfags continuen a + // l'estat OPEN, com era l'acord. + // TODO(itinerari): seleccionar la pròxima sala (incrementar + // `room_index_`, intercanviar `config_.map_yaml`, recrear + // tilemap/entitats) abans de tornar a ROOM_INTRO. + setState(State::ROOM_INTRO); + return; + } +} + void GameScene::spawnDust(float feet_x_center, float feet_y, bool is_landing) { // Mida del sprite de Dust llegida del seu YAML. Hardcoded per // simplificar — si canvia, només cal tocar dust.yaml + aquesta @@ -516,6 +572,11 @@ void GameScene::update(int delta_ms) { handlePauseInput(); updatePauseTransition(delta_ms); + // Transició de sala: viu en temps real (no escalada per la pausa) + // perquè és UI. Avança avans del bloc de món perquè quan PLAYING + // arrenque ja sigui efectiu en el mateix frame. + updateRoomTransition(delta_ms); + #ifdef _DEBUG // Toggle de l'editor: només si no hi ha cap transició de pausa ni // pausa consolidada. L'editor mateix no consumeix la tecla — la diff --git a/source/game/scenes/game_scene.hpp b/source/game/scenes/game_scene.hpp index f982164..113bee7 100644 --- a/source/game/scenes/game_scene.hpp +++ b/source/game/scenes/game_scene.hpp @@ -38,6 +38,18 @@ class GameScene : public Scene { [[nodiscard]] static auto defaultConfig() -> Config; + // Estat de l'escena dins del cicle d'una sala. La màquina és interna a + // GameScene: SceneManager segueix tractant GameScene com una sola + // escena. ROOM_INTRO i ROOM_OUTRO bloquegen input i render dels + // jugadors; l'EntityManager segueix avançant i el HUD/Notifier també. + // L'enum es declara públic perquè els helpers de log (stateName) i + // tests viuen fora de la classe; el camp `state_` segueix privat. + enum class State : std::uint8_t { + ROOM_INTRO, // Entrada de sala: fade in + text d'entrada. + PLAYING, // Joc normal. + ROOM_OUTRO, // Sortida de sala: text de sortida + fade out. + }; + explicit GameScene(Config config = defaultConfig()); ~GameScene() override; @@ -45,6 +57,18 @@ class GameScene : public Scene { void render() override; private: + // Punt centralitzat per canviar d'estat. Imprimeix la transició i + // reinicialitza els acumuladors de la transició de sala. Cap estat + // s'ha de canviar fora d'aquí. + void setState(State new_state); + + // Avança la transició activa (sub-fases, timings). En aquesta fita + // les transicions són instantànies (sense efectes); les sub-fases + // amb fades i text arribaran als commits següents. S'executa cada + // frame amb delta real (no escalat per la pausa) perquè la + // transició viu en temps de UI. + void updateRoomTransition(int delta_ms); + // Consumeix events d'aquest frame (F11 del KeyConfig "pause" o botó // START del gamepad primari) per disparar les transicions de pausa. // No alterna un flag binari: inicia (o inverteix mid-flight) un @@ -131,6 +155,17 @@ class GameScene : public Scene { UiConfig::CurveFn curve{nullptr}; // Corba activa. } pause_; + // Estat actual de la FSM de sala. S'inicialitza a ROOM_INTRO al ctor + // perquè cada vegada que entres al joc passes per la presentació de + // la sala. La transició a PLAYING l'orquestra `updateRoomTransition`. + State state_{State::ROOM_INTRO}; + + // Índex de sala que es passa com a paràmetre {n} al text d'intro. + // Avui és sempre 1 (no hi ha itinerari de sales encara). Quan + // arribi l'itinerari, és el camp que es modificarà al transicionar + // de ROOM_OUTRO → ROOM_INTRO per saltar a la pròxima. + int room_index_{1}; + #ifdef _DEBUG // Editor modal del tilemap. Mútuament excloent amb la pausa: si la // pausa no està IDLE la tecla de toggle de l'editor s'ignora, i