diff --git a/source/game/scenes/game_scene.cpp b/source/game/scenes/game_scene.cpp index 9528bba..bc7e04e 100644 --- a/source/game/scenes/game_scene.cpp +++ b/source/game/scenes/game_scene.cpp @@ -524,14 +524,25 @@ void GameScene::updateRoomTransition(int delta_ms) { 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. La - // implementació de les sub-fases d'OUTRO arribarà al commit 4. - setState(State::ROOM_INTRO); + const std::array DURS = { + transition_config_.outro_text_enter_ms, + transition_config_.outro_text_hold_ms, + transition_config_.outro_text_exit_ms, + transition_config_.outro_fade_out_ms, + }; + while (transition_.phase < static_cast(DURS.size()) && transition_.elapsed_ms >= DURS[transition_.phase]) { + transition_.elapsed_ms -= DURS[transition_.phase]; + ++transition_.phase; + } + if (transition_.phase >= static_cast(DURS.size())) { + // 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; } } @@ -551,43 +562,77 @@ auto GameScene::formatRoomTitle(int n) -> std::string { } void GameScene::renderRoomFade() { - // Fade negre opac → transparent durant ROOM_INTRO/fase 0. Pintat - // sobre el món un cop els actors ja s'han dibuixat. Al commit 4 - // s'afegirà el fade out simètric per a ROOM_OUTRO/fase final. - if (state_ != State::ROOM_INTRO || transition_.phase != 0) { return; } - const int DUR = transition_config_.intro_fade_in_ms; - const float T = (DUR > 0) - ? std::clamp(static_cast(transition_.elapsed_ms) / static_cast(DUR), 0.0F, 1.0F) + // Fade negre sobre el món, pintat un cop els actors ja estan al + // canvas i abans del HUD. Dos casos: + // ROOM_INTRO/fase 0: 255 → 0 (entrada de sala, món emergent) + // ROOM_OUTRO/fase 3: 0 → 255 (sortida de sala, món s'apaga) + int dur = 0; + float t = 0.0F; + bool fade_in = false; + if (state_ == State::ROOM_INTRO && transition_.phase == 0) { + dur = transition_config_.intro_fade_in_ms; + fade_in = true; + } else if (state_ == State::ROOM_OUTRO && transition_.phase == 3) { + dur = transition_config_.outro_fade_out_ms; + fade_in = false; + } else { + return; + } + t = (dur > 0) + ? std::clamp(static_cast(transition_.elapsed_ms) / static_cast(dur), 0.0F, 1.0F) : 1.0F; - const auto ALPHA = static_cast(255.0F * (1.0F - T)); + const float ALPHA_F = fade_in ? (1.0F - t) : t; + const auto ALPHA = static_cast(255.0F * ALPHA_F); if (ALPHA == 0) { return; } Screen::get().blendRect(0, 0, GameCanvas::WIDTH, GameCanvas::HEIGHT, 0x00000000U, ALPHA); } void GameScene::renderRoomText() { - // Text d'entrada de sala. Visible només durant les sub-fases 2-4 - // de ROOM_INTRO (enter, hold, exit). Reusa la font cachejada per - // a la pausa: mateix tipus de lletra i mateix índex de color. - if (state_ != State::ROOM_INTRO || transition_.phase < 2 || transition_.phase > 4) { return; } + // Text de la transició de sala. Reusa la font cachejada per a la + // pausa (mateix tipus de lletra, mateix índex de color), i fa el + // mateix patró esquerra → centre → esquerra que el text de pausa + // però amb les durades pròpies de la transició. + // + // Mapatge per estat (només els estats amb text actiu hi entren): + // ROOM_INTRO: phases 2..4 → enter / hold / exit; text = sala N + // ROOM_OUTRO: phases 0..2 → enter / hold / exit; text = "fase completada" if (!pause_font_) { return; } - const std::string TEXT = formatRoomTitle(room_index_); + std::string text; + int phase_enter = 0; + int phase_exit = 0; + int dur_enter = 0; + int dur_exit = 0; + if (state_ == State::ROOM_INTRO && transition_.phase >= 2 && transition_.phase <= 4) { + text = formatRoomTitle(room_index_); + phase_enter = 2; + phase_exit = 4; + dur_enter = transition_config_.intro_text_enter_ms; + dur_exit = transition_config_.intro_text_exit_ms; + } else if (state_ == State::ROOM_OUTRO && transition_.phase >= 0 && transition_.phase <= 2) { + text = Locale::get().text("game.room_complete"); + phase_enter = 0; + phase_exit = 2; + dur_enter = transition_config_.outro_text_enter_ms; + dur_exit = transition_config_.outro_text_exit_ms; + } else { + return; + } + constexpr int KERNING = 0; - const int W = pause_font_->length(TEXT, KERNING); + const int W = pause_font_->length(text, KERNING); const int OFF_LEFT = -W; int x = GameCanvas::CENTER_X; - if (transition_.phase == 2) { - const int DUR = transition_config_.intro_text_enter_ms; - const float T = (DUR > 0) - ? std::clamp(static_cast(transition_.elapsed_ms) / static_cast(DUR), 0.0F, 1.0F) + if (transition_.phase == phase_enter) { + const float T = (dur_enter > 0) + ? std::clamp(static_cast(transition_.elapsed_ms) / static_cast(dur_enter), 0.0F, 1.0F) : 1.0F; const auto CURVE = (transition_config_.text_enter_curve != nullptr) ? transition_config_.text_enter_curve : &Easing::linear; x = Easing::lerpInt(OFF_LEFT, GameCanvas::CENTER_X, CURVE(T)); - } else if (transition_.phase == 4) { - const int DUR = transition_config_.intro_text_exit_ms; - const float T = (DUR > 0) - ? std::clamp(static_cast(transition_.elapsed_ms) / static_cast(DUR), 0.0F, 1.0F) + } else if (transition_.phase == phase_exit) { + const float T = (dur_exit > 0) + ? std::clamp(static_cast(transition_.elapsed_ms) / static_cast(dur_exit), 0.0F, 1.0F) : 1.0F; const auto CURVE = (transition_config_.text_exit_curve != nullptr) ? transition_config_.text_exit_curve : &Easing::linear; x = Easing::lerpInt(GameCanvas::CENTER_X, OFF_LEFT, CURVE(T)); @@ -599,7 +644,7 @@ void GameScene::renderRoomText() { pause_font_->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG | Text::STROKE4_FLAG, x, Y, - TEXT, + text, KERNING, UiConfig::get().notificationText(), STROKE_DIST,