diff --git a/CMakeLists.txt b/CMakeLists.txt index b285019..0d31d9d 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/slides_scene.cpp # Game source/game/options.cpp diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index 832f8ae..54de74d 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -26,6 +26,7 @@ #include "scenes/mort_scene.hpp" #include "scenes/scene.hpp" #include "scenes/scene_registry.hpp" +#include "scenes/slides_scene.hpp" // Cheats del joc original — declarats a jinput.cpp extern void JI_moveCheats(Uint8 new_key); @@ -61,6 +62,16 @@ void gameFiberEntry() { // amb un mini-loop tick-based. El codi de la Scene no toca // fibers; el JD8_Flip() entre ticks és el que cedeix al Director. if (gameState == 1) { + // Replica del redirect que el `ModuleSequence::Go()` vell feia + // al principi: si el jugador arriba a la piràmide Secreta (6) + // sense prou diners, salta directament als slides de fracàs (7). + // Cal fer-ho ací perquè el SceneRegistry consulta num_piramide + // abans del fallback legacy; mentres doSecreta no estiga migrada + // també continuarà al Go() vell amb num_piramide ja corregida. + if (info::ctx.num_piramide == 6 && info::ctx.diners < 200) { + info::ctx.num_piramide = 7; + } + if (auto scene = scenes::SceneRegistry::instance().tryCreate(info::ctx.num_piramide)) { scene->onEnter(); Uint32 last = SDL_GetTicks(); @@ -112,6 +123,12 @@ void Director::init() { for (int p = 2; p <= 5; ++p) { registry.registerScene(p, [] { return std::make_unique(); }); } + // SlidesScene cobreix els dos states on el vell `doSlides` s'invocava: + // - num_piramide == 1: slides narratius inicials (entrada al joc) + // - num_piramide == 7: slides de fracàs (ve del redirect 6→7 quan + // l'usuari no té prou diners per a la Secreta) + registry.registerScene(1, [] { return std::make_unique(); }); + registry.registerScene(7, [] { 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/modulesequence.cpp b/source/game/modulesequence.cpp index d7d6499..0aaab49 100644 --- a/source/game/modulesequence.cpp +++ b/source/game/modulesequence.cpp @@ -39,10 +39,7 @@ int ModuleSequence::Go() { doIntro(); break; // case 0 (Menú) → migrat a scenes::MenuScene. - case 1: // Slides - case 7: - doSlides(); - break; + // case 1 i 7 (Slides) → migrat a scenes::SlidesScene. // case 2..5 (Pre-piràmide) → migrat a scenes::BannerScene. case 6: // Pre-Secreta doSecreta(); @@ -823,85 +820,7 @@ void ModuleSequence::doIntroSprites(JD8_Surface gfx) { // doMenu() — migrat a scenes::MenuScene (source/scenes/menu_scene.cpp) -void ModuleSequence::doSlides() { - JG_SetUpdateTicks(20); - - const char* arxiu; - if (info::ctx.num_piramide == 7) { - play_music("00000005.ogg", 1); - if (info::ctx.diners < 200) { - arxiu = "intro2.gif"; - } else { - arxiu = "intro3.gif"; - } - } else { - arxiu = "intro.gif"; - } - - JD8_Surface gfx = JD8_LoadSurface(arxiu); - JD8_Palette pal_aux = JD8_LoadPalette(arxiu); - JD8_Palette pal = (JD8_Palette)malloc(768); - memcpy(pal, pal_aux, 768); - JD8_ClearScreen(255); - JD8_SetScreenPalette(pal); - - bool exit = false; - int step = 0; - contador = 1; - while (!exit && !JG_Quitting()) { - if (JG_ShouldUpdate()) { - JI_Update(); - - if (JI_AnyKey()) { - exit = true; - } - - switch (step) { - case 0: - JD8_Blit(320 - (contador * 4), 65, gfx, 0, 0, contador * 4, 65); - JD8_Flip(); - contador++; - if (contador == 80) step++; - break; - case 3: - JD8_Blit(0, 65, gfx, 320 - (contador * 4), 65, contador * 4, 65); - JD8_Flip(); - contador++; - if (contador == 80) step++; - break; - case 6: - JD8_Blit(320 - (contador * 4), 65, gfx, 0, 130, contador * 4, 65); - JD8_Flip(); - contador++; - if (contador == 80) step++; - break; - case 1: - case 4: - case 7: - contador--; - if (contador == -150) { - contador = 0; - step++; - } - break; - case 2: - case 5: - JD8_FadeOut(); - memcpy(pal, pal_aux, 768); - JD8_ClearScreen(255); - step++; - break; - case 8: - if (info::ctx.num_piramide != 7) JA_FadeOutMusic(250); - exit = true; - break; - } - } - } - - JD8_FreeSurface(gfx); - free(pal_aux); -} +// doSlides() — migrat a scenes::SlidesScene (source/scenes/slides_scene.cpp) // doBanner() — migrat a scenes::BannerScene (source/scenes/banner_scene.cpp) diff --git a/source/game/modulesequence.hpp b/source/game/modulesequence.hpp index ecbf750..080e920 100644 --- a/source/game/modulesequence.hpp +++ b/source/game/modulesequence.hpp @@ -21,7 +21,7 @@ class ModuleSequence { void doIntro(); void doIntroNewLogo(); // doMenu() → migrat a scenes::MenuScene - void doSlides(); + // doSlides() → migrat a scenes::SlidesScene // doBanner() → migrat a scenes::BannerScene void doSecreta(); void doCredits(); diff --git a/source/scenes/intro_new_logo_scene.cpp b/source/scenes/intro_new_logo_scene.cpp index 1118a78..2a0960d 100644 --- a/source/scenes/intro_new_logo_scene.cpp +++ b/source/scenes/intro_new_logo_scene.cpp @@ -4,6 +4,7 @@ #include "core/jail/jdraw8.hpp" #include "core/jail/jinput.hpp" +#include "game/info.hpp" #include "game/modulesequence.hpp" #include "scenes/scene_utils.hpp" @@ -120,11 +121,14 @@ void IntroNewLogoScene::advancePaletteCycle() { } 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; + // 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_) { @@ -192,13 +196,18 @@ void IntroNewLogoScene::tick(int delta_ms) { 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à. + // 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_.get()); + 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; } diff --git a/source/scenes/scene_utils.cpp b/source/scenes/scene_utils.cpp index 3065c25..102d9c9 100644 --- a/source/scenes/scene_utils.cpp +++ b/source/scenes/scene_utils.cpp @@ -7,7 +7,7 @@ namespace scenes { -void playMusic(const char* filename) { +void playMusic(const char* filename, int loop) { if (!filename) return; int size = 0; char* buffer = file_getfilebuffer(filename, size); @@ -15,7 +15,7 @@ void playMusic(const char* filename) { // JA_LoadMusic fa una còpia del OGG comprimit (SDL_malloc), així que // el `buffer` original es queda huérfano. Leak conegut heredat del // codi original — es tractarà quan jfile tinga una API std::vector. - JA_PlayMusic(JA_LoadMusic(reinterpret_cast(buffer), size, filename)); + JA_PlayMusic(JA_LoadMusic(reinterpret_cast(buffer), size, filename), loop); } } // namespace scenes diff --git a/source/scenes/scene_utils.hpp b/source/scenes/scene_utils.hpp index 01ff71d..dab5d2e 100644 --- a/source/scenes/scene_utils.hpp +++ b/source/scenes/scene_utils.hpp @@ -7,6 +7,7 @@ namespace scenes { // Carrega un OGG de `data/` i arranca'l com a música de fons. Substituïx // el `play_music()` repetit en tots els doX() del vell modulesequence. -void playMusic(const char* filename); +// `loop`: -1 = infinit (per defecte), 0 = una sola vegada, N = N+1 passades. +void playMusic(const char* filename, int loop = -1); } // namespace scenes diff --git a/source/scenes/slides_scene.cpp b/source/scenes/slides_scene.cpp new file mode 100644 index 0000000..8b51717 --- /dev/null +++ b/source/scenes/slides_scene.cpp @@ -0,0 +1,186 @@ +#include "scenes/slides_scene.hpp" + +#include +#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" +#include "utils/easing.hpp" + +namespace { + +constexpr int SCROLL_MS = 1600; // 80 iters × 20 ms del vell doSlides +constexpr int HOLD_MS = 4600; // 230 iters × 20 ms (80 + 150) del vell +constexpr int SLIDE_Y = 65; +constexpr int SLIDE_H = 65; +constexpr int BG_COLOR_INDEX = 255; + +// Desplaçament inicial del slide segons direcció del wipe. +// Slide 1 i 3: "scroll in from right" (pos_x va de 320 → 0). +// Slide 2: "wipe reverse" (pos_x va de -320 → 0), el mateix efecte +// estrany del doSlides vell on la imatge es desplaça des de l'esquerra +// però revela primer el lateral dret del src. +constexpr int SLIDE_START_X[3] = {320, -320, 320}; + +} // namespace + +namespace scenes { + +SlidesScene::~SlidesScene() { + if (pal_aux_) std::free(pal_aux_); + // pal_active_ NO s'allibera: propietat de main_palette via SetScreenPalette. +} + +void SlidesScene::onEnter() { + num_piramide_at_start_ = info::ctx.num_piramide; + + const char* arxiu = nullptr; + if (num_piramide_at_start_ == 7) { + // loop=1 per replicar el vell `play_music("00000005.ogg", 1)`. + playMusic("00000005.ogg", 1); + arxiu = (info::ctx.diners < 200) ? "intro2.gif" : "intro3.gif"; + } else { + arxiu = "intro.gif"; + } + + gfx_ = SurfaceHandle(arxiu); + pal_aux_ = JD8_LoadPalette(arxiu); + + // Còpia editable de la paleta. `pal_active_` comparteix memòria amb + // main_palette després del SetScreenPalette — modificar-la modifica + // main_palette directament. `pal_aux_` es manté intacte per a poder + // restaurar després de cada fade-out intermedi. + pal_active_ = static_cast(std::malloc(768)); + std::memcpy(pal_active_, pal_aux_, 768); + JD8_SetScreenPalette(pal_active_); + + JD8_ClearScreen(BG_COLOR_INDEX); + + phase_ = Phase::Slide1Enter; + phase_acc_ms_ = 0; + next_state_ = 0; +} + +void SlidesScene::drawSlide(int slide_idx, int pos_x) { + const int src_y = slide_idx * SLIDE_H; + + // Clipping manual: translada un rect de 320×65 des de (pos_x, SLIDE_Y) + // a l'àrea visible (0..319, SLIDE_Y..SLIDE_Y+64). + int dst_x = pos_x; + int src_x = 0; + int w = 320; + + if (dst_x < 0) { + src_x = -dst_x; + w = 320 + dst_x; + dst_x = 0; + } else if (dst_x > 0) { + w = 320 - dst_x; + } + + if (w > 0) { + JD8_Blit(dst_x, SLIDE_Y, gfx_, src_x, src_y, w, SLIDE_H); + } +} + +void SlidesScene::restorePalette() { + std::memcpy(pal_active_, pal_aux_, 768); +} + +void SlidesScene::beginFinalFade() { + if (num_piramide_at_start_ != 7) { + JA_FadeOutMusic(250); + } + fade_.startFadeOut(); + phase_ = Phase::FadeFinal; +} + +void SlidesScene::tick(int delta_ms) { + // Skip: qualsevol tecla salta directament al fade final. Per fidelitat + // al vell doSlides, el skip NO atura la música explícitament — només + // el final natural crida JA_FadeOutMusic (beginFinalFade() distingeix). + if (!skip_triggered_ && JI_AnyKey()) { + skip_triggered_ = true; + if (num_piramide_at_start_ != 7) JA_FadeOutMusic(250); + fade_.startFadeOut(); + phase_ = Phase::FadeFinal; + } + + switch (phase_) { + case Phase::Slide1Enter: + case Phase::Slide2Enter: + case Phase::Slide3Enter: { + phase_acc_ms_ += delta_ms; + const int slide_idx = (phase_ == Phase::Slide1Enter ? 0 + : phase_ == Phase::Slide2Enter ? 1 + : 2); + const float t = std::min(1.0f, static_cast(phase_acc_ms_) / + static_cast(SCROLL_MS)); + const float eased = Easing::outCubic(t); + const int pos_x = Easing::lerpInt(SLIDE_START_X[slide_idx], 0, eased); + drawSlide(slide_idx, pos_x); + + if (phase_acc_ms_ >= SCROLL_MS) { + // Garanteix posició final exacta (pos_x=0). + drawSlide(slide_idx, 0); + if (phase_ == Phase::Slide1Enter) phase_ = Phase::Slide1Hold; + else if (phase_ == Phase::Slide2Enter) phase_ = Phase::Slide2Hold; + else phase_ = Phase::Slide3Hold; + phase_acc_ms_ = 0; + } + break; + } + + case Phase::Slide1Hold: + case Phase::Slide2Hold: + phase_acc_ms_ += delta_ms; + if (phase_acc_ms_ >= HOLD_MS) { + fade_.startFadeOut(); + if (phase_ == Phase::Slide1Hold) phase_ = Phase::FadeOut1; + else phase_ = Phase::FadeOut2; + phase_acc_ms_ = 0; + } + break; + + case Phase::Slide3Hold: + phase_acc_ms_ += delta_ms; + if (phase_acc_ms_ >= HOLD_MS) { + beginFinalFade(); + } + break; + + case Phase::FadeOut1: + case Phase::FadeOut2: + fade_.tick(delta_ms); + if (fade_.done()) { + restorePalette(); + JD8_ClearScreen(BG_COLOR_INDEX); + if (phase_ == Phase::FadeOut1) phase_ = Phase::Slide2Enter; + else phase_ = Phase::Slide3Enter; + phase_acc_ms_ = 0; + } + break; + + case Phase::FadeFinal: + fade_.tick(delta_ms); + if (fade_.done()) { + if (num_piramide_at_start_ == 7) { + info::ctx.num_piramide = 8; + next_state_ = 1; + } else { + next_state_ = 0; + } + phase_ = Phase::Done; + } + break; + + case Phase::Done: + break; + } +} + +} // namespace scenes diff --git a/source/scenes/slides_scene.hpp b/source/scenes/slides_scene.hpp new file mode 100644 index 0000000..43fc1de --- /dev/null +++ b/source/scenes/slides_scene.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include "core/jail/jdraw8.hpp" +#include "scenes/palette_fade.hpp" +#include "scenes/scene.hpp" +#include "scenes/surface_handle.hpp" + +namespace scenes { + +// 3 slides narratius amb scroll d'entrada + espera + transició amb +// fade-out. Reemplaça `ModuleSequence::doSlides()`. +// +// Tria d'asset segons context: +// - num_piramide == 7 i diners < 200: intro2.gif + música "00000005.ogg" +// - num_piramide == 7 i diners >= 200: intro3.gif + música "00000005.ogg" +// - altre cas (num_piramide == 1): intro.gif, sense música nova +// +// Flux: +// Slide1Enter (1600 ms scroll dreta→centre, easing outCubic) +// → Slide1Hold (4600 ms) +// → FadeOut1 + clear + reset paleta +// → Slide2Enter (1600 ms scroll esquerra→centre) +// → Slide2Hold (4600 ms) +// → FadeOut2 + clear + reset paleta +// → Slide3Enter (1600 ms scroll dreta→centre) +// → Slide3Hold (4600 ms) +// → FadeFinal (JA_FadeOutMusic si num_piramide != 7 + fade paleta) +// → Done +// +// Qualsevol tecla salta directament a FadeFinal (sense cortar la música +// si hem entrat per num_piramide==7, per fidelitat al vell). +// +// NextState: +// - num_piramide==7 al entrar → num_piramide=8 + return 1 (a Credits) +// - altre cas → return 0 (entra al ModuleGame) +class SlidesScene : public Scene { + public: + SlidesScene() = default; + ~SlidesScene() override; + + void onEnter() override; + void tick(int delta_ms) override; + bool done() const override { return phase_ == Phase::Done; } + int nextState() const override { return next_state_; } + + private: + enum class Phase { + Slide1Enter, + Slide1Hold, + FadeOut1, + Slide2Enter, + Slide2Hold, + FadeOut2, + Slide3Enter, + Slide3Hold, + FadeFinal, + Done, + }; + + // Pinta un slide amb desplaçament horitzontal. `slide_idx` = 0..2 + // (correspon a la franja 65x65 a y = 0, 65, 130 dins de gfx_). + // `pos_x` = desplaçament, amb clipping manual quan surt de pantalla. + void drawSlide(int slide_idx, int pos_x); + void restorePalette(); + void beginFinalFade(); + + SurfaceHandle gfx_; + JD8_Palette pal_aux_{nullptr}; // còpia "neta" que preservem + JD8_Palette pal_active_{nullptr}; // propietat transferida a main_palette + PaletteFade fade_; + + Phase phase_{Phase::Slide1Enter}; + int phase_acc_ms_{0}; + int num_piramide_at_start_{1}; + int next_state_{0}; + bool skip_triggered_{false}; +}; + +} // namespace scenes diff --git a/source/scenes/surface_handle.cpp b/source/scenes/surface_handle.cpp index 6d5b159..5b5a589 100644 --- a/source/scenes/surface_handle.cpp +++ b/source/scenes/surface_handle.cpp @@ -33,4 +33,10 @@ void SurfaceHandle::adopt(JD8_Surface raw) { surface_ = raw; } +JD8_Surface SurfaceHandle::release() { + JD8_Surface r = surface_; + surface_ = nullptr; + return r; +} + } // namespace scenes diff --git a/source/scenes/surface_handle.hpp b/source/scenes/surface_handle.hpp index 11a13a2..7c92968 100644 --- a/source/scenes/surface_handle.hpp +++ b/source/scenes/surface_handle.hpp @@ -29,6 +29,13 @@ class SurfaceHandle { // — la surface adoptada s'allibera al destructor o al següent reset/adopt. void adopt(JD8_Surface raw); + // Allibera ownership sense destruir la surface. Retorna el pointer cru; + // el caller passa a ser responsable d'alliberar-lo (o de passar-lo a un + // altre propietari). Usat quan una escena delega a codi legacy que + // també allibera la mateixa surface — cal "soltar" el ownership per + // evitar double free. + [[nodiscard]] JD8_Surface release(); + // Conversió implícita per al confort d'ús: JD8_Blit(handle) // en lloc de JD8_Blit(handle.get()). operator JD8_Surface() const { return surface_; }