From 4e18f83ec504287a9b8369cb1fafe513b8100ad4 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 16 Apr 2026 10:27:04 +0200 Subject: [PATCH] step B.1: fades de ModuleGame tick-based amb scenes::PaletteFade (fases FadingIn/FadingOut sense redibuixar, per no perdre el frame final) --- source/core/system/director.cpp | 42 ++-- source/game/modulegame.cpp | 369 ++++++++++++++++++-------------- source/game/modulegame.hpp | 91 +++++--- 3 files changed, 284 insertions(+), 218 deletions(-) diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index 34066b0..7ee9109 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -37,11 +37,11 @@ Director* Director::instance_ = nullptr; namespace { -// Entry del fiber del joc. Alterna entre la capa `scenes::` (cinemàtiques -// i menús, triats per `info::ctx.num_piramide` via el SceneRegistry) i -// ModuleGame (gameplay) fins que el joc demana eixir. Quan el codi de -// gameplay o una escena crida JD8_Flip(), el control torna automàticament -// al Director via `GameFiber::yield()`. +// Entry del fiber del joc. Dispatcha a una escena segons l'estat actual: +// `gameState == 0` → ModuleGame (gameplay), `gameState == 1` → una +// `scenes::Scene` del registry triada per `info::ctx.num_piramide`. Cada +// escena és tick-based; el JD8_Flip() entre ticks cedeix al Director +// via `GameFiber::yield()`. void gameFiberEntry() { info::ctx.num_habitacio = Options::game.habitacio_inicial; info::ctx.num_piramide = Options::game.piramide_inicial; @@ -60,31 +60,29 @@ void gameFiberEntry() { int gameState = 1; while (gameState != -1 && !JG_Quitting()) { + std::unique_ptr scene; + if (gameState == 0) { - // Gameplay pur (ModuleGame encara és cooperatiu-clàssic: conté - // el seu while intern i crida JD8_Flip manualment). Fora - // d'abast de la migració scenes:: — es tractarà en una fase - // posterior quan el fiber es puga eliminar. - auto* mg = new ModuleGame(); - gameState = mg->Go(); - delete mg; - continue; + // Gameplay. ModuleGame és una scenes::Scene des de Phase A de + // la migració — mateix mini-loop tick+flip que la resta. + scene = std::make_unique(); + } else { + // gameState == 1: dispatch al registry per num_piramide. El + // vell ModuleSequence::Go() feia aquest redirect al principi: + // si el jugador arriba a la Secreta (6) sense prou diners, + // salta als slides de fracàs (7) abans de buscar l'escena. + if (info::ctx.num_piramide == 6 && info::ctx.diners < 200) { + info::ctx.num_piramide = 7; + } + scene = scenes::SceneRegistry::instance().tryCreate(info::ctx.num_piramide); } - // gameState == 1: dispatch a la capa scenes::. - // El vell `ModuleSequence::Go()` feia aquest redirect al principi: - // si el jugador arriba a la Secreta (6) sense prou diners, salta - // als slides de fracàs (7) abans de buscar l'escena al registry. - if (info::ctx.num_piramide == 6 && info::ctx.diners < 200) { - info::ctx.num_piramide = 7; - } - - auto scene = scenes::SceneRegistry::instance().tryCreate(info::ctx.num_piramide); if (!scene) { // State no registrat — indica un bug del dispatcher o del // registre d'escenes. Eixim ordenadament en lloc de cremar CPU. break; } + scene->onEnter(); Uint32 last = SDL_GetTicks(); while (!scene->done() && !JG_Quitting()) { diff --git a/source/game/modulegame.cpp b/source/game/modulegame.cpp index c6198ef..951afeb 100644 --- a/source/game/modulegame.cpp +++ b/source/game/modulegame.cpp @@ -1,165 +1,204 @@ -#include "game/modulegame.hpp" - -#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" - -ModuleGame::ModuleGame() { - this->gfx = JD8_LoadSurface(info::ctx.pepe_activat ? "frames2.gif" : "frames.gif"); - JG_SetUpdateTicks(10); - - this->sam = new Prota(this->gfx); - this->mapa = new Mapa(this->gfx, this->sam); - this->marcador = new Marcador(this->gfx, this->sam); - if (info::ctx.num_piramide == 2) { - this->bola = new Bola(this->gfx, this->sam); - } else { - this->bola = NULL; - } - this->momies = NULL; - - this->final = 0; - this->iniciarMomies(); -} - -ModuleGame::~ModuleGame(void) { - JD8_FadeOut(); - - if (this->bola != NULL) delete this->bola; - if (this->momies != NULL) { - this->momies->clear(); - delete this->momies; - } - delete this->marcador; - delete this->mapa; - delete this->sam; - - JD8_FreeSurface(this->gfx); -} - -int ModuleGame::Go() { - this->Draw(); - - const char* music = info::ctx.num_piramide == 3 ? "00000008.ogg" : (info::ctx.num_piramide == 2 ? "00000007.ogg" : (info::ctx.num_piramide == 6 ? "00000002.ogg" : "00000006.ogg")); - const char* current_music = JA_GetMusicFilename(); - if ((JA_GetMusicState() != JA_MUSIC_PLAYING) || !(strcmp(music, current_music) == 0)) { - auto buffer = file_readfile(music); - JA_PlayMusic(JA_LoadMusic(reinterpret_cast(buffer.data()), - static_cast(buffer.size()), music)); - } - - JD8_FadeToPal(JD8_LoadPalette(info::ctx.pepe_activat ? "frames2.gif" : "frames.gif")); - - while (this->final == 0 && !JG_Quitting()) { - this->Draw(); - this->Update(); - } - - // JS_FadeOutMusic(); - - if (this->final == 1) { - info::ctx.num_habitacio++; - if (info::ctx.num_habitacio == 6) { - info::ctx.num_habitacio = 1; - info::ctx.num_piramide++; - } - if (info::ctx.num_piramide == 6 && info::ctx.num_habitacio == 2) info::ctx.num_piramide++; - } else if (this->final == 2) { - info::ctx.num_piramide = 100; - } - - if (JG_Quitting()) { - return -1; - } else { - if (info::ctx.num_habitacio == 1 || info::ctx.num_piramide == 100 || info::ctx.num_piramide == 7) { - return 1; - } else { - return 0; - } - } -} - -void ModuleGame::Draw() { - this->mapa->draw(); - this->marcador->draw(); - this->sam->draw(); - if (this->momies != NULL) this->momies->draw(); - if (this->bola != NULL) this->bola->draw(); - - JD8_Flip(); -} - -void ModuleGame::Update() { - if (JG_ShouldUpdate()) { - JI_Update(); - - this->final = this->sam->update(); - if (this->momies != NULL && this->momies->update()) { - Momia* seguent = this->momies->next; - delete this->momies; - this->momies = seguent; - info::ctx.momies--; - } - if (this->bola != NULL) this->bola->update(); - this->mapa->update(); - if (this->mapa->novaMomia()) { - if (this->momies != NULL) { - this->momies->insertar(new Momia(this->gfx, true, 0, 0, this->sam)); - info::ctx.momies++; - } else { - this->momies = new Momia(this->gfx, true, 0, 0, this->sam); - info::ctx.momies++; - } - } - - if (JI_CheatActivated("reviu")) info::ctx.vida = 5; - if (JI_CheatActivated("alone")) { - if (this->momies != NULL) { - this->momies->clear(); - delete this->momies; - this->momies = NULL; - info::ctx.momies = 0; - } - } - if (JI_CheatActivated("obert")) { - for (int i = 0; i < 16; i++) { - this->mapa->tombes[i].costat[0] = true; - this->mapa->tombes[i].costat[1] = true; - this->mapa->tombes[i].costat[2] = true; - this->mapa->tombes[i].costat[3] = true; - this->mapa->comprovaCaixa(i); - } - } - - if (JI_KeyPressed(SDL_SCANCODE_ESCAPE)) { - JG_QuitSignal(); - } - } -} - -void ModuleGame::iniciarMomies() { - if (info::ctx.num_habitacio == 1) { - info::ctx.momies = 1; - } else { - info::ctx.momies++; - } - if (info::ctx.num_piramide == 6) info::ctx.momies = 8; - - int x = 20; - int y = 170; - bool dimonis = info::ctx.num_piramide == 6; - for (int i = 0; i < info::ctx.momies; i++) { - if (this->momies == NULL) { - this->momies = new Momia(this->gfx, dimonis, x, y, this->sam); - } else { - this->momies->insertar(new Momia(this->gfx, dimonis, x, y, this->sam)); - } - x += 65; - if (x == 345) { - x = 20; - y -= 35; - } - } -} +#include "game/modulegame.hpp" + +#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" + +ModuleGame::ModuleGame() { + this->gfx = JD8_LoadSurface(info::ctx.pepe_activat ? "frames2.gif" : "frames.gif"); + JG_SetUpdateTicks(10); + + this->sam = new Prota(this->gfx); + this->mapa = new Mapa(this->gfx, this->sam); + this->marcador = new Marcador(this->gfx, this->sam); + if (info::ctx.num_piramide == 2) { + this->bola = new Bola(this->gfx, this->sam); + } else { + this->bola = nullptr; + } + this->momies = nullptr; + + this->iniciarMomies(); +} + +ModuleGame::~ModuleGame() { + if (this->bola != nullptr) delete this->bola; + if (this->momies != nullptr) { + this->momies->clear(); + delete this->momies; + } + delete this->marcador; + delete this->mapa; + delete this->sam; + + JD8_FreeSurface(this->gfx); +} + +void ModuleGame::onEnter() { + // Primera Draw per omplir `screen` amb el contingut del gameplay + // abans que el fade-in arranque. Si no, les primeres iteracions del + // fade interpolarien cap a una paleta amb pantalla buida. + this->Draw(); + + const char* music = info::ctx.num_piramide == 3 ? "00000008.ogg" + : info::ctx.num_piramide == 2 ? "00000007.ogg" + : info::ctx.num_piramide == 6 ? "00000002.ogg" + : "00000006.ogg"; + const char* current_music = JA_GetMusicFilename(); + if ((JA_GetMusicState() != JA_MUSIC_PLAYING) || !current_music || + strcmp(music, current_music) != 0) { + auto buffer = file_readfile(music); + JA_PlayMusic(JA_LoadMusic(reinterpret_cast(buffer.data()), + static_cast(buffer.size()), music)); + } + + // Arranca el fade-in tick-based. El `PaletteFade` avança un pas (de + // 32) per cada tick; durant aquesta fase el gameplay no corre, + // només Draw+fade. Substituïx la crida bloquejant `JD8_FadeToPal`. + fade_.startFadeTo(JD8_LoadPalette(info::ctx.pepe_activat ? "frames2.gif" : "frames.gif")); + phase_ = Phase::FadingIn; +} + +void ModuleGame::tick(int delta_ms) { + switch (phase_) { + case Phase::FadingIn: + // No redibuixem durant el fade: el `screen` ja va ser omplit + // per la Draw() d'onEnter. Només el JD8_Flip del caller muta + // pixel_data segons la paleta que avança pas a pas. + fade_.tick(delta_ms); + if (fade_.done()) phase_ = Phase::Playing; + break; + + case Phase::Playing: + this->Draw(); + this->Update(); + if (this->final_ != 0) { + this->applyFinalTransitions(); + fade_.startFadeOut(); + phase_ = Phase::FadingOut; + } + break; + + case Phase::FadingOut: + // No redibuixem: el `screen` té l'últim frame pintat per la + // fase Playing (just abans que Update() setegés `final_`). + // El vell `JD8_FadeOut` feia exactament això — flips amb + // paleta fading però sense tocar el buffer. Redibuixar ací + // mostraria l'estat post-Update del sprite (p.ex. el prota + // "tornant" davant la porta després d'haver eixit). + fade_.tick(delta_ms); + if (fade_.done()) phase_ = Phase::Done; + break; + + case Phase::Done: + break; + } +} + +int ModuleGame::nextState() const { + if (JG_Quitting()) return -1; + if (info::ctx.num_habitacio == 1 || + info::ctx.num_piramide == 100 || + info::ctx.num_piramide == 7) { + return 1; + } + return 0; +} + +void ModuleGame::applyFinalTransitions() { + if (this->final_ == 1) { + info::ctx.num_habitacio++; + if (info::ctx.num_habitacio == 6) { + info::ctx.num_habitacio = 1; + info::ctx.num_piramide++; + } + if (info::ctx.num_piramide == 6 && info::ctx.num_habitacio == 2) info::ctx.num_piramide++; + } else if (this->final_ == 2) { + info::ctx.num_piramide = 100; + } +} + +void ModuleGame::Draw() { + // No crida JD8_Flip — el caller (mini-loop del fiber, o Director a + // Phase B.2) ho fa després de cada tick. + this->mapa->draw(); + this->marcador->draw(); + this->sam->draw(); + if (this->momies != nullptr) this->momies->draw(); + if (this->bola != nullptr) this->bola->draw(); +} + +void ModuleGame::Update() { + if (JG_ShouldUpdate()) { + JI_Update(); + + this->final_ = this->sam->update(); + if (this->momies != nullptr && this->momies->update()) { + Momia* seguent = this->momies->next; + delete this->momies; + this->momies = seguent; + info::ctx.momies--; + } + if (this->bola != nullptr) this->bola->update(); + this->mapa->update(); + if (this->mapa->novaMomia()) { + if (this->momies != nullptr) { + this->momies->insertar(new Momia(this->gfx, true, 0, 0, this->sam)); + info::ctx.momies++; + } else { + this->momies = new Momia(this->gfx, true, 0, 0, this->sam); + info::ctx.momies++; + } + } + + if (JI_CheatActivated("reviu")) info::ctx.vida = 5; + if (JI_CheatActivated("alone")) { + if (this->momies != nullptr) { + this->momies->clear(); + delete this->momies; + this->momies = nullptr; + info::ctx.momies = 0; + } + } + if (JI_CheatActivated("obert")) { + for (int i = 0; i < 16; i++) { + this->mapa->tombes[i].costat[0] = true; + this->mapa->tombes[i].costat[1] = true; + this->mapa->tombes[i].costat[2] = true; + this->mapa->tombes[i].costat[3] = true; + this->mapa->comprovaCaixa(i); + } + } + + if (JI_KeyPressed(SDL_SCANCODE_ESCAPE)) { + JG_QuitSignal(); + } + } +} + +void ModuleGame::iniciarMomies() { + if (info::ctx.num_habitacio == 1) { + info::ctx.momies = 1; + } else { + info::ctx.momies++; + } + if (info::ctx.num_piramide == 6) info::ctx.momies = 8; + + int x = 20; + int y = 170; + bool dimonis = info::ctx.num_piramide == 6; + for (int i = 0; i < info::ctx.momies; i++) { + if (this->momies == nullptr) { + this->momies = new Momia(this->gfx, dimonis, x, y, this->sam); + } else { + this->momies->insertar(new Momia(this->gfx, dimonis, x, y, this->sam)); + } + x += 65; + if (x == 345) { + x = 20; + y -= 35; + } + } +} diff --git a/source/game/modulegame.hpp b/source/game/modulegame.hpp index 600d68f..338b92d 100644 --- a/source/game/modulegame.hpp +++ b/source/game/modulegame.hpp @@ -1,31 +1,60 @@ -#pragma once - -#include "game/bola.hpp" -#include "game/info.hpp" -#include "game/mapa.hpp" -#include "game/marcador.hpp" -#include "game/momia.hpp" -#include "game/prota.hpp" - -class ModuleGame { - public: - ModuleGame(); - ~ModuleGame(void); - - int Go(); - - private: - void Draw(); - void Update(); - - void iniciarMomies(); - - Uint8 final; - JD8_Surface gfx; - - Mapa* mapa; - Prota* sam; - Marcador* marcador; - Momia* momies; - Bola* bola; -}; +#pragma once + +#include "game/bola.hpp" +#include "game/info.hpp" +#include "game/mapa.hpp" +#include "game/marcador.hpp" +#include "game/momia.hpp" +#include "game/prota.hpp" +#include "scenes/palette_fade.hpp" +#include "scenes/scene.hpp" + +// Escena de gameplay pur. Reemplaça el vell `Go()` bloquejant amb +// l'interfície `scenes::Scene` tick-based: `onEnter()` arranca la +// música i un fade-in, el `tick()` avança un frame (Draw + Update +// gated per JG_ShouldUpdate), i quan la partida acaba fa un fade-out +// abans de retornar el next state. +// +// Tres fases internes: +// 1. FadingIn — fade-in 32 passos mentre el render segueix viu. +// 2. Playing — gameplay normal; `final_` es setja quan el prota mor +// o canvia de sala. `Update()` només avança cada 10 ms +// via `JG_ShouldUpdate` (ticker fix del jail). +// 3. FadingOut — fade-out 32 passos mantenint l'últim frame visible +// (substituïx el `JD8_FadeOut` bloquejant que feia el +// destructor legacy). +class ModuleGame : public scenes::Scene { + public: + ModuleGame(); + ~ModuleGame() override; + + void onEnter() override; + void tick(int delta_ms) override; + bool done() const override { return phase_ == Phase::Done; } + int nextState() const override; + + private: + enum class Phase { + FadingIn, + Playing, + FadingOut, + Done, + }; + + void Draw(); // render a `screen`; no crida JD8_Flip (ho fa el caller) + void Update(); // gated per JG_ShouldUpdate + + void iniciarMomies(); + void applyFinalTransitions(); // muta info::ctx quan final_ passa a !=0 + + Phase phase_{Phase::FadingIn}; + scenes::PaletteFade fade_; + Uint8 final_{0}; + JD8_Surface gfx{nullptr}; + + Mapa* mapa{nullptr}; + Prota* sam{nullptr}; + Marcador* marcador{nullptr}; + Momia* momies{nullptr}; + Bola* bola{nullptr}; +};