#include "scenes/intro_scene.hpp" #include "core/jail/jdraw8.hpp" #include "core/jail/jinput.hpp" #include "game/info.hpp" #include "scenes/scene_utils.hpp" namespace { // Timings idèntics als del vell `doIntro()`: el JG_SetUpdateTicks(1000) // inicial, (100) per a les 3 primeres lletres (J, A, I), (200) per a // "JAIL" i el seu clear, (100) per a les 4 lletres centrals // (G, A, M, E) i (200) per a la resta fins al cicle de paleta. constexpr int INITIAL_MS = 1000; constexpr int PALETTE_CYCLE_STEP_MS = 20; constexpr int PALETTE_CYCLE_STEPS = 256; constexpr int FINAL_WAIT_MS = 200; // Un pas del revelat. Dos blits configurables (cos del wordmark + avió) // més una variant per al wordmark sencer i un flag de ClearScreen previ. struct RevealStep { int duration_ms; int body_w; // amplada del blit body (43,78) ← (43,155, body_w, 45); 0 si s'usa wordmark int plane_x; // x del blit de l'avió (274,155, 27×45); -1 = no avió bool clear; // fa ClearScreen(0) abans dels blits bool wordmark; // usa drawWordmark() en lloc del blit body (wordmark complet) }; constexpr RevealStep REVEAL_STEPS[] = { {100, 27, 68, false, false}, // J {100, 53, 96, false, false}, // JA {100, 66, 109, false, false}, // JAI {200, 92, 136, false, false}, // JAIL {200, 92, -1, true, false}, // JAIL (clear, sense avió — parpelleig) {100, 118, 160, false, false}, // JAILG {100, 145, 188, false, false}, // JAILGA {100, 178, 221, false, false}, // JAILGAM {100, 205, 248, false, false}, // JAILGAME {200, 0, 274, false, true}, // JAILGAMES (wordmark complet) + avió {200, 0, -1, true, true}, // JAILGAMES (clear, sense avió) {200, 0, 274, false, true}, // JAILGAMES + avió (sense clear) {200, 0, -1, true, true}, // JAILGAMES (clear, sense avió) {200, 0, 274, false, true}, // JAILGAMES + avió (sense clear) {200, 0, -1, true, true}, // JAILGAMES (clear, sense avió) }; constexpr int REVEAL_COUNT = sizeof(REVEAL_STEPS) / sizeof(REVEAL_STEPS[0]); // Branca `!use_new_logo` del drawIntroWordmark de modulesequence.cpp: // blit únic del wordmark "JAILGAMES" complet (231×45 al destí 43,78). // IntroScene només s'activa quan use_new_logo == false, així que la // branca use_new_logo d'aquell helper aquí no es necessita. void drawWordmark(JD8_Surface gfx) { JD8_Blit(43, 78, gfx, 43, 155, 231, 45); } } // namespace namespace scenes { IntroScene::IntroScene() = default; IntroScene::~IntroScene() { // No alliberem `pal_`: JD8_SetScreenPalette n'ha pres ownership i el // proper SetScreenPalette / FadeToPal la lliurarà. Alliberar-la ací // provocaria double free. } void IntroScene::onEnter() { playMusic("music/menu.ogg"); gfx_ = SurfaceHandle("gfx/logo.gif"); pal_ = JD8_LoadPalette("gfx/logo.gif"); JD8_SetScreenPalette(pal_); JD8_ClearScreen(0); phase_ = Phase::InitialWait; phase_acc_ms_ = 0; reveal_index_ = 0; palette_step_ = 0; } void IntroScene::render() { switch (phase_) { case Phase::InitialWait: JD8_ClearScreen(0); break; case Phase::Reveal: { const RevealStep& s = REVEAL_STEPS[reveal_index_]; if (s.clear) JD8_ClearScreen(0); if (s.wordmark) { drawWordmark(gfx_); } else if (s.body_w > 0) { JD8_Blit(43, 78, gfx_, 43, 155, s.body_w, 45); } if (s.plane_x >= 0) { JD8_Blit(s.plane_x, 78, gfx_, 274, 155, 27, 45); } break; } case Phase::PaletteCycle: case Phase::FinalWait: // Wordmark complet fix mentre cicla la paleta — l'últim // pas del revelat (PAS 15) deixa la pantalla en aquest mateix // estat, i el vell doIntro no redibuixava durant el cicle. JD8_ClearScreen(0); drawWordmark(gfx_); break; case Phase::Sprites: case Phase::Done: break; } } void IntroScene::advancePaletteCycle() { // Replica exacta del cicle del doIntro vell sobre pal[16..31] — el // grup del verd brillant del logo. Index 17 s'acosta a blanc mentre // els altres convergeixen cap al mateix gris mitjà. for (int i = 16; i < 32; i++) { if (i == 17) { if (pal_[i].r < 255) pal_[i].r++; if (pal_[i].g < 255) pal_[i].g++; if (pal_[i].b < 255) pal_[i].b++; } if (pal_[i].b < pal_[i].g) pal_[i].b++; if (pal_[i].b > pal_[i].g) pal_[i].b--; if (pal_[i].r < pal_[i].g) pal_[i].r++; if (pal_[i].r > pal_[i].g) pal_[i].r--; } } void IntroScene::tick(int delta_ms) { // Qualsevol tecla durant revelat/paleta salta TOTA la intro // (inclou saltar la fase de sprites). Durant Sprites deixem que // la sub-escena gestione el seu propi skip internament, que a més // respecta la fase "final" no skippable de la variant 0. if (phase_ != Phase::Sprites && phase_ != Phase::Done && JI_AnyKey()) { info::ctx.num_piramide = 0; phase_ = Phase::Done; return; } switch (phase_) { case Phase::InitialWait: phase_acc_ms_ += delta_ms; render(); if (phase_acc_ms_ >= INITIAL_MS) { phase_ = Phase::Reveal; phase_acc_ms_ = 0; reveal_index_ = 0; } break; case Phase::Reveal: phase_acc_ms_ += delta_ms; render(); if (phase_acc_ms_ >= REVEAL_STEPS[reveal_index_].duration_ms) { phase_acc_ms_ = 0; ++reveal_index_; if (reveal_index_ >= REVEAL_COUNT) { phase_ = Phase::PaletteCycle; } } break; case Phase::PaletteCycle: phase_acc_ms_ += delta_ms; // Avancem tants passos com permet el delta, per evitar // saltar-ne si el frame ha vingut lent. while (phase_acc_ms_ >= PALETTE_CYCLE_STEP_MS && palette_step_ < PALETTE_CYCLE_STEPS) { phase_acc_ms_ -= PALETTE_CYCLE_STEP_MS; advancePaletteCycle(); ++palette_step_; } render(); if (palette_step_ >= PALETTE_CYCLE_STEPS) { phase_ = Phase::FinalWait; phase_acc_ms_ = 0; } break; case Phase::FinalWait: phase_acc_ms_ += delta_ms; render(); if (phase_acc_ms_ >= FINAL_WAIT_MS) { phase_ = Phase::Sprites; } break; case Phase::Sprites: // Sub-escena construïda al vol al primer tick d'aquesta fase. // Transferim el gfx_ per move — la sub-escena se n'ocupa // fins que es destruix. Una vegada feta, els ticks delegats // avancen l'animació dels sprites. if (!sprites_scene_) { sprites_scene_ = std::make_unique(std::move(gfx_)); sprites_scene_->onEnter(); } sprites_scene_->tick(delta_ms); if (sprites_scene_->done()) { // Equivalent al vell `Go()` post-switch: passem al menú. // Sense açò el while del fiber tornaria a crear IntroScene // infinitament amb num_piramide encara a 255. info::ctx.num_piramide = 0; phase_ = Phase::Done; } break; case Phase::Done: break; } } } // namespace scenes