From 829d7431c1d6bf1706f9284b0090d5a66ae33146 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 16 Apr 2026 00:03:25 +0200 Subject: [PATCH] step 6: credits_scene substituix doCredits() (scroll vertical + parallax condicional) --- CMakeLists.txt | 1 + source/core/system/director.cpp | 4 +- source/game/defaults.hpp | 1 + source/game/modulesequence.cpp | 70 +--------------- source/game/modulesequence.hpp | 2 +- source/game/options.cpp | 3 + source/game/options.hpp | 1 + source/scenes/credits_scene.cpp | 139 ++++++++++++++++++++++++++++++++ source/scenes/credits_scene.hpp | 52 ++++++++++++ 9 files changed, 203 insertions(+), 70 deletions(-) create mode 100644 source/scenes/credits_scene.cpp create mode 100644 source/scenes/credits_scene.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d31d9d..ebf5f95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,7 @@ set(APP_SOURCES source/scenes/menu_scene.cpp source/scenes/intro_new_logo_scene.cpp source/scenes/slides_scene.cpp + source/scenes/credits_scene.cpp # Game source/game/options.cpp diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index 54de74d..7b094ae 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/credits_scene.hpp" #include "scenes/intro_new_logo_scene.hpp" #include "scenes/menu_scene.hpp" #include "scenes/mort_scene.hpp" @@ -43,7 +44,7 @@ void gameFiberEntry() { info::ctx.num_habitacio = Options::game.habitacio_inicial; info::ctx.num_piramide = Options::game.piramide_inicial; info::ctx.diners = 0; - info::ctx.diamants = 0; + info::ctx.diamants = Options::game.diamants_inicial; info::ctx.vida = Options::game.vides; info::ctx.momies = 0; info::ctx.nou_personatge = false; @@ -129,6 +130,7 @@ void Director::init() { // l'usuari no té prou diners per a la Secreta) registry.registerScene(1, [] { return std::make_unique(); }); registry.registerScene(7, [] { 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. diff --git a/source/game/defaults.hpp b/source/game/defaults.hpp index caf7a6b..ef9e588 100644 --- a/source/game/defaults.hpp +++ b/source/game/defaults.hpp @@ -57,6 +57,7 @@ namespace Defaults::Game { constexpr int HABITACIO_INICIAL = 1; constexpr int PIRAMIDE_INICIAL = 255; constexpr int VIDES = 5; + constexpr int DIAMANTS_INICIAL = 0; constexpr bool USE_NEW_LOGO = true; constexpr bool SHOW_TITLE_CREDITS = true; } // namespace Defaults::Game diff --git a/source/game/modulesequence.cpp b/source/game/modulesequence.cpp index 0aaab49..252576b 100644 --- a/source/game/modulesequence.cpp +++ b/source/game/modulesequence.cpp @@ -44,9 +44,7 @@ int ModuleSequence::Go() { case 6: // Pre-Secreta doSecreta(); break; - case 8: // Credits - doCredits(); - break; + // case 8 (Credits) → migrat a scenes::CreditsScene. // case 100 (Mort) → migrat a scenes::MortScene, dispatch via SceneRegistry. } @@ -911,70 +909,6 @@ void ModuleSequence::doSecreta() { free(pal_aux); } -void ModuleSequence::doCredits() { - struct { - Uint16 x, y; - } frames_coche[8] = {{214, 152}, {214, 104}, {214, 56}, {214, 104}, {214, 152}, {214, 8}, {108, 152}, {214, 8}}; - const Uint32 n_frames_coche = 8; - const Uint32 velocitat_coche = 3; - - JD8_Surface vaddr2 = JD8_LoadSurface("final.gif"); - JD8_Surface vaddr3 = JD8_LoadSurface("finals.gif"); - JD8_Palette pal = JD8_LoadPalette("final.gif"); - JD8_SetScreenPalette(pal); - - int contador = 0; - - bool exit = false; - contador = 1; - while (!exit && !JG_Quitting()) { - if (JG_ShouldUpdate()) { - JI_Update(); - - if (JI_AnyKey() || contador >= 3100) { - exit = true; - } - - JD8_ClearScreen(255); - - if (contador < 2750) { - JD8_BlitCKCut(115, 200 - (contador / 6), vaddr2, 0, 0, 80, 200, 0); - } - - if ((contador > 1200) && (contador < 2280)) { - JD8_BlitCKCut(100, 200 - ((contador - 1200) / 6), vaddr2, 85, 0, 120, 140, 0); - } else if (contador >= 2250) { - JD8_BlitCK(100, 20, vaddr2, 85, 0, 120, 140, 0); - } - - if (info::ctx.diamants == 16) { - // scroll_final_joc(vaddr3, vaddr, contador_final); - JD8_BlitCKScroll(50, vaddr3, ((contador >> 3) % 320) + 1, 0, 50, 255); - JD8_BlitCKScroll(50, vaddr3, ((contador >> 2) % 320) + 1, 50, 50, 255); - JD8_BlitCKScroll(50, vaddr3, ((contador >> 1) % 320) + 1, 100, 50, 255); - JD8_BlitCKScroll(50, vaddr3, (contador % 320) + 1, 150, 50, 255); - - JD8_BlitCK(100, 50, vaddr2, frames_coche[(contador / velocitat_coche) % n_frames_coche].x, frames_coche[(contador / velocitat_coche) % n_frames_coche].y, 106, 48, 255); - } else { - JD8_BlitCK(0, 50, vaddr3, 0, 0, 320, 50, 255); - JD8_BlitCK(0, 50, vaddr3, 0, 50, 320, 50, 255); - } - - JD8_FillSquare(0, 50, 255); - JD8_FillSquare(100, 10, 255); - - JD8_Flip(); - contador++; - } - } - - FILE* ini = fopen("trick.ini", "wb"); - fwrite("1", 1, 1, ini); - fclose(ini); - info::ctx.nou_personatge = true; - - JD8_FreeSurface(vaddr3); - JD8_FreeSurface(vaddr2); -} +// doCredits() — migrat a scenes::CreditsScene (source/scenes/credits_scene.cpp) // doMort() — migrat a scenes::MortScene (source/scenes/mort_scene.cpp) diff --git a/source/game/modulesequence.hpp b/source/game/modulesequence.hpp index 080e920..a55d2d3 100644 --- a/source/game/modulesequence.hpp +++ b/source/game/modulesequence.hpp @@ -24,7 +24,7 @@ class ModuleSequence { // doSlides() → migrat a scenes::SlidesScene // doBanner() → migrat a scenes::BannerScene void doSecreta(); - void doCredits(); + // doCredits() → migrat a scenes::CreditsScene // doMort() → migrat a scenes::MortScene int contador; diff --git a/source/game/options.cpp b/source/game/options.cpp index e58e068..6c38165 100644 --- a/source/game/options.cpp +++ b/source/game/options.cpp @@ -144,6 +144,8 @@ namespace Options { game.piramide_inicial = node["piramide_inicial"].get_value(); if (node.contains("vides")) game.vides = node["vides"].get_value(); + if (node.contains("diamants_inicial")) + game.diamants_inicial = node["diamants_inicial"].get_value(); if (node.contains("use_new_logo")) game.use_new_logo = node["use_new_logo"].get_value(); if (node.contains("show_title_credits")) @@ -278,6 +280,7 @@ namespace Options { file << " habitacio_inicial: " << game.habitacio_inicial << "\n"; file << " piramide_inicial: " << game.piramide_inicial << "\n"; file << " vides: " << game.vides << "\n"; + file << " diamants_inicial: " << game.diamants_inicial << "\n"; file << " use_new_logo: " << (game.use_new_logo ? "true" : "false") << "\n"; file << " show_title_credits: " << (game.show_title_credits ? "true" : "false") << "\n"; file << "\n"; diff --git a/source/game/options.hpp b/source/game/options.hpp index c17823e..e5cf5a7 100644 --- a/source/game/options.hpp +++ b/source/game/options.hpp @@ -83,6 +83,7 @@ namespace Options { int habitacio_inicial{Defaults::Game::HABITACIO_INICIAL}; int piramide_inicial{Defaults::Game::PIRAMIDE_INICIAL}; int vides{Defaults::Game::VIDES}; + int diamants_inicial{Defaults::Game::DIAMANTS_INICIAL}; bool use_new_logo{Defaults::Game::USE_NEW_LOGO}; bool show_title_credits{Defaults::Game::SHOW_TITLE_CREDITS}; }; diff --git a/source/scenes/credits_scene.cpp b/source/scenes/credits_scene.cpp new file mode 100644 index 0000000..78c635e --- /dev/null +++ b/source/scenes/credits_scene.cpp @@ -0,0 +1,139 @@ +#include "scenes/credits_scene.hpp" + +#include +#include + +#include "core/jail/jail_audio.hpp" +#include "core/jail/jdraw8.hpp" +#include "core/jail/jinput.hpp" +#include "game/info.hpp" +#include "scenes/scene_utils.hpp" + +namespace { + +// Frames del cotxe: 8 posicions dins del sprite sheet final.gif. El +// vell doCredits tenia aquesta taula inline — la reproduïm idèntica. +struct CocheFrame { + Uint16 x, y; +}; + +constexpr CocheFrame COCHE_FRAMES[8] = { + {214, 152}, {214, 104}, {214, 56}, {214, 104}, {214, 152}, {214, 8}, {108, 152}, {214, 8}, +}; + +constexpr int CONTADOR_MAX = 3100; // ~62 s de crèdits a 20 ms/tick +constexpr int TICK_MS = 20; // JG_SetUpdateTicks heretat del doSlides previ +constexpr int BG_INDEX = 255; + +} // namespace + +namespace scenes { + +CreditsScene::~CreditsScene() { + // No toquem la paleta activa: SetScreenPalette n'ha pres ownership. +} + +void CreditsScene::onEnter() { + // El vell doCredits no tocava música — heretava la del doSlides + // previ ("00000005.ogg"). Si l'escena s'arrenca directament (test + // amb piramide_inicial=8) no hi ha res que heretar, així que + // arranquem la mateixa pista només si no sona res. Inocu en el + // flux normal: JA_MUSIC_PLAYING fa que no la tornem a tocar. + if (JA_GetMusicState() != JA_MUSIC_PLAYING) { + playMusic("00000005.ogg"); + } + + vaddr2_ = SurfaceHandle("final.gif"); + vaddr3_ = SurfaceHandle("finals.gif"); + + JD8_Palette pal = JD8_LoadPalette("final.gif"); + JD8_SetScreenPalette(pal); + // `pal` passa a ser propietat de main_palette — no l'alliberem. + + phase_ = Phase::Rolling; + contador_ = 1; + contador_acc_ms_ = 0; +} + +void CreditsScene::render() { + JD8_ClearScreen(BG_INDEX); + + // Columna 1: scroll vertical del bloc (0,0,80,200) pujant des de + // y=200 fins que el contador supera 2750. + if (contador_ < 2750) { + JD8_BlitCKCut(115, 200 - (contador_ / 6), vaddr2_, 0, 0, 80, 200, 0); + } + + // Columna 2: scroll vertical del bloc (85,0,120,140), arrenca + // a contador 1200 i s'atura (fix en y=20) a partir de 2250. + if ((contador_ > 1200) && (contador_ < 2280)) { + JD8_BlitCKCut(100, 200 - ((contador_ - 1200) / 6), vaddr2_, 85, 0, 120, 140, 0); + } else if (contador_ >= 2250) { + JD8_BlitCK(100, 20, vaddr2_, 85, 0, 120, 140, 0); + } + + // Fons: 4 capes parallax + cotxe només si l'usuari ha aconseguit + // tots els diamants (final "bo"). Altrament fons estàtic. + if (info::ctx.diamants == 16) { + JD8_BlitCKScroll(50, vaddr3_, ((contador_ >> 3) % 320) + 1, 0, 50, 255); + JD8_BlitCKScroll(50, vaddr3_, ((contador_ >> 2) % 320) + 1, 50, 50, 255); + JD8_BlitCKScroll(50, vaddr3_, ((contador_ >> 1) % 320) + 1, 100, 50, 255); + JD8_BlitCKScroll(50, vaddr3_, (contador_ % 320) + 1, 150, 50, 255); + + const CocheFrame& cf = COCHE_FRAMES[coche_.frame()]; + JD8_BlitCK(100, 50, vaddr2_, cf.x, cf.y, 106, 48, 255); + } else { + JD8_BlitCK(0, 50, vaddr3_, 0, 0, 320, 50, 255); + JD8_BlitCK(0, 50, vaddr3_, 0, 50, 320, 50, 255); + } + + // Barres de marc que cobreixen els extrems del scroll vertical. + JD8_FillSquare(0, 50, BG_INDEX); + JD8_FillSquare(100, 10, BG_INDEX); +} + +void CreditsScene::writeTrickIni() { + FILE* ini = std::fopen("trick.ini", "wb"); + if (ini) { + std::fwrite("1", 1, 1, ini); + std::fclose(ini); + } + info::ctx.nou_personatge = true; +} + +void CreditsScene::tick(int delta_ms) { + switch (phase_) { + case Phase::Rolling: { + // Avancem el contador en passos discrets de 20 ms, igual + // que feia JG_ShouldUpdate(20) al vell doCredits. + contador_acc_ms_ += delta_ms; + while (contador_acc_ms_ >= TICK_MS) { + contador_acc_ms_ -= TICK_MS; + ++contador_; + } + + coche_.tick(delta_ms); + render(); + + if (JI_AnyKey() || contador_ >= CONTADOR_MAX) { + writeTrickIni(); + fade_.startFadeOut(); + phase_ = Phase::FadingOut; + } + break; + } + + case Phase::FadingOut: + fade_.tick(delta_ms); + if (fade_.done()) { + info::ctx.num_piramide = 255; + phase_ = Phase::Done; + } + break; + + case Phase::Done: + break; + } +} + +} // namespace scenes diff --git a/source/scenes/credits_scene.hpp b/source/scenes/credits_scene.hpp new file mode 100644 index 0000000..246ea72 --- /dev/null +++ b/source/scenes/credits_scene.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include "core/jail/jdraw8.hpp" +#include "scenes/frame_animator.hpp" +#include "scenes/palette_fade.hpp" +#include "scenes/scene.hpp" +#include "scenes/surface_handle.hpp" + +namespace scenes { + +// Crèdits finals del joc. Reemplaça `ModuleSequence::doCredits()`. +// +// Flux: +// 1. Carrega final.gif (sprites de crèdits) i finals.gif (fons). +// 2. Mostra els crèdits amb scroll vertical de 2 columnes durant +// ~62 segons (contador 0..3100 × 20 ms). +// 3. Si `info::ctx.diamants == 16`, pinta addicionalment un parallax +// de 4 capes amb cotxe animat (8 frames). Si no, 2 blits fixos. +// 4. Al acabar (per tecla o per contador), crea el fitxer `trick.ini` +// i activa `info::ctx.nou_personatge`. +// 5. Fade-out de paleta. Torna a la intro (num_piramide = 255). +// +// Registrada al SceneRegistry amb state_key = 8. +class CreditsScene : public Scene { + public: + CreditsScene() = default; + ~CreditsScene() 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 { Rolling, + FadingOut, + Done }; + + void render(); + void writeTrickIni(); + + SurfaceHandle vaddr2_; // final.gif (sprites i coches) + SurfaceHandle vaddr3_; // finals.gif (fons / parallax) + PaletteFade fade_; + FrameAnimator coche_{8, 60, true}; // 8 frames × 60 ms (~3 × 20 ms tick vell) + + Phase phase_{Phase::Rolling}; + int contador_{1}; + int contador_acc_ms_{0}; +}; + +} // namespace scenes