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

+61 -11
View File
@@ -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);