From b147590a39f23e5171c35f4790e5aa808a31b830 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sun, 26 Apr 2026 14:59:34 +0200 Subject: [PATCH] =?UTF-8?q?feat(level-manager):=20salts=20debug=20entre=20?= =?UTF-8?q?sales=20(nom=C3=A9s=20Debug)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tecles [ i ] al keys.yaml (prev_room / next_room) que mouen LevelManager::setIndex en ±1 i recarreguen la sala via loadRoom + enterRoomIntro. Ignorades si la pausa està activa o l'editor obert (qualsevol dels dos invalida l'estat editat). Refactor de la FSM al mateix pas: l'avanç de sala (LevelManager + loadRoom) ja no viu dins setState — el dispara updateRoomTransition quan detecta el final de l'OUTRO via advanceToNextRoom. setState torna a ser una transició pura, condició necessària perquè el salt debug puga entrar a INTRO sense disparar un advance addicional. Nou helper enterRoomIntro() reusable per la sortida de l'OUTRO i pels salts; idempotent quan ja s'estava a INTRO. --- data/input/keys.yaml | 6 +++ source/game/scenes/game_scene.cpp | 72 ++++++++++++++++++++++++++----- source/game/scenes/game_scene.hpp | 15 +++++-- 3 files changed, 78 insertions(+), 15 deletions(-) diff --git a/data/input/keys.yaml b/data/input/keys.yaml index 147eb2e..0c889e4 100644 --- a/data/input/keys.yaml +++ b/data/input/keys.yaml @@ -47,6 +47,12 @@ keys: - id: editor_picker code: "T" desc: "Obri/tanca el tilepicker dins de l'editor" + - id: prev_room + code: "[" + desc: "Salta a la sala anterior del levels.yaml (només Debug)" + - id: next_room + code: "]" + desc: "Salta a la sala següent del levels.yaml (només Debug)" - id: exit code: "Escape" desc: "Eixir (confirmar amb segona pulsació)" diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index 614607f..557c4b1 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -500,6 +500,21 @@ void GameScene::advanceToNextRoom() { } room_index_ = mgr.currentRoom(); loadRoom(mgr.currentPath()); + enterRoomIntro(); +} + +void GameScene::enterRoomIntro() { + // Anotem la transició al log si veníem d'un altre estat; si ja + // estàvem a INTRO (cas típic d'un salt debug) també queda ben clar. + if (state_ != RoomFsm::State::ROOM_INTRO) { + std::cout << "[ROOM] " << RoomFsm::stateName(state_) << " -> ROOM_INTRO\n"; + } else { + std::cout << "[ROOM] ROOM_INTRO restart (cinemàtica reiniciada)\n"; + } + state_ = RoomFsm::State::ROOM_INTRO; + transition_ = {}; + room_intro_elapsed_ms_ = 0; + players_spawned_in_intro_ = false; } void GameScene::triggerActivePlayerSpawn() { @@ -528,7 +543,6 @@ void GameScene::triggerActivePlayerSpawn() { void GameScene::setState(RoomFsm::State new_state) { if (new_state == state_) { return; } std::cout << "[ROOM] " << RoomFsm::stateName(state_) << " -> " << RoomFsm::stateName(new_state) << '\n'; - const auto PREV = state_; state_ = new_state; transition_ = {}; // En entrar a un nou cicle d'INTRO, resetegem el timer i el flag de @@ -537,15 +551,12 @@ void GameScene::setState(RoomFsm::State new_state) { room_intro_elapsed_ms_ = 0; players_spawned_in_intro_ = false; } - // En tornar a ROOM_INTRO des de ROOM_OUTRO ja hi ha itinerari: el - // jugador acaba de creuar la porta, així que avancem al següent - // nivell del LevelManager i carreguem la nova sala. Si era l'última, - // `advanceToNextRoom` fa loop a la primera. Per a la transició - // inicial (PREV també és ROOM_INTRO, impossible amb el guard de dalt) - // o vinguda d'un altre estat, no fem res. - if (new_state == RoomFsm::State::ROOM_INTRO && PREV == RoomFsm::State::ROOM_OUTRO) { - advanceToNextRoom(); - } + // L'avanç de sala (OUTRO → INTRO) NO viu ací: el dispara + // `updateRoomTransition` quan la FSM detecta el final de l'OUTRO, + // cridant directament `advanceToNextRoom`. Això manté `setState` + // com una transició pura, sense efectes col·laterals que + // recarreguen sales — necessari per als salts debug que també + // entren a INTRO sense passar per OUTRO. } void GameScene::updateRoomTransition(int delta_ms) { @@ -561,7 +572,18 @@ void GameScene::updateRoomTransition(int delta_ms) { } const auto NEXT = RoomFsm::advance(state_, transition_, transition_config_, delta_ms); - if (NEXT != state_) { setState(NEXT); } + if (NEXT != state_) { + // El final de l'OUTRO no es resol amb un setState normal: cal + // demanar al LevelManager el següent nivell i carregar-lo abans + // d'entrar a INTRO. `advanceToNextRoom` fa tot el bloc i acaba + // amb la FSM a ROOM_INTRO (via `enterRoomIntro`), així que no + // fa falta el setState d'aquesta branca. + if (NEXT == RoomFsm::State::ROOM_INTRO && state_ == RoomFsm::State::ROOM_OUTRO) { + advanceToNextRoom(); + } else { + setState(NEXT); + } + } } void GameScene::renderRoomFade() { @@ -769,6 +791,20 @@ namespace { return ev.type == SDL_EVENT_KEY_DOWN && ev.key.scancode == SC; }); } + + // Retorna -1 si s'ha premut prev_room, +1 si next_room, 0 si cap. + // Si tots dos arriben en el mateix frame (poc probable amb teclat), + // prev té prioritat — l'usuari sempre pot tornar a prémer next. + auto detectRoomJump() -> int { + const SDL_Scancode PREV_SC = KeyConfig::get().scancode("prev_room"); + const SDL_Scancode NEXT_SC = KeyConfig::get().scancode("next_room"); + for (const auto& ev : EventBuffer::events) { + if (ev.type != SDL_EVENT_KEY_DOWN) { continue; } + if (PREV_SC != SDL_SCANCODE_UNKNOWN && ev.key.scancode == PREV_SC) { return -1; } + if (NEXT_SC != SDL_SCANCODE_UNKNOWN && ev.key.scancode == NEXT_SC) { return +1; } + } + return 0; + } } // namespace #endif @@ -788,6 +824,20 @@ void GameScene::update(int delta_ms) { if (pause_.state == PauseState::IDLE && detectEditorToggle()) { editor_.toggle(tilemap_); } + + // Salts debug entre sales (`[` / `]`). Mútuament excloents amb la + // pausa i amb l'editor: si qualsevol està actiu, ignorem la tecla + // perquè el salt recarrega entitats i invalidaria l'estat editat. + if (pause_.state == PauseState::IDLE && !editor_.active()) { + const int DELTA = detectRoomJump(); + if (DELTA != 0) { + auto& mgr = Levels::LevelManager::get(); + mgr.setIndex(mgr.index() + DELTA); + room_index_ = mgr.currentRoom(); + loadRoom(mgr.currentPath()); + enterRoomIntro(); + } + } #endif GlobalInputs::update(delta_ms); diff --git a/source/game/scenes/game_scene.hpp b/source/game/scenes/game_scene.hpp index 63deb0b..9dfc5c6 100644 --- a/source/game/scenes/game_scene.hpp +++ b/source/game/scenes/game_scene.hpp @@ -217,12 +217,19 @@ class GameScene : public Scene { void loadRoom(const std::string& yaml_path); // Demana al LevelManager el següent nivell i el carrega via - // `loadRoom`. Si ja era a l'última entrada, fa loop a la primera - // (política temporal mentre no hi haja pantalla de fi de joc). - // Cridat des de `setState(ROOM_INTRO)` quan la transició anterior - // era OUTRO — és la conseqüència de creuar la porta. + // `loadRoom`, deixant la FSM al començament d'un nou cicle d'INTRO. + // Si ja era a l'última entrada, fa loop a la primera (política + // temporal mentre no hi haja pantalla de fi de joc). Cridat des de + // `updateRoomTransition` quan l'OUTRO arriba a la fase final. void advanceToNextRoom(); + // Inicia (o reinicia) el cicle d'entrada de sala: posa l'estat a + // ROOM_INTRO i reseteja `transition_`, `room_intro_elapsed_ms_` i el + // flag d'spawn aeri. Idempotent quan ja s'estava a INTRO (els + // timers tornen a començar des de zero — útil per als salts debug + // que recarreguen la sala estant ja en INTRO). + void enterRoomIntro(); + #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