From e18b7321eba077bbfdbcb54edf197078ac920c1f Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 16 Apr 2026 08:00:22 +0200 Subject: [PATCH] step 8: intro_scene substituix doIntro() (revelat JAILGAMES lletra a lletra + cicle de paleta) --- CMakeLists.txt | 1 + source/core/system/director.cpp | 10 +- source/game/modulesequence.cpp | 250 ++------------------------------ source/game/modulesequence.hpp | 4 +- source/scenes/intro_scene.cpp | 218 ++++++++++++++++++++++++++++ source/scenes/intro_scene.hpp | 61 ++++++++ 6 files changed, 300 insertions(+), 244 deletions(-) create mode 100644 source/scenes/intro_scene.cpp create mode 100644 source/scenes/intro_scene.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9eb2972..252e996 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,7 @@ set(APP_SOURCES source/scenes/banner_scene.cpp source/scenes/menu_scene.cpp source/scenes/intro_new_logo_scene.cpp + source/scenes/intro_scene.cpp source/scenes/slides_scene.cpp source/scenes/credits_scene.cpp source/scenes/secreta_scene.cpp diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index 9148239..3ab5f86 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -23,6 +23,7 @@ #include "scenes/banner_scene.hpp" #include "scenes/credits_scene.hpp" #include "scenes/intro_new_logo_scene.hpp" +#include "scenes/intro_scene.hpp" #include "scenes/menu_scene.hpp" #include "scenes/mort_scene.hpp" #include "scenes/scene.hpp" @@ -133,14 +134,15 @@ void Director::init() { registry.registerScene(7, [] { return std::make_unique(); }); registry.registerScene(6, [] { return std::make_unique(); }); registry.registerScene(8, [] { return std::make_unique(); }); - // IntroNewLogoScene només es registra quan `use_new_logo` està actiu; - // si no, la factory retorna nullptr i el gameFiberEntry cau al vell - // ModuleSequence::doIntro() legacy que no ha sigut migrat encara. + // State 255 (intro): dues variants segons `Options::game.use_new_logo`. + // La factory tria a runtime — així es pot togglar des del menú sense + // re-registrar. Les dues escenes acaben delegant a doIntroSprites + // legacy fins al Step 9 de la migració. registry.registerScene(255, []() -> std::unique_ptr { if (Options::game.use_new_logo) { return std::make_unique(); } - return nullptr; + return std::make_unique(); }); GameFiber::init(gameFiberEntry); diff --git a/source/game/modulesequence.cpp b/source/game/modulesequence.cpp index 33355a4..71c2468 100644 --- a/source/game/modulesequence.cpp +++ b/source/game/modulesequence.cpp @@ -2,11 +2,7 @@ #include -#include - -#include "core/jail/jail_audio.hpp" #include "core/jail/jdraw8.hpp" -#include "core/jail/jfile.hpp" #include "core/jail/jgame.hpp" #include "core/jail/jinput.hpp" #include "game/options.hpp" @@ -34,17 +30,18 @@ bool wait_frame_or_skip() { int ModuleSequence::Go() { if (info::ctx.num_piramide == 6 && info::ctx.diners < 200) info::ctx.num_piramide = 7; - switch (info::ctx.num_piramide) { - case 255: // Intro - doIntro(); - break; - // case 0 (Menú) → migrat a scenes::MenuScene. - // case 1 i 7 (Slides) → migrat a scenes::SlidesScene. - // case 2..5 (Pre-piràmide) → migrat a scenes::BannerScene. - // case 6 (Pre-Secreta) → migrat a scenes::SecretaScene. - // case 8 (Credits) → migrat a scenes::CreditsScene. - // case 100 (Mort) → migrat a scenes::MortScene, dispatch via SceneRegistry. - } + // Totes les branques del vell switch han estat migrades a scenes: + // case 0 (Menú) → scenes::MenuScene + // case 1 i 7 (Slides) → scenes::SlidesScene + // case 2..5 (Banner) → scenes::BannerScene + // case 6 (Secreta) → scenes::SecretaScene + // case 8 (Credits) → scenes::CreditsScene + // case 100 (Mort) → scenes::MortScene + // case 255 (Intro) → scenes::IntroScene / scenes::IntroNewLogoScene + // El gameFiberEntry les dispatcha via SceneRegistry abans de caure a + // aquest Go() — així que en la pràctica ja no s'arriba ací. El codi + // que queda sota (JD8_FadeOut + transicions de num_piramide) serà + // eliminat al Step 10 del pla, junt amb ModuleSequence::Go() sencer. JD8_FadeOut(); @@ -94,229 +91,6 @@ static void drawIntroWordmark(JD8_Surface gfx) { } } -void play_music(const char* music, bool loop = -1) { - int size; - char* buffer = file_getfilebuffer(music, size); - JA_PlayMusic(JA_LoadMusic((Uint8*)buffer, size, music), loop); -} - -void ModuleSequence::doIntro() { - // Branca `if (use_new_logo)` eliminada: scenes::IntroNewLogoScene - // agafa eixe camí via el SceneRegistry. Aquest `doIntro()` legacy - // només s'executa quan `use_new_logo == false`. - - JG_SetUpdateTicks(1000); - - play_music("00000003.ogg"); - - JD8_Surface gfx = JD8_LoadSurface("logo.gif"); - JD8_Palette pal = JD8_LoadPalette("logo.gif"); - JD8_SetScreenPalette(pal); - - JD8_ClearScreen(0); - JD8_Flip(); - if (wait_frame_or_skip()) { - - JD8_FreeSurface(gfx); - - return; - - } - JG_SetUpdateTicks(100); - - JD8_Blit(43, 78, gfx, 43, 155, 27, 45); - JD8_Blit(68, 78, gfx, 274, 155, 27, 45); - JD8_Flip(); - if (wait_frame_or_skip()) { - - JD8_FreeSurface(gfx); - - return; - - } - - JD8_Blit(43, 78, gfx, 43, 155, 53, 45); - JD8_Blit(96, 78, gfx, 274, 155, 27, 45); - JD8_Flip(); - if (wait_frame_or_skip()) { - - JD8_FreeSurface(gfx); - - return; - - } - - JD8_Blit(43, 78, gfx, 43, 155, 66, 45); - JD8_Blit(109, 78, gfx, 274, 155, 27, 45); - JD8_Flip(); - if (wait_frame_or_skip()) { - - JD8_FreeSurface(gfx); - - return; - - } - JG_SetUpdateTicks(200); - - JD8_Blit(43, 78, gfx, 43, 155, 92, 45); - JD8_Blit(136, 78, gfx, 274, 155, 27, 45); - JD8_Flip(); - if (wait_frame_or_skip()) { - - JD8_FreeSurface(gfx); - - return; - - } - - JD8_ClearScreen(0); - JD8_Blit(43, 78, gfx, 43, 155, 92, 45); - // JD8_Blit( 136, 78, gfx, 274, 155, 27, 45 ); - JD8_Flip(); - if (wait_frame_or_skip()) { - - JD8_FreeSurface(gfx); - - return; - - } - JG_SetUpdateTicks(100); - - JD8_Blit(43, 78, gfx, 43, 155, 118, 45); - JD8_Blit(160, 78, gfx, 274, 155, 27, 45); - JD8_Flip(); - if (wait_frame_or_skip()) { - - JD8_FreeSurface(gfx); - - return; - - } - - JD8_Blit(43, 78, gfx, 43, 155, 145, 45); - JD8_Blit(188, 78, gfx, 274, 155, 27, 45); - JD8_Flip(); - if (wait_frame_or_skip()) { - - JD8_FreeSurface(gfx); - - return; - - } - - JD8_Blit(43, 78, gfx, 43, 155, 178, 45); - JD8_Blit(221, 78, gfx, 274, 155, 27, 45); - JD8_Flip(); - if (wait_frame_or_skip()) { - - JD8_FreeSurface(gfx); - - return; - - } - - JD8_Blit(43, 78, gfx, 43, 155, 205, 45); - JD8_Blit(248, 78, gfx, 274, 155, 27, 45); - JD8_Flip(); - if (wait_frame_or_skip()) { - - JD8_FreeSurface(gfx); - - return; - - } - - JG_SetUpdateTicks(200); - drawIntroWordmark(gfx); - JD8_Blit(274, 78, gfx, 274, 155, 27, 45); - JD8_Flip(); - if (wait_frame_or_skip()) { - - JD8_FreeSurface(gfx); - - return; - - } - JD8_ClearScreen(0); - drawIntroWordmark(gfx); - JD8_Flip(); - if (wait_frame_or_skip()) { - - JD8_FreeSurface(gfx); - - return; - - } - drawIntroWordmark(gfx); - JD8_Blit(274, 78, gfx, 274, 155, 27, 45); - JD8_Flip(); - if (wait_frame_or_skip()) { - - JD8_FreeSurface(gfx); - - return; - - } - JD8_ClearScreen(0); - drawIntroWordmark(gfx); - JD8_Flip(); - if (wait_frame_or_skip()) { - - JD8_FreeSurface(gfx); - - return; - - } - drawIntroWordmark(gfx); - JD8_Blit(274, 78, gfx, 274, 155, 27, 45); - JD8_Flip(); - if (wait_frame_or_skip()) { - - JD8_FreeSurface(gfx); - - return; - - } - JD8_ClearScreen(0); - drawIntroWordmark(gfx); - JD8_Flip(); - if (wait_frame_or_skip()) { - - JD8_FreeSurface(gfx); - - return; - - } - - for (int j = 0; j < 256; j++) { - 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--; - } - JD8_Flip(); - } - - if (wait_frame_or_skip()) { - - - JD8_FreeSurface(gfx); - - - return; - - - } - - doIntroSprites(gfx); -} - void ModuleSequence::doIntroSprites(JD8_Surface gfx) { JG_SetUpdateTicks(20); diff --git a/source/game/modulesequence.hpp b/source/game/modulesequence.hpp index 2b059b6..efe73aa 100644 --- a/source/game/modulesequence.hpp +++ b/source/game/modulesequence.hpp @@ -18,8 +18,8 @@ class ModuleSequence { void doIntroSprites(Uint8* gfx); private: - void doIntro(); - void doIntroNewLogo(); + // doIntro() → migrat a scenes::IntroScene + // doIntroNewLogo() → migrat a scenes::IntroNewLogoScene // doMenu() → migrat a scenes::MenuScene // doSlides() → migrat a scenes::SlidesScene // doBanner() → migrat a scenes::BannerScene diff --git a/source/scenes/intro_scene.cpp b/source/scenes/intro_scene.cpp new file mode 100644 index 0000000..c883266 --- /dev/null +++ b/source/scenes/intro_scene.cpp @@ -0,0 +1,218 @@ +#include "scenes/intro_scene.hpp" + +#include "core/jail/jdraw8.hpp" +#include "core/jail/jinput.hpp" +#include "game/info.hpp" +#include "game/modulesequence.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("00000003.ogg"); + + gfx_ = SurfaceHandle("logo.gif"); + pal_ = JD8_LoadPalette("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::Delegate: + 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 el revelat/paleta salta TOTA la intro + // (inclou saltar doIntroSprites). El vell `doIntro` tornava abans + // de cridar doIntroSprites quan `wait_frame_or_skip` detectava una + // tecla. Durant `Delegate` deixem que doIntroSprites gestione el + // seu propi skip internament. + if (phase_ != Phase::Delegate && 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::Delegate; + } + break; + + case Phase::Delegate: { + // Delegació temporal al codi legacy. La funció legacy sempre + // allibera `gfx` ella mateixa (al final i als paths de skip + // amb JI_AnyKey), així que transferim ownership via release() + // per evitar double free al destructor de SurfaceHandle. + // Step 9 reescriurà doIntroSprites com a IntroSpritesScene i + // aquesta delegació desapareixerà. + ModuleSequence legacy; + legacy.doIntroSprites(gfx_.release()); + // El vell `Go()` post-switch feia `num_piramide = 0` per + // passar al menú. Replicat ací: si no, el while del fiber + // tornaria a crear IntroScene infinitament. + info::ctx.num_piramide = 0; + phase_ = Phase::Done; + break; + } + + case Phase::Done: + break; + } +} + +} // namespace scenes diff --git a/source/scenes/intro_scene.hpp b/source/scenes/intro_scene.hpp new file mode 100644 index 0000000..7772801 --- /dev/null +++ b/source/scenes/intro_scene.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include "core/jail/jdraw8.hpp" +#include "scenes/scene.hpp" +#include "scenes/surface_handle.hpp" + +namespace scenes { + +// Intro "legacy" del wordmark JAILGAMES lletra a lletra + cicle de paleta. +// Reemplaça `ModuleSequence::doIntro()`. S'activa quan +// `Options::game.use_new_logo == false`; l'alternativa moderna és +// `IntroNewLogoScene`. +// +// Flux: +// 1. Carrega logo.gif, arranca música "00000003.ogg", pantalla negra +// 1000 ms. +// 2. Revelat: 15 passos (100 o 200 ms) que van acumulant les lletres +// "JAILGAMES" d'esquerra a dreta amb un avió escombrant al final +// de la paraula. Els passos 5, 11, 13 i 15 netegen la pantalla +// per generar els parpelleigs finals. +// 3. Cicle de paleta: 256 passos × 20 ms modificant els índexs 16..31. +// 4. Espera final 200 ms. +// 5. Delega a `ModuleSequence::doIntroSprites(gfx)` (temporal — Step 9 +// del pla la reescriurà com a IntroSpritesScene i la delegació +// desapareixerà). +// +// Registrada al SceneRegistry amb state_key = 255: la mateixa factory que +// per a IntroNewLogoScene, però retornada quan `use_new_logo == false`. +class IntroScene : public Scene { + public: + IntroScene(); + ~IntroScene() override; + + void onEnter() override; + void tick(int delta_ms) override; + bool done() const override { return phase_ == Phase::Done; } + int nextState() const override { return 1; } + + private: + enum class Phase { + InitialWait, // 1000 ms pantalla negra + Reveal, // 15 passos del wordmark + PaletteCycle, // 256 × 20 ms mutant pal[16..31] + FinalWait, // 200 ms abans de delegar + Delegate, // crida ModuleSequence::doIntroSprites (Step 9 elimina) + Done, + }; + + void render(); + void advancePaletteCycle(); + + SurfaceHandle gfx_; + JD8_Palette pal_{nullptr}; // propietat transferida a main_palette via SetScreenPalette + + Phase phase_{Phase::InitialWait}; + int phase_acc_ms_{0}; + int reveal_index_{0}; + int palette_step_{0}; +}; + +} // namespace scenes