5 Commits

20 changed files with 1078 additions and 1235 deletions

View File

@@ -58,7 +58,7 @@ The scenes layer itself lives in [source/scenes/](source/scenes/): `scene.hpp` (
- Collision detection, scoring, lives, level progression - Collision detection, scoring, lives, level progression
- Visible animation cadence (once translated to ms, must look identical) - Visible animation cadence (once translated to ms, must look identical)
- Difficulty curves and cinematic timings - Difficulty curves and cinematic timings
- Cheat codes (`reviu`, `alone`, `obert`) — currently broken, should be restored - Cheat codes (`reviu`, `alone`, `obert`)
- Original palettes, fades, music cues - Original palettes, fades, music cues
**Free to change** (internal representation): **Free to change** (internal representation):
@@ -235,10 +235,10 @@ JD8_Flip produces ABGR byte order: `0xFF000000 + R + (G<<8) + (B<<16)`. SDL text
### Known Issues & Technical Debt ### Known Issues & Technical Debt
1. **gif.h cannot be included twice**: Functions are not `static` or `inline`, causing multiple definition errors. Text class uses `extern` forward declarations as workaround 1. **gif.h cannot be included twice**: Functions are not `static` or `inline`, causing multiple definition errors. Text class uses `extern` forward declarations as workaround
2. **Cheats are broken (`reviu`, `alone`, `obert`)**: `JI_CheatActivated` in [jinput.cpp:46](source/core/jail/jinput.cpp#L46) compares `SDL_Scancode` values (e.g. `SDL_SCANCODE_R`=21) against ASCII chars (`'r'`=114). They never match. Regression from SDL3 migration. **Now fixable** — scheduled for Phase 1 of modernization since jail is no longer off-limits. 2. ~~**Cheats are broken (`reviu`, `alone`, `obert`)**~~: Fixed. `JI_moveCheats` tradueix `SDL_Scancode` → ASCII via `scancode_to_ascii` abans de ficar-los al buffer ([jinput.cpp:32-37, 55-61](source/core/jail/jinput.cpp#L32-L61)), i `JI_CheatActivated` compara ASCII amb ASCII.
3. **No sound effects in game**: Game code never calls `JA_PlaySound*`/`JA_LoadSound` — only music via `JA_PlayMusic`/`JA_FadeOutMusic`. The SONS and VOL SONS menu items control volume of an empty channel pool. Infrastructure ready for future SFX. 3. **No sound effects in game**: Game code never calls `JA_PlaySound*`/`JA_LoadSound` — only music via `JA_PlayMusic`/`JA_FadeOutMusic`. The SONS and VOL SONS menu items control volume of an empty channel pool. Infrastructure ready for future SFX.
4. **Raw `malloc`/`free` in gameplay structs**: `Sprite`/`Entitat` use `malloc` for `Frame[]` and `Animacio[]`; `jfile.cpp` uses a global `scratch[255]` buffer (UB under concurrent calls); `jail_audio.cpp` mixes `new`/`malloc`/`SDL_malloc`. Scheduled for Phase 1 (RAII pass). 4. ~~**Raw `malloc`/`free` in gameplay structs**~~: Majoritàriament arreglat. `Sprite`/`Entitat` usen `std::vector<Frame>` i `std::vector<Animacio>` ([sprite.hpp](source/game/sprite.hpp)). `jfile.cpp` ja no té el global `scratch[255]` (substituït per `thread_local std::string`). L'API `file_getfilebuffer` (que tornava raw `char*` amb `malloc`) s'ha substituït per `file_readfile` que retorna `std::vector<char>` RAII — elimina els leaks silenciosos que hi havia a `JD8_LoadPalette`, `ModuleGame::Go()` i `scenes::playMusic`. Queda `jail_audio.hpp` barrejant `new`/`malloc`/`SDL_malloc` de forma pairada i correcta (no leak), pendent de polir amb `std::unique_ptr` quan toque.
5. **Blocking loops in cinematics and fades**: `ModuleSequence::doIntro()` has 15+ `while(!JG_ShouldUpdate())` spin-waits; `JD8_FadeOut`/`JD8_FadeToPal` run 32 internal iterations calling `JD8_Flip`. Incompatible with `SDL_AppIterate`. Scheduled for Phase 2 (state-machine refactor). 5. ~~**Blocking loops in cinematics and fades**~~: Fixed. La migració completa de `ModuleSequence::do*()` a la capa `scenes::` (Steps 19) ha eliminat tots els `while(!JG_ShouldUpdate())` i `wait_frame_or_skip()`. Les cinemàtiques ara són tick-based amb acumuladors ms. `JD8_FadeOut`/`JD8_FadeToPal` encara tenen el seu bucle intern de 32 passos (usat per a transicions fora d'escena com al final de `ModuleGame`); el wrapper tick-based `scenes::PaletteFade` el consumeix un pas per tick quan es crida des d'una escena.
6. ~~**`SDL_AddTimer` in audio**~~: Fixed in Phase 3. `jail_audio` is now a header-only `inline` module (single `.cpp` stub only hosts the `stb_vorbis` implementation to avoid multiple definitions). Music uses true streaming via `stb_vorbis_open_memory` + `JA_PumpMusic` with a 0.5s low-water-mark instead of decoding the whole OGG into RAM. Mixing/fade update runs manually via `JA_Update()` called once per frame from `Director::run()`. Ported from the `jaildoctors_dilemma` codebase. 6. ~~**`SDL_AddTimer` in audio**~~: Fixed in Phase 3. `jail_audio` is now a header-only `inline` module (single `.cpp` stub only hosts the `stb_vorbis` implementation to avoid multiple definitions). Music uses true streaming via `stb_vorbis_open_memory` + `JA_PumpMusic` with a 0.5s low-water-mark instead of decoding the whole OGG into RAM. Mixing/fade update runs manually via `JA_Update()` called once per frame from `Director::run()`. Ported from the `jaildoctors_dilemma` codebase.
7. ~~**Game thread + `publishFrame` mutex/cv**~~: Fixed in Phase 4+5. Replaced by a cooperative `GameFiber` (ucontext on POSIX, Fibers API on Windows). `JD8_Flip()` calls `GameFiber::yield()`, Director calls `GameFiber::resume()` once per frame. Zero threads, zero mutexes. Emscripten fiber backend still pending for Phase 7. 7. ~~**Game thread + `publishFrame` mutex/cv**~~: Fixed in Phase 4+5. Replaced by a cooperative `GameFiber` (ucontext on POSIX, Fibers API on Windows). `JD8_Flip()` calls `GameFiber::yield()`, Director calls `GameFiber::resume()` once per frame. Zero threads, zero mutexes. Emscripten fiber backend still pending for Phase 7.

View File

@@ -53,6 +53,8 @@ set(APP_SOURCES
source/scenes/banner_scene.cpp source/scenes/banner_scene.cpp
source/scenes/menu_scene.cpp source/scenes/menu_scene.cpp
source/scenes/intro_new_logo_scene.cpp source/scenes/intro_new_logo_scene.cpp
source/scenes/intro_scene.cpp
source/scenes/intro_sprites_scene.cpp
source/scenes/slides_scene.cpp source/scenes/slides_scene.cpp
source/scenes/credits_scene.cpp source/scenes/credits_scene.cpp
source/scenes/secreta_scene.cpp source/scenes/secreta_scene.cpp
@@ -65,7 +67,6 @@ set(APP_SOURCES
source/game/mapa.cpp source/game/mapa.cpp
source/game/marcador.cpp source/game/marcador.cpp
source/game/modulegame.cpp source/game/modulegame.cpp
source/game/modulesequence.cpp
source/game/momia.cpp source/game/momia.cpp
source/game/prota.cpp source/game/prota.cpp
source/game/sprite.cpp source/game/sprite.cpp

View File

@@ -7,6 +7,9 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <string>
#include <vector>
#define STB_VORBIS_HEADER_ONLY #define STB_VORBIS_HEADER_ONLY
#include "external/stb_vorbis.h" #include "external/stb_vorbis.h"
@@ -48,13 +51,15 @@ struct JA_Channel_t {
struct JA_Music_t { struct JA_Music_t {
SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000}; SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
// OGG comprimit en memòria. Propietat nostra; es copia des del fitxer una // OGG comprimit en memòria. Propietat nostra; es copia des del buffer
// sola vegada en JA_LoadMusic i es descomprimix en chunks per streaming. // d'entrada una sola vegada en JA_LoadMusic i es descomprimix en chunks
Uint8* ogg_data{nullptr}; // per streaming. Com que stb_vorbis guarda un punter persistent al
Uint32 ogg_length{0}; // `.data()` d'aquest vector, no el podem resize'jar un cop establert
// (una reallocation invalidaria el punter que el decoder conserva).
std::vector<Uint8> ogg_data;
stb_vorbis* vorbis{nullptr}; // handle del decoder, viu tot el cicle del JA_Music_t stb_vorbis* vorbis{nullptr}; // handle del decoder, viu tot el cicle del JA_Music_t
char* filename{nullptr}; std::string filename;
int times{0}; // loops restants (-1 = infinit, 0 = un sol play) int times{0}; // loops restants (-1 = infinit, 0 = un sol play)
SDL_AudioStream* stream{nullptr}; SDL_AudioStream* stream{nullptr};
@@ -200,26 +205,23 @@ inline void JA_Quit() {
inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) { inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) {
if (!buffer || length == 0) return nullptr; if (!buffer || length == 0) return nullptr;
// Còpia del OGG comprimit: stb_vorbis llig de forma persistent aquesta // Allocem el JA_Music_t primer per aprofitar el seu `std::vector<Uint8>`
// memòria mentre el handle estiga viu, així que hem de posseir-la nosaltres. // com a propietari del OGG comprimit. stb_vorbis guarda un punter
Uint8* ogg_copy = static_cast<Uint8*>(SDL_malloc(length)); // persistent al buffer; com que ací no el resize'jem, el .data() és
if (!ogg_copy) return nullptr; // estable durant tot el cicle de vida del music.
SDL_memcpy(ogg_copy, buffer, length); auto* music = new JA_Music_t();
music->ogg_data.assign(buffer, buffer + length);
int error = 0; int error = 0;
stb_vorbis* vorbis = stb_vorbis_open_memory(ogg_copy, static_cast<int>(length), &error, nullptr); music->vorbis = stb_vorbis_open_memory(music->ogg_data.data(),
if (!vorbis) { static_cast<int>(length), &error, nullptr);
SDL_free(ogg_copy); if (!music->vorbis) {
SDL_Log("JA_LoadMusic: stb_vorbis_open_memory failed (error %d)", error); SDL_Log("JA_LoadMusic: stb_vorbis_open_memory failed (error %d)", error);
delete music;
return nullptr; return nullptr;
} }
auto* music = new JA_Music_t(); const stb_vorbis_info info = stb_vorbis_get_info(music->vorbis);
music->ogg_data = ogg_copy;
music->ogg_length = length;
music->vorbis = vorbis;
const stb_vorbis_info info = stb_vorbis_get_info(vorbis);
music->spec.channels = info.channels; music->spec.channels = info.channels;
music->spec.freq = static_cast<int>(info.sample_rate); music->spec.freq = static_cast<int>(info.sample_rate);
music->spec.format = SDL_AUDIO_S16; music->spec.format = SDL_AUDIO_S16;
@@ -228,38 +230,11 @@ inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) {
return music; return music;
} }
// Overload de compatibilitat: accepta filename per a no trencar els call // Overload amb filename — els callers l'usen per poder comparar la música
// sites existents que passaven el nom del fitxer junt amb el buffer. // en curs amb JA_GetMusicFilename() i no rearrancar-la si ja és la mateixa.
inline JA_Music_t* JA_LoadMusic(Uint8* buffer, Uint32 length, const char* filename) { inline JA_Music_t* JA_LoadMusic(Uint8* buffer, Uint32 length, const char* filename) {
JA_Music_t* music = JA_LoadMusic(static_cast<const Uint8*>(buffer), length); JA_Music_t* music = JA_LoadMusic(static_cast<const Uint8*>(buffer), length);
if (music && filename) { if (music && filename) music->filename = filename;
music->filename = static_cast<char*>(malloc(strlen(filename) + 1));
if (music->filename) strcpy(music->filename, filename);
}
return music;
}
inline JA_Music_t* JA_LoadMusic(const char* filename) {
FILE* f = fopen(filename, "rb");
if (!f) return nullptr;
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET);
auto* buffer = static_cast<Uint8*>(malloc(fsize + 1));
if (!buffer) {
fclose(f);
return nullptr;
}
if (fread(buffer, fsize, 1, f) != 1) {
fclose(f);
free(buffer);
return nullptr;
}
fclose(f);
JA_Music_t* music = JA_LoadMusic(buffer, static_cast<Uint32>(fsize), filename);
free(buffer);
return music; return music;
} }
@@ -290,10 +265,10 @@ inline void JA_PlayMusic(JA_Music_t* music, const int loop = -1) {
if (!SDL_BindAudioStream(sdlAudioDevice, current_music->stream)) printf("[ERROR] SDL_BindAudioStream failed!\n"); if (!SDL_BindAudioStream(sdlAudioDevice, current_music->stream)) printf("[ERROR] SDL_BindAudioStream failed!\n");
} }
inline char* JA_GetMusicFilename(JA_Music_t* music = nullptr) { inline const char* JA_GetMusicFilename(JA_Music_t* music = nullptr) {
if (!music) music = current_music; if (!music) music = current_music;
if (!music) return nullptr; if (!music || music->filename.empty()) return nullptr;
return music->filename; return music->filename.c_str();
} }
inline void JA_PauseMusic() { inline void JA_PauseMusic() {
@@ -352,8 +327,8 @@ inline void JA_DeleteMusic(JA_Music_t* music) {
} }
if (music->stream) SDL_DestroyAudioStream(music->stream); if (music->stream) SDL_DestroyAudioStream(music->stream);
if (music->vorbis) stb_vorbis_close(music->vorbis); if (music->vorbis) stb_vorbis_close(music->vorbis);
SDL_free(music->ogg_data); // ogg_data (std::vector) i filename (std::string) s'alliberen sols
free(music->filename); // al destructor de JA_Music_t.
delete music; delete music;
} }

View File

@@ -45,13 +45,10 @@ JD8_Surface JD8_NewSurface() {
} }
JD8_Surface JD8_LoadSurface(const char* file) { JD8_Surface JD8_LoadSurface(const char* file) {
int filesize = 0; auto buffer = file_readfile(file);
char* buffer = file_getfilebuffer(file, filesize);
unsigned short w, h; unsigned short w, h;
Uint8* pixels = LoadGif((unsigned char*)buffer, &w, &h); Uint8* pixels = LoadGif(reinterpret_cast<unsigned char*>(buffer.data()), &w, &h);
free(buffer);
if (pixels == NULL) { if (pixels == NULL) {
printf("Unable to load bitmap: %s\n", SDL_GetError()); printf("Unable to load bitmap: %s\n", SDL_GetError());
@@ -66,13 +63,8 @@ JD8_Surface JD8_LoadSurface(const char* file) {
} }
JD8_Palette JD8_LoadPalette(const char* file) { JD8_Palette JD8_LoadPalette(const char* file) {
int filesize = 0; auto buffer = file_readfile(file);
char* buffer = NULL; return (JD8_Palette)LoadPalette(reinterpret_cast<unsigned char*>(buffer.data()));
buffer = file_getfilebuffer(file, filesize);
JD8_Palette palette = (JD8_Palette)LoadPalette((unsigned char*)buffer);
return palette;
} }
void JD8_SetScreenPalette(JD8_Palette palette) { void JD8_SetScreenPalette(JD8_Palette palette) {

View File

@@ -147,12 +147,12 @@ FILE* file_getfilepointer(const char* resourcename, int& filesize, const bool bi
return f; return f;
} }
char* file_getfilebuffer(const char* resourcename, int& filesize, const bool zero_terminate) { std::vector<char> file_readfile(const char* resourcename) {
int filesize = 0;
FILE* f = file_getfilepointer(resourcename, filesize, true); FILE* f = file_getfilepointer(resourcename, filesize, true);
if (!f) return nullptr; if (!f) return {};
char* buffer = static_cast<char*>(malloc(zero_terminate ? filesize + 1 : filesize)); std::vector<char> buffer(filesize);
fread(buffer, filesize, 1, f); fread(buffer.data(), filesize, 1, f);
if (zero_terminate) buffer[filesize] = 0;
fclose(f); fclose(f);
return buffer; return buffer;
} }

View File

@@ -1,6 +1,8 @@
#pragma once #pragma once
#include <stdio.h> #include <stdio.h>
#include <vector>
#define SOURCE_FILE 0 #define SOURCE_FILE 0
#define SOURCE_FOLDER 1 #define SOURCE_FOLDER 1
@@ -12,7 +14,12 @@ void file_setresourcefolder(const char* str);
void file_setsource(const int src); void file_setsource(const int src);
FILE* file_getfilepointer(const char* resourcename, int& filesize, const bool binary = false); FILE* file_getfilepointer(const char* resourcename, int& filesize, const bool binary = false);
char* file_getfilebuffer(const char* resourcename, int& filesize, const bool zero_terminate = false);
// Llig tot el contingut d'un recurs (fitxer solt o entrada del .jrf).
// Retorna un vector buit si el recurs no existeix. El vector es destrueix
// automàticament en eixir d'àmbit — no fa falta cap free() manual. Mida =
// bytes llegits (el buffer no està null-terminated).
std::vector<char> file_readfile(const char* resourcename);
const char* file_getconfigvalue(const char* key); const char* file_getconfigvalue(const char* key);
void file_setconfigvalue(const char* key, const char* value); void file_setconfigvalue(const char* key, const char* value);

View File

@@ -27,14 +27,12 @@ namespace Locale {
} }
bool load(const char* filename) { bool load(const char* filename) {
int size = 0; auto buffer = file_readfile(filename);
char* buffer = file_getfilebuffer(filename, size, true); if (buffer.empty()) {
if (!buffer || size <= 0) {
std::cerr << "Locale: unable to load " << filename << '\n'; std::cerr << "Locale: unable to load " << filename << '\n';
return false; return false;
} }
std::string content(buffer, size); std::string content(buffer.data(), buffer.size());
free(buffer);
try { try {
auto yaml = fkyaml::node::deserialize(content); auto yaml = fkyaml::node::deserialize(content);

View File

@@ -62,15 +62,13 @@ auto Text::nextCodepoint(const char*& ptr) -> uint32_t {
// --- Càrrega de font --- // --- Càrrega de font ---
void Text::loadFont(const char* fnt_file) { void Text::loadFont(const char* fnt_file) {
int filesize = 0; auto buffer = file_readfile(fnt_file);
char* buffer = file_getfilebuffer(fnt_file, filesize, true); if (buffer.empty()) {
if (!buffer) {
std::cerr << "Text: unable to load font file: " << fnt_file << '\n'; std::cerr << "Text: unable to load font file: " << fnt_file << '\n';
return; return;
} }
std::istringstream stream(std::string(buffer, filesize)); std::istringstream stream(std::string(buffer.data(), buffer.size()));
free(buffer);
std::string line; std::string line;
int glyph_index = 0; int glyph_index = 0;
@@ -128,15 +126,14 @@ void Text::loadFont(const char* fnt_file) {
} }
void Text::loadBitmap(const char* gif_file) { void Text::loadBitmap(const char* gif_file) {
int filesize = 0; auto buffer = file_readfile(gif_file);
char* buffer = file_getfilebuffer(gif_file, filesize); if (buffer.empty()) {
if (!buffer) {
std::cerr << "Text: unable to load bitmap: " << gif_file << '\n'; std::cerr << "Text: unable to load bitmap: " << gif_file << '\n';
return; return;
} }
// Extrau dimensions del header GIF (bytes 6-7 = width, 8-9 = height, little-endian) // Extrau dimensions del header GIF (bytes 6-7 = width, 8-9 = height, little-endian)
auto* raw = reinterpret_cast<unsigned char*>(buffer); auto* raw = reinterpret_cast<unsigned char*>(buffer.data());
int w = raw[6] | (raw[7] << 8); int w = raw[6] | (raw[7] << 8);
int h = raw[8] | (raw[9] << 8); int h = raw[8] | (raw[9] << 8);
@@ -144,7 +141,6 @@ void Text::loadBitmap(const char* gif_file) {
Uint8* pixels = LoadGif(raw, &gw, &gh); Uint8* pixels = LoadGif(raw, &gw, &gh);
if (!pixels) { if (!pixels) {
std::cerr << "Text: unable to decode GIF: " << gif_file << '\n'; std::cerr << "Text: unable to decode GIF: " << gif_file << '\n';
free(buffer);
return; return;
} }
@@ -152,7 +148,6 @@ void Text::loadBitmap(const char* gif_file) {
bitmap_height_ = h; bitmap_height_ = h;
bitmap_ = pixels; bitmap_ = pixels;
free(buffer);
std::cout << "Text: bitmap loaded " << w << "x" << h << '\n'; std::cout << "Text: bitmap loaded " << w << "x" << h << '\n';
} }

View File

@@ -18,11 +18,11 @@
#include "core/system/fiber.hpp" #include "core/system/fiber.hpp"
#include "game/info.hpp" #include "game/info.hpp"
#include "game/modulegame.hpp" #include "game/modulegame.hpp"
#include "game/modulesequence.hpp"
#include "game/options.hpp" #include "game/options.hpp"
#include "scenes/banner_scene.hpp" #include "scenes/banner_scene.hpp"
#include "scenes/credits_scene.hpp" #include "scenes/credits_scene.hpp"
#include "scenes/intro_new_logo_scene.hpp" #include "scenes/intro_new_logo_scene.hpp"
#include "scenes/intro_scene.hpp"
#include "scenes/menu_scene.hpp" #include "scenes/menu_scene.hpp"
#include "scenes/mort_scene.hpp" #include "scenes/mort_scene.hpp"
#include "scenes/scene.hpp" #include "scenes/scene.hpp"
@@ -37,10 +37,11 @@ Director* Director::instance_ = nullptr;
namespace { namespace {
// Entry del fiber del joc. Executa la màquina d'estats que alterna entre // Entry del fiber del joc. Dispatcha a una escena segons l'estat actual:
// ModuleSequence (state=1) i ModuleGame (state=0) fins que el joc demana // `gameState == 0` → ModuleGame (gameplay), `gameState == 1` → una
// eixir. Quan el joc crida JD8_Flip() des de dins d'aquest fiber, el // `scenes::Scene` del registry triada per `info::ctx.num_piramide`. Cada
// control torna automàticament al Director. // escena és tick-based; el JD8_Flip() entre ticks cedeix al Director
// via `GameFiber::yield()`.
void gameFiberEntry() { void gameFiberEntry() {
info::ctx.num_habitacio = Options::game.habitacio_inicial; info::ctx.num_habitacio = Options::game.habitacio_inicial;
info::ctx.num_piramide = Options::game.piramide_inicial; info::ctx.num_piramide = Options::game.piramide_inicial;
@@ -59,51 +60,39 @@ void gameFiberEntry() {
int gameState = 1; int gameState = 1;
while (gameState != -1 && !JG_Quitting()) { while (gameState != -1 && !JG_Quitting()) {
// Mode "Scene nova": si el state actual és de seqüència i el std::unique_ptr<scenes::Scene> scene;
// registry té una escena migrada per al num_piramide, l'executem
// amb un mini-loop tick-based. El codi de la Scene no toca if (gameState == 0) {
// fibers; el JD8_Flip() entre ticks és el que cedeix al Director. // Gameplay. ModuleGame és una scenes::Scene des de Phase A de
if (gameState == 1) { // la migració — mateix mini-loop tick+flip que la resta.
// Replica del redirect que el `ModuleSequence::Go()` vell feia scene = std::make_unique<ModuleGame>();
// al principi: si el jugador arriba a la piràmide Secreta (6) } else {
// sense prou diners, salta directament als slides de fracàs (7). // gameState == 1: dispatch al registry per num_piramide. El
// Cal fer-ho ací perquè el SceneRegistry consulta num_piramide // vell ModuleSequence::Go() feia aquest redirect al principi:
// abans del fallback legacy; mentres doSecreta no estiga migrada // si el jugador arriba a la Secreta (6) sense prou diners,
// també continuarà al Go() vell amb num_piramide ja corregida. // salta als slides de fracàs (7) abans de buscar l'escena.
if (info::ctx.num_piramide == 6 && info::ctx.diners < 200) { if (info::ctx.num_piramide == 6 && info::ctx.diners < 200) {
info::ctx.num_piramide = 7; info::ctx.num_piramide = 7;
} }
scene = scenes::SceneRegistry::instance().tryCreate(info::ctx.num_piramide);
if (auto scene = scenes::SceneRegistry::instance().tryCreate(info::ctx.num_piramide)) {
scene->onEnter();
Uint32 last = SDL_GetTicks();
while (!scene->done() && !JG_Quitting()) {
JI_Update(); // refresca key_pressed/any_key per a les escenes
const Uint32 now = SDL_GetTicks();
scene->tick(static_cast<int>(now - last));
last = now;
JD8_Flip(); // presenta i cedix al Director
}
gameState = scene->nextState();
continue;
}
} }
// Fallback al codi legacy (encara no migrat a Scene). if (!scene) {
switch (gameState) { // State no registrat — indica un bug del dispatcher o del
case 0: { // registre d'escenes. Eixim ordenadament en lloc de cremar CPU.
auto* moduleGame = new ModuleGame(); break;
gameState = moduleGame->Go();
delete moduleGame;
break;
}
case 1: {
auto* moduleSequence = new ModuleSequence();
gameState = moduleSequence->Go();
delete moduleSequence;
break;
}
} }
scene->onEnter();
Uint32 last = SDL_GetTicks();
while (!scene->done() && !JG_Quitting()) {
JI_Update(); // refresca key_pressed/any_key per a les escenes
const Uint32 now = SDL_GetTicks();
scene->tick(static_cast<int>(now - last));
last = now;
JD8_Flip(); // presenta i cedix al Director
}
gameState = scene->nextState();
} }
} }
@@ -113,10 +102,10 @@ void Director::init() {
instance_ = new Director(); instance_ = new Director();
Gamepad::init(); Gamepad::init();
// Registre d'escenes migrades. Cada entrada = una funció del vell // Registre d'escenes. Cada entrada = un state_key (`num_piramide`)
// ModuleSequence reescrita com a `scenes::*Scene`. Mentre vagen // amb una factory de `scenes::Scene`. El gameFiberEntry consulta
// caient, el fallback al switch legacy de gameFiberEntry deixa de // aquest registry per a tots els states de seqüència; si una clau
// rebre aquests states. // no apareix ací, el fiber eixirà del loop.
auto& registry = scenes::SceneRegistry::instance(); auto& registry = scenes::SceneRegistry::instance();
registry.registerScene(0, [] { return std::make_unique<scenes::MenuScene>(); }); registry.registerScene(0, [] { return std::make_unique<scenes::MenuScene>(); });
registry.registerScene(100, [] { return std::make_unique<scenes::MortScene>(); }); registry.registerScene(100, [] { return std::make_unique<scenes::MortScene>(); });
@@ -133,14 +122,15 @@ void Director::init() {
registry.registerScene(7, [] { return std::make_unique<scenes::SlidesScene>(); }); registry.registerScene(7, [] { return std::make_unique<scenes::SlidesScene>(); });
registry.registerScene(6, [] { return std::make_unique<scenes::SecretaScene>(); }); registry.registerScene(6, [] { return std::make_unique<scenes::SecretaScene>(); });
registry.registerScene(8, [] { return std::make_unique<scenes::CreditsScene>(); }); registry.registerScene(8, [] { return std::make_unique<scenes::CreditsScene>(); });
// IntroNewLogoScene només es registra quan `use_new_logo` està actiu; // State 255 (intro): dues variants segons `Options::game.use_new_logo`.
// si no, la factory retorna nullptr i el gameFiberEntry cau al vell // La factory tria a runtime — així es pot togglar des del menú sense
// ModuleSequence::doIntro() legacy que no ha sigut migrat encara. // re-registrar. Les dues escenes construeixen una IntroSpritesScene
// com a sub-escena per a la part d'animacions de sprites.
registry.registerScene(255, []() -> std::unique_ptr<scenes::Scene> { registry.registerScene(255, []() -> std::unique_ptr<scenes::Scene> {
if (Options::game.use_new_logo) { if (Options::game.use_new_logo) {
return std::make_unique<scenes::IntroNewLogoScene>(); return std::make_unique<scenes::IntroNewLogoScene>();
} }
return nullptr; return std::make_unique<scenes::IntroScene>();
}); });
GameFiber::init(gameFiberEntry); GameFiber::init(gameFiberEntry);

View File

@@ -16,19 +16,16 @@ ModuleGame::ModuleGame() {
if (info::ctx.num_piramide == 2) { if (info::ctx.num_piramide == 2) {
this->bola = new Bola(this->gfx, this->sam); this->bola = new Bola(this->gfx, this->sam);
} else { } else {
this->bola = NULL; this->bola = nullptr;
} }
this->momies = NULL; this->momies = nullptr;
this->final = 0;
this->iniciarMomies(); this->iniciarMomies();
} }
ModuleGame::~ModuleGame(void) { ModuleGame::~ModuleGame() {
JD8_FadeOut(); if (this->bola != nullptr) delete this->bola;
if (this->momies != nullptr) {
if (this->bola != NULL) delete this->bola;
if (this->momies != NULL) {
this->momies->clear(); this->momies->clear();
delete this->momies; delete this->momies;
} }
@@ -39,73 +36,115 @@ ModuleGame::~ModuleGame(void) {
JD8_FreeSurface(this->gfx); JD8_FreeSurface(this->gfx);
} }
int ModuleGame::Go() { 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(); 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* 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(); const char* current_music = JA_GetMusicFilename();
if ((JA_GetMusicState() != JA_MUSIC_PLAYING) || !(strcmp(music, current_music) == 0)) { if ((JA_GetMusicState() != JA_MUSIC_PLAYING) || !current_music ||
int size; strcmp(music, current_music) != 0) {
char* buffer = file_getfilebuffer(music, size); auto buffer = file_readfile(music);
JA_PlayMusic(JA_LoadMusic((Uint8*)buffer, size, music)); JA_PlayMusic(JA_LoadMusic(reinterpret_cast<Uint8*>(buffer.data()),
static_cast<Uint32>(buffer.size()), music));
} }
JD8_FadeToPal(JD8_LoadPalette(info::ctx.pepe_activat ? "frames2.gif" : "frames.gif")); // 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;
}
while (this->final == 0 && !JG_Quitting()) { void ModuleGame::tick(int delta_ms) {
this->Draw(); switch (phase_) {
this->Update(); 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;
} }
}
// JS_FadeOutMusic(); 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;
}
if (this->final == 1) { void ModuleGame::applyFinalTransitions() {
if (this->final_ == 1) {
info::ctx.num_habitacio++; info::ctx.num_habitacio++;
if (info::ctx.num_habitacio == 6) { if (info::ctx.num_habitacio == 6) {
info::ctx.num_habitacio = 1; info::ctx.num_habitacio = 1;
info::ctx.num_piramide++; info::ctx.num_piramide++;
} }
if (info::ctx.num_piramide == 6 && info::ctx.num_habitacio == 2) info::ctx.num_piramide++; if (info::ctx.num_piramide == 6 && info::ctx.num_habitacio == 2) info::ctx.num_piramide++;
} else if (this->final == 2) { } else if (this->final_ == 2) {
info::ctx.num_piramide = 100; 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() { 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->mapa->draw();
this->marcador->draw(); this->marcador->draw();
this->sam->draw(); this->sam->draw();
if (this->momies != NULL) this->momies->draw(); if (this->momies != nullptr) this->momies->draw();
if (this->bola != NULL) this->bola->draw(); if (this->bola != nullptr) this->bola->draw();
JD8_Flip();
} }
void ModuleGame::Update() { void ModuleGame::Update() {
if (JG_ShouldUpdate()) { if (JG_ShouldUpdate()) {
JI_Update(); JI_Update();
this->final = this->sam->update(); this->final_ = this->sam->update();
if (this->momies != NULL && this->momies->update()) { if (this->momies != nullptr && this->momies->update()) {
Momia* seguent = this->momies->next; Momia* seguent = this->momies->next;
delete this->momies; delete this->momies;
this->momies = seguent; this->momies = seguent;
info::ctx.momies--; info::ctx.momies--;
} }
if (this->bola != NULL) this->bola->update(); if (this->bola != nullptr) this->bola->update();
this->mapa->update(); this->mapa->update();
if (this->mapa->novaMomia()) { if (this->mapa->novaMomia()) {
if (this->momies != NULL) { if (this->momies != nullptr) {
this->momies->insertar(new Momia(this->gfx, true, 0, 0, this->sam)); this->momies->insertar(new Momia(this->gfx, true, 0, 0, this->sam));
info::ctx.momies++; info::ctx.momies++;
} else { } else {
@@ -116,10 +155,10 @@ void ModuleGame::Update() {
if (JI_CheatActivated("reviu")) info::ctx.vida = 5; if (JI_CheatActivated("reviu")) info::ctx.vida = 5;
if (JI_CheatActivated("alone")) { if (JI_CheatActivated("alone")) {
if (this->momies != NULL) { if (this->momies != nullptr) {
this->momies->clear(); this->momies->clear();
delete this->momies; delete this->momies;
this->momies = NULL; this->momies = nullptr;
info::ctx.momies = 0; info::ctx.momies = 0;
} }
} }
@@ -151,7 +190,7 @@ void ModuleGame::iniciarMomies() {
int y = 170; int y = 170;
bool dimonis = info::ctx.num_piramide == 6; bool dimonis = info::ctx.num_piramide == 6;
for (int i = 0; i < info::ctx.momies; i++) { for (int i = 0; i < info::ctx.momies; i++) {
if (this->momies == NULL) { if (this->momies == nullptr) {
this->momies = new Momia(this->gfx, dimonis, x, y, this->sam); this->momies = new Momia(this->gfx, dimonis, x, y, this->sam);
} else { } else {
this->momies->insertar(new Momia(this->gfx, dimonis, x, y, this->sam)); this->momies->insertar(new Momia(this->gfx, dimonis, x, y, this->sam));

View File

@@ -6,26 +6,55 @@
#include "game/marcador.hpp" #include "game/marcador.hpp"
#include "game/momia.hpp" #include "game/momia.hpp"
#include "game/prota.hpp" #include "game/prota.hpp"
#include "scenes/palette_fade.hpp"
#include "scenes/scene.hpp"
class ModuleGame { // 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: public:
ModuleGame(); ModuleGame();
~ModuleGame(void); ~ModuleGame() override;
int Go(); void onEnter() override;
void tick(int delta_ms) override;
bool done() const override { return phase_ == Phase::Done; }
int nextState() const override;
private: private:
void Draw(); enum class Phase {
void Update(); 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 iniciarMomies();
void applyFinalTransitions(); // muta info::ctx quan final_ passa a !=0
Uint8 final; Phase phase_{Phase::FadingIn};
JD8_Surface gfx; scenes::PaletteFade fade_;
Uint8 final_{0};
JD8_Surface gfx{nullptr};
Mapa* mapa; Mapa* mapa{nullptr};
Prota* sam; Prota* sam{nullptr};
Marcador* marcador; Marcador* marcador{nullptr};
Momia* momies; Momia* momies{nullptr};
Bola* bola; Bola* bola{nullptr};
}; };

View File

@@ -1,827 +0,0 @@
#include "game/modulesequence.hpp"
#include <stdlib.h>
#include <string>
#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"
ModuleSequence::ModuleSequence() {
}
ModuleSequence::~ModuleSequence(void) {
}
namespace {
// Espera fins que passe el tick de JG_ShouldUpdate o fins que el jugador
// polse qualsevol tecla. Torna `true` si la seqüència s'ha de cancel·lar.
// Centralitza el patró repetit de les cinemàtiques; a la Fase 5 (quan
// eliminem el game thread) passarà a ser un pas de màquina d'estats real.
bool wait_frame_or_skip() {
while (!JG_ShouldUpdate()) {
JI_Update();
if (JI_AnyKey() || JG_Quitting()) return true;
}
return false;
}
} // namespace
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.
}
JD8_FadeOut();
if (JG_Quitting()) {
return -1;
} else {
if (info::ctx.num_piramide == 255) {
info::ctx.num_piramide = 0;
return 1;
} else if (info::ctx.num_piramide == 0) {
info::ctx.num_piramide = 1;
// info::ctx.num_piramide = 6;
// info::ctx.diners = 200;
return 1;
} else if (info::ctx.num_piramide == 7) {
info::ctx.num_piramide = 8;
return 1;
} else if (info::ctx.num_piramide == 8) {
info::ctx.num_piramide = 255;
return 1;
} else if (info::ctx.num_piramide == 100) {
info::ctx.num_piramide = 0;
return 1;
} else {
return 0;
}
}
return 0;
}
const int minim(const int a, const int b) {
if (b < a) {
return b;
} else {
return a;
}
}
// Pinta el wordmark JAILGAMES/Jailgames en el mateix lloc (y=78) durant les
// animacions de sprites de l'intro. Branqueja entre logo vell i nou segons
// Options::game.use_new_logo.
static void drawIntroWordmark(JD8_Surface gfx) {
if (Options::game.use_new_logo) {
JD8_Blit(60, 78, gfx, 60, 158, 188, 28);
} else {
JD8_Blit(43, 78, gfx, 43, 155, 231, 45);
}
}
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);
Uint16 fr1 = 13;
Uint16 fr2 = fr1;
Uint16 fr3 = 11;
Uint16 fr4 = fr3;
Uint16 fr5 = 20;
Uint16 fr6 = 8;
Uint16 fr7 = 29;
Uint16 fr8 = 4;
Uint16 fr9 = 16;
Uint16 fr10 = fr9;
Uint16 fr11 = 6;
Uint16 creu = 75;
Uint16 interrogant = 90;
Uint16 fr_ani_1[13]; // camina dreta
Uint16 fr_ani_2[13]; // camina esquerra
Uint16 fr_ani_3[11]; // trau el mapa DRETA
Uint16 fr_ani_4[11]; // trau el mapa ESQUERRA
Uint16 fr_ani_5[20]; // bot de susto
Uint16 fr_ani_6[8]; // momia
Uint16 fr_ani_7[29]; // deixa caure el PAPER i SOMBRA
Uint16 fr_ani_8[4]; // PEDRA
Uint16 fr_ani_9[16]; // prota BALL
Uint16 fr_ani_10[16]; // momia BALL
Uint16 fr_ani_11[6]; // altaveu
for (int i = 0; i < fr1; i++) fr_ani_1[i] = i * 15;
for (int i = 0; i < fr2; i++) fr_ani_2[i] = i * 15; // 15
for (int i = 0; i < fr3; i++) fr_ani_3[i] = i * 15; // 30
for (int i = 0; i < fr4; i++) fr_ani_4[i] = i * 15; // 45
for (int i = 0; i <= 9; i++) fr_ani_5[i] = (i + 11) * 15; // 45
for (int i = 10; i <= 19; i++) fr_ani_5[i] = fr_ani_5[19 - i];
for (int i = 0; i < fr6; i++) fr_ani_6[i] = i * 15; // 60
for (int i = 0; i <= 13; i++) fr_ani_7[i] = (i + 5) * 15; // 75
for (int i = 14; i < fr7; i++) fr_ani_7[i] = (i - 14) * 15; // 105
for (int i = 0; i < fr8; i++) fr_ani_8[i] = (i + 1) * 15; // 75
for (int i = 0; i < fr9; i++) fr_ani_9[i] = i * 15; // 120
for (int i = 0; i < fr10; i++) fr_ani_10[i] = i * 15; // 135
for (int i = 0; i < fr11; i++) fr_ani_11[i] = (i + 1) * 15; // 90
fr_ani_11[fr11 - 1] = fr_ani_11[3];
switch (rand() % 3) {
case 0:
// camina cap a la DRETA }
for (int i = 0; i <= 200; i++) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,fr_ani_1[(i div 5) mod fr1],15,15,i,150);
JD8_BlitCK(i, 150, gfx, fr_ani_1[(i / 5) % fr1], 0, 15, 15, 0);
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
// trau el MAPA DRETA }
for (int i = 0; i <= 200; i++) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,fr_ani_3[minim((i div 5),fr3-1)],15,15,200,150);
JD8_BlitCK(200, 150, gfx, fr_ani_3[minim((i / 5), fr3 - 1)], 30, 15, 15, 0);
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
// guarda el MAPA }
for (int i = 200; i >= 0; i--) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,fr_ani_3[minim((i div 5),fr3-1)],15,15,200,150);
JD8_BlitCK(200, 150, gfx, fr_ani_3[minim((i / 5), fr3 - 1)], 30, 15, 15, 0);
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
// camina cap a la ESQUERRA }
for (int i = 200; i >= 80; i--) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,fr_ani_2[(i div 5) mod fr2],15,15,i,150);
JD8_BlitCK(i, 150, gfx, fr_ani_2[(i / 5) % fr2], 15, 15, 15, 0);
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
// trau el MAPA ESQUERRA }
for (int i = 0; i <= 200; i++) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,fr_ani_4[minim((i div 5),fr4-1)],15,15,80,150);
JD8_BlitCK(80, 150, gfx, fr_ani_4[minim((i / 5), fr4 - 1)], 45, 15, 15, 0);
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
// momia cap a la ESQUERRA }
for (int i = 300; i >= 95; i--) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,fr_ani_6[(i div 10) mod fr6],15,15,i,150);
JD8_BlitCK(i, 150, gfx, fr_ani_6[(i / 5) % fr6], 60, 15, 15, 0);
// Put_sprite(from,where,fr_ani_4[fr4-1],15,15,80,150);
JD8_BlitCK(80, 150, gfx, fr_ani_4[fr4 - 1], 45, 15, 15, 0);
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
// girar-se }
for (int i = 0; i <= 50; i++) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,fr_ani_1[1],15,15,80,150);
JD8_BlitCK(80, 150, gfx, fr_ani_1[1], 0, 15, 15, 0);
// Put_sprite(from,where,fr_ani_6[4],15,15,95,150);
JD8_BlitCK(95, 150, gfx, fr_ani_6[4], 60, 15, 15, 0);
// Put_sprite(from,where,interrogant,15,15,80,133);
JD8_BlitCK(80, 133, gfx, 0, interrogant, 15, 15, 0);
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
// bot de SUSTO }
for (int i = 0; i <= 49; i++) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,fr_ani_5[minim((i div 5),fr5-1)],15,15,80,150-((i mod 50) div 5));
JD8_BlitCK(80, 150 - ((i % 50) / 5), gfx, fr_ani_5[minim(i / 5, fr5 - 1)], 45, 15, 15, 0);
// Put_sprite(from,where,fr_ani_6[4],15,15,95,150);
JD8_BlitCK(95, 150, gfx, fr_ani_6[4], 60, 15, 15, 0);
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
// bot de SUSTO }
for (int i = 50; i <= 99; i++) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,fr_ani_5[minim((i div 5),fr5-1)],15,15,80,140+((i mod 50) div 5));
JD8_BlitCK(80, 140 + ((i % 50) / 5), gfx, fr_ani_5[minim(i / 5, fr5 - 1)], 45, 15, 15, 0);
// Put_sprite(from,where,fr_ani_6[4],15,15,95,150);
JD8_BlitCK(95, 150, gfx, fr_ani_6[4], 60, 15, 15, 0);
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
// camina cap a la ESQUERRA }
for (int i = 80; i >= 0; i--) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,fr_ani_2[(i div 5) mod fr2],15,15,i,150);
JD8_BlitCK(i, 150, gfx, fr_ani_2[(i / 5) % fr2], 15, 15, 15, 0);
// Put_sprite(from,where,fr_ani_6[4],15,15,95,150);
JD8_BlitCK(95, 150, gfx, fr_ani_6[4], 60, 15, 15, 0);
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
// final }
for (int i = 0; i <= 150; i++) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,fr_ani_6[4],15,15,95,150);
JD8_BlitCK(95, 150, gfx, fr_ani_6[4], 60, 15, 15, 0);
// Put_sprite(from,where,interrogant,15,15,95,133);
JD8_BlitCK(95, 133, gfx, 0, interrogant, 15, 15, 0);
JD8_Flip();
}
//-----}
break;
case 1:
// camina cap a la DRETA }
for (int i = 0; i <= 200; i++) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,creu,15,15,200,155);
JD8_BlitCK(200, 155, gfx, 0, creu, 15, 15, 255);
// Put_sprite(from,where,fr_ani_1[(i div 5) mod fr1],15,15,i,150);
JD8_BlitCK(i, 150, gfx, fr_ani_1[(i / 5) % fr1], 0, 15, 15, 255);
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
// trau el MAPA DRETA }
for (int i = 0; i <= 300; i++) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,creu,15,15,200,155);
JD8_BlitCK(200, 155, gfx, 0, creu, 15, 15, 255);
// Put_sprite(from,where,fr_ani_3[minim((i div 5),fr3-1)],15,15,200,150);
JD8_BlitCK(200, 150, gfx, fr_ani_3[minim(i / 5, fr3 - 1)], 30, 15, 15, 255);
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
// INTERROGANT }
for (int i = 0; i <= 100; i++) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,creu,15,15,200,155);
JD8_BlitCK(200, 155, gfx, 0, creu, 15, 15, 255);
// Put_sprite(from,where,interrogant,15,15,200,134);
JD8_BlitCK(200, 134, gfx, 0, interrogant, 15, 15, 255);
// Put_sprite(from,where,fr_ani_3[fr3-1],15,15,200,150);
JD8_BlitCK(200, 150, gfx, fr_ani_3[fr3 - 1], 30, 15, 15, 255);
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
// deixa caure el MAPA i SOMBRA }
for (int i = 0; i <= 200; i++) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,creu,15,15,200,155);
JD8_BlitCK(200, 155, gfx, 0, creu, 15, 15, 255);
// Put_sprite(from,where,fr_ani_7[minim((i div 5),fr7-1)],15,15,200,150);
if (minim(i / 5, fr7 - 1) <= 13) {
JD8_BlitCK(200, 150, gfx, fr_ani_7[minim(i / 5, fr7 - 1)], 75, 15, 15, 255);
} else {
JD8_BlitCK(200, 150, gfx, fr_ani_7[minim(i / 5, fr7 - 1)], 105, 15, 15, 255);
}
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
// SOMBRA i PEDRA }
for (int i = 0; i <= 75; i++) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,creu,15,15,200,155);
JD8_BlitCK(200, 155, gfx, 0, creu, 15, 15, 255);
// Put_sprite(from,where,fr_ani_7[fr7-1],15,15,200,150);
JD8_BlitCK(200, 150, gfx, fr_ani_7[fr7 - 1], 105, 15, 15, 255);
// Put_sprite(from,where,fr_ani_8[0],15,15,200,i*2);
JD8_BlitCK(200, i * 2, gfx, fr_ani_8[0], 75, 15, 15, 255);
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
// trencar PEDRA }
for (int i = 0; i <= 19; i++) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,creu,15,15,200,155);
JD8_BlitCK(200, 155, gfx, 0, creu, 15, 15, 255);
// Put_sprite(from,where,fr_ani_8[i div 10],15,15,200,150);
JD8_BlitCK(200, 150, gfx, fr_ani_8[i / 10], 75, 15, 15, 255);
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
// FINAL }
for (int i = 0; i <= 200; i++) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,creu,15,15,200,155);
JD8_BlitCK(200, 155, gfx, 0, creu, 15, 15, 255);
// Put_sprite(from,where,fr_ani_8[1],15,15,200,150);
JD8_BlitCK(200, 150, gfx, fr_ani_8[1], 75, 15, 15, 255);
// Put_sprite(from,where,fr_ani_8[2],15,15,185,150);
JD8_BlitCK(185, 150, gfx, fr_ani_8[2], 75, 15, 15, 255);
// Put_sprite(from,where,fr_ani_8[3],15,15,215,150);
JD8_BlitCK(215, 150, gfx, fr_ani_8[3], 75, 15, 15, 255);
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
break;
case 2:
// camina cap a la DRETA }
for (int i = 0; i <= 145; i++) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,fr_ani_1[(i div 5) mod fr1],15,15,i,150);
JD8_BlitCK(i, 150, gfx, fr_ani_1[(i / 5) % fr1], 0, 15, 15, 255);
// Put_sprite(from,where,fr_ani_6[(i div 10) mod fr6],15,15,304-i,150);
JD8_BlitCK(304 - i, 150, gfx, fr_ani_6[(i / 10) % fr6], 60, 15, 15, 255);
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
// els dos quets }
for (int i = 0; i <= 100; i++) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,fr_ani_1[1],15,15,145,150);
JD8_BlitCK(145, 150, gfx, fr_ani_1[1], 0, 15, 15, 255);
// Put_sprite(from,where,fr_ani_6[1],15,15,160,150);
JD8_BlitCK(160, 150, gfx, fr_ani_6[1], 60, 15, 15, 255);
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
// aparicio altaveu }
for (int i = 0; i <= 50; i++) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,fr_ani_11[(i div 10) mod 2],15,15,125,150);
JD8_BlitCK(125, 150, gfx, fr_ani_11[(i / 10) % 2], 90, 15, 15, 255);
// Put_sprite(from,where,fr_ani_1[1],15,15,145,150);
JD8_BlitCK(145, 150, gfx, fr_ani_1[1], 0, 15, 15, 255);
// Put_sprite(from,where,fr_ani_6[1],15,15,160,150);
JD8_BlitCK(160, 150, gfx, fr_ani_6[1], 60, 15, 15, 255);
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
// BALL }
for (int i = 0; i <= 800; i++) {
JD8_ClearScreen(0);
drawIntroWordmark(gfx);
// Put_sprite(from,where,fr_ani_9[(i div 10) mod fr9],15,15,145,150);
JD8_BlitCK(145, 150, gfx, fr_ani_9[(i / 10) % fr9], 120, 15, 15, 255);
// Put_sprite(from,where,fr_ani_10[(i div 10) mod fr10],15,15,160,150);
JD8_BlitCK(160, 150, gfx, fr_ani_10[(i / 10) % fr10], 135, 15, 15, 255);
// Put_sprite(from,where,fr_ani_11[((i div 5) mod 4)+2],15,15,125,150);
JD8_BlitCK(125, 150, gfx, fr_ani_11[((i / 5) % 4) + 2], 90, 15, 15, 255);
JD8_Flip();
if (wait_frame_or_skip()) {
JD8_FreeSurface(gfx);
return;
}
}
break;
}
JD8_FreeSurface(gfx);
}
// doIntroNewLogo() — migrat a scenes::IntroNewLogoScene (source/scenes/intro_new_logo_scene.cpp)
// doMenu() — migrat a scenes::MenuScene (source/scenes/menu_scene.cpp)
// doSlides() — migrat a scenes::SlidesScene (source/scenes/slides_scene.cpp)
// doBanner() — migrat a scenes::BannerScene (source/scenes/banner_scene.cpp)
// doSecreta() — migrat a scenes::SecretaScene (source/scenes/secreta_scene.cpp)
// doCredits() — migrat a scenes::CreditsScene (source/scenes/credits_scene.cpp)
// doMort() — migrat a scenes::MortScene (source/scenes/mort_scene.cpp)

View File

@@ -1,31 +0,0 @@
#pragma once
#include <SDL3/SDL.h>
#include "game/info.hpp"
class ModuleSequence {
public:
ModuleSequence();
~ModuleSequence(void);
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();
// doMenu() → migrat a scenes::MenuScene
// doSlides() → migrat a scenes::SlidesScene
// doBanner() → migrat a scenes::BannerScene
// doSecreta() → migrat a scenes::SecretaScene
// doCredits() → migrat a scenes::CreditsScene
// doMort() → migrat a scenes::MortScene
int contador;
};

View File

@@ -5,7 +5,6 @@
#include "core/jail/jdraw8.hpp" #include "core/jail/jdraw8.hpp"
#include "core/jail/jinput.hpp" #include "core/jail/jinput.hpp"
#include "game/info.hpp" #include "game/info.hpp"
#include "game/modulesequence.hpp"
#include "scenes/scene_utils.hpp" #include "scenes/scene_utils.hpp"
namespace { namespace {
@@ -98,7 +97,7 @@ void IntroNewLogoScene::render() {
LETTER_WIDTHS[8], LOGO_HEIGHT); LETTER_WIDTHS[8], LOGO_HEIGHT);
break; break;
case Phase::Delegate: case Phase::Sprites:
case Phase::Done: case Phase::Done:
break; break;
} }
@@ -122,10 +121,10 @@ void IntroNewLogoScene::advancePaletteCycle() {
void IntroNewLogoScene::tick(int delta_ms) { void IntroNewLogoScene::tick(int delta_ms) {
// Qualsevol tecla durant el revelat o el ciclo de paleta salta // Qualsevol tecla durant el revelat o el ciclo de paleta salta
// TOTA la intro (inclou saltar doIntroSprites). Aquest era el // TOTA la intro (inclou saltar la fase de sprites). Durant Sprites
// comportament del vell `doIntroNewLogo`: a cada `waitTick()` // deixem que la sub-escena gestione el seu propi skip (que a més
// retornava abans de cridar `doIntroSprites`. // respecta la fase "final" no skippable de la variant 0).
if (phase_ != Phase::Delegate && phase_ != Phase::Done && JI_AnyKey()) { if (phase_ != Phase::Sprites && phase_ != Phase::Done && JI_AnyKey()) {
info::ctx.num_piramide = 0; info::ctx.num_piramide = 0;
phase_ = Phase::Done; phase_ = Phase::Done;
return; return;
@@ -189,28 +188,27 @@ void IntroNewLogoScene::tick(int delta_ms) {
phase_acc_ms_ += delta_ms; phase_acc_ms_ += delta_ms;
render(); render();
if (phase_acc_ms_ >= FINAL_WAIT_MS) { if (phase_acc_ms_ >= FINAL_WAIT_MS) {
phase_ = Phase::Delegate; phase_ = Phase::Sprites;
} }
break; break;
case Phase::Delegate: { case Phase::Sprites:
// Delegació temporal al codi legacy: crea un ModuleSequence // Sub-escena construïda al primer tick. Transferim el gfx_
// instància i li crida `doIntroSprites(gfx)`. La funció // per move — la sub-escena se n'ocupa fins que es destruix.
// legacy *sempre* allibera `gfx` ella mateixa (al final o en // Cada tick successiu delega l'animació dels sprites.
// els paths de skip amb JI_AnyKey), així que necessitem if (!sprites_scene_) {
// transferir-li ownership via `release()` per evitar un sprites_scene_ = std::make_unique<IntroSpritesScene>(std::move(gfx_));
// double free al destructor de SurfaceHandle. Step 9 sprites_scene_->onEnter();
// d'aquesta migració la reescriurà com a IntroSpritesScene }
// i la delegació desapareixerà. sprites_scene_->tick(delta_ms);
ModuleSequence legacy; if (sprites_scene_->done()) {
legacy.doIntroSprites(gfx_.release()); // El vell `Go()` post-switch feia `num_piramide = 0`
// El vell `Go()` post-switch feia `num_piramide = 0` per a // per passar al menú. Sense açò el while del fiber
// passar al menú. Ho reproduïm ací — si no, el while extern // tornaria a crear IntroNewLogoScene infinitament.
// del fiber tornaria a crear IntroNewLogoScene infinitament. info::ctx.num_piramide = 0;
info::ctx.num_piramide = 0; phase_ = Phase::Done;
phase_ = Phase::Done; }
break; break;
}
case Phase::Done: case Phase::Done:
break; break;

View File

@@ -1,6 +1,9 @@
#pragma once #pragma once
#include <memory>
#include "core/jail/jdraw8.hpp" #include "core/jail/jdraw8.hpp"
#include "scenes/intro_sprites_scene.hpp"
#include "scenes/scene.hpp" #include "scenes/scene.hpp"
#include "scenes/surface_handle.hpp" #include "scenes/surface_handle.hpp"
@@ -17,10 +20,10 @@ namespace scenes {
// 3. Logo complet amb cursor fix 200 ms. // 3. Logo complet amb cursor fix 200 ms.
// 4. Cicle de paleta de 256 passos modificant índexs 1631 cada 20 ms. // 4. Cicle de paleta de 256 passos modificant índexs 1631 cada 20 ms.
// 5. Espera final 20 ms. // 5. Espera final 20 ms.
// 6. Delega a la funció legacy `ModuleSequence::doIntroSprites(gfx)` // 6. Transfereix el gfx_ a una `IntroSpritesScene` com a sub-escena
// (que s'executa dins del mateix fiber i fa els seus propis Flips // i li delega els ticks fins que acaba (anima el prota + momia +
// cooperatius). Aquesta delegació desapareixerà al Step 9 del pla, // mapa, amb 3 variants aleatòries). En acabar, setzea num_piramide
// quan `doIntroSprites` es reescriga com a `IntroSpritesScene`. // = 0 per passar al menú.
// //
// Registrada al SceneRegistry amb state_key = 255, amb una factory // Registrada al SceneRegistry amb state_key = 255, amb una factory
// condicional: només s'activa si `Options::game.use_new_logo == true`. // condicional: només s'activa si `Options::game.use_new_logo == true`.
@@ -43,7 +46,7 @@ class IntroNewLogoScene : public Scene {
FullLogoFlash, // logo complet + cursor, 200 ms FullLogoFlash, // logo complet + cursor, 200 ms
PaletteCycle, // 256 passos × 20 ms modificant paleta PaletteCycle, // 256 passos × 20 ms modificant paleta
FinalWait, // 20 ms final FinalWait, // 20 ms final
Delegate, // delega a doIntroSprites legacy i marca done Sprites, // tick delegat a IntroSpritesScene fins que acaba
Done, Done,
}; };
@@ -53,6 +56,7 @@ class IntroNewLogoScene : public Scene {
SurfaceHandle gfx_; SurfaceHandle gfx_;
SurfaceHandle cursor_surf_; SurfaceHandle cursor_surf_;
JD8_Palette pal_{nullptr}; // propietat transferida a main_palette via SetScreenPalette JD8_Palette pal_{nullptr}; // propietat transferida a main_palette via SetScreenPalette
std::unique_ptr<IntroSpritesScene> sprites_scene_;
Phase phase_{Phase::Initial}; Phase phase_{Phase::Initial};
int phase_acc_ms_{0}; int phase_acc_ms_{0};

View File

@@ -0,0 +1,218 @@
#include "scenes/intro_scene.hpp"
#include "core/jail/jdraw8.hpp"
#include "core/jail/jinput.hpp"
#include "game/info.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::Sprites:
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 revelat/paleta salta TOTA la intro
// (inclou saltar la fase de sprites). Durant Sprites deixem que
// la sub-escena gestione el seu propi skip internament, que a més
// respecta la fase "final" no skippable de la variant 0.
if (phase_ != Phase::Sprites && 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::Sprites;
}
break;
case Phase::Sprites:
// Sub-escena construïda al vol al primer tick d'aquesta fase.
// Transferim el gfx_ per move — la sub-escena se n'ocupa
// fins que es destruix. Una vegada feta, els ticks delegats
// avancen l'animació dels sprites.
if (!sprites_scene_) {
sprites_scene_ = std::make_unique<IntroSpritesScene>(std::move(gfx_));
sprites_scene_->onEnter();
}
sprites_scene_->tick(delta_ms);
if (sprites_scene_->done()) {
// Equivalent al vell `Go()` post-switch: passem al menú.
// Sense açò el while del fiber tornaria a crear IntroScene
// infinitament amb num_piramide encara a 255.
info::ctx.num_piramide = 0;
phase_ = Phase::Done;
}
break;
case Phase::Done:
break;
}
}
} // namespace scenes

View File

@@ -0,0 +1,66 @@
#pragma once
#include <memory>
#include "core/jail/jdraw8.hpp"
#include "scenes/intro_sprites_scene.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. Transfereix el gfx_ a una `IntroSpritesScene` com a sub-escena
// i li delega els ticks fins que acaba (anima el prota + momia +
// mapa, amb 3 variants aleatòries). En acabar, setzea num_piramide
// = 0 per passar al menú.
//
// 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 la sub-escena de sprites
Sprites, // tick delegat a IntroSpritesScene fins que acaba
Done,
};
void render();
void advancePaletteCycle();
SurfaceHandle gfx_;
JD8_Palette pal_{nullptr}; // propietat transferida a main_palette via SetScreenPalette
std::unique_ptr<IntroSpritesScene> sprites_scene_;
Phase phase_{Phase::InitialWait};
int phase_acc_ms_{0};
int reveal_index_{0};
int palette_step_{0};
};
} // namespace scenes

View File

@@ -0,0 +1,346 @@
#include "scenes/intro_sprites_scene.hpp"
#include <algorithm>
#include <cstdlib>
#include "core/jail/jdraw8.hpp"
#include "core/jail/jinput.hpp"
#include "game/options.hpp"
namespace {
// Duració d'un pas. El vell doIntroSprites feia JG_SetUpdateTicks(20);
// cada iteració del seu for (i) consumia un tick de 20 ms.
constexpr int TICK_MS = 20;
// Taules de frames. Ubicacions de cada sprite dins el gfx de la intro
// (logo.gif o logo/logo_new.gif — el layout de sprites és el mateix).
// Cada sprite ocupa 15×15 px, disposats horitzontalment per fila.
// Els valors són els offsets x (la y la posa l'invocador al src_y).
// Derivats dels `fr_ani_N[i] = ...` del vell doIntroSprites.
constexpr Uint16 fr1[] = {0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180}; // camina dreta (y=0)
constexpr Uint16 fr2[] = {0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180}; // camina esquerra (y=15)
constexpr Uint16 fr3[] = {0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150}; // trau mapa dreta (y=30)
constexpr Uint16 fr4[] = {0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150}; // trau mapa esquerra (y=45)
constexpr Uint16 fr5[] = {165, 180, 195, 210, 225, 240, 255, 270, 285, 300,
300, 285, 270, 255, 240, 225, 210, 195, 180, 165}; // bot de susto (y=45, mirror)
constexpr Uint16 fr6[] = {0, 15, 30, 45, 60, 75, 90, 105}; // momia (y=60)
constexpr Uint16 fr7[] = {75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 255, 270, // paper (y=75, idx 0..13)
0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210}; // sombra (y=105, idx 14..28)
constexpr Uint16 fr8[] = {15, 30, 45, 60}; // pedra (y=75)
constexpr Uint16 fr9[] = {0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225}; // prota ball (y=120)
constexpr Uint16 fr10[] = {0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225}; // momia ball (y=135)
constexpr Uint16 fr11[] = {15, 30, 45, 60, 75, 60}; // altaveu (y=90, [5]=[3] pel loop de 4)
constexpr Uint16 CREU = 75; // src_y de la creu (overlay)
constexpr Uint16 INTERROGANT = 90; // src_y del signe d'interrogant
// Equivalent de la funció `drawIntroWordmark` de modulesequence.cpp.
// Branqueja segons use_new_logo perquè la mateixa sub-escena es
// reutilitza des de IntroScene (logo vell) i IntroNewLogoScene (logo
// nou) amb arxius diferents però mateix layout de sprites.
void drawWordmark(JD8_Surface gfx) {
if (Options::game.use_new_logo) {
JD8_Blit(60, 78, gfx, 60, 158, 188, 28);
} else {
JD8_Blit(43, 78, gfx, 43, 155, 231, 45);
}
}
using RenderFn = void (*)(JD8_Surface, int);
// Una fase — rang [start_i..end_i] inclusive (direcció implícita per
// signe), funció de render, i flag d'skippable. Totes les fases actuals
// són skippables; el flag es conserva per si alguna futura ha de ser
// no interrompuda (p.ex. un logo fatídic que cal veure sencer).
struct SpritePhase {
int start_i;
int end_i;
RenderFn render;
bool skippable;
};
// =========================================================================
// Variant 0 — Interrogant / Momia
// =========================================================================
void v0_walk_right(JD8_Surface gfx, int i) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(i, 150, gfx, fr1[(i / 5) % 13], 0, 15, 15, 0);
}
void v0_pull_map_right(JD8_Surface gfx, int i) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(200, 150, gfx, fr3[std::min(i / 5, 10)], 30, 15, 15, 0);
}
void v0_walk_left_to_80(JD8_Surface gfx, int i) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(i, 150, gfx, fr2[(i / 5) % 13], 15, 15, 15, 0);
}
void v0_pull_map_left(JD8_Surface gfx, int i) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(80, 150, gfx, fr4[std::min(i / 5, 10)], 45, 15, 15, 0);
}
void v0_momia_left(JD8_Surface gfx, int i) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(i, 150, gfx, fr6[(i / 5) % 8], 60, 15, 15, 0);
JD8_BlitCK(80, 150, gfx, fr4[10], 45, 15, 15, 0);
}
void v0_turn(JD8_Surface gfx, int /*i*/) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(80, 150, gfx, fr1[1], 0, 15, 15, 0);
JD8_BlitCK(95, 150, gfx, fr6[4], 60, 15, 15, 0);
JD8_BlitCK(80, 133, gfx, 0, INTERROGANT, 15, 15, 0);
}
void v0_jump1(JD8_Surface gfx, int i) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(80, 150 - ((i % 50) / 5), gfx, fr5[std::min(i / 5, 19)], 45, 15, 15, 0);
JD8_BlitCK(95, 150, gfx, fr6[4], 60, 15, 15, 0);
}
void v0_jump2(JD8_Surface gfx, int i) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(80, 140 + ((i % 50) / 5), gfx, fr5[std::min(i / 5, 19)], 45, 15, 15, 0);
JD8_BlitCK(95, 150, gfx, fr6[4], 60, 15, 15, 0);
}
void v0_walk_final(JD8_Surface gfx, int i) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(i, 150, gfx, fr2[(i / 5) % 13], 15, 15, 15, 0);
JD8_BlitCK(95, 150, gfx, fr6[4], 60, 15, 15, 0);
}
void v0_final(JD8_Surface gfx, int /*i*/) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(95, 150, gfx, fr6[4], 60, 15, 15, 0);
JD8_BlitCK(95, 133, gfx, 0, INTERROGANT, 15, 15, 0);
}
constexpr SpritePhase variant_0[] = {
{0, 200, v0_walk_right, true},
{0, 200, v0_pull_map_right, true},
{200, 0, v0_pull_map_right, true}, // guarda el mapa (reprodueix inversament)
{200, 80, v0_walk_left_to_80, true},
{0, 200, v0_pull_map_left, true},
{300, 95, v0_momia_left, true},
{0, 50, v0_turn, true},
{0, 49, v0_jump1, true},
{50, 99, v0_jump2, true},
{80, 0, v0_walk_final, true},
{0, 150, v0_final, true},
};
// =========================================================================
// Variant 1 — Creu / Pedra
// =========================================================================
void v1_walk_right(JD8_Surface gfx, int i) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(200, 155, gfx, 0, CREU, 15, 15, 255);
JD8_BlitCK(i, 150, gfx, fr1[(i / 5) % 13], 0, 15, 15, 255);
}
void v1_pull_map(JD8_Surface gfx, int i) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(200, 155, gfx, 0, CREU, 15, 15, 255);
JD8_BlitCK(200, 150, gfx, fr3[std::min(i / 5, 10)], 30, 15, 15, 255);
}
void v1_interrogant(JD8_Surface gfx, int /*i*/) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(200, 155, gfx, 0, CREU, 15, 15, 255);
JD8_BlitCK(200, 134, gfx, 0, INTERROGANT, 15, 15, 255);
JD8_BlitCK(200, 150, gfx, fr3[10], 30, 15, 15, 255);
}
void v1_drop_map(JD8_Surface gfx, int i) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(200, 155, gfx, 0, CREU, 15, 15, 255);
const int idx = std::min(i / 5, 28);
// fr7 té 29 frames dividits en dos grups: paper (idx 0..13, src_y=75)
// i sombra (idx 14..28, src_y=105). El vell feia una branca al bucle.
if (idx <= 13) {
JD8_BlitCK(200, 150, gfx, fr7[idx], 75, 15, 15, 255);
} else {
JD8_BlitCK(200, 150, gfx, fr7[idx], 105, 15, 15, 255);
}
}
void v1_stone_fall(JD8_Surface gfx, int i) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(200, 155, gfx, 0, CREU, 15, 15, 255);
JD8_BlitCK(200, 150, gfx, fr7[28], 105, 15, 15, 255);
JD8_BlitCK(200, i * 2, gfx, fr8[0], 75, 15, 15, 255);
}
void v1_stone_break(JD8_Surface gfx, int i) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(200, 155, gfx, 0, CREU, 15, 15, 255);
JD8_BlitCK(200, 150, gfx, fr8[i / 10], 75, 15, 15, 255);
}
void v1_final(JD8_Surface gfx, int /*i*/) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(200, 155, gfx, 0, CREU, 15, 15, 255);
JD8_BlitCK(200, 150, gfx, fr8[1], 75, 15, 15, 255);
JD8_BlitCK(185, 150, gfx, fr8[2], 75, 15, 15, 255);
JD8_BlitCK(215, 150, gfx, fr8[3], 75, 15, 15, 255);
}
constexpr SpritePhase variant_1[] = {
{0, 200, v1_walk_right, true},
{0, 300, v1_pull_map, true},
{0, 100, v1_interrogant, true},
{0, 200, v1_drop_map, true},
{0, 75, v1_stone_fall, true},
{0, 19, v1_stone_break, true},
{0, 200, v1_final, true},
};
// =========================================================================
// Variant 2 — Ball de carnaval
// =========================================================================
void v2_approach(JD8_Surface gfx, int i) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(i, 150, gfx, fr1[(i / 5) % 13], 0, 15, 15, 255);
JD8_BlitCK(304 - i, 150, gfx, fr6[(i / 10) % 8], 60, 15, 15, 255);
}
void v2_still(JD8_Surface gfx, int /*i*/) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(145, 150, gfx, fr1[1], 0, 15, 15, 255);
JD8_BlitCK(160, 150, gfx, fr6[1], 60, 15, 15, 255);
}
void v2_horn(JD8_Surface gfx, int i) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(125, 150, gfx, fr11[(i / 10) % 2], 90, 15, 15, 255);
JD8_BlitCK(145, 150, gfx, fr1[1], 0, 15, 15, 255);
JD8_BlitCK(160, 150, gfx, fr6[1], 60, 15, 15, 255);
}
void v2_ball(JD8_Surface gfx, int i) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(145, 150, gfx, fr9[(i / 10) % 16], 120, 15, 15, 255);
JD8_BlitCK(160, 150, gfx, fr10[(i / 10) % 16], 135, 15, 15, 255);
JD8_BlitCK(125, 150, gfx, fr11[((i / 5) % 4) + 2], 90, 15, 15, 255);
}
constexpr SpritePhase variant_2[] = {
{0, 145, v2_approach, true},
{0, 100, v2_still, true},
{0, 50, v2_horn, true},
{0, 800, v2_ball, true},
};
// =========================================================================
// Dispatch per variant
// =========================================================================
const SpritePhase* variant_table(int variant) {
switch (variant) {
case 0: return variant_0;
case 1: return variant_1;
case 2: return variant_2;
}
return variant_0;
}
int variant_length(int variant) {
switch (variant) {
case 0: return sizeof(variant_0) / sizeof(variant_0[0]);
case 1: return sizeof(variant_1) / sizeof(variant_1[0]);
case 2: return sizeof(variant_2) / sizeof(variant_2[0]);
}
return 0;
}
int phase_step_count(const SpritePhase& p) {
return std::abs(p.end_i - p.start_i) + 1;
}
int phase_current_i(const SpritePhase& p, int step) {
return p.end_i >= p.start_i ? p.start_i + step : p.start_i - step;
}
} // namespace
namespace scenes {
IntroSpritesScene::IntroSpritesScene(SurfaceHandle&& gfx)
: gfx_(std::move(gfx)) {}
void IntroSpritesScene::onEnter() {
// El vell doIntroSprites feia `rand() % 3` al principi. El seed ve
// establert per `srand(time(0))` al boot del joc (info.cpp / main),
// així que la variant canvia entre execucions.
variant_ = std::rand() % 3;
phase_ = 0;
phase_step_ = 0;
step_acc_ms_ = 0;
done_ = false;
// Renderitzem ja el primer frame (step 0 de la primera fase) perquè
// el JD8_Flip del mini-loop del fiber el pinte al primer cicle.
const SpritePhase* phases = variant_table(variant_);
phases[0].render(gfx_.get(), phase_current_i(phases[0], 0));
}
void IntroSpritesScene::tick(int delta_ms) {
if (done_) return;
const SpritePhase* phases = variant_table(variant_);
const int num_phases = variant_length(variant_);
// Skip per tecla. Durant la fase marcada com a no skippable (només
// v0_final al vell codi) s'ignora — preserva la semàntica del vell
// bucle final de la variant 0 que no cridava wait_frame_or_skip.
if (phases[phase_].skippable && JI_AnyKey()) {
done_ = true;
return;
}
step_acc_ms_ += delta_ms;
while (step_acc_ms_ >= TICK_MS && !done_) {
step_acc_ms_ -= TICK_MS;
++phase_step_;
if (phase_step_ >= phase_step_count(phases[phase_])) {
++phase_;
phase_step_ = 0;
if (phase_ >= num_phases) {
done_ = true;
return;
}
}
}
phases[phase_].render(gfx_.get(), phase_current_i(phases[phase_], phase_step_));
}
} // namespace scenes

View File

@@ -0,0 +1,43 @@
#pragma once
#include "scenes/scene.hpp"
#include "scenes/surface_handle.hpp"
namespace scenes {
// Sub-escena de sprites de la intro (prota + momia + mapa + etc).
// Reemplaça `ModuleSequence::doIntroSprites()`. No es registra al
// SceneRegistry — es construeix com a membre de `IntroScene` o
// `IntroNewLogoScene` quan aquestes completen el seu revelat del logo.
// Rep el `SurfaceHandle` del gfx de la intro via move, de manera que
// quan acabe l'escena el surface es lliberarà automàticament.
//
// En entrar tria una de 3 variants (`rand() % 3`): "interrogant/momia",
// "creu/pedra" o "ball de carnaval". Cada variant té un nombre
// diferent de fases però comparteixen el mateix motor: un comptador
// `step` que s'incrementa cada 20 ms, amb una taula per variant que
// mapeja (rang d'i, renderer) a cada fase. Qualsevol tecla salta
// l'escena — el flag `skippable` per fase es manté com a mecanisme
// per si alguna fase futura ha de ser no interrompuda (al vell codi
// la fase "final" de la variant 0 no cridava wait_frame_or_skip, cosa
// molt probablement un oversight: ací es tracta com a skippable).
class IntroSpritesScene : public Scene {
public:
explicit IntroSpritesScene(SurfaceHandle&& gfx);
~IntroSpritesScene() override = default;
void onEnter() override;
void tick(int delta_ms) override;
bool done() const override { return done_; }
int nextState() const override { return 1; }
private:
SurfaceHandle gfx_;
int variant_{0}; // 0..2 — triada a onEnter() amb rand() % 3
int phase_{0}; // índex dins la variant actual
int phase_step_{0}; // passos consumits dins la fase actual
int step_acc_ms_{0}; // acumulador per emetre steps de 20 ms
bool done_{false};
};
} // namespace scenes

View File

@@ -9,13 +9,13 @@ namespace scenes {
void playMusic(const char* filename, int loop) { void playMusic(const char* filename, int loop) {
if (!filename) return; if (!filename) return;
int size = 0; auto buffer = file_readfile(filename);
char* buffer = file_getfilebuffer(filename, size); if (buffer.empty()) return;
if (!buffer) return; // JA_LoadMusic fa una còpia interna del OGG comprimit (via SDL_malloc)
// JA_LoadMusic fa una còpia del OGG comprimit (SDL_malloc), així que // per a stb_vorbis. El `buffer` local es destruirà en sortir d'àmbit.
// el `buffer` original es queda huérfano. Leak conegut heredat del JA_PlayMusic(JA_LoadMusic(reinterpret_cast<Uint8*>(buffer.data()),
// codi original — es tractarà quan jfile tinga una API std::vector. static_cast<Uint32>(buffer.size()), filename),
JA_PlayMusic(JA_LoadMusic(reinterpret_cast<Uint8*>(buffer), size, filename), loop); loop);
} }
} // namespace scenes } // namespace scenes