#include "scenes/intro_new_logo_scene.hpp" #include #include "core/jail/jdraw8.hpp" #include "core/jail/jinput.hpp" #include "game/info.hpp" #include "game/modulesequence.hpp" #include "scenes/scene_utils.hpp" namespace { // Coordenades mesurades del wordmark "Jailgames" dins logo/logo_new.gif. // Idèntiques a les del doIntroNewLogo vell — si canvies el logo, aquí i // al GIF són els únics llocs a tocar. constexpr int LOGO_SRC_X = 60; constexpr int LOGO_SRC_Y = 158; constexpr int LOGO_DST_Y = 78; constexpr int LOGO_HEIGHT = 28; constexpr int LETTER_WIDTHS[9] = {16, 39, 50, 69, 92, 115, 146, 169, 188}; constexpr int CURSOR_X[9] = {77, 100, 111, 130, 153, 176, 207, 230, 249}; constexpr int CURSOR_W = 12; constexpr int CURSOR_H = 3; constexpr int CURSOR_Y = LOGO_DST_Y + LOGO_HEIGHT - CURSOR_H; // y = 103 constexpr Uint8 CURSOR_COLOR = 17; // mateix index verd que les lletres // Timings (ms) — idèntics als de doIntroNewLogo vell. constexpr int INITIAL_MS = 1000; constexpr int REVEAL_FRAME_MS = 150; constexpr int FULL_LOGO_MS = 200; constexpr int PALETTE_CYCLE_STEP_MS = 20; constexpr int FINAL_WAIT_MS = 20; constexpr int PALETTE_CYCLE_STEPS = 256; } // namespace namespace scenes { IntroNewLogoScene::IntroNewLogoScene() = default; IntroNewLogoScene::~IntroNewLogoScene() { // No alliberem `pal_`: JD8_SetScreenPalette n'ha pres ownership i // el proper SetScreenPalette / FadeToPal el lliurarà. Alliberar-lo // ací provocaria double free. } void IntroNewLogoScene::onEnter() { playMusic("00000003.ogg"); gfx_ = SurfaceHandle("logo/logo_new.gif"); pal_ = JD8_LoadPalette("logo/logo_new.gif"); JD8_SetScreenPalette(pal_); // Surface auxiliar omplida amb el color del cursor — permet pintar // el "subratllat" amb un blit normal. cursor_surf_.adopt(JD8_NewSurface()); std::memset(cursor_surf_.get(), CURSOR_COLOR, 64000); JD8_ClearScreen(0); phase_ = Phase::Initial; phase_acc_ms_ = 0; reveal_letter_ = 0; reveal_cursor_visible_ = true; palette_step_ = 0; } void IntroNewLogoScene::render() { switch (phase_) { case Phase::Initial: JD8_ClearScreen(0); break; case Phase::Revealing: { JD8_ClearScreen(0); JD8_Blit(LOGO_SRC_X, LOGO_DST_Y, gfx_, LOGO_SRC_X, LOGO_SRC_Y, LETTER_WIDTHS[reveal_letter_], LOGO_HEIGHT); if (reveal_cursor_visible_) { JD8_Blit(CURSOR_X[reveal_letter_], CURSOR_Y, cursor_surf_, 0, 0, CURSOR_W, CURSOR_H); } break; } case Phase::FullLogoFlash: JD8_ClearScreen(0); JD8_Blit(LOGO_SRC_X, LOGO_DST_Y, gfx_, LOGO_SRC_X, LOGO_SRC_Y, LETTER_WIDTHS[8], LOGO_HEIGHT); JD8_Blit(CURSOR_X[8], CURSOR_Y, cursor_surf_, 0, 0, CURSOR_W, CURSOR_H); break; case Phase::PaletteCycle: case Phase::FinalWait: // Logo complet sense cursor — els pixels del cursor // ciclarien de color durant el cicle de paleta. JD8_ClearScreen(0); JD8_Blit(LOGO_SRC_X, LOGO_DST_Y, gfx_, LOGO_SRC_X, LOGO_SRC_Y, LETTER_WIDTHS[8], LOGO_HEIGHT); break; case Phase::Delegate: case Phase::Done: break; } } void IntroNewLogoScene::advancePaletteCycle() { // Replica exacta del ciclo de paleta del doIntroNewLogo vell sobre // els índexs 16..31 (grup del verd brillant del logo). 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 IntroNewLogoScene::tick(int delta_ms) { // Qualsevol tecla durant el revelat o el ciclo de paleta salta // TOTA la intro (inclou saltar doIntroSprites). Aquest era el // comportament del vell `doIntroNewLogo`: a cada `waitTick()` // retornava abans de cridar `doIntroSprites`. if (phase_ != Phase::Delegate && phase_ != Phase::Done && JI_AnyKey()) { info::ctx.num_piramide = 0; phase_ = Phase::Done; return; } switch (phase_) { case Phase::Initial: phase_acc_ms_ += delta_ms; render(); if (phase_acc_ms_ >= INITIAL_MS) { phase_ = Phase::Revealing; phase_acc_ms_ = 0; } break; case Phase::Revealing: phase_acc_ms_ += delta_ms; render(); if (phase_acc_ms_ >= REVEAL_FRAME_MS) { phase_acc_ms_ = 0; reveal_cursor_visible_ = !reveal_cursor_visible_; // Quan acabem els dos frames d'una lletra (cursor on → off), // passem a la següent lletra. if (reveal_cursor_visible_) { ++reveal_letter_; if (reveal_letter_ >= 9) { phase_ = Phase::FullLogoFlash; reveal_letter_ = 8; } } } break; case Phase::FullLogoFlash: phase_acc_ms_ += delta_ms; render(); if (phase_acc_ms_ >= FULL_LOGO_MS) { phase_ = Phase::PaletteCycle; phase_acc_ms_ = 0; } break; case Phase::PaletteCycle: phase_acc_ms_ += delta_ms; // Avancem passos de paleta cada 20 ms. Si el delta és gran, // consumim múltiples passos en la mateixa crida. 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::Delegate; } break; case Phase::Delegate: { // Delegació temporal al codi legacy: crea un ModuleSequence // instància i li crida `doIntroSprites(gfx)`. La funció // legacy *sempre* allibera `gfx` ella mateixa (al final o en // els paths de skip amb JI_AnyKey), així que necessitem // transferir-li ownership via `release()` per evitar un // double free al destructor de SurfaceHandle. Step 9 // d'aquesta migració la reescriurà com a IntroSpritesScene // i la delegació desapareixerà. ModuleSequence legacy; legacy.doIntroSprites(gfx_.release()); // El vell `Go()` post-switch feia `num_piramide = 0` per a // passar al menú. Ho reproduïm ací — si no, el while extern // del fiber tornaria a crear IntroNewLogoScene infinitament. info::ctx.num_piramide = 0; phase_ = Phase::Done; break; } case Phase::Done: break; } } } // namespace scenes