clang-format

mogudes coses de config.yaml a debug.yaml
This commit is contained in:
2026-04-16 16:46:18 +02:00
parent 0cd09f6d28
commit fe41919e1e
44 changed files with 2258 additions and 2162 deletions

View File

@@ -226,7 +226,9 @@ inline JA_Music_t* JA_LoadMusic(const Uint8* buffer, Uint32 length) {
int error = 0;
music->vorbis = stb_vorbis_open_memory(music->ogg_data.data(),
static_cast<int>(length), &error, nullptr);
static_cast<int>(length),
&error,
nullptr);
if (!music->vorbis) {
SDL_Log("JA_LoadMusic: stb_vorbis_open_memory failed (error %d)", error);
delete music;

View File

@@ -189,19 +189,19 @@ void JD8_SetPaletteColor(Uint8 index, Uint8 r, Uint8 g, Uint8 b) {
// el caller decideix quan fer Flip.
namespace {
enum FadeType {
enum FadeType {
FADE_NONE = 0,
FADE_OUT,
FADE_TO_PAL,
};
};
constexpr int FADE_STEPS = 32;
constexpr int FADE_STEPS = 32;
FadeType fade_type = FADE_NONE;
Color fade_target[256];
int fade_step = 0;
FadeType fade_type = FADE_NONE;
Color fade_target[256];
int fade_step = 0;
void apply_fade_step() {
void apply_fade_step() {
if (fade_type == FADE_OUT) {
for (int i = 0; i < 256; i++) {
main_palette[i].r = main_palette[i].r >= 8 ? main_palette[i].r - 8 : 0;
@@ -221,7 +221,7 @@ void apply_fade_step() {
: fade_target[i].b;
}
}
}
}
} // namespace

View File

@@ -14,16 +14,16 @@
namespace {
struct keyvalue {
struct keyvalue {
std::string key;
std::string value;
};
};
std::vector<keyvalue> config;
std::string resource_folder;
std::string config_folder;
std::vector<keyvalue> config;
std::string resource_folder;
std::string config_folder;
void load_config_values() {
void load_config_values() {
config.clear();
const std::string config_file = config_folder + "/config.txt";
std::ifstream fi(config_file);
@@ -35,16 +35,16 @@ void load_config_values() {
if (eq == std::string::npos) continue;
config.push_back({line.substr(0, eq), line.substr(eq + 1)});
}
}
}
void save_config_values() {
void save_config_values() {
const std::string config_file = config_folder + "/config.txt";
std::ofstream fo(config_file);
if (!fo.is_open()) return;
for (const auto& pair : config) {
fo << pair.key << '=' << pair.value << '\n';
}
}
}
} // namespace

View File

@@ -2,11 +2,11 @@
namespace {
bool quitting = false;
Uint32 update_ticks = 0;
Uint32 update_time = 0;
Uint32 cycle_counter = 0;
Uint32 last_delta_time = 0;
bool quitting = false;
Uint32 update_ticks = 0;
Uint32 update_time = 0;
Uint32 cycle_counter = 0;
Uint32 last_delta_time = 0;
} // namespace

View File

@@ -6,35 +6,35 @@
namespace {
// keystates és actualitzat per SDL internament. Des del joc només fem lectures.
const bool* keystates = nullptr;
// keystates és actualitzat per SDL internament. Des del joc només fem lectures.
const bool* keystates = nullptr;
// Buffer dels últims 5 caràcters tecle. Emmagatzemem caràcters ASCII
// lowercase (traduïts des de SDL_Scancode) per a poder comparar directament
// amb les cadenes dels cheats ("reviu", "alone", "obert").
Uint8 cheat[5] = {0, 0, 0, 0, 0};
// Buffer dels últims 5 caràcters tecle. Emmagatzemem caràcters ASCII
// lowercase (traduïts des de SDL_Scancode) per a poder comparar directament
// amb les cadenes dels cheats ("reviu", "alone", "obert").
Uint8 cheat[5] = {0, 0, 0, 0, 0};
bool key_pressed = false;
bool key_pressed = false;
// Temps restant en mil·lisegons durant el qual JI_KeyPressed/JI_AnyKey
// retornen false. Utilitzat per a evitar que pulsacions fortuïtes
// saltin cinemàtiques al començament.
float wait_ms = 0.0f;
// Temps restant en mil·lisegons durant el qual JI_KeyPressed/JI_AnyKey
// retornen false. Utilitzat per a evitar que pulsacions fortuïtes
// saltin cinemàtiques al començament.
float wait_ms = 0.0f;
// Per a calcular el delta entre crides a JI_Update sense que els callers
// hagen de passar-lo explícitament. Es reinicia a la primera crida.
Uint64 last_update_tick = 0;
// Per a calcular el delta entre crides a JI_Update sense que els callers
// hagen de passar-lo explícitament. Es reinicia a la primera crida.
Uint64 last_update_tick = 0;
bool input_blocked = false;
bool input_blocked = false;
Uint8 virtual_keystates[JI_VSRC_COUNT][SDL_SCANCODE_COUNT] = {{0}};
Uint8 virtual_keystates[JI_VSRC_COUNT][SDL_SCANCODE_COUNT] = {{0}};
Uint8 scancode_to_ascii(Uint8 scancode) {
Uint8 scancode_to_ascii(Uint8 scancode) {
if (scancode >= SDL_SCANCODE_A && scancode <= SDL_SCANCODE_Z) {
return static_cast<Uint8>('a' + (scancode - SDL_SCANCODE_A));
}
return 0;
}
}
} // namespace

View File

@@ -9,8 +9,8 @@
const std::string ResourcePack::DEFAULT_ENCRYPT_KEY = "AEE_RESOURCES__2026";
namespace {
constexpr const char* MAGIC = "AEE1";
constexpr uint32_t VERSION = 1;
constexpr const char* MAGIC = "AEE1";
constexpr uint32_t VERSION = 1;
} // namespace
ResourcePack::ResourcePack() = default;

View File

@@ -2,7 +2,7 @@
namespace info {
struct GameContext {
struct GameContext {
int num_piramide = 0;
int num_habitacio = 0;
int diners = 0;
@@ -14,11 +14,11 @@ struct GameContext {
bool pepe_activat = false;
void reset() { *this = GameContext{}; }
};
};
// Instància única de l'estat del joc. Reemplaça les variables soltes del
// namespace `info::` per una struct encapsulada. A Fase 5 (single-threaded)
// es podrà passar per referència als mòduls en lloc d'accedir via singleton.
inline GameContext ctx;
// Instància única de l'estat del joc. Reemplaça les variables soltes del
// namespace `info::` per una struct encapsulada. A Fase 5 (single-threaded)
// es podrà passar per referència als mòduls en lloc d'accedir via singleton.
inline GameContext ctx;
} // namespace info

View File

@@ -51,7 +51,8 @@ void ModuleGame::onEnter() {
strcmp(music, current_music) != 0) {
auto buffer = ResourceHelper::loadFile(music);
JA_PlayMusic(JA_LoadMusic(buffer.data(),
static_cast<Uint32>(buffer.size()), music));
static_cast<Uint32>(buffer.size()),
music));
}
// Arranca el fade-in tick-based. El `PaletteFade` avança un pas (de

View File

@@ -16,6 +16,66 @@ namespace Options {
config_file_path = path;
}
void setDebugFile(const std::string& path) {
debug_file_path = path;
}
auto saveDebugToFile() -> bool {
std::ofstream file(debug_file_path);
if (!file.is_open()) {
std::cerr << "Error: Unable to open file " << debug_file_path << " for writing\n";
return false;
}
std::cout << "Writing debug file: " << debug_file_path << '\n';
file << "# Aventures En Egipte - Debug Configuration File\n";
file << "#\n";
file << "# Loaded only in debug builds. Override gameplay starting state for testing.\n";
file << "\n";
file << "game:\n";
file << " habitacio_inicial: " << game.habitacio_inicial << "\n";
file << " piramide_inicial: " << game.piramide_inicial << "\n";
file << " vides: " << game.vides << "\n";
file << " diamants_inicial: " << game.diamants_inicial << "\n";
file << " diners_inicial: " << game.diners_inicial << "\n";
return true;
}
auto loadDebugFromFile() -> bool {
std::ifstream file(debug_file_path);
if (!file.good()) {
std::cout << "Debug file not found, creating default: " << debug_file_path << '\n';
return saveDebugToFile();
}
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
file.close();
try {
std::cout << "Reading debug file: " << debug_file_path << '\n';
auto yaml = fkyaml::node::deserialize(content);
if (yaml.contains("game")) {
const auto& node = yaml["game"];
if (node.contains("habitacio_inicial"))
game.habitacio_inicial = node["habitacio_inicial"].get_value<int>();
if (node.contains("piramide_inicial"))
game.piramide_inicial = node["piramide_inicial"].get_value<int>();
if (node.contains("vides"))
game.vides = node["vides"].get_value<int>();
if (node.contains("diamants_inicial"))
game.diamants_inicial = node["diamants_inicial"].get_value<int>();
if (node.contains("diners_inicial"))
game.diners_inicial = node["diners_inicial"].get_value<int>();
}
return true;
} catch (const fkyaml::exception& e) {
std::cerr << "Error parsing YAML debug: " << e.what() << '\n';
return saveDebugToFile();
}
}
void applyAudio() {
const float master = audio.enabled ? audio.volume : 0.0F;
JA_EnableMusic(audio.music_enabled);
@@ -138,16 +198,6 @@ namespace Options {
if (!yaml.contains("game")) return;
const auto& node = yaml["game"];
if (node.contains("habitacio_inicial"))
game.habitacio_inicial = node["habitacio_inicial"].get_value<int>();
if (node.contains("piramide_inicial"))
game.piramide_inicial = node["piramide_inicial"].get_value<int>();
if (node.contains("vides"))
game.vides = node["vides"].get_value<int>();
if (node.contains("diamants_inicial"))
game.diamants_inicial = node["diamants_inicial"].get_value<int>();
if (node.contains("diners_inicial"))
game.diners_inicial = node["diners_inicial"].get_value<int>();
if (node.contains("use_new_logo"))
game.use_new_logo = node["use_new_logo"].get_value<bool>();
if (node.contains("show_title_credits"))
@@ -279,11 +329,6 @@ namespace Options {
// GAME
file << "# GAME\n";
file << "game:\n";
file << " habitacio_inicial: " << game.habitacio_inicial << "\n";
file << " piramide_inicial: " << game.piramide_inicial << "\n";
file << " vides: " << game.vides << "\n";
file << " diamants_inicial: " << game.diamants_inicial << "\n";
file << " diners_inicial: " << game.diners_inicial << "\n";
file << " use_new_logo: " << (game.use_new_logo ? "true" : "false") << "\n";
file << " show_title_credits: " << (game.show_title_credits ? "true" : "false") << "\n";
file << "\n";

View File

@@ -141,11 +141,21 @@ namespace Options {
inline std::string crtpi_file_path{};
inline int current_crtpi_preset{0};
inline std::string debug_file_path{};
// --- API ---
void setConfigFile(const std::string& path);
auto loadFromFile() -> bool;
auto saveToFile() -> bool;
// debug.yaml: estat inicial de gameplay per a tests ràpids
// (`habitacio_inicial`, `piramide_inicial`, `vides`, `diamants_inicial`,
// `diners_inicial`). Només es carrega/desa en builds de debug; en release
// els camps queden als seus defaults.
void setDebugFile(const std::string& path);
auto loadDebugFromFile() -> bool;
auto saveDebugToFile() -> bool;
void setPostFXFile(const std::string& path);
auto loadPostFXFromFile() -> bool;

View File

@@ -57,6 +57,13 @@ SDL_AppResult SDL_AppInit(void** /*appstate*/, int /*argc*/, char* /*argv*/[]) {
Options::setConfigFile(std::string(file_getconfigfolder()) + "config.yaml");
Options::loadFromFile();
#ifndef NDEBUG
// debug.yaml: estat inicial de gameplay per a tests ràpids,
// només en builds de debug.
Options::setDebugFile(std::string(file_getconfigfolder()) + "debug.yaml");
Options::loadDebugFromFile();
#endif
#ifdef __EMSCRIPTEN__
// MEMFS no persistix entre recàrregues: força valors sensats per a web.
Options::window.fullscreen = false;
@@ -112,6 +119,9 @@ void SDL_AppQuit(void* /*appstate*/, SDL_AppResult /*result*/) {
Director::get()->teardown();
Options::saveToFile();
#ifndef NDEBUG
Options::saveDebugToFile();
#endif
Director::destroy();
Menu::destroy();

View File

@@ -10,7 +10,7 @@
namespace scenes {
void BannerScene::onEnter() {
void BannerScene::onEnter() {
playMusic("music/00000004.ogg");
gfx_ = SurfaceHandle("gfx/ffase.gif");
@@ -36,9 +36,9 @@ void BannerScene::onEnter() {
phase_ = Phase::FadingIn;
remaining_ms_ = 5000;
}
}
void BannerScene::tick(int delta_ms) {
void BannerScene::tick(int delta_ms) {
switch (phase_) {
case Phase::FadingIn:
fade_.tick(delta_ms);
@@ -66,6 +66,6 @@ void BannerScene::tick(int delta_ms) {
case Phase::Done:
break;
}
}
}
} // namespace scenes

View File

@@ -6,19 +6,19 @@
namespace scenes {
// Banner pre-piràmide ("PIRÀMIDE X"). Reemplaça `ModuleSequence::doBanner()`.
//
// Flux:
// 1. Arranca música "music/00000004.ogg" i carrega gfx/ffase.gif.
// 2. Pinta títol, subtítol i número de piràmide segons info::ctx.num_piramide.
// 3. Fade-in de paleta.
// 4. Mostra ~5s o fins que es polse una tecla.
// 5. JA_FadeOutMusic(250) + fade-out de paleta.
// 6. Retorna nextState=0 per a entrar al ModuleGame.
//
// Registrat al SceneRegistry amb state_keys 2..5 (els num_piramide on
// el vell `doBanner()` es cridava).
class BannerScene : public Scene {
// Banner pre-piràmide ("PIRÀMIDE X"). Reemplaça `ModuleSequence::doBanner()`.
//
// Flux:
// 1. Arranca música "music/00000004.ogg" i carrega gfx/ffase.gif.
// 2. Pinta títol, subtítol i número de piràmide segons info::ctx.num_piramide.
// 3. Fade-in de paleta.
// 4. Mostra ~5s o fins que es polse una tecla.
// 5. JA_FadeOutMusic(250) + fade-out de paleta.
// 6. Retorna nextState=0 per a entrar al ModuleGame.
//
// Registrat al SceneRegistry amb state_keys 2..5 (els num_piramide on
// el vell `doBanner()` es cridava).
class BannerScene : public Scene {
public:
void onEnter() override;
void tick(int delta_ms) override;
@@ -35,6 +35,6 @@ class BannerScene : public Scene {
PaletteFade fade_;
Phase phase_{Phase::FadingIn};
int remaining_ms_{5000};
};
};
} // namespace scenes

View File

@@ -11,29 +11,36 @@
namespace {
// Frames del cotxe: 8 posicions dins del sprite sheet gfx/final.gif. El
// vell doCredits tenia aquesta taula inline — la reproduïm idèntica.
struct CocheFrame {
// Frames del cotxe: 8 posicions dins del sprite sheet gfx/final.gif. El
// vell doCredits tenia aquesta taula inline — la reproduïm idèntica.
struct CocheFrame {
Uint16 x, y;
};
};
constexpr CocheFrame COCHE_FRAMES[8] = {
{214, 152}, {214, 104}, {214, 56}, {214, 104}, {214, 152}, {214, 8}, {108, 152}, {214, 8},
};
constexpr CocheFrame COCHE_FRAMES[8] = {
{214, 152},
{214, 104},
{214, 56},
{214, 104},
{214, 152},
{214, 8},
{108, 152},
{214, 8},
};
constexpr int CONTADOR_MAX = 3100; // ~62 s de crèdits a 20 ms/tick
constexpr int TICK_MS = 20; // JG_SetUpdateTicks heretat del doSlides previ
constexpr int BG_INDEX = 255;
constexpr int CONTADOR_MAX = 3100; // ~62 s de crèdits a 20 ms/tick
constexpr int TICK_MS = 20; // JG_SetUpdateTicks heretat del doSlides previ
constexpr int BG_INDEX = 255;
} // namespace
namespace scenes {
CreditsScene::~CreditsScene() {
CreditsScene::~CreditsScene() {
// No toquem la paleta activa: SetScreenPalette n'ha pres ownership.
}
}
void CreditsScene::onEnter() {
void CreditsScene::onEnter() {
// El vell doCredits no tocava música — heretava la del doSlides
// previ ("music/00000005.ogg"). Si l'escena s'arrenca directament (test
// amb piramide_inicial=8) no hi ha res que heretar, així que
@@ -53,9 +60,9 @@ void CreditsScene::onEnter() {
phase_ = Phase::Rolling;
contador_ = 1;
contador_acc_ms_ = 0;
}
}
void CreditsScene::render() {
void CreditsScene::render() {
JD8_ClearScreen(BG_INDEX);
// Columna 1: scroll vertical del bloc (0,0,80,200) pujant des de
@@ -90,18 +97,18 @@ void CreditsScene::render() {
// Barres de marc que cobreixen els extrems del scroll vertical.
JD8_FillSquare(0, 50, BG_INDEX);
JD8_FillSquare(100, 10, BG_INDEX);
}
}
void CreditsScene::writeTrickIni() {
void CreditsScene::writeTrickIni() {
FILE* ini = std::fopen("trick.ini", "wb");
if (ini) {
std::fwrite("1", 1, 1, ini);
std::fclose(ini);
}
info::ctx.nou_personatge = true;
}
}
void CreditsScene::tick(int delta_ms) {
void CreditsScene::tick(int delta_ms) {
switch (phase_) {
case Phase::Rolling: {
// Avancem el contador en passos discrets de 20 ms, igual
@@ -134,6 +141,6 @@ void CreditsScene::tick(int delta_ms) {
case Phase::Done:
break;
}
}
}
} // namespace scenes

View File

@@ -8,20 +8,20 @@
namespace scenes {
// Crèdits finals del joc. Reemplaça `ModuleSequence::doCredits()`.
//
// Flux:
// 1. Carrega gfx/final.gif (sprites de crèdits) i gfx/finals.gif (fons).
// 2. Mostra els crèdits amb scroll vertical de 2 columnes durant
// ~62 segons (contador 0..3100 × 20 ms).
// 3. Si `info::ctx.diamants == 16`, pinta addicionalment un parallax
// de 4 capes amb cotxe animat (8 frames). Si no, 2 blits fixos.
// 4. Al acabar (per tecla o per contador), crea el fitxer `trick.ini`
// i activa `info::ctx.nou_personatge`.
// 5. Fade-out de paleta. Torna a la intro (num_piramide = 255).
//
// Registrada al SceneRegistry amb state_key = 8.
class CreditsScene : public Scene {
// Crèdits finals del joc. Reemplaça `ModuleSequence::doCredits()`.
//
// Flux:
// 1. Carrega gfx/final.gif (sprites de crèdits) i gfx/finals.gif (fons).
// 2. Mostra els crèdits amb scroll vertical de 2 columnes durant
// ~62 segons (contador 0..3100 × 20 ms).
// 3. Si `info::ctx.diamants == 16`, pinta addicionalment un parallax
// de 4 capes amb cotxe animat (8 frames). Si no, 2 blits fixos.
// 4. Al acabar (per tecla o per contador), crea el fitxer `trick.ini`
// i activa `info::ctx.nou_personatge`.
// 5. Fade-out de paleta. Torna a la intro (num_piramide = 255).
//
// Registrada al SceneRegistry amb state_key = 8.
class CreditsScene : public Scene {
public:
CreditsScene() = default;
~CreditsScene() override;
@@ -47,6 +47,6 @@ class CreditsScene : public Scene {
Phase phase_{Phase::Rolling};
int contador_{1};
int contador_acc_ms_{0};
};
};
} // namespace scenes

View File

@@ -4,12 +4,12 @@
namespace scenes {
FrameAnimator::FrameAnimator(int num_frames, int frame_ms, bool loop)
FrameAnimator::FrameAnimator(int num_frames, int frame_ms, bool loop)
: num_frames_(std::max(1, num_frames)),
frame_ms_(std::max(1, frame_ms)),
loop_(loop) {}
void FrameAnimator::tick(int delta_ms) {
void FrameAnimator::tick(int delta_ms) {
if (finished_) return;
elapsed_ms_ += delta_ms;
while (elapsed_ms_ >= frame_ms_) {
@@ -25,12 +25,12 @@ void FrameAnimator::tick(int delta_ms) {
}
}
}
}
}
void FrameAnimator::reset() {
void FrameAnimator::reset() {
current_frame_ = 0;
elapsed_ms_ = 0;
finished_ = false;
}
}
} // namespace scenes

View File

@@ -2,13 +2,13 @@
namespace scenes {
// Cicla per un conjunt de frames numerats (0..num_frames-1) avançant un
// frame cada `frame_ms` mil·lisegons. No carrega ni dibuixa cap sprite —
// només el caller sap quins frames dibuixar a partir de `frame()`.
//
// Usat per animacions periòdiques amb frames subsamplejats: palmeres,
// camell, aigua, torxes, Sam caminant amb `(i/5) % fr` del codi original.
class FrameAnimator {
// Cicla per un conjunt de frames numerats (0..num_frames-1) avançant un
// frame cada `frame_ms` mil·lisegons. No carrega ni dibuixa cap sprite —
// només el caller sap quins frames dibuixar a partir de `frame()`.
//
// Usat per animacions periòdiques amb frames subsamplejats: palmeres,
// camell, aigua, torxes, Sam caminant amb `(i/5) % fr` del codi original.
class FrameAnimator {
public:
FrameAnimator() = default;
FrameAnimator(int num_frames, int frame_ms, bool loop = true);
@@ -29,6 +29,6 @@ class FrameAnimator {
int current_frame_{0};
int elapsed_ms_{0};
bool finished_{false};
};
};
} // namespace scenes

View File

@@ -9,41 +9,41 @@
namespace {
// Coordenades mesurades del wordmark "Jailgames" dins gfx/logo_new.gif.
// Idèntiques a les del doIntroNewLogo vell — si canvies el logo, aquí i
// al GIF són els únics llocs a tocar.
constexpr int LOGO_SRC_X = 60;
constexpr int LOGO_SRC_Y = 158;
constexpr int LOGO_DST_Y = 78;
constexpr int LOGO_HEIGHT = 28;
constexpr int LETTER_WIDTHS[9] = {16, 39, 50, 69, 92, 115, 146, 169, 188};
constexpr int CURSOR_X[9] = {77, 100, 111, 130, 153, 176, 207, 230, 249};
constexpr int CURSOR_W = 12;
constexpr int CURSOR_H = 3;
constexpr int CURSOR_Y = LOGO_DST_Y + LOGO_HEIGHT - CURSOR_H; // y = 103
constexpr Uint8 CURSOR_COLOR = 17; // mateix index verd que les lletres
// Coordenades mesurades del wordmark "Jailgames" dins gfx/logo_new.gif.
// Idèntiques a les del doIntroNewLogo vell — si canvies el logo, aquí i
// al GIF són els únics llocs a tocar.
constexpr int LOGO_SRC_X = 60;
constexpr int LOGO_SRC_Y = 158;
constexpr int LOGO_DST_Y = 78;
constexpr int LOGO_HEIGHT = 28;
constexpr int LETTER_WIDTHS[9] = {16, 39, 50, 69, 92, 115, 146, 169, 188};
constexpr int CURSOR_X[9] = {77, 100, 111, 130, 153, 176, 207, 230, 249};
constexpr int CURSOR_W = 12;
constexpr int CURSOR_H = 3;
constexpr int CURSOR_Y = LOGO_DST_Y + LOGO_HEIGHT - CURSOR_H; // y = 103
constexpr Uint8 CURSOR_COLOR = 17; // mateix index verd que les lletres
// Timings (ms) — idèntics als de doIntroNewLogo vell.
constexpr int INITIAL_MS = 1000;
constexpr int REVEAL_FRAME_MS = 150;
constexpr int FULL_LOGO_MS = 200;
constexpr int PALETTE_CYCLE_STEP_MS = 20;
constexpr int FINAL_WAIT_MS = 20;
constexpr int PALETTE_CYCLE_STEPS = 256;
// Timings (ms) — idèntics als de doIntroNewLogo vell.
constexpr int INITIAL_MS = 1000;
constexpr int REVEAL_FRAME_MS = 150;
constexpr int FULL_LOGO_MS = 200;
constexpr int PALETTE_CYCLE_STEP_MS = 20;
constexpr int FINAL_WAIT_MS = 20;
constexpr int PALETTE_CYCLE_STEPS = 256;
} // namespace
namespace scenes {
IntroNewLogoScene::IntroNewLogoScene() = default;
IntroNewLogoScene::IntroNewLogoScene() = default;
IntroNewLogoScene::~IntroNewLogoScene() {
IntroNewLogoScene::~IntroNewLogoScene() {
// No alliberem `pal_`: JD8_SetScreenPalette n'ha pres ownership i
// el proper SetScreenPalette / FadeToPal el lliurarà. Alliberar-lo
// ací provocaria double free.
}
}
void IntroNewLogoScene::onEnter() {
void IntroNewLogoScene::onEnter() {
playMusic("music/00000003.ogg");
gfx_ = SurfaceHandle("gfx/logo_new.gif");
@@ -62,9 +62,9 @@ void IntroNewLogoScene::onEnter() {
reveal_letter_ = 0;
reveal_cursor_visible_ = true;
palette_step_ = 0;
}
}
void IntroNewLogoScene::render() {
void IntroNewLogoScene::render() {
switch (phase_) {
case Phase::Initial:
JD8_ClearScreen(0);
@@ -72,19 +72,16 @@ void IntroNewLogoScene::render() {
case Phase::Revealing: {
JD8_ClearScreen(0);
JD8_Blit(LOGO_SRC_X, LOGO_DST_Y, gfx_, LOGO_SRC_X, LOGO_SRC_Y,
LETTER_WIDTHS[reveal_letter_], LOGO_HEIGHT);
JD8_Blit(LOGO_SRC_X, LOGO_DST_Y, gfx_, LOGO_SRC_X, LOGO_SRC_Y, LETTER_WIDTHS[reveal_letter_], LOGO_HEIGHT);
if (reveal_cursor_visible_) {
JD8_Blit(CURSOR_X[reveal_letter_], CURSOR_Y, cursor_surf_,
0, 0, CURSOR_W, CURSOR_H);
JD8_Blit(CURSOR_X[reveal_letter_], CURSOR_Y, cursor_surf_, 0, 0, CURSOR_W, CURSOR_H);
}
break;
}
case Phase::FullLogoFlash:
JD8_ClearScreen(0);
JD8_Blit(LOGO_SRC_X, LOGO_DST_Y, gfx_, LOGO_SRC_X, LOGO_SRC_Y,
LETTER_WIDTHS[8], LOGO_HEIGHT);
JD8_Blit(LOGO_SRC_X, LOGO_DST_Y, gfx_, LOGO_SRC_X, LOGO_SRC_Y, LETTER_WIDTHS[8], LOGO_HEIGHT);
JD8_Blit(CURSOR_X[8], CURSOR_Y, cursor_surf_, 0, 0, CURSOR_W, CURSOR_H);
break;
@@ -93,17 +90,16 @@ void IntroNewLogoScene::render() {
// Logo complet sense cursor — els pixels del cursor
// ciclarien de color durant el cicle de paleta.
JD8_ClearScreen(0);
JD8_Blit(LOGO_SRC_X, LOGO_DST_Y, gfx_, LOGO_SRC_X, LOGO_SRC_Y,
LETTER_WIDTHS[8], LOGO_HEIGHT);
JD8_Blit(LOGO_SRC_X, LOGO_DST_Y, gfx_, LOGO_SRC_X, LOGO_SRC_Y, LETTER_WIDTHS[8], LOGO_HEIGHT);
break;
case Phase::Sprites:
case Phase::Done:
break;
}
}
}
void IntroNewLogoScene::advancePaletteCycle() {
void IntroNewLogoScene::advancePaletteCycle() {
// Replica exacta del ciclo de paleta del doIntroNewLogo vell sobre
// els índexs 16..31 (grup del verd brillant del logo).
for (int i = 16; i < 32; i++) {
@@ -117,9 +113,9 @@ void IntroNewLogoScene::advancePaletteCycle() {
if (pal_[i].r < pal_[i].g) pal_[i].r++;
if (pal_[i].r > pal_[i].g) pal_[i].r--;
}
}
}
void IntroNewLogoScene::tick(int delta_ms) {
void IntroNewLogoScene::tick(int delta_ms) {
// Qualsevol tecla durant el revelat o el ciclo de paleta salta
// TOTA la intro (inclou saltar la fase de sprites). Durant Sprites
// deixem que la sub-escena gestione el seu propi skip (que a més
@@ -213,6 +209,6 @@ void IntroNewLogoScene::tick(int delta_ms) {
case Phase::Done:
break;
}
}
}
} // namespace scenes

View File

@@ -9,27 +9,27 @@
namespace scenes {
// Intro "moderna" del logo Jailgames amb revelat lletra-a-lletra +
// ciclo de paleta final. Reemplaça `ModuleSequence::doIntroNewLogo()`.
//
// Flux:
// 1. Carrega gfx/logo_new.gif, arranca música "music/00000003.ogg" i posa
// la paleta directament (sense fade-in). Mostra pantalla negra 1s.
// 2. Revelat: 9 lletres × 2 frames (amb cursor / sense cursor), 150 ms
// cada frame.
// 3. Logo complet amb cursor fix 200 ms.
// 4. Cicle de paleta de 256 passos modificant índexs 1631 cada 20 ms.
// 5. Espera final 20 ms.
// 6. 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, amb una factory
// condicional: només s'activa si `Options::game.use_new_logo == true`.
// Si és false, la factory retorna nullptr i el gameFiberEntry cau al
// path legacy (`ModuleSequence::doIntro()` vell).
class IntroNewLogoScene : public Scene {
// Intro "moderna" del logo Jailgames amb revelat lletra-a-lletra +
// ciclo de paleta final. Reemplaça `ModuleSequence::doIntroNewLogo()`.
//
// Flux:
// 1. Carrega gfx/logo_new.gif, arranca música "music/00000003.ogg" i posa
// la paleta directament (sense fade-in). Mostra pantalla negra 1s.
// 2. Revelat: 9 lletres × 2 frames (amb cursor / sense cursor), 150 ms
// cada frame.
// 3. Logo complet amb cursor fix 200 ms.
// 4. Cicle de paleta de 256 passos modificant índexs 1631 cada 20 ms.
// 5. Espera final 20 ms.
// 6. 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, amb una factory
// condicional: només s'activa si `Options::game.use_new_logo == true`.
// Si és false, la factory retorna nullptr i el gameFiberEntry cau al
// path legacy (`ModuleSequence::doIntro()` vell).
class IntroNewLogoScene : public Scene {
public:
IntroNewLogoScene();
~IntroNewLogoScene() override;
@@ -63,6 +63,6 @@ class IntroNewLogoScene : public Scene {
int reveal_letter_{0};
bool reveal_cursor_visible_{true};
int palette_step_{0};
};
};
} // namespace scenes

View File

@@ -7,26 +7,26 @@
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;
// 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 {
// 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[] = {
constexpr RevealStep REVEAL_STEPS[] = {
{100, 27, 68, false, false}, // J
{100, 53, 96, false, false}, // JA
{100, 66, 109, false, false}, // JAI
@@ -42,30 +42,30 @@ constexpr RevealStep REVEAL_STEPS[] = {
{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]);
};
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) {
// 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() = default;
IntroScene::~IntroScene() {
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() {
void IntroScene::onEnter() {
playMusic("music/00000003.ogg");
gfx_ = SurfaceHandle("gfx/logo.gif");
@@ -78,9 +78,9 @@ void IntroScene::onEnter() {
phase_acc_ms_ = 0;
reveal_index_ = 0;
palette_step_ = 0;
}
}
void IntroScene::render() {
void IntroScene::render() {
switch (phase_) {
case Phase::InitialWait:
JD8_ClearScreen(0);
@@ -113,9 +113,9 @@ void IntroScene::render() {
case Phase::Done:
break;
}
}
}
void IntroScene::advancePaletteCycle() {
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à.
@@ -130,9 +130,9 @@ void IntroScene::advancePaletteCycle() {
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) {
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
@@ -213,6 +213,6 @@ void IntroScene::tick(int delta_ms) {
case Phase::Done:
break;
}
}
}
} // namespace scenes

View File

@@ -9,28 +9,28 @@
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 gfx/logo.gif, arranca música "music/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 {
// 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 gfx/logo.gif, arranca música "music/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;
@@ -61,6 +61,6 @@ class IntroScene : public Scene {
int phase_acc_ms_{0};
int reveal_index_{0};
int palette_step_{0};
};
};
} // namespace scenes

View File

@@ -9,129 +9,142 @@
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;
// 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
// (gfx/logo.gif o gfx/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)
// Taules de frames. Ubicacions de cada sprite dins el gfx de la intro
// (gfx/logo.gif o gfx/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
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) {
// 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);
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 {
// 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
// =========================================================================
// =========================================================================
// Variant 0 — Interrogant / Momia
// =========================================================================
void v0_walk_right(JD8_Surface gfx, int i) {
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) {
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) {
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) {
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) {
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*/) {
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) {
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) {
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) {
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*/) {
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[] = {
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)
@@ -143,35 +156,35 @@ constexpr SpritePhase variant_0[] = {
{50, 99, v0_jump2, true},
{80, 0, v0_walk_final, true},
{0, 150, v0_final, true},
};
};
// =========================================================================
// Variant 1 — Creu / Pedra
// =========================================================================
// =========================================================================
// Variant 1 — Creu / Pedra
// =========================================================================
void v1_walk_right(JD8_Surface gfx, int i) {
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) {
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*/) {
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) {
void v1_drop_map(JD8_Surface gfx, int i) {
JD8_ClearScreen(0);
drawWordmark(gfx);
JD8_BlitCK(200, 155, gfx, 0, CREU, 15, 15, 255);
@@ -183,33 +196,33 @@ void v1_drop_map(JD8_Surface gfx, int i) {
} else {
JD8_BlitCK(200, 150, gfx, fr7[idx], 105, 15, 15, 255);
}
}
}
void v1_stone_fall(JD8_Surface gfx, int i) {
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) {
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*/) {
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[] = {
constexpr SpritePhase variant_1[] = {
{0, 200, v1_walk_right, true},
{0, 300, v1_pull_map, true},
{0, 100, v1_interrogant, true},
@@ -217,87 +230,93 @@ constexpr SpritePhase variant_1[] = {
{0, 75, v1_stone_fall, true},
{0, 19, v1_stone_break, true},
{0, 200, v1_final, true},
};
};
// =========================================================================
// Variant 2 — Ball de carnaval
// =========================================================================
// =========================================================================
// Variant 2 — Ball de carnaval
// =========================================================================
void v2_approach(JD8_Surface gfx, int i) {
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*/) {
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) {
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) {
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[] = {
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
// =========================================================================
// =========================================================================
// Dispatch per variant
// =========================================================================
const SpritePhase* variant_table(int variant) {
const SpritePhase* variant_table(int variant) {
switch (variant) {
case 0: return variant_0;
case 1: return variant_1;
case 2: return variant_2;
case 0:
return variant_0;
case 1:
return variant_1;
case 2:
return variant_2;
}
return variant_0;
}
}
int variant_length(int variant) {
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]);
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) {
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) {
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)
IntroSpritesScene::IntroSpritesScene(SurfaceHandle&& gfx)
: gfx_(std::move(gfx)) {}
void IntroSpritesScene::onEnter() {
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.
@@ -311,9 +330,9 @@ void IntroSpritesScene::onEnter() {
// 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) {
void IntroSpritesScene::tick(int delta_ms) {
if (done_) return;
const SpritePhase* phases = variant_table(variant_);
@@ -341,6 +360,6 @@ void IntroSpritesScene::tick(int delta_ms) {
}
}
phases[phase_].render(gfx_.get(), phase_current_i(phases[phase_], phase_step_));
}
}
} // namespace scenes

View File

@@ -5,23 +5,23 @@
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 {
// 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;
@@ -38,6 +38,6 @@ class IntroSpritesScene : public Scene {
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

@@ -8,7 +8,7 @@
namespace scenes {
void MenuScene::onEnter() {
void MenuScene::onEnter() {
fondo_ = SurfaceHandle("gfx/menu.gif");
gfx_ = SurfaceHandle("gfx/menu2.gif");
@@ -24,9 +24,9 @@ void MenuScene::onEnter() {
std::free(pal);
phase_ = Phase::FadingIn;
}
}
void MenuScene::render() {
void MenuScene::render() {
// Cel estàtic (els primers 100 pixels verticals)
JD8_Blit(0, 0, fondo_, 0, 0, 320, 100);
@@ -56,9 +56,9 @@ void MenuScene::render() {
JD8_BlitCK(68, 141, gfx_, 128, 105, 189, 9, 255);
}
}
}
}
void MenuScene::tick(int delta_ms) {
void MenuScene::tick(int delta_ms) {
switch (phase_) {
case Phase::FadingIn:
fade_.tick(delta_ms);
@@ -108,6 +108,6 @@ void MenuScene::tick(int delta_ms) {
case Phase::Done:
break;
}
}
}
} // namespace scenes

View File

@@ -7,22 +7,22 @@
namespace scenes {
// Menú del títol. Reemplaça `ModuleSequence::doMenu()`.
//
// Flux:
// 1. Carrega gfx/menu.gif (fondo) i gfx/menu2.gif (sprites) + paleta.
// 2. Pintat inicial estàtic (fondo, logo, camell frame 0, base "jdes"),
// fade-in de paleta.
// 3. Loop d'animació: escroll parallax de horitzó (cada 320 ms) i
// palmeres (cada 80 ms), cicle del camell (4 frames × 160 ms),
// i el text "polsa tecla" parpallejant cada 2 s (visible 1.4 s,
// amagat 0.6 s, igual que el `contador % 100 > 30` original).
// 4. Quan l'usuari polsa qualsevol tecla — o 'P' per a activar Pepe —
// llegim `info::ctx.pepe_activat`, disparem fade-out i marquem
// num_piramide=1 (vas a doSlides).
//
// Registrat al SceneRegistry amb state_key = 0.
class MenuScene : public Scene {
// Menú del títol. Reemplaça `ModuleSequence::doMenu()`.
//
// Flux:
// 1. Carrega gfx/menu.gif (fondo) i gfx/menu2.gif (sprites) + paleta.
// 2. Pintat inicial estàtic (fondo, logo, camell frame 0, base "jdes"),
// fade-in de paleta.
// 3. Loop d'animació: escroll parallax de horitzó (cada 320 ms) i
// palmeres (cada 80 ms), cicle del camell (4 frames × 160 ms),
// i el text "polsa tecla" parpallejant cada 2 s (visible 1.4 s,
// amagat 0.6 s, igual que el `contador % 100 > 30` original).
// 4. Quan l'usuari polsa qualsevol tecla — o 'P' per a activar Pepe —
// llegim `info::ctx.pepe_activat`, disparem fade-out i marquem
// num_piramide=1 (vas a doSlides).
//
// Registrat al SceneRegistry amb state_key = 0.
class MenuScene : public Scene {
public:
void onEnter() override;
void tick(int delta_ms) override;
@@ -52,6 +52,6 @@ class MenuScene : public Scene {
// Acumulador per al parpalleig del text "polsa tecla".
int blink_ms_{0};
};
};
} // namespace scenes

View File

@@ -9,7 +9,7 @@
namespace scenes {
void MortScene::onEnter() {
void MortScene::onEnter() {
playMusic("music/00000001.ogg");
JI_DisableKeyboard(60);
info::ctx.vida = 5;
@@ -26,9 +26,9 @@ void MortScene::onEnter() {
phase_ = Phase::FadingIn;
remaining_ms_ = 10000;
}
}
void MortScene::tick(int delta_ms) {
void MortScene::tick(int delta_ms) {
switch (phase_) {
case Phase::FadingIn:
fade_.tick(delta_ms);
@@ -59,6 +59,6 @@ void MortScene::tick(int delta_ms) {
case Phase::Done:
break;
}
}
}
} // namespace scenes

View File

@@ -6,15 +6,15 @@
namespace scenes {
// Pantalla de "game over". Reemplaça `ModuleSequence::doMort()`.
//
// Flux:
// 1. Carrega gfx/gameover.gif, arranca música "music/00000001.ogg", fade-in de paleta.
// 2. Mostra la pantalla ~10 segons o fins que l'usuari polse una tecla.
// 3. Arranca música del menú ("music/00000003.ogg") i fade-out de paleta.
// 4. Marca num_piramide=0 i retorna nextState=1 perquè el Director
// passe a l'escena del menú.
class MortScene : public Scene {
// Pantalla de "game over". Reemplaça `ModuleSequence::doMort()`.
//
// Flux:
// 1. Carrega gfx/gameover.gif, arranca música "music/00000001.ogg", fade-in de paleta.
// 2. Mostra la pantalla ~10 segons o fins que l'usuari polse una tecla.
// 3. Arranca música del menú ("music/00000003.ogg") i fade-out de paleta.
// 4. Marca num_piramide=0 i retorna nextState=1 perquè el Director
// passe a l'escena del menú.
class MortScene : public Scene {
public:
void onEnter() override;
void tick(int delta_ms) override;
@@ -31,6 +31,6 @@ class MortScene : public Scene {
PaletteFade fade_;
Phase phase_{Phase::FadingIn};
int remaining_ms_{10000}; // 1000 ticks × 10 ms/tick del doMort original
};
};
} // namespace scenes

View File

@@ -2,17 +2,17 @@
namespace scenes {
void PaletteFade::startFadeOut() {
void PaletteFade::startFadeOut() {
JD8_FadeStartOut();
active_ = true;
}
}
void PaletteFade::startFadeTo(JD8_Palette target) {
void PaletteFade::startFadeTo(JD8_Palette target) {
JD8_FadeStartToPal(target);
active_ = true;
}
}
void PaletteFade::tick(int /*delta_ms*/) {
void PaletteFade::tick(int /*delta_ms*/) {
if (!active_) return;
// El fade té 32 passos interns. Amb un tick per frame (~16ms)
// dura ~512ms — el mateix temps que la versió bloquejant original.
@@ -23,6 +23,6 @@ void PaletteFade::tick(int /*delta_ms*/) {
if (JD8_FadeTickStep()) {
active_ = false;
}
}
}
} // namespace scenes

View File

@@ -4,14 +4,14 @@
namespace scenes {
// Embolcall fi damunt de la màquina d'estats de fade de jdraw8
// (`JD8_FadeStart*` / `JD8_FadeTickStep`). Exposa una API time-based
// però internament avança un pas del fade per cada crida a `tick()`.
// La raó de tindre-ho com a classe a banda: que una escena no puga
// cridar accidentalment a `JD8_FadeOut`/`JD8_FadeToPal` (els shims
// bloquejants vells) i que el `done()` siga consultable com la resta
// dels helpers.
class PaletteFade {
// Embolcall fi damunt de la màquina d'estats de fade de jdraw8
// (`JD8_FadeStart*` / `JD8_FadeTickStep`). Exposa una API time-based
// però internament avança un pas del fade per cada crida a `tick()`.
// La raó de tindre-ho com a classe a banda: que una escena no puga
// cridar accidentalment a `JD8_FadeOut`/`JD8_FadeToPal` (els shims
// bloquejants vells) i que el `done()` siga consultable com la resta
// dels helpers.
class PaletteFade {
public:
PaletteFade() = default;
@@ -25,6 +25,6 @@ class PaletteFade {
private:
bool active_{false};
};
};
} // namespace scenes

View File

@@ -16,7 +16,7 @@
namespace scenes {
class Scene {
class Scene {
public:
virtual ~Scene() = default;
@@ -32,6 +32,6 @@ class Scene {
// 0 = entrar al gameplay (ModuleGame)
// -1 = eixir del joc
virtual int nextState() const { return 1; }
};
};
} // namespace scenes

View File

@@ -2,19 +2,19 @@
namespace scenes {
SceneRegistry& SceneRegistry::instance() {
SceneRegistry& SceneRegistry::instance() {
static SceneRegistry inst;
return inst;
}
}
void SceneRegistry::registerScene(int state_key, Factory factory) {
void SceneRegistry::registerScene(int state_key, Factory factory) {
factories_[state_key] = std::move(factory);
}
}
std::unique_ptr<Scene> SceneRegistry::tryCreate(int state_key) const {
std::unique_ptr<Scene> SceneRegistry::tryCreate(int state_key) const {
const auto it = factories_.find(state_key);
if (it == factories_.end()) return nullptr;
return it->second();
}
}
} // namespace scenes

View File

@@ -8,16 +8,16 @@
namespace scenes {
// Mapa de `state_key` (actualment = `info::ctx.num_piramide`) a factory
// d'escena. Permet que el dispatch de `gameFiberEntry` provi primer una
// Scene nova i caiga al vell `ModuleSequence::Go()` si encara no està
// migrada.
//
// Registre inicial: `Director::init()` cridarà `instance()` i afegirà
// una entrada per cada escena ja portada. A mesura que vagen caient, les
// línies del registre creixen i les funcions `doX()` del modulesequence
// desapareixen.
class SceneRegistry {
// Mapa de `state_key` (actualment = `info::ctx.num_piramide`) a factory
// d'escena. Permet que el dispatch de `gameFiberEntry` provi primer una
// Scene nova i caiga al vell `ModuleSequence::Go()` si encara no està
// migrada.
//
// Registre inicial: `Director::init()` cridarà `instance()` i afegirà
// una entrada per cada escena ja portada. A mesura que vagen caient, les
// línies del registre creixen i les funcions `doX()` del modulesequence
// desapareixen.
class SceneRegistry {
public:
using Factory = std::function<std::unique_ptr<Scene>()>;
@@ -32,6 +32,6 @@ class SceneRegistry {
private:
SceneRegistry() = default;
std::unordered_map<int, Factory> factories_;
};
};
} // namespace scenes

View File

@@ -7,15 +7,16 @@
namespace scenes {
void playMusic(const char* filename, int loop) {
void playMusic(const char* filename, int loop) {
if (!filename) return;
auto buffer = ResourceHelper::loadFile(filename);
if (buffer.empty()) return;
// JA_LoadMusic fa una còpia interna del OGG comprimit (via SDL_malloc)
// per a stb_vorbis. El `buffer` local es destruirà en sortir d'àmbit.
JA_PlayMusic(JA_LoadMusic(buffer.data(),
static_cast<Uint32>(buffer.size()), filename),
static_cast<Uint32>(buffer.size()),
filename),
loop);
}
}
} // namespace scenes

View File

@@ -5,9 +5,9 @@
namespace scenes {
// Carrega un OGG de `data/` i arranca'l com a música de fons. Substituïx
// el `play_music()` repetit en tots els doX() del vell modulesequence.
// `loop`: -1 = infinit (per defecte), 0 = una sola vegada, N = N+1 passades.
void playMusic(const char* filename, int loop = -1);
// Carrega un OGG de `data/` i arranca'l com a música de fons. Substituïx
// el `play_music()` repetit en tots els doX() del vell modulesequence.
// `loop`: -1 = infinit (per defecte), 0 = una sola vegada, N = N+1 passades.
void playMusic(const char* filename, int loop = -1);
} // namespace scenes

View File

@@ -12,36 +12,36 @@
namespace {
constexpr int TICK_MS = 20; // JG_SetUpdateTicks(20) del vell doSecreta
constexpr int TICK_MS = 20; // JG_SetUpdateTicks(20) del vell doSecreta
// Durades per fase, derivades dels contador-thresholds del vell:
// tomba1 scroll: 127 passos (contador 1→128) × 20ms
// tomba1 hold: 128 passos (contador 128→0) × 20ms
// tomba2 scroll: 94 passos × 20ms
// tomba2 hold: 94 passos × 20ms
// reveal horit: 80 passos × 20ms
// reveal hold: 80 passos × 20ms
// red pulse: 51 passos × 20ms
// red pulse hold: 51 passos × 20ms
constexpr int TOMBA1_SCROLL_MS = 127 * TICK_MS;
constexpr int TOMBA1_HOLD_MS = 128 * TICK_MS;
constexpr int TOMBA2_SCROLL_MS = 94 * TICK_MS;
constexpr int TOMBA2_HOLD_MS = 94 * TICK_MS;
constexpr int TOMBA2_REVEAL_MS = 80 * TICK_MS;
constexpr int TOMBA2_REVEAL_HOLD_MS = 80 * TICK_MS;
constexpr int RED_PULSE_MS = 51 * TICK_MS;
constexpr int RED_PULSE_HOLD_MS = 51 * TICK_MS;
// Durades per fase, derivades dels contador-thresholds del vell:
// tomba1 scroll: 127 passos (contador 1→128) × 20ms
// tomba1 hold: 128 passos (contador 128→0) × 20ms
// tomba2 scroll: 94 passos × 20ms
// tomba2 hold: 94 passos × 20ms
// reveal horit: 80 passos × 20ms
// reveal hold: 80 passos × 20ms
// red pulse: 51 passos × 20ms
// red pulse hold: 51 passos × 20ms
constexpr int TOMBA1_SCROLL_MS = 127 * TICK_MS;
constexpr int TOMBA1_HOLD_MS = 128 * TICK_MS;
constexpr int TOMBA2_SCROLL_MS = 94 * TICK_MS;
constexpr int TOMBA2_HOLD_MS = 94 * TICK_MS;
constexpr int TOMBA2_REVEAL_MS = 80 * TICK_MS;
constexpr int TOMBA2_REVEAL_HOLD_MS = 80 * TICK_MS;
constexpr int RED_PULSE_MS = 51 * TICK_MS;
constexpr int RED_PULSE_HOLD_MS = 51 * TICK_MS;
} // namespace
namespace scenes {
SecretaScene::~SecretaScene() {
SecretaScene::~SecretaScene() {
if (pal_aux_) std::free(pal_aux_);
// pal_active_ NO s'allibera: propietat de main_palette via SetScreenPalette.
}
}
void SecretaScene::onEnter() {
void SecretaScene::onEnter() {
playMusic("music/00000002.ogg");
// Fade-out de la paleta anterior. Els assets es carreguen ja
@@ -56,9 +56,9 @@ void SecretaScene::onEnter() {
phase_ = Phase::InitialFadeOut;
phase_acc_ms_ = 0;
}
}
void SecretaScene::swapToTomba2() {
void SecretaScene::swapToTomba2() {
JD8_ClearScreen(255);
gfx_.reset("gfx/tomba2.gif");
@@ -67,21 +67,21 @@ void SecretaScene::swapToTomba2() {
// pal_active_ continua sent el mateix buffer: només actualitzem
// el seu contingut. main_palette ja apunta ací.
std::memcpy(pal_active_, pal_aux_, 768);
}
}
void SecretaScene::beginRedPulseSetup() {
void SecretaScene::beginRedPulseSetup() {
JD8_ClearScreen(0);
JD8_SetPaletteColor(254, 12, 11, 11);
JD8_SetPaletteColor(253, 12, 11, 11);
}
}
void SecretaScene::beginFinalFade() {
void SecretaScene::beginFinalFade() {
JA_FadeOutMusic(250);
fade_.startFadeOut();
phase_ = Phase::FinalFadeOut;
}
}
void SecretaScene::tick(int delta_ms) {
void SecretaScene::tick(int delta_ms) {
// Skip per tecla (després del fade inicial, no mentre). Salta
// directament al FinalFadeOut. Mateix patró que el vell, on
// qualsevol tecla sortia del loop.
@@ -200,6 +200,6 @@ void SecretaScene::tick(int delta_ms) {
case Phase::Done:
break;
}
}
}
} // namespace scenes

View File

@@ -7,24 +7,24 @@
namespace scenes {
// Pre-Secreta. Reemplaça `ModuleSequence::doSecreta()`.
//
// Flux:
// 1. Arranca música "music/00000002.ogg" i fa fade-out de la paleta anterior.
// 2. Carrega gfx/tomba1.gif + paleta i pinta un scroll vertical doble
// (dos blits solapats, un a velocitat meitat que l'altre) durant
// ~2.5 s + ~2.5 s de pausa.
// 3. Swap a gfx/tomba2.gif + reset de paleta, scroll vertical del segon
// asset (~1.9 s + ~1.9 s de pausa).
// 4. ClearScreen a 0, set colors 253/254 a vermell fosc (12,11,11)
// i pinta un revelat horitzontal (~1.6 s + ~1.6 s de pausa).
// 5. "Red pulse": anima els colors 253/254 incrementant el canal R
// de 12 a 62 durant ~1 s (+ ~1 s de pausa).
// 6. FadeOut + JA_FadeOutMusic(250).
// 7. Retorna nextState=0 per entrar al ModuleGame amb num_piramide=6.
//
// Registrada al SceneRegistry amb state_key = 6.
class SecretaScene : public Scene {
// Pre-Secreta. Reemplaça `ModuleSequence::doSecreta()`.
//
// Flux:
// 1. Arranca música "music/00000002.ogg" i fa fade-out de la paleta anterior.
// 2. Carrega gfx/tomba1.gif + paleta i pinta un scroll vertical doble
// (dos blits solapats, un a velocitat meitat que l'altre) durant
// ~2.5 s + ~2.5 s de pausa.
// 3. Swap a gfx/tomba2.gif + reset de paleta, scroll vertical del segon
// asset (~1.9 s + ~1.9 s de pausa).
// 4. ClearScreen a 0, set colors 253/254 a vermell fosc (12,11,11)
// i pinta un revelat horitzontal (~1.6 s + ~1.6 s de pausa).
// 5. "Red pulse": anima els colors 253/254 incrementant el canal R
// de 12 a 62 durant ~1 s (+ ~1 s de pausa).
// 6. FadeOut + JA_FadeOutMusic(250).
// 7. Retorna nextState=0 per entrar al ModuleGame amb num_piramide=6.
//
// Registrada al SceneRegistry amb state_key = 6.
class SecretaScene : public Scene {
public:
SecretaScene() = default;
~SecretaScene() override;
@@ -61,6 +61,6 @@ class SecretaScene : public Scene {
Phase phase_{Phase::InitialFadeOut};
int phase_acc_ms_{0};
bool skip_triggered_{false};
};
};
} // namespace scenes

View File

@@ -13,29 +13,29 @@
namespace {
constexpr int SCROLL_MS = 1600; // 80 iters × 20 ms del vell doSlides
constexpr int HOLD_MS = 4600; // 230 iters × 20 ms (80 + 150) del vell
constexpr int SLIDE_Y = 65;
constexpr int SLIDE_H = 65;
constexpr int BG_COLOR_INDEX = 255;
constexpr int SCROLL_MS = 1600; // 80 iters × 20 ms del vell doSlides
constexpr int HOLD_MS = 4600; // 230 iters × 20 ms (80 + 150) del vell
constexpr int SLIDE_Y = 65;
constexpr int SLIDE_H = 65;
constexpr int BG_COLOR_INDEX = 255;
// Desplaçament inicial del slide segons direcció del wipe.
// Slide 1 i 3: "scroll in from right" (pos_x va de 320 → 0).
// Slide 2: "wipe reverse" (pos_x va de -320 → 0), el mateix efecte
// estrany del doSlides vell on la imatge es desplaça des de l'esquerra
// però revela primer el lateral dret del src.
constexpr int SLIDE_START_X[3] = {320, -320, 320};
// Desplaçament inicial del slide segons direcció del wipe.
// Slide 1 i 3: "scroll in from right" (pos_x va de 320 → 0).
// Slide 2: "wipe reverse" (pos_x va de -320 → 0), el mateix efecte
// estrany del doSlides vell on la imatge es desplaça des de l'esquerra
// però revela primer el lateral dret del src.
constexpr int SLIDE_START_X[3] = {320, -320, 320};
} // namespace
namespace scenes {
SlidesScene::~SlidesScene() {
SlidesScene::~SlidesScene() {
if (pal_aux_) std::free(pal_aux_);
// pal_active_ NO s'allibera: propietat de main_palette via SetScreenPalette.
}
}
void SlidesScene::onEnter() {
void SlidesScene::onEnter() {
num_piramide_at_start_ = info::ctx.num_piramide;
const char* arxiu = nullptr;
@@ -63,9 +63,9 @@ void SlidesScene::onEnter() {
phase_ = Phase::Slide1Enter;
phase_acc_ms_ = 0;
next_state_ = 0;
}
}
void SlidesScene::drawSlide(int slide_idx, int pos_x) {
void SlidesScene::drawSlide(int slide_idx, int pos_x) {
const int src_y = slide_idx * SLIDE_H;
// Clipping manual: translada un rect de 320×65 des de (pos_x, SLIDE_Y)
@@ -85,21 +85,21 @@ void SlidesScene::drawSlide(int slide_idx, int pos_x) {
if (w > 0) {
JD8_Blit(dst_x, SLIDE_Y, gfx_, src_x, src_y, w, SLIDE_H);
}
}
}
void SlidesScene::restorePalette() {
void SlidesScene::restorePalette() {
std::memcpy(pal_active_, pal_aux_, 768);
}
}
void SlidesScene::beginFinalFade() {
void SlidesScene::beginFinalFade() {
if (num_piramide_at_start_ != 7) {
JA_FadeOutMusic(250);
}
fade_.startFadeOut();
phase_ = Phase::FadeFinal;
}
}
void SlidesScene::tick(int delta_ms) {
void SlidesScene::tick(int delta_ms) {
// Skip: qualsevol tecla salta directament al fade final. Per fidelitat
// al vell doSlides, el skip NO atura la música explícitament — només
// el final natural crida JA_FadeOutMusic (beginFinalFade() distingeix).
@@ -118,8 +118,7 @@ void SlidesScene::tick(int delta_ms) {
const int slide_idx = (phase_ == Phase::Slide1Enter ? 0
: phase_ == Phase::Slide2Enter ? 1
: 2);
const float t = std::min(1.0f, static_cast<float>(phase_acc_ms_) /
static_cast<float>(SCROLL_MS));
const float t = std::min(1.0f, static_cast<float>(phase_acc_ms_) / static_cast<float>(SCROLL_MS));
const float eased = Easing::outCubic(t);
const int pos_x = Easing::lerpInt(SLIDE_START_X[slide_idx], 0, eased);
drawSlide(slide_idx, pos_x);
@@ -127,9 +126,12 @@ void SlidesScene::tick(int delta_ms) {
if (phase_acc_ms_ >= SCROLL_MS) {
// Garanteix posició final exacta (pos_x=0).
drawSlide(slide_idx, 0);
if (phase_ == Phase::Slide1Enter) phase_ = Phase::Slide1Hold;
else if (phase_ == Phase::Slide2Enter) phase_ = Phase::Slide2Hold;
else phase_ = Phase::Slide3Hold;
if (phase_ == Phase::Slide1Enter)
phase_ = Phase::Slide1Hold;
else if (phase_ == Phase::Slide2Enter)
phase_ = Phase::Slide2Hold;
else
phase_ = Phase::Slide3Hold;
phase_acc_ms_ = 0;
}
break;
@@ -140,8 +142,10 @@ void SlidesScene::tick(int delta_ms) {
phase_acc_ms_ += delta_ms;
if (phase_acc_ms_ >= HOLD_MS) {
fade_.startFadeOut();
if (phase_ == Phase::Slide1Hold) phase_ = Phase::FadeOut1;
else phase_ = Phase::FadeOut2;
if (phase_ == Phase::Slide1Hold)
phase_ = Phase::FadeOut1;
else
phase_ = Phase::FadeOut2;
phase_acc_ms_ = 0;
}
break;
@@ -159,8 +163,10 @@ void SlidesScene::tick(int delta_ms) {
if (fade_.done()) {
restorePalette();
JD8_ClearScreen(BG_COLOR_INDEX);
if (phase_ == Phase::FadeOut1) phase_ = Phase::Slide2Enter;
else phase_ = Phase::Slide3Enter;
if (phase_ == Phase::FadeOut1)
phase_ = Phase::Slide2Enter;
else
phase_ = Phase::Slide3Enter;
phase_acc_ms_ = 0;
}
break;
@@ -181,6 +187,6 @@ void SlidesScene::tick(int delta_ms) {
case Phase::Done:
break;
}
}
}
} // namespace scenes

View File

@@ -7,33 +7,33 @@
namespace scenes {
// 3 slides narratius amb scroll d'entrada + espera + transició amb
// fade-out. Reemplaça `ModuleSequence::doSlides()`.
//
// Tria d'asset segons context:
// - num_piramide == 7 i diners < 200: gfx/intro2.gif + música "music/00000005.ogg"
// - num_piramide == 7 i diners >= 200: gfx/intro3.gif + música "music/00000005.ogg"
// - altre cas (num_piramide == 1): gfx/intro.gif, sense música nova
//
// Flux:
// Slide1Enter (1600 ms scroll dreta→centre, easing outCubic)
// → Slide1Hold (4600 ms)
// → FadeOut1 + clear + reset paleta
// → Slide2Enter (1600 ms scroll esquerra→centre)
// → Slide2Hold (4600 ms)
// → FadeOut2 + clear + reset paleta
// → Slide3Enter (1600 ms scroll dreta→centre)
// → Slide3Hold (4600 ms)
// → FadeFinal (JA_FadeOutMusic si num_piramide != 7 + fade paleta)
// → Done
//
// Qualsevol tecla salta directament a FadeFinal (sense cortar la música
// si hem entrat per num_piramide==7, per fidelitat al vell).
//
// NextState:
// - num_piramide==7 al entrar → num_piramide=8 + return 1 (a Credits)
// - altre cas → return 0 (entra al ModuleGame)
class SlidesScene : public Scene {
// 3 slides narratius amb scroll d'entrada + espera + transició amb
// fade-out. Reemplaça `ModuleSequence::doSlides()`.
//
// Tria d'asset segons context:
// - num_piramide == 7 i diners < 200: gfx/intro2.gif + música "music/00000005.ogg"
// - num_piramide == 7 i diners >= 200: gfx/intro3.gif + música "music/00000005.ogg"
// - altre cas (num_piramide == 1): gfx/intro.gif, sense música nova
//
// Flux:
// Slide1Enter (1600 ms scroll dreta→centre, easing outCubic)
// → Slide1Hold (4600 ms)
// → FadeOut1 + clear + reset paleta
// → Slide2Enter (1600 ms scroll esquerra→centre)
// → Slide2Hold (4600 ms)
// → FadeOut2 + clear + reset paleta
// → Slide3Enter (1600 ms scroll dreta→centre)
// → Slide3Hold (4600 ms)
// → FadeFinal (JA_FadeOutMusic si num_piramide != 7 + fade paleta)
// → Done
//
// Qualsevol tecla salta directament a FadeFinal (sense cortar la música
// si hem entrat per num_piramide==7, per fidelitat al vell).
//
// NextState:
// - num_piramide==7 al entrar → num_piramide=8 + return 1 (a Credits)
// - altre cas → return 0 (entra al ModuleGame)
class SlidesScene : public Scene {
public:
SlidesScene() = default;
~SlidesScene() override;
@@ -74,6 +74,6 @@ class SlidesScene : public Scene {
int num_piramide_at_start_{1};
int next_state_{0};
bool skip_triggered_{false};
};
};
} // namespace scenes

View File

@@ -4,7 +4,7 @@
namespace scenes {
void SpriteMover::moveTo(int x0, int y0, int x1, int y1, int duration_ms, EaseFn ease) {
void SpriteMover::moveTo(int x0, int y0, int x1, int y1, int duration_ms, EaseFn ease) {
x0_ = x0;
y0_ = y0;
x1_ = x1;
@@ -14,18 +14,18 @@ void SpriteMover::moveTo(int x0, int y0, int x1, int y1, int duration_ms, EaseFn
ease_ = ease ? ease : Easing::linear;
cur_x_ = x0;
cur_y_ = y0;
}
}
void SpriteMover::setPosition(int x, int y) {
void SpriteMover::setPosition(int x, int y) {
cur_x_ = x;
cur_y_ = y;
x0_ = x1_ = x;
y0_ = y1_ = y;
duration_ms_ = 0;
elapsed_ms_ = 0;
}
}
void SpriteMover::tick(int delta_ms) {
void SpriteMover::tick(int delta_ms) {
if (duration_ms_ <= 0) {
cur_x_ = x1_;
cur_y_ = y1_;
@@ -36,11 +36,11 @@ void SpriteMover::tick(int delta_ms) {
const float eased = ease_(t);
cur_x_ = Easing::lerpInt(x0_, x1_, eased);
cur_y_ = Easing::lerpInt(y0_, y1_, eased);
}
}
float SpriteMover::progress() const {
float SpriteMover::progress() const {
if (duration_ms_ <= 0) return 1.0f;
return static_cast<float>(elapsed_ms_) / static_cast<float>(duration_ms_);
}
}
} // namespace scenes

View File

@@ -4,19 +4,18 @@
namespace scenes {
// Interpola una posició 2D entre dos punts durant un temps donat amb
// una funció d'easing. No toca cap surface — el caller llegix x()/y()
// i fa el blit ell mateix. Pensat per a scrolls, sprites que entren/
// ixen per pantalla, i moviments d'objectes interpolats.
class SpriteMover {
// Interpola una posició 2D entre dos punts durant un temps donat amb
// una funció d'easing. No toca cap surface — el caller llegix x()/y()
// i fa el blit ell mateix. Pensat per a scrolls, sprites que entren/
// ixen per pantalla, i moviments d'objectes interpolats.
class SpriteMover {
public:
using EaseFn = float (*)(float);
SpriteMover() = default;
// Arrenca un moviment nou. Si ja n'hi havia un en curs, es descarta.
void moveTo(int x0, int y0, int x1, int y1, int duration_ms,
EaseFn ease = Easing::linear);
void moveTo(int x0, int y0, int x1, int y1, int duration_ms, EaseFn ease = Easing::linear);
// Posicionament immediat (útil per a "teleportar" entre moviments).
void setPosition(int x, int y);
@@ -34,6 +33,6 @@ class SpriteMover {
int elapsed_ms_{0};
int cur_x_{0}, cur_y_{0};
EaseFn ease_{Easing::linear};
};
};
} // namespace scenes

View File

@@ -2,41 +2,41 @@
namespace scenes {
SurfaceHandle::SurfaceHandle(const char* file)
SurfaceHandle::SurfaceHandle(const char* file)
: surface_(JD8_LoadSurface(file)) {}
SurfaceHandle::~SurfaceHandle() {
SurfaceHandle::~SurfaceHandle() {
if (surface_) JD8_FreeSurface(surface_);
}
}
SurfaceHandle::SurfaceHandle(SurfaceHandle&& other) noexcept
SurfaceHandle::SurfaceHandle(SurfaceHandle&& other) noexcept
: surface_(other.surface_) {
other.surface_ = nullptr;
}
}
SurfaceHandle& SurfaceHandle::operator=(SurfaceHandle&& other) noexcept {
SurfaceHandle& SurfaceHandle::operator=(SurfaceHandle&& other) noexcept {
if (this != &other) {
if (surface_) JD8_FreeSurface(surface_);
surface_ = other.surface_;
other.surface_ = nullptr;
}
return *this;
}
}
void SurfaceHandle::reset(const char* file) {
void SurfaceHandle::reset(const char* file) {
if (surface_) JD8_FreeSurface(surface_);
surface_ = file ? JD8_LoadSurface(file) : nullptr;
}
}
void SurfaceHandle::adopt(JD8_Surface raw) {
void SurfaceHandle::adopt(JD8_Surface raw) {
if (surface_) JD8_FreeSurface(surface_);
surface_ = raw;
}
}
JD8_Surface SurfaceHandle::release() {
JD8_Surface SurfaceHandle::release() {
JD8_Surface r = surface_;
surface_ = nullptr;
return r;
}
}
} // namespace scenes

View File

@@ -4,11 +4,11 @@
namespace scenes {
// Wrapper RAII damunt de `JD8_Surface`. Allibera automàticament amb
// `JD8_FreeSurface` al destructor. Move-only per evitar dobles alliberaments.
// Converteix implícitament a `JD8_Surface` per a poder passar-lo
// directament a `JD8_Blit*` sense haver de cridar `.get()`.
class SurfaceHandle {
// Wrapper RAII damunt de `JD8_Surface`. Allibera automàticament amb
// `JD8_FreeSurface` al destructor. Move-only per evitar dobles alliberaments.
// Converteix implícitament a `JD8_Surface` per a poder passar-lo
// directament a `JD8_Blit*` sense haver de cridar `.get()`.
class SurfaceHandle {
public:
SurfaceHandle() = default;
explicit SurfaceHandle(const char* file);
@@ -44,6 +44,6 @@ class SurfaceHandle {
private:
JD8_Surface surface_{nullptr};
};
};
} // namespace scenes

View File

@@ -4,23 +4,23 @@
namespace scenes {
Timeline& Timeline::step(int duration_ms, StepFn fn) {
Timeline& Timeline::step(int duration_ms, StepFn fn) {
Step s;
s.duration_ms = duration_ms;
s.continuous = std::move(fn);
steps_.push_back(std::move(s));
return *this;
}
}
Timeline& Timeline::once(OnceFn fn) {
Timeline& Timeline::once(OnceFn fn) {
Step s;
s.duration_ms = 0;
s.oneshot = std::move(fn);
steps_.push_back(std::move(s));
return *this;
}
}
void Timeline::flushOneShots() {
void Timeline::flushOneShots() {
while (current_ < steps_.size() && steps_[current_].duration_ms == 0) {
auto& s = steps_[current_];
if (!s.entered) {
@@ -30,9 +30,9 @@ void Timeline::flushOneShots() {
++current_;
elapsed_in_step_ = 0;
}
}
}
void Timeline::tick(int delta_ms) {
void Timeline::tick(int delta_ms) {
if (skipped_) return;
flushOneShots();
if (current_ >= steps_.size()) return;
@@ -57,29 +57,29 @@ void Timeline::tick(int delta_ms) {
static_cast<float>(std::max(1, s.duration_ms));
s.continuous(p);
}
}
}
void Timeline::skip() {
void Timeline::skip() {
skipped_ = true;
current_ = steps_.size();
}
}
void Timeline::reset() {
void Timeline::reset() {
for (auto& s : steps_) s.entered = false;
current_ = 0;
elapsed_in_step_ = 0;
skipped_ = false;
}
}
bool Timeline::done() const {
bool Timeline::done() const {
return skipped_ || current_ >= steps_.size();
}
}
float Timeline::currentProgress() const {
float Timeline::currentProgress() const {
if (current_ >= steps_.size()) return 1.0f;
const auto& s = steps_[current_];
if (s.duration_ms <= 0) return 0.0f;
return static_cast<float>(elapsed_in_step_) / static_cast<float>(s.duration_ms);
}
}
} // namespace scenes

View File

@@ -5,22 +5,22 @@
namespace scenes {
// Timeline declaratiu de passos seqüencials. Cada pas té una duració en
// ms i un callback. Exemple d'ús:
//
// timeline_
// .once([this] { JD8_ClearScreen(0); fade_.startFadeTo(pal); })
// .step(5000) // espera pura
// .step(1000, [this](float p) { /*...*/ }) // animat amb progress
// .once([this] { JA_FadeOutMusic(250); });
//
// `tick(delta_ms)` avança el temps. Els passos one-shot s'executen al
// moment d'entrar-hi i avancen immediatament. Els passos amb duració
// criden el seu callback cada tick amb el progress [0..1] i passen al
// següent quan s'exhaureix el temps. `skip()` marca tota la timeline
// com a acabada (no executa res més) — útil per als "polsa una tecla
// per a saltar la cinemàtica".
class Timeline {
// Timeline declaratiu de passos seqüencials. Cada pas té una duració en
// ms i un callback. Exemple d'ús:
//
// timeline_
// .once([this] { JD8_ClearScreen(0); fade_.startFadeTo(pal); })
// .step(5000) // espera pura
// .step(1000, [this](float p) { /*...*/ }) // animat amb progress
// .once([this] { JA_FadeOutMusic(250); });
//
// `tick(delta_ms)` avança el temps. Els passos one-shot s'executen al
// moment d'entrar-hi i avancen immediatament. Els passos amb duració
// criden el seu callback cada tick amb el progress [0..1] i passen al
// següent quan s'exhaureix el temps. `skip()` marca tota la timeline
// com a acabada (no executa res més) — útil per als "polsa una tecla
// per a saltar la cinemàtica".
class Timeline {
public:
using StepFn = std::function<void(float progress_0_1)>;
using OnceFn = std::function<void()>;
@@ -52,6 +52,6 @@ class Timeline {
std::size_t current_{0};
int elapsed_in_step_{0};
bool skipped_{false};
};
};
} // namespace scenes