From ad38fc09cfca56f32d41d4ce491c7095598efdf9 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Wed, 15 Apr 2026 23:28:22 +0200 Subject: [PATCH] step 4: intro_new_logo_scene substituix doIntroNewLogo(); doIntroSprites exposat temporalment --- CMakeLists.txt | 1 + source/core/system/director.cpp | 10 ++ source/game/modulesequence.cpp | 130 +-------------- source/game/modulesequence.hpp | 7 +- source/scenes/intro_new_logo_scene.cpp | 211 +++++++++++++++++++++++++ source/scenes/intro_new_logo_scene.hpp | 64 ++++++++ source/scenes/surface_handle.cpp | 5 + source/scenes/surface_handle.hpp | 4 + 8 files changed, 305 insertions(+), 127 deletions(-) create mode 100644 source/scenes/intro_new_logo_scene.cpp create mode 100644 source/scenes/intro_new_logo_scene.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bd22e43..b285019 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,7 @@ set(APP_SOURCES source/scenes/mort_scene.cpp source/scenes/banner_scene.cpp source/scenes/menu_scene.cpp + source/scenes/intro_new_logo_scene.cpp # Game source/game/options.cpp diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index 971c2ea..832f8ae 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -21,6 +21,7 @@ #include "game/modulesequence.hpp" #include "game/options.hpp" #include "scenes/banner_scene.hpp" +#include "scenes/intro_new_logo_scene.hpp" #include "scenes/menu_scene.hpp" #include "scenes/mort_scene.hpp" #include "scenes/scene.hpp" @@ -111,6 +112,15 @@ void Director::init() { for (int p = 2; p <= 5; ++p) { registry.registerScene(p, [] { 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. + registry.registerScene(255, []() -> std::unique_ptr { + if (Options::game.use_new_logo) { + return std::make_unique(); + } + return nullptr; + }); GameFiber::init(gameFiberEntry); } diff --git a/source/game/modulesequence.cpp b/source/game/modulesequence.cpp index d00ac9c..d7d6499 100644 --- a/source/game/modulesequence.cpp +++ b/source/game/modulesequence.cpp @@ -108,10 +108,9 @@ void play_music(const char* music, bool loop = -1) { } void ModuleSequence::doIntro() { - if (Options::game.use_new_logo) { - doIntroNewLogo(); - return; - } + // 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); @@ -820,128 +819,7 @@ void ModuleSequence::doIntroSprites(JD8_Surface gfx) { JD8_FreeSurface(gfx); } -void ModuleSequence::doIntroNewLogo() { - // Coordenades mesurades del wordmark "Jailgames" dins logo/logo_new.gif - // (imatge 320x200, fons negre = index 0, lletres en index 17 = verd brillant). - // TUNE: ajusta aquests valors si canvies el logo. - constexpr int LOGO_SRC_X = 60; // x d'inici de la 'J' a la imatge font - constexpr int LOGO_SRC_Y = 158; // y del top del wordmark a la imatge font - constexpr int LOGO_DST_Y = 78; // y de destinació en pantalla (igual que logo vell) - constexpr int LOGO_HEIGHT = 28; // alçada del wordmark - // Amplada del crop des de LOGO_SRC_X fins al final de cada lletra - // (J, Ja, Jai, Jail, Jailg, Jailga, Jailgam, Jailgame, Jailgames): - constexpr int LETTER_WIDTHS[9] = {16, 39, 50, 69, 92, 115, 146, 169, 188}; - // Cursor horitzontal (subratllat) al peu de la lletra següent que apareixerà. - // x absolut en pantalla, on comença cada cursor (just després de l'última lletra revelada): - 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; // peu de la lletra (y=103) - constexpr Uint8 CURSOR_COLOR = 17; // mateix index verd que les lletres (cicla amb el palette) - - JG_SetUpdateTicks(1000); - - play_music("00000003.ogg"); - - JD8_Surface gfx = JD8_LoadSurface("logo/logo_new.gif"); - JD8_Palette pal = JD8_LoadPalette("logo/logo_new.gif"); - JD8_SetScreenPalette(pal); - - // Surface auxiliar plena amb el color del cursor, per poder "blittejar" rectangles. - JD8_Surface cursor_surf = JD8_NewSurface(); - memset(cursor_surf, CURSOR_COLOR, 64000); - - auto cleanup = [&]() { - JD8_FreeSurface(gfx); - JD8_FreeSurface(cursor_surf); - }; - auto waitTick = [&]() -> bool { - // Retorna true si cal sortir (tecla o quitting). - while (!JG_ShouldUpdate()) { - JI_Update(); - if (JI_AnyKey() || JG_Quitting()) return true; - } - return false; - }; - - JD8_ClearScreen(0); - JD8_Flip(); - if (waitTick()) { - cleanup(); - return; - } - - JG_SetUpdateTicks(150); - - // Revelat progressiu lletra-a-lletra amb cursor parpadejant (subratllat horitzontal). - for (int i = 0; i < 9; i++) { - // Frame amb cursor visible - JD8_ClearScreen(0); - JD8_Blit(LOGO_SRC_X, LOGO_DST_Y, gfx, LOGO_SRC_X, LOGO_SRC_Y, LETTER_WIDTHS[i], LOGO_HEIGHT); - JD8_Blit(CURSOR_X[i], CURSOR_Y, cursor_surf, 0, 0, CURSOR_W, CURSOR_H); - JD8_Flip(); - if (waitTick()) { - cleanup(); - return; - } - - // Frame sense cursor (parpadeig) - JD8_ClearScreen(0); - JD8_Blit(LOGO_SRC_X, LOGO_DST_Y, gfx, LOGO_SRC_X, LOGO_SRC_Y, LETTER_WIDTHS[i], LOGO_HEIGHT); - JD8_Flip(); - if (waitTick()) { - cleanup(); - return; - } - } - - // Mostra el logo complet amb el cursor final fix un moment més. - JG_SetUpdateTicks(200); - 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); - JD8_Flip(); - if (waitTick()) { - cleanup(); - return; - } - - // Treu el cursor abans del cicle de paleta (els seus pixels cicla rien amb les lletres). - JD8_ClearScreen(0); - JD8_Blit(LOGO_SRC_X, LOGO_DST_Y, gfx, LOGO_SRC_X, LOGO_SRC_Y, LETTER_WIDTHS[8], LOGO_HEIGHT); - JD8_Flip(); - - // Cicle de paleta final (mateix efecte que l'intro original, indexs 16-31). - JG_SetUpdateTicks(20); - 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 (waitTick()) { - cleanup(); - return; - } - } - - // Espera abans d'entrar a les animacions de sprites (igual que l'intro vella). - if (waitTick()) { - cleanup(); - return; - } - - // doIntroSprites pren propietat de gfx i el allibera ell mateix. - JD8_FreeSurface(cursor_surf); - doIntroSprites(gfx); -} +// doIntroNewLogo() — migrat a scenes::IntroNewLogoScene (source/scenes/intro_new_logo_scene.cpp) // doMenu() — migrat a scenes::MenuScene (source/scenes/menu_scene.cpp) diff --git a/source/game/modulesequence.hpp b/source/game/modulesequence.hpp index e7dc29e..ecbf750 100644 --- a/source/game/modulesequence.hpp +++ b/source/game/modulesequence.hpp @@ -11,10 +11,15 @@ class ModuleSequence { int Go(); + // Exposat temporalment (public) fins al Step 9 del pla de migració, + // quan `doIntroSprites` es reescriurà com a scenes::IntroSpritesScene. + // Mentrestant, scenes::IntroNewLogoScene el crida al final del seu + // ciclo per delegar la part de les animacions de sprites. + void doIntroSprites(Uint8* gfx); + private: void doIntro(); void doIntroNewLogo(); - void doIntroSprites(Uint8* gfx); // doMenu() → migrat a scenes::MenuScene void doSlides(); // doBanner() → migrat a scenes::BannerScene diff --git a/source/scenes/intro_new_logo_scene.cpp b/source/scenes/intro_new_logo_scene.cpp new file mode 100644 index 0000000..1118a78 --- /dev/null +++ b/source/scenes/intro_new_logo_scene.cpp @@ -0,0 +1,211 @@ +#include "scenes/intro_new_logo_scene.hpp" + +#include + +#include "core/jail/jdraw8.hpp" +#include "core/jail/jinput.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 salta tota la intro del logo i va directament + // a la delegació final — el mini-while del fiber no ha pintat + // res encara, però doIntroSprites fa la seua pròpia intro. + if (JI_AnyKey()) { + phase_ = Phase::Delegate; + } + + 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 fa els seus propis `JD8_Flip()` (que cedeixen al + // Director via GameFiber::yield) i torna quan la cinemàtica + // de sprites ha acabat. Step 9 d'aquesta migració la + // reescriurà com a scenes::IntroSpritesScene i aquesta + // delegació desapareixerà. + ModuleSequence legacy; + legacy.doIntroSprites(gfx_.get()); + phase_ = Phase::Done; + break; + } + + case Phase::Done: + break; + } +} + +} // namespace scenes diff --git a/source/scenes/intro_new_logo_scene.hpp b/source/scenes/intro_new_logo_scene.hpp new file mode 100644 index 0000000..8a4bc42 --- /dev/null +++ b/source/scenes/intro_new_logo_scene.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include "core/jail/jdraw8.hpp" +#include "scenes/scene.hpp" +#include "scenes/surface_handle.hpp" + +namespace scenes { + +// Intro "moderna" del logo Jailgames amb revelat lletra-a-lletra + +// ciclo de paleta final. Reemplaça `ModuleSequence::doIntroNewLogo()`. +// +// Flux: +// 1. Carrega logo/logo_new.gif, arranca música "00000003.ogg" i posa +// la paleta directament (sense fade-in). Mostra pantalla negra 1s. +// 2. Revelat: 9 lletres × 2 frames (amb cursor / sense cursor), 150 ms +// cada frame. +// 3. Logo complet amb cursor fix 200 ms. +// 4. Cicle de paleta de 256 passos modificant índexs 16–31 cada 20 ms. +// 5. Espera final 20 ms. +// 6. Delega a la funció legacy `ModuleSequence::doIntroSprites(gfx)` +// (que s'executa dins del mateix fiber i fa els seus propis Flips +// cooperatius). Aquesta delegació desapareixerà al Step 9 del pla, +// quan `doIntroSprites` es reescriga com a `IntroSpritesScene`. +// +// Registrada al SceneRegistry amb state_key = 255, amb una factory +// condicional: només s'activa si `Options::game.use_new_logo == true`. +// Si és false, la factory retorna nullptr i el gameFiberEntry cau al +// path legacy (`ModuleSequence::doIntro()` vell). +class IntroNewLogoScene : public Scene { + public: + IntroNewLogoScene(); + ~IntroNewLogoScene() 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 { + Initial, // pantalla negra 1000 ms + Revealing, // 9 × 2 frames × 150 ms cada un + FullLogoFlash, // logo complet + cursor, 200 ms + PaletteCycle, // 256 passos × 20 ms modificant paleta + FinalWait, // 20 ms final + Delegate, // delega a doIntroSprites legacy i marca done + Done, + }; + + void render(); + void advancePaletteCycle(); + + SurfaceHandle gfx_; + SurfaceHandle cursor_surf_; + JD8_Palette pal_{nullptr}; // propietat transferida a main_palette via SetScreenPalette + + Phase phase_{Phase::Initial}; + int phase_acc_ms_{0}; + int reveal_letter_{0}; + bool reveal_cursor_visible_{true}; + int palette_step_{0}; +}; + +} // namespace scenes diff --git a/source/scenes/surface_handle.cpp b/source/scenes/surface_handle.cpp index 731171b..6d5b159 100644 --- a/source/scenes/surface_handle.cpp +++ b/source/scenes/surface_handle.cpp @@ -28,4 +28,9 @@ void SurfaceHandle::reset(const char* file) { surface_ = file ? JD8_LoadSurface(file) : nullptr; } +void SurfaceHandle::adopt(JD8_Surface raw) { + if (surface_) JD8_FreeSurface(surface_); + surface_ = raw; +} + } // namespace scenes diff --git a/source/scenes/surface_handle.hpp b/source/scenes/surface_handle.hpp index 19663fd..11a13a2 100644 --- a/source/scenes/surface_handle.hpp +++ b/source/scenes/surface_handle.hpp @@ -25,6 +25,10 @@ class SurfaceHandle { // (p.ex. doSecreta que passa de tomba1 a tomba2). void reset(const char* file); + // Adopta una surface ja creada (p.ex. amb JD8_NewSurface). Pren ownership + // — la surface adoptada s'allibera al destructor o al següent reset/adopt. + void adopt(JD8_Surface raw); + // Conversió implícita per al confort d'ús: JD8_Blit(handle) // en lloc de JD8_Blit(handle.get()). operator JD8_Surface() const { return surface_; }