feat(level-manager): salts debug entre sales (només Debug)

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.
This commit is contained in:
JailDesigner committed 2026-04-26 14:59:34 +02:00
1 parent d52cc1c509
commit b147590a39
3 files changed
+78 -15

No files matched your search

+6
View File
@@ -47,6 +47,12 @@ keys:
- id: editor_picker - id: editor_picker
code: "T" code: "T"
desc: "Obri/tanca el tilepicker dins de l'editor" 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 - id: exit
code: "Escape" code: "Escape"
desc: "Eixir (confirmar amb segona pulsació)" desc: "Eixir (confirmar amb segona pulsació)"
+61 -11
View File
@@ -500,6 +500,21 @@ void GameScene::advanceToNextRoom() {
} }
room_index_ = mgr.currentRoom(); room_index_ = mgr.currentRoom();
loadRoom(mgr.currentPath()); 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() { void GameScene::triggerActivePlayerSpawn() {
@@ -528,7 +543,6 @@ void GameScene::triggerActivePlayerSpawn() {
void GameScene::setState(RoomFsm::State new_state) { void GameScene::setState(RoomFsm::State new_state) {
if (new_state == state_) { return; } if (new_state == state_) { return; }
std::cout << "[ROOM] " << RoomFsm::stateName(state_) << " -> " << RoomFsm::stateName(new_state) << '\n'; std::cout << "[ROOM] " << RoomFsm::stateName(state_) << " -> " << RoomFsm::stateName(new_state) << '\n';
const auto PREV = state_;
state_ = new_state; state_ = new_state;
transition_ = {}; transition_ = {};
// En entrar a un nou cicle d'INTRO, resetegem el timer i el flag de // 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; room_intro_elapsed_ms_ = 0;
players_spawned_in_intro_ = false; players_spawned_in_intro_ = false;
} }
// En tornar a ROOM_INTRO des de ROOM_OUTRO ja hi ha itinerari: el // L'avanç de sala (OUTRO → INTRO) NO viu ací: el dispara
// jugador acaba de creuar la porta, així que avancem al següent // `updateRoomTransition` quan la FSM detecta el final de l'OUTRO,
// nivell del LevelManager i carreguem la nova sala. Si era l'última, // cridant directament `advanceToNextRoom`. Això manté `setState`
// `advanceToNextRoom` fa loop a la primera. Per a la transició // com una transició pura, sense efectes col·laterals que
// inicial (PREV també és ROOM_INTRO, impossible amb el guard de dalt) // recarreguen sales — necessari per als salts debug que també
// o vinguda d'un altre estat, no fem res. // entren a INTRO sense passar per OUTRO.
if (new_state == RoomFsm::State::ROOM_INTRO && PREV == RoomFsm::State::ROOM_OUTRO) {
advanceToNextRoom();
}
} }
void GameScene::updateRoomTransition(int delta_ms) { 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); 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() { void GameScene::renderRoomFade() {
@@ -769,6 +791,20 @@ namespace {
return ev.type == SDL_EVENT_KEY_DOWN && ev.key.scancode == SC; 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 } // namespace
#endif #endif
@@ -788,6 +824,20 @@ void GameScene::update(int delta_ms) {
if (pause_.state == PauseState::IDLE && detectEditorToggle()) { if (pause_.state == PauseState::IDLE && detectEditorToggle()) {
editor_.toggle(tilemap_); 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 #endif
GlobalInputs::update(delta_ms); GlobalInputs::update(delta_ms);
+11 -4
View File
@@ -217,12 +217,19 @@ class GameScene : public Scene {
void loadRoom(const std::string& yaml_path); void loadRoom(const std::string& yaml_path);
// Demana al LevelManager el següent nivell i el carrega via // Demana al LevelManager el següent nivell i el carrega via
// `loadRoom`. Si ja era a l'última entrada, fa loop a la primera // `loadRoom`, deixant la FSM al començament d'un nou cicle d'INTRO.
// (política temporal mentre no hi haja pantalla de fi de joc). // Si ja era a l'última entrada, fa loop a la primera (política
// Cridat des de `setState(ROOM_INTRO)` quan la transició anterior // temporal mentre no hi haja pantalla de fi de joc). Cridat des de
// era OUTRO — és la conseqüència de creuar la porta. // `updateRoomTransition` quan l'OUTRO arriba a la fase final.
void advanceToNextRoom(); 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 #ifdef _DEBUG
// Editor modal del tilemap. Mútuament excloent amb la pausa: si la // 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 // pausa no està IDLE la tecla de toggle de l'editor s'ignora, i