From fe41919e1ee7c803aa373db989e3a24eca1afb57 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 16 Apr 2026 16:46:18 +0200 Subject: [PATCH] clang-format mogudes coses de config.yaml a debug.yaml --- source/core/jail/jail_audio.hpp | 4 +- source/core/jail/jdraw8.cpp | 56 +-- source/core/jail/jfile.cpp | 50 +- source/core/jail/jgame.cpp | 10 +- source/core/jail/jinput.cpp | 42 +- source/core/resources/resource_pack.cpp | 4 +- source/game/info.hpp | 32 +- source/game/modulegame.cpp | 11 +- source/game/options.cpp | 75 ++- source/game/options.hpp | 10 + source/main.cpp | 10 + source/scenes/banner_scene.cpp | 96 ++-- source/scenes/banner_scene.hpp | 56 +-- source/scenes/credits_scene.cpp | 229 ++++----- source/scenes/credits_scene.hpp | 70 +-- source/scenes/frame_animator.cpp | 46 +- source/scenes/frame_animator.hpp | 48 +- source/scenes/intro_new_logo_scene.cpp | 368 +++++++------- source/scenes/intro_new_logo_scene.hpp | 104 ++-- source/scenes/intro_scene.cpp | 380 +++++++------- source/scenes/intro_scene.hpp | 100 ++-- source/scenes/intro_sprites_scene.cpp | 631 ++++++++++++------------ source/scenes/intro_sprites_scene.hpp | 64 +-- source/scenes/menu_scene.cpp | 166 +++---- source/scenes/menu_scene.hpp | 80 +-- source/scenes/mort_scene.cpp | 94 ++-- source/scenes/mort_scene.hpp | 48 +- source/scenes/palette_fade.cpp | 42 +- source/scenes/palette_fade.hpp | 36 +- source/scenes/scene.hpp | 26 +- source/scenes/scene_registry.cpp | 24 +- source/scenes/scene_registry.hpp | 42 +- source/scenes/scene_utils.cpp | 21 +- source/scenes/scene_utils.hpp | 8 +- source/scenes/secreta_scene.cpp | 354 ++++++------- source/scenes/secreta_scene.hpp | 104 ++-- source/scenes/slides_scene.cpp | 284 +++++------ source/scenes/slides_scene.hpp | 130 ++--- source/scenes/sprite_mover.cpp | 72 +-- source/scenes/sprite_mover.hpp | 49 +- source/scenes/surface_handle.cpp | 58 +-- source/scenes/surface_handle.hpp | 68 +-- source/scenes/timeline.cpp | 132 ++--- source/scenes/timeline.hpp | 86 ++-- 44 files changed, 2258 insertions(+), 2162 deletions(-) diff --git a/source/core/jail/jail_audio.hpp b/source/core/jail/jail_audio.hpp index 6c86727..600eaa5 100644 --- a/source/core/jail/jail_audio.hpp +++ b/source/core/jail/jail_audio.hpp @@ -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(length), &error, nullptr); + static_cast(length), + &error, + nullptr); if (!music->vorbis) { SDL_Log("JA_LoadMusic: stb_vorbis_open_memory failed (error %d)", error); delete music; diff --git a/source/core/jail/jdraw8.cpp b/source/core/jail/jdraw8.cpp index 354df25..0c0840d 100644 --- a/source/core/jail/jdraw8.cpp +++ b/source/core/jail/jdraw8.cpp @@ -189,39 +189,39 @@ void JD8_SetPaletteColor(Uint8 index, Uint8 r, Uint8 g, Uint8 b) { // el caller decideix quan fer Flip. namespace { -enum FadeType { - FADE_NONE = 0, - FADE_OUT, - FADE_TO_PAL, -}; + 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() { - 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; - main_palette[i].g = main_palette[i].g >= 8 ? main_palette[i].g - 8 : 0; - main_palette[i].b = main_palette[i].b >= 8 ? main_palette[i].b - 8 : 0; - } - } else if (fade_type == FADE_TO_PAL) { - for (int i = 0; i < 256; i++) { - main_palette[i].r = main_palette[i].r <= int(fade_target[i].r) - 8 - ? main_palette[i].r + 8 - : fade_target[i].r; - main_palette[i].g = main_palette[i].g <= int(fade_target[i].g) - 8 - ? main_palette[i].g + 8 - : fade_target[i].g; - main_palette[i].b = main_palette[i].b <= int(fade_target[i].b) - 8 - ? main_palette[i].b + 8 - : fade_target[i].b; + 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; + main_palette[i].g = main_palette[i].g >= 8 ? main_palette[i].g - 8 : 0; + main_palette[i].b = main_palette[i].b >= 8 ? main_palette[i].b - 8 : 0; + } + } else if (fade_type == FADE_TO_PAL) { + for (int i = 0; i < 256; i++) { + main_palette[i].r = main_palette[i].r <= int(fade_target[i].r) - 8 + ? main_palette[i].r + 8 + : fade_target[i].r; + main_palette[i].g = main_palette[i].g <= int(fade_target[i].g) - 8 + ? main_palette[i].g + 8 + : fade_target[i].g; + main_palette[i].b = main_palette[i].b <= int(fade_target[i].b) - 8 + ? main_palette[i].b + 8 + : fade_target[i].b; + } } } -} } // namespace diff --git a/source/core/jail/jfile.cpp b/source/core/jail/jfile.cpp index e63972a..8efe30b 100644 --- a/source/core/jail/jfile.cpp +++ b/source/core/jail/jfile.cpp @@ -14,37 +14,37 @@ namespace { -struct keyvalue { - std::string key; - std::string value; -}; + struct keyvalue { + std::string key; + std::string value; + }; -std::vector config; -std::string resource_folder; -std::string config_folder; + std::vector config; + std::string resource_folder; + std::string config_folder; -void load_config_values() { - config.clear(); - const std::string config_file = config_folder + "/config.txt"; - std::ifstream fi(config_file); - if (!fi.is_open()) return; + void load_config_values() { + config.clear(); + const std::string config_file = config_folder + "/config.txt"; + std::ifstream fi(config_file); + if (!fi.is_open()) return; - std::string line; - while (std::getline(fi, line)) { - const auto eq = line.find('='); - if (eq == std::string::npos) continue; - config.push_back({line.substr(0, eq), line.substr(eq + 1)}); + std::string line; + while (std::getline(fi, line)) { + const auto eq = line.find('='); + if (eq == std::string::npos) continue; + config.push_back({line.substr(0, eq), line.substr(eq + 1)}); + } } -} -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'; + 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 diff --git a/source/core/jail/jgame.cpp b/source/core/jail/jgame.cpp index cf84bad..c7bfe70 100644 --- a/source/core/jail/jgame.cpp +++ b/source/core/jail/jgame.cpp @@ -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 diff --git a/source/core/jail/jinput.cpp b/source/core/jail/jinput.cpp index f960153..c8882d8 100644 --- a/source/core/jail/jinput.cpp +++ b/source/core/jail/jinput.cpp @@ -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) { - if (scancode >= SDL_SCANCODE_A && scancode <= SDL_SCANCODE_Z) { - return static_cast('a' + (scancode - SDL_SCANCODE_A)); + Uint8 scancode_to_ascii(Uint8 scancode) { + if (scancode >= SDL_SCANCODE_A && scancode <= SDL_SCANCODE_Z) { + return static_cast('a' + (scancode - SDL_SCANCODE_A)); + } + return 0; } - return 0; -} } // namespace diff --git a/source/core/resources/resource_pack.cpp b/source/core/resources/resource_pack.cpp index 8e848c4..b610ac6 100644 --- a/source/core/resources/resource_pack.cpp +++ b/source/core/resources/resource_pack.cpp @@ -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; diff --git a/source/game/info.hpp b/source/game/info.hpp index 7d1c463..7c8feaf 100644 --- a/source/game/info.hpp +++ b/source/game/info.hpp @@ -2,23 +2,23 @@ namespace info { -struct GameContext { - int num_piramide = 0; - int num_habitacio = 0; - int diners = 0; - int diamants = 0; - int vida = 0; - int momies = 0; - int engendros = 0; - bool nou_personatge = false; - bool pepe_activat = false; + struct GameContext { + int num_piramide = 0; + int num_habitacio = 0; + int diners = 0; + int diamants = 0; + int vida = 0; + int momies = 0; + int engendros = 0; + bool nou_personatge = false; + bool pepe_activat = false; - void reset() { *this = GameContext{}; } -}; + 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 diff --git a/source/game/modulegame.cpp b/source/game/modulegame.cpp index 3ec2d40..d2eaed2 100644 --- a/source/game/modulegame.cpp +++ b/source/game/modulegame.cpp @@ -42,16 +42,17 @@ void ModuleGame::onEnter() { // fade interpolarien cap a una paleta amb pantalla buida. this->Draw(); - const char* music = info::ctx.num_piramide == 3 ? "music/00000008.ogg" - : info::ctx.num_piramide == 2 ? "music/00000007.ogg" - : info::ctx.num_piramide == 6 ? "music/00000002.ogg" - : "music/00000006.ogg"; + const char* music = info::ctx.num_piramide == 3 ? "music/00000008.ogg" + : info::ctx.num_piramide == 2 ? "music/00000007.ogg" + : info::ctx.num_piramide == 6 ? "music/00000002.ogg" + : "music/00000006.ogg"; const char* current_music = JA_GetMusicFilename(); if ((JA_GetMusicState() != JA_MUSIC_PLAYING) || !current_music || strcmp(music, current_music) != 0) { auto buffer = ResourceHelper::loadFile(music); JA_PlayMusic(JA_LoadMusic(buffer.data(), - static_cast(buffer.size()), music)); + static_cast(buffer.size()), + music)); } // Arranca el fade-in tick-based. El `PaletteFade` avança un pas (de diff --git a/source/game/options.cpp b/source/game/options.cpp index e86e849..b8fff33 100644 --- a/source/game/options.cpp +++ b/source/game/options.cpp @@ -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(file)), std::istreambuf_iterator()); + 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(); + if (node.contains("piramide_inicial")) + game.piramide_inicial = node["piramide_inicial"].get_value(); + if (node.contains("vides")) + game.vides = node["vides"].get_value(); + if (node.contains("diamants_inicial")) + game.diamants_inicial = node["diamants_inicial"].get_value(); + if (node.contains("diners_inicial")) + game.diners_inicial = node["diners_inicial"].get_value(); + } + 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(); - if (node.contains("piramide_inicial")) - game.piramide_inicial = node["piramide_inicial"].get_value(); - if (node.contains("vides")) - game.vides = node["vides"].get_value(); - if (node.contains("diamants_inicial")) - game.diamants_inicial = node["diamants_inicial"].get_value(); - if (node.contains("diners_inicial")) - game.diners_inicial = node["diners_inicial"].get_value(); if (node.contains("use_new_logo")) game.use_new_logo = node["use_new_logo"].get_value(); if (node.contains("show_title_credits")) @@ -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"; diff --git a/source/game/options.hpp b/source/game/options.hpp index 82517bf..eb217f4 100644 --- a/source/game/options.hpp +++ b/source/game/options.hpp @@ -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; diff --git a/source/main.cpp b/source/main.cpp index ef74dff..58ffbae 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -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(); diff --git a/source/scenes/banner_scene.cpp b/source/scenes/banner_scene.cpp index 875c447..e6ab3da 100644 --- a/source/scenes/banner_scene.cpp +++ b/source/scenes/banner_scene.cpp @@ -10,62 +10,62 @@ namespace scenes { -void BannerScene::onEnter() { - playMusic("music/00000004.ogg"); + void BannerScene::onEnter() { + playMusic("music/00000004.ogg"); - gfx_ = SurfaceHandle("gfx/ffase.gif"); + gfx_ = SurfaceHandle("gfx/ffase.gif"); - JD8_ClearScreen(0); - // Títols superior i inferior del banner (compartits per tots els nivells) - JD8_Blit(81, 24, gfx_, 81, 155, 168, 21); - JD8_Blit(39, 150, gfx_, 39, 175, 248, 20); + JD8_ClearScreen(0); + // Títols superior i inferior del banner (compartits per tots els nivells) + JD8_Blit(81, 24, gfx_, 81, 155, 168, 21); + JD8_Blit(39, 150, gfx_, 39, 175, 248, 20); - // Número de piràmide: les 4 variants del vell `doBanner` es reduïxen - // a coordenades (sx,sy) calculades a partir de l'índex 0..3. - const int idx = info::ctx.num_piramide - 2; // 2..5 → 0..3 - if (idx >= 0 && idx <= 3) { - const int sx = (idx % 2) * 160; - const int sy = (idx / 2) * 75; - JD8_Blit(82, 60, gfx_, sx, sy, 160, 75); + // Número de piràmide: les 4 variants del vell `doBanner` es reduïxen + // a coordenades (sx,sy) calculades a partir de l'índex 0..3. + const int idx = info::ctx.num_piramide - 2; // 2..5 → 0..3 + if (idx >= 0 && idx <= 3) { + const int sx = (idx % 2) * 160; + const int sy = (idx / 2) * 75; + JD8_Blit(82, 60, gfx_, sx, sy, 160, 75); + } + + // PaletteFade copia internament amb memcpy; alliberem la paleta temporal. + JD8_Palette pal = JD8_LoadPalette("gfx/ffase.gif"); + fade_.startFadeTo(pal); + std::free(pal); + + phase_ = Phase::FadingIn; + remaining_ms_ = 5000; } - // PaletteFade copia internament amb memcpy; alliberem la paleta temporal. - JD8_Palette pal = JD8_LoadPalette("gfx/ffase.gif"); - fade_.startFadeTo(pal); - std::free(pal); + void BannerScene::tick(int delta_ms) { + switch (phase_) { + case Phase::FadingIn: + fade_.tick(delta_ms); + if (fade_.done()) phase_ = Phase::Showing; + break; - phase_ = Phase::FadingIn; - remaining_ms_ = 5000; -} + case Phase::Showing: + if (JI_AnyKey()) { + remaining_ms_ = 0; + } else { + remaining_ms_ -= delta_ms; + } + if (remaining_ms_ <= 0) { + JA_FadeOutMusic(250); + fade_.startFadeOut(); + phase_ = Phase::FadingOut; + } + break; -void BannerScene::tick(int delta_ms) { - switch (phase_) { - case Phase::FadingIn: - fade_.tick(delta_ms); - if (fade_.done()) phase_ = Phase::Showing; - break; + case Phase::FadingOut: + fade_.tick(delta_ms); + if (fade_.done()) phase_ = Phase::Done; + break; - case Phase::Showing: - if (JI_AnyKey()) { - remaining_ms_ = 0; - } else { - remaining_ms_ -= delta_ms; - } - if (remaining_ms_ <= 0) { - JA_FadeOutMusic(250); - fade_.startFadeOut(); - phase_ = Phase::FadingOut; - } - break; - - case Phase::FadingOut: - fade_.tick(delta_ms); - if (fade_.done()) phase_ = Phase::Done; - break; - - case Phase::Done: - break; + case Phase::Done: + break; + } } -} } // namespace scenes diff --git a/source/scenes/banner_scene.hpp b/source/scenes/banner_scene.hpp index bf3121e..725bb77 100644 --- a/source/scenes/banner_scene.hpp +++ b/source/scenes/banner_scene.hpp @@ -6,35 +6,35 @@ 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 { - public: - void onEnter() override; - void tick(int delta_ms) override; - bool done() const override { return phase_ == Phase::Done; } - int nextState() const override { return 0; } + // 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; + bool done() const override { return phase_ == Phase::Done; } + int nextState() const override { return 0; } - private: - enum class Phase { FadingIn, - Showing, - FadingOut, - Done }; + private: + enum class Phase { FadingIn, + Showing, + FadingOut, + Done }; - SurfaceHandle gfx_; - PaletteFade fade_; - Phase phase_{Phase::FadingIn}; - int remaining_ms_{5000}; -}; + SurfaceHandle gfx_; + PaletteFade fade_; + Phase phase_{Phase::FadingIn}; + int remaining_ms_{5000}; + }; } // namespace scenes diff --git a/source/scenes/credits_scene.cpp b/source/scenes/credits_scene.cpp index bc3a87a..05f391b 100644 --- a/source/scenes/credits_scene.cpp +++ b/source/scenes/credits_scene.cpp @@ -11,129 +11,136 @@ 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 { - Uint16 x, y; -}; + // 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() { - // No toquem la paleta activa: SetScreenPalette n'ha pres ownership. -} - -void CreditsScene::onEnter() { - // El vell doCredits no tocava música — heretava la del doSlides - // previ ("music/00000005.ogg"). Si l'escena s'arrenca directament (test - // amb piramide_inicial=8) no hi ha res que heretar, així que - // arranquem la mateixa pista només si no sona res. Inocu en el - // flux normal: JA_MUSIC_PLAYING fa que no la tornem a tocar. - if (JA_GetMusicState() != JA_MUSIC_PLAYING) { - playMusic("music/00000005.ogg"); + CreditsScene::~CreditsScene() { + // No toquem la paleta activa: SetScreenPalette n'ha pres ownership. } - vaddr2_ = SurfaceHandle("gfx/final.gif"); - vaddr3_ = SurfaceHandle("gfx/finals.gif"); - - JD8_Palette pal = JD8_LoadPalette("gfx/final.gif"); - JD8_SetScreenPalette(pal); - // `pal` passa a ser propietat de main_palette — no l'alliberem. - - phase_ = Phase::Rolling; - contador_ = 1; - contador_acc_ms_ = 0; -} - -void CreditsScene::render() { - JD8_ClearScreen(BG_INDEX); - - // Columna 1: scroll vertical del bloc (0,0,80,200) pujant des de - // y=200 fins que el contador supera 2750. - if (contador_ < 2750) { - JD8_BlitCKCut(115, 200 - (contador_ / 6), vaddr2_, 0, 0, 80, 200, 0); - } - - // Columna 2: scroll vertical del bloc (85,0,120,140), arrenca - // a contador 1200 i s'atura (fix en y=20) a partir de 2250. - if ((contador_ > 1200) && (contador_ < 2280)) { - JD8_BlitCKCut(100, 200 - ((contador_ - 1200) / 6), vaddr2_, 85, 0, 120, 140, 0); - } else if (contador_ >= 2250) { - JD8_BlitCK(100, 20, vaddr2_, 85, 0, 120, 140, 0); - } - - // Fons: 4 capes parallax + cotxe només si l'usuari ha aconseguit - // tots els diamants (final "bo"). Altrament fons estàtic. - if (info::ctx.diamants == 16) { - JD8_BlitCKScroll(50, vaddr3_, ((contador_ >> 3) % 320) + 1, 0, 50, 255); - JD8_BlitCKScroll(50, vaddr3_, ((contador_ >> 2) % 320) + 1, 50, 50, 255); - JD8_BlitCKScroll(50, vaddr3_, ((contador_ >> 1) % 320) + 1, 100, 50, 255); - JD8_BlitCKScroll(50, vaddr3_, (contador_ % 320) + 1, 150, 50, 255); - - const CocheFrame& cf = COCHE_FRAMES[coche_.frame()]; - JD8_BlitCK(100, 50, vaddr2_, cf.x, cf.y, 106, 48, 255); - } else { - JD8_BlitCK(0, 50, vaddr3_, 0, 0, 320, 50, 255); - JD8_BlitCK(0, 50, vaddr3_, 0, 50, 320, 50, 255); - } - - // Barres de marc que cobreixen els extrems del scroll vertical. - JD8_FillSquare(0, 50, BG_INDEX); - JD8_FillSquare(100, 10, BG_INDEX); -} - -void CreditsScene::writeTrickIni() { - FILE* ini = std::fopen("trick.ini", "wb"); - if (ini) { - std::fwrite("1", 1, 1, ini); - std::fclose(ini); - } - info::ctx.nou_personatge = true; -} - -void CreditsScene::tick(int delta_ms) { - switch (phase_) { - case Phase::Rolling: { - // Avancem el contador en passos discrets de 20 ms, igual - // que feia JG_ShouldUpdate(20) al vell doCredits. - contador_acc_ms_ += delta_ms; - while (contador_acc_ms_ >= TICK_MS) { - contador_acc_ms_ -= TICK_MS; - ++contador_; - } - - coche_.tick(delta_ms); - render(); - - if (JI_AnyKey() || contador_ >= CONTADOR_MAX) { - writeTrickIni(); - fade_.startFadeOut(); - phase_ = Phase::FadingOut; - } - break; + 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 + // arranquem la mateixa pista només si no sona res. Inocu en el + // flux normal: JA_MUSIC_PLAYING fa que no la tornem a tocar. + if (JA_GetMusicState() != JA_MUSIC_PLAYING) { + playMusic("music/00000005.ogg"); } - case Phase::FadingOut: - fade_.tick(delta_ms); - if (fade_.done()) { - info::ctx.num_piramide = 255; - phase_ = Phase::Done; - } - break; + vaddr2_ = SurfaceHandle("gfx/final.gif"); + vaddr3_ = SurfaceHandle("gfx/finals.gif"); - case Phase::Done: - break; + JD8_Palette pal = JD8_LoadPalette("gfx/final.gif"); + JD8_SetScreenPalette(pal); + // `pal` passa a ser propietat de main_palette — no l'alliberem. + + phase_ = Phase::Rolling; + contador_ = 1; + contador_acc_ms_ = 0; + } + + void CreditsScene::render() { + JD8_ClearScreen(BG_INDEX); + + // Columna 1: scroll vertical del bloc (0,0,80,200) pujant des de + // y=200 fins que el contador supera 2750. + if (contador_ < 2750) { + JD8_BlitCKCut(115, 200 - (contador_ / 6), vaddr2_, 0, 0, 80, 200, 0); + } + + // Columna 2: scroll vertical del bloc (85,0,120,140), arrenca + // a contador 1200 i s'atura (fix en y=20) a partir de 2250. + if ((contador_ > 1200) && (contador_ < 2280)) { + JD8_BlitCKCut(100, 200 - ((contador_ - 1200) / 6), vaddr2_, 85, 0, 120, 140, 0); + } else if (contador_ >= 2250) { + JD8_BlitCK(100, 20, vaddr2_, 85, 0, 120, 140, 0); + } + + // Fons: 4 capes parallax + cotxe només si l'usuari ha aconseguit + // tots els diamants (final "bo"). Altrament fons estàtic. + if (info::ctx.diamants == 16) { + JD8_BlitCKScroll(50, vaddr3_, ((contador_ >> 3) % 320) + 1, 0, 50, 255); + JD8_BlitCKScroll(50, vaddr3_, ((contador_ >> 2) % 320) + 1, 50, 50, 255); + JD8_BlitCKScroll(50, vaddr3_, ((contador_ >> 1) % 320) + 1, 100, 50, 255); + JD8_BlitCKScroll(50, vaddr3_, (contador_ % 320) + 1, 150, 50, 255); + + const CocheFrame& cf = COCHE_FRAMES[coche_.frame()]; + JD8_BlitCK(100, 50, vaddr2_, cf.x, cf.y, 106, 48, 255); + } else { + JD8_BlitCK(0, 50, vaddr3_, 0, 0, 320, 50, 255); + JD8_BlitCK(0, 50, vaddr3_, 0, 50, 320, 50, 255); + } + + // Barres de marc que cobreixen els extrems del scroll vertical. + JD8_FillSquare(0, 50, BG_INDEX); + JD8_FillSquare(100, 10, BG_INDEX); + } + + void CreditsScene::writeTrickIni() { + FILE* ini = std::fopen("trick.ini", "wb"); + if (ini) { + std::fwrite("1", 1, 1, ini); + std::fclose(ini); + } + info::ctx.nou_personatge = true; + } + + void CreditsScene::tick(int delta_ms) { + switch (phase_) { + case Phase::Rolling: { + // Avancem el contador en passos discrets de 20 ms, igual + // que feia JG_ShouldUpdate(20) al vell doCredits. + contador_acc_ms_ += delta_ms; + while (contador_acc_ms_ >= TICK_MS) { + contador_acc_ms_ -= TICK_MS; + ++contador_; + } + + coche_.tick(delta_ms); + render(); + + if (JI_AnyKey() || contador_ >= CONTADOR_MAX) { + writeTrickIni(); + fade_.startFadeOut(); + phase_ = Phase::FadingOut; + } + break; + } + + case Phase::FadingOut: + fade_.tick(delta_ms); + if (fade_.done()) { + info::ctx.num_piramide = 255; + phase_ = Phase::Done; + } + break; + + case Phase::Done: + break; + } } -} } // namespace scenes diff --git a/source/scenes/credits_scene.hpp b/source/scenes/credits_scene.hpp index ee4810a..6862f95 100644 --- a/source/scenes/credits_scene.hpp +++ b/source/scenes/credits_scene.hpp @@ -8,45 +8,45 @@ 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 { - public: - CreditsScene() = default; - ~CreditsScene() override; + // 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; - void onEnter() override; - void tick(int delta_ms) override; - bool done() const override { return phase_ == Phase::Done; } - int nextState() const override { return 1; } + void onEnter() override; + void tick(int delta_ms) override; + bool done() const override { return phase_ == Phase::Done; } + int nextState() const override { return 1; } - private: - enum class Phase { Rolling, - FadingOut, - Done }; + private: + enum class Phase { Rolling, + FadingOut, + Done }; - void render(); - void writeTrickIni(); + void render(); + void writeTrickIni(); - SurfaceHandle vaddr2_; // gfx/final.gif (sprites i coches) - SurfaceHandle vaddr3_; // gfx/finals.gif (fons / parallax) - PaletteFade fade_; - FrameAnimator coche_{8, 60, true}; // 8 frames × 60 ms (~3 × 20 ms tick vell) + SurfaceHandle vaddr2_; // gfx/final.gif (sprites i coches) + SurfaceHandle vaddr3_; // gfx/finals.gif (fons / parallax) + PaletteFade fade_; + FrameAnimator coche_{8, 60, true}; // 8 frames × 60 ms (~3 × 20 ms tick vell) - Phase phase_{Phase::Rolling}; - int contador_{1}; - int contador_acc_ms_{0}; -}; + Phase phase_{Phase::Rolling}; + int contador_{1}; + int contador_acc_ms_{0}; + }; } // namespace scenes diff --git a/source/scenes/frame_animator.cpp b/source/scenes/frame_animator.cpp index 57d1f28..1ec7bbf 100644 --- a/source/scenes/frame_animator.cpp +++ b/source/scenes/frame_animator.cpp @@ -4,33 +4,33 @@ namespace scenes { -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) {} + 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) { - if (finished_) return; - elapsed_ms_ += delta_ms; - while (elapsed_ms_ >= frame_ms_) { - elapsed_ms_ -= frame_ms_; - ++current_frame_; - if (current_frame_ >= num_frames_) { - if (loop_) { - current_frame_ = 0; - } else { - current_frame_ = num_frames_ - 1; - finished_ = true; - return; + void FrameAnimator::tick(int delta_ms) { + if (finished_) return; + elapsed_ms_ += delta_ms; + while (elapsed_ms_ >= frame_ms_) { + elapsed_ms_ -= frame_ms_; + ++current_frame_; + if (current_frame_ >= num_frames_) { + if (loop_) { + current_frame_ = 0; + } else { + current_frame_ = num_frames_ - 1; + finished_ = true; + return; + } } } } -} -void FrameAnimator::reset() { - current_frame_ = 0; - elapsed_ms_ = 0; - finished_ = false; -} + void FrameAnimator::reset() { + current_frame_ = 0; + elapsed_ms_ = 0; + finished_ = false; + } } // namespace scenes diff --git a/source/scenes/frame_animator.hpp b/source/scenes/frame_animator.hpp index 4152061..b76442e 100644 --- a/source/scenes/frame_animator.hpp +++ b/source/scenes/frame_animator.hpp @@ -2,33 +2,33 @@ 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 { - public: - FrameAnimator() = default; - FrameAnimator(int num_frames, int frame_ms, bool loop = true); + // 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); - void tick(int delta_ms); + void tick(int delta_ms); - int frame() const { return current_frame_; } - bool done() const { return !loop_ && finished_; } - int numFrames() const { return num_frames_; } + int frame() const { return current_frame_; } + bool done() const { return !loop_ && finished_; } + int numFrames() const { return num_frames_; } - void reset(); - void setFrameMs(int frame_ms) { frame_ms_ = frame_ms; } + void reset(); + void setFrameMs(int frame_ms) { frame_ms_ = frame_ms; } - private: - int num_frames_{1}; - int frame_ms_{100}; - bool loop_{true}; - int current_frame_{0}; - int elapsed_ms_{0}; - bool finished_{false}; -}; + private: + int num_frames_{1}; + int frame_ms_{100}; + bool loop_{true}; + int current_frame_{0}; + int elapsed_ms_{0}; + bool finished_{false}; + }; } // namespace scenes diff --git a/source/scenes/intro_new_logo_scene.cpp b/source/scenes/intro_new_logo_scene.cpp index 653b95c..74e2f10 100644 --- a/source/scenes/intro_new_logo_scene.cpp +++ b/source/scenes/intro_new_logo_scene.cpp @@ -9,210 +9,206 @@ 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() { - // 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() { - playMusic("music/00000003.ogg"); - - gfx_ = SurfaceHandle("gfx/logo_new.gif"); - pal_ = JD8_LoadPalette("gfx/logo_new.gif"); - JD8_SetScreenPalette(pal_); - - // Surface auxiliar omplida amb el color del cursor — permet pintar - // el "subratllat" amb un blit normal. - cursor_surf_.adopt(JD8_NewSurface()); - std::memset(cursor_surf_.get(), CURSOR_COLOR, 64000); - - JD8_ClearScreen(0); - - phase_ = Phase::Initial; - phase_acc_ms_ = 0; - reveal_letter_ = 0; - reveal_cursor_visible_ = true; - palette_step_ = 0; -} - -void IntroNewLogoScene::render() { - switch (phase_) { - case Phase::Initial: - JD8_ClearScreen(0); - break; - - 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); - if (reveal_cursor_visible_) { - 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(CURSOR_X[8], CURSOR_Y, cursor_surf_, 0, 0, CURSOR_W, CURSOR_H); - break; - - case Phase::PaletteCycle: - case Phase::FinalWait: - // 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); - break; - - case Phase::Sprites: - case Phase::Done: - break; - } -} - -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++) { - 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 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 - // 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; + IntroNewLogoScene::~IntroNewLogoScene() { + // No alliberem `pal_`: JD8_SetScreenPalette n'ha pres ownership i + // el proper SetScreenPalette / FadeToPal el lliurarà. Alliberar-lo + // ací provocaria double free. } - switch (phase_) { - case Phase::Initial: - phase_acc_ms_ += delta_ms; - render(); - if (phase_acc_ms_ >= INITIAL_MS) { - phase_ = Phase::Revealing; - phase_acc_ms_ = 0; - } - break; + void IntroNewLogoScene::onEnter() { + playMusic("music/00000003.ogg"); - case Phase::Revealing: - phase_acc_ms_ += delta_ms; - render(); - if (phase_acc_ms_ >= REVEAL_FRAME_MS) { - phase_acc_ms_ = 0; - reveal_cursor_visible_ = !reveal_cursor_visible_; - // Quan acabem els dos frames d'una lletra (cursor on → off), - // passem a la següent lletra. + gfx_ = SurfaceHandle("gfx/logo_new.gif"); + pal_ = JD8_LoadPalette("gfx/logo_new.gif"); + JD8_SetScreenPalette(pal_); + + // Surface auxiliar omplida amb el color del cursor — permet pintar + // el "subratllat" amb un blit normal. + cursor_surf_.adopt(JD8_NewSurface()); + std::memset(cursor_surf_.get(), CURSOR_COLOR, 64000); + + JD8_ClearScreen(0); + + phase_ = Phase::Initial; + phase_acc_ms_ = 0; + reveal_letter_ = 0; + reveal_cursor_visible_ = true; + palette_step_ = 0; + } + + void IntroNewLogoScene::render() { + switch (phase_) { + case Phase::Initial: + JD8_ClearScreen(0); + break; + + 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); if (reveal_cursor_visible_) { - ++reveal_letter_; - if (reveal_letter_ >= 9) { - phase_ = Phase::FullLogoFlash; - reveal_letter_ = 8; + 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(CURSOR_X[8], CURSOR_Y, cursor_surf_, 0, 0, CURSOR_W, CURSOR_H); + break; + + case Phase::PaletteCycle: + case Phase::FinalWait: + // 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); + break; + + case Phase::Sprites: + case Phase::Done: + break; + } + } + + 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++) { + 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 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 + // 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::Initial: + phase_acc_ms_ += delta_ms; + render(); + if (phase_acc_ms_ >= INITIAL_MS) { + phase_ = Phase::Revealing; + phase_acc_ms_ = 0; + } + break; + + case Phase::Revealing: + phase_acc_ms_ += delta_ms; + render(); + if (phase_acc_ms_ >= REVEAL_FRAME_MS) { + phase_acc_ms_ = 0; + reveal_cursor_visible_ = !reveal_cursor_visible_; + // Quan acabem els dos frames d'una lletra (cursor on → off), + // passem a la següent lletra. + if (reveal_cursor_visible_) { + ++reveal_letter_; + if (reveal_letter_ >= 9) { + phase_ = Phase::FullLogoFlash; + reveal_letter_ = 8; + } } } - } - break; + break; - case Phase::FullLogoFlash: - phase_acc_ms_ += delta_ms; - render(); - if (phase_acc_ms_ >= FULL_LOGO_MS) { - phase_ = Phase::PaletteCycle; - phase_acc_ms_ = 0; - } - break; + case Phase::FullLogoFlash: + phase_acc_ms_ += delta_ms; + render(); + if (phase_acc_ms_ >= FULL_LOGO_MS) { + phase_ = Phase::PaletteCycle; + phase_acc_ms_ = 0; + } + break; - case Phase::PaletteCycle: - phase_acc_ms_ += delta_ms; - // Avancem passos de paleta cada 20 ms. Si el delta és gran, - // consumim múltiples passos en la mateixa crida. - 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::PaletteCycle: + phase_acc_ms_ += delta_ms; + // Avancem passos de paleta cada 20 ms. Si el delta és gran, + // consumim múltiples passos en la mateixa crida. + 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::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 primer tick. Transferim el gfx_ - // per move — la sub-escena se n'ocupa fins que es destruix. - // Cada tick successiu delega l'animació dels sprites. - if (!sprites_scene_) { - sprites_scene_ = std::make_unique(std::move(gfx_)); - sprites_scene_->onEnter(); - } - sprites_scene_->tick(delta_ms); - if (sprites_scene_->done()) { - // El vell `Go()` post-switch feia `num_piramide = 0` - // per passar al menú. Sense açò el while del fiber - // tornaria a crear IntroNewLogoScene infinitament. - info::ctx.num_piramide = 0; - phase_ = Phase::Done; - } - break; + case Phase::Sprites: + // Sub-escena construïda al primer tick. Transferim el gfx_ + // per move — la sub-escena se n'ocupa fins que es destruix. + // Cada tick successiu delega l'animació dels sprites. + if (!sprites_scene_) { + sprites_scene_ = std::make_unique(std::move(gfx_)); + sprites_scene_->onEnter(); + } + sprites_scene_->tick(delta_ms); + if (sprites_scene_->done()) { + // El vell `Go()` post-switch feia `num_piramide = 0` + // per passar al menú. Sense açò el while del fiber + // tornaria a crear IntroNewLogoScene infinitament. + info::ctx.num_piramide = 0; + phase_ = Phase::Done; + } + break; - case Phase::Done: - break; + case Phase::Done: + break; + } } -} } // namespace scenes diff --git a/source/scenes/intro_new_logo_scene.hpp b/source/scenes/intro_new_logo_scene.hpp index 66aa23e..fee76e0 100644 --- a/source/scenes/intro_new_logo_scene.hpp +++ b/source/scenes/intro_new_logo_scene.hpp @@ -9,60 +9,60 @@ 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 16–31 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; + // 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 16–31 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; - void onEnter() override; - void tick(int delta_ms) override; - bool done() const override { return phase_ == Phase::Done; } - int nextState() const override { return 1; } + 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 { - Initial, // pantalla negra 1000 ms - Revealing, // 9 × 2 frames × 150 ms cada un - FullLogoFlash, // logo complet + cursor, 200 ms - PaletteCycle, // 256 passos × 20 ms modificant paleta - FinalWait, // 20 ms final - Sprites, // tick delegat a IntroSpritesScene fins que acaba - Done, + private: + enum class Phase { + Initial, // pantalla negra 1000 ms + Revealing, // 9 × 2 frames × 150 ms cada un + FullLogoFlash, // logo complet + cursor, 200 ms + PaletteCycle, // 256 passos × 20 ms modificant paleta + FinalWait, // 20 ms final + Sprites, // tick delegat a IntroSpritesScene fins que acaba + Done, + }; + + void render(); + void advancePaletteCycle(); + + SurfaceHandle gfx_; + SurfaceHandle cursor_surf_; + JD8_Palette pal_{nullptr}; // propietat transferida a main_palette via SetScreenPalette + std::unique_ptr sprites_scene_; + + Phase phase_{Phase::Initial}; + int phase_acc_ms_{0}; + int reveal_letter_{0}; + bool reveal_cursor_visible_{true}; + int palette_step_{0}; }; - void render(); - void advancePaletteCycle(); - - SurfaceHandle gfx_; - SurfaceHandle cursor_surf_; - JD8_Palette pal_{nullptr}; // propietat transferida a main_palette via SetScreenPalette - std::unique_ptr sprites_scene_; - - Phase phase_{Phase::Initial}; - int phase_acc_ms_{0}; - int reveal_letter_{0}; - bool reveal_cursor_visible_{true}; - int palette_step_{0}; -}; - } // namespace scenes diff --git a/source/scenes/intro_scene.cpp b/source/scenes/intro_scene.cpp index eae8638..dd2b39c 100644 --- a/source/scenes/intro_scene.cpp +++ b/source/scenes/intro_scene.cpp @@ -7,212 +7,212 @@ 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 { - 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) -}; + // 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]); + 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); -} + // 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() { - // 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("music/00000003.ogg"); - - gfx_ = SurfaceHandle("gfx/logo.gif"); - pal_ = JD8_LoadPalette("gfx/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; + IntroScene::~IntroScene() { + // No alliberem `pal_`: JD8_SetScreenPalette n'ha pres ownership i el + // proper SetScreenPalette / FadeToPal la lliurarà. Alliberar-la ací + // provocaria double free. } - 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; + void IntroScene::onEnter() { + playMusic("music/00000003.ogg"); - 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; + gfx_ = SurfaceHandle("gfx/logo.gif"); + pal_ = JD8_LoadPalette("gfx/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; } - 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::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::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(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; + 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(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 diff --git a/source/scenes/intro_scene.hpp b/source/scenes/intro_scene.hpp index 656b6b2..1b6c609 100644 --- a/source/scenes/intro_scene.hpp +++ b/source/scenes/intro_scene.hpp @@ -9,58 +9,58 @@ 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 { - public: - IntroScene(); - ~IntroScene() override; + // 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; - void onEnter() override; - void tick(int delta_ms) override; - bool done() const override { return phase_ == Phase::Done; } - int nextState() const override { return 1; } + 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, + 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 sprites_scene_; + + Phase phase_{Phase::InitialWait}; + int phase_acc_ms_{0}; + int reveal_index_{0}; + int palette_step_{0}; }; - void render(); - void advancePaletteCycle(); - - SurfaceHandle gfx_; - JD8_Palette pal_{nullptr}; // propietat transferida a main_palette via SetScreenPalette - std::unique_ptr sprites_scene_; - - Phase phase_{Phase::InitialWait}; - int phase_acc_ms_{0}; - int reveal_index_{0}; - int palette_step_{0}; -}; - } // namespace scenes diff --git a/source/scenes/intro_sprites_scene.cpp b/source/scenes/intro_sprites_scene.cpp index bfc6273..78a340b 100644 --- a/source/scenes/intro_sprites_scene.cpp +++ b/source/scenes/intro_sprites_scene.cpp @@ -9,338 +9,357 @@ 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) { - 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); + // 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 { - int start_i; - int end_i; - RenderFn render; - bool skippable; -}; + // 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) { - 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 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 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; + 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); } - 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]); + 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); } - return 0; -} -int phase_step_count(const SpritePhase& p) { - return std::abs(p.end_i - p.start_i) + 1; -} + 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); + } -int phase_current_i(const SpritePhase& p, int step) { - return p.end_i >= p.start_i ? p.start_i + step : p.start_i - step; -} + 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)) {} + 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; + 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; + // 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)); } - 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; + 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_)); } - phases[phase_].render(gfx_.get(), phase_current_i(phases[phase_], phase_step_)); -} } // namespace scenes diff --git a/source/scenes/intro_sprites_scene.hpp b/source/scenes/intro_sprites_scene.hpp index 5b8b464..176ed9e 100644 --- a/source/scenes/intro_sprites_scene.hpp +++ b/source/scenes/intro_sprites_scene.hpp @@ -5,39 +5,39 @@ 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; + // 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; } + 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}; -}; + 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 diff --git a/source/scenes/menu_scene.cpp b/source/scenes/menu_scene.cpp index 4a1d336..bf34cb6 100644 --- a/source/scenes/menu_scene.cpp +++ b/source/scenes/menu_scene.cpp @@ -8,106 +8,106 @@ namespace scenes { -void MenuScene::onEnter() { - fondo_ = SurfaceHandle("gfx/menu.gif"); - gfx_ = SurfaceHandle("gfx/menu2.gif"); + void MenuScene::onEnter() { + fondo_ = SurfaceHandle("gfx/menu.gif"); + gfx_ = SurfaceHandle("gfx/menu2.gif"); - // Pintat inicial (congelat durant el fade-in de paleta). El loop - // d'animació repintarà tot des de zero en el primer tick de Showing. - JD8_Blit(fondo_); - JD8_BlitCK(100, 25, gfx_, 0, 74, 124, 68, 255); // logo - JD8_BlitCK(130, 100, gfx_, 0, 0, 80, 74, 255); // camell (frame 0) - JD8_BlitCK(0, 150, gfx_, 0, 150, 320, 50, 255); // base "jdes" + // Pintat inicial (congelat durant el fade-in de paleta). El loop + // d'animació repintarà tot des de zero en el primer tick de Showing. + JD8_Blit(fondo_); + JD8_BlitCK(100, 25, gfx_, 0, 74, 124, 68, 255); // logo + JD8_BlitCK(130, 100, gfx_, 0, 0, 80, 74, 255); // camell (frame 0) + JD8_BlitCK(0, 150, gfx_, 0, 150, 320, 50, 255); // base "jdes" - JD8_Palette pal = JD8_LoadPalette("gfx/menu2.gif"); - fade_.startFadeTo(pal); - std::free(pal); + JD8_Palette pal = JD8_LoadPalette("gfx/menu2.gif"); + fade_.startFadeTo(pal); + std::free(pal); - phase_ = Phase::FadingIn; -} + phase_ = Phase::FadingIn; + } -void MenuScene::render() { - // Cel estàtic (els primers 100 pixels verticals) - JD8_Blit(0, 0, fondo_, 0, 0, 320, 100); + void MenuScene::render() { + // Cel estàtic (els primers 100 pixels verticals) + JD8_Blit(0, 0, fondo_, 0, 0, 320, 100); - // Fondo mòvil (horitzó) amb wrap a 320 - JD8_BlitCK(horitzo_, 100, fondo_, 0, 100, 320 - horitzo_, 100, 255); - JD8_BlitCK(0, 100, fondo_, 320 - horitzo_, 100, horitzo_, 100, 255); + // Fondo mòvil (horitzó) amb wrap a 320 + JD8_BlitCK(horitzo_, 100, fondo_, 0, 100, 320 - horitzo_, 100, 255); + JD8_BlitCK(0, 100, fondo_, 320 - horitzo_, 100, horitzo_, 100, 255); - // Logo i camell animat - JD8_BlitCK(100, 25, gfx_, 0, 74, 124, 68, 255); - JD8_BlitCK(130, 100, gfx_, camello_.frame() * 80, 0, 80, 74, 255); + // Logo i camell animat + JD8_BlitCK(100, 25, gfx_, 0, 74, 124, 68, 255); + JD8_BlitCK(130, 100, gfx_, camello_.frame() * 80, 0, 80, 74, 255); - // Palmeres mòvils amb wrap a 320 - JD8_BlitCK(palmeres_, 150, gfx_, 0, 150, 320 - palmeres_, 50, 255); - JD8_BlitCK(0, 150, gfx_, 320 - palmeres_, 150, palmeres_, 50, 255); + // Palmeres mòvils amb wrap a 320 + JD8_BlitCK(palmeres_, 150, gfx_, 0, 150, 320 - palmeres_, 50, 255); + JD8_BlitCK(0, 150, gfx_, 320 - palmeres_, 150, palmeres_, 50, 255); - // "jdes" estàtic (davant dels scrollers) i versió a la cantonada - JD8_BlitCK(87, 167, gfx_, 127, 124, 150, 24, 255); - JD8_BlitCK(303, 193, gfx_, 305, 143, 15, 5, 255); + // "jdes" estàtic (davant dels scrollers) i versió a la cantonada + JD8_BlitCK(87, 167, gfx_, 127, 124, 150, 24, 255); + JD8_BlitCK(303, 193, gfx_, 305, 143, 15, 5, 255); - // "Polsa tecla" parpallejant. Al vell `contador % 100 > 30` amb - // updateTicks=20 ms, el cicle són 2000 ms amb un llindar de 600 ms: - // amagat els primers 600 ms, visible els següents 1400 ms. - const bool blink_on = (blink_ms_ % 2000) > 600; - if (blink_on) { - JD8_BlitCK(98, 130, gfx_, 161, 92, 127, 9, 255); - if (info::ctx.nou_personatge) { - JD8_BlitCK(68, 141, gfx_, 128, 105, 189, 9, 255); + // "Polsa tecla" parpallejant. Al vell `contador % 100 > 30` amb + // updateTicks=20 ms, el cicle són 2000 ms amb un llindar de 600 ms: + // amagat els primers 600 ms, visible els següents 1400 ms. + const bool blink_on = (blink_ms_ % 2000) > 600; + if (blink_on) { + JD8_BlitCK(98, 130, gfx_, 161, 92, 127, 9, 255); + if (info::ctx.nou_personatge) { + JD8_BlitCK(68, 141, gfx_, 128, 105, 189, 9, 255); + } } } -} -void MenuScene::tick(int delta_ms) { - switch (phase_) { - case Phase::FadingIn: - fade_.tick(delta_ms); - if (fade_.done()) phase_ = Phase::Showing; - break; + void MenuScene::tick(int delta_ms) { + switch (phase_) { + case Phase::FadingIn: + fade_.tick(delta_ms); + if (fade_.done()) phase_ = Phase::Showing; + break; - case Phase::Showing: { - // Palmeres: 1 pixel cada 80 ms (= cada 4 ticks × 20 ms originals) - palmeres_acc_ms_ += delta_ms; - while (palmeres_acc_ms_ >= 80) { - palmeres_acc_ms_ -= 80; - if (--palmeres_ < 0) palmeres_ = 319; + case Phase::Showing: { + // Palmeres: 1 pixel cada 80 ms (= cada 4 ticks × 20 ms originals) + palmeres_acc_ms_ += delta_ms; + while (palmeres_acc_ms_ >= 80) { + palmeres_acc_ms_ -= 80; + if (--palmeres_ < 0) palmeres_ = 319; + } + + // Horitzó: 1 pixel cada 320 ms (= cada 16 ticks × 20 ms) + horitzo_acc_ms_ += delta_ms; + while (horitzo_acc_ms_ >= 320) { + horitzo_acc_ms_ -= 320; + if (--horitzo_ < 0) horitzo_ = 319; + } + + camello_.tick(delta_ms); + + blink_ms_ += delta_ms; + if (blink_ms_ >= 2000) blink_ms_ %= 2000; + + render(); + + // Qualsevol tecla tanca el menú. Llegim 'P' explícitament abans + // de reiniciar el flag de input perquè `info::ctx.pepe_activat` + // reflecteixca si l'usuari estava polsant P al moment d'eixir. + if (JI_AnyKey() || JI_KeyPressed(SDL_SCANCODE_P)) { + info::ctx.pepe_activat = JI_KeyPressed(SDL_SCANCODE_P); + JI_DisableKeyboard(60); + info::ctx.num_piramide = 1; + fade_.startFadeOut(); + phase_ = Phase::FadingOut; + } + break; } - // Horitzó: 1 pixel cada 320 ms (= cada 16 ticks × 20 ms) - horitzo_acc_ms_ += delta_ms; - while (horitzo_acc_ms_ >= 320) { - horitzo_acc_ms_ -= 320; - if (--horitzo_ < 0) horitzo_ = 319; - } + case Phase::FadingOut: + fade_.tick(delta_ms); + if (fade_.done()) phase_ = Phase::Done; + break; - camello_.tick(delta_ms); - - blink_ms_ += delta_ms; - if (blink_ms_ >= 2000) blink_ms_ %= 2000; - - render(); - - // Qualsevol tecla tanca el menú. Llegim 'P' explícitament abans - // de reiniciar el flag de input perquè `info::ctx.pepe_activat` - // reflecteixca si l'usuari estava polsant P al moment d'eixir. - if (JI_AnyKey() || JI_KeyPressed(SDL_SCANCODE_P)) { - info::ctx.pepe_activat = JI_KeyPressed(SDL_SCANCODE_P); - JI_DisableKeyboard(60); - info::ctx.num_piramide = 1; - fade_.startFadeOut(); - phase_ = Phase::FadingOut; - } - break; + case Phase::Done: + break; } - - case Phase::FadingOut: - fade_.tick(delta_ms); - if (fade_.done()) phase_ = Phase::Done; - break; - - case Phase::Done: - break; } -} } // namespace scenes diff --git a/source/scenes/menu_scene.hpp b/source/scenes/menu_scene.hpp index 66f0617..64eca4e 100644 --- a/source/scenes/menu_scene.hpp +++ b/source/scenes/menu_scene.hpp @@ -7,51 +7,51 @@ 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 { - public: - void onEnter() override; - void tick(int delta_ms) override; - bool done() const override { return phase_ == Phase::Done; } - int nextState() const override { return 1; } + // 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; + bool done() const override { return phase_ == Phase::Done; } + int nextState() const override { return 1; } - private: - enum class Phase { FadingIn, - Showing, - FadingOut, - Done }; + private: + enum class Phase { FadingIn, + Showing, + FadingOut, + Done }; - void render(); + void render(); - SurfaceHandle fondo_; - SurfaceHandle gfx_; - PaletteFade fade_; - FrameAnimator camello_{4, 160, true}; + SurfaceHandle fondo_; + SurfaceHandle gfx_; + PaletteFade fade_; + FrameAnimator camello_{4, 160, true}; - Phase phase_{Phase::FadingIn}; + Phase phase_{Phase::FadingIn}; - // Scrollers horizontals. Mouen 1 pixel per pas. - int palmeres_{0}; - int horitzo_{0}; - int palmeres_acc_ms_{0}; - int horitzo_acc_ms_{0}; + // Scrollers horizontals. Mouen 1 pixel per pas. + int palmeres_{0}; + int horitzo_{0}; + int palmeres_acc_ms_{0}; + int horitzo_acc_ms_{0}; - // Acumulador per al parpalleig del text "polsa tecla". - int blink_ms_{0}; -}; + // Acumulador per al parpalleig del text "polsa tecla". + int blink_ms_{0}; + }; } // namespace scenes diff --git a/source/scenes/mort_scene.cpp b/source/scenes/mort_scene.cpp index e624244..c3aa265 100644 --- a/source/scenes/mort_scene.cpp +++ b/source/scenes/mort_scene.cpp @@ -9,56 +9,56 @@ namespace scenes { -void MortScene::onEnter() { - playMusic("music/00000001.ogg"); - JI_DisableKeyboard(60); - info::ctx.vida = 5; + void MortScene::onEnter() { + playMusic("music/00000001.ogg"); + JI_DisableKeyboard(60); + info::ctx.vida = 5; - gfx_ = SurfaceHandle("gfx/gameover.gif"); - JD8_ClearScreen(0); - JD8_Blit(gfx_); + gfx_ = SurfaceHandle("gfx/gameover.gif"); + JD8_ClearScreen(0); + JD8_Blit(gfx_); - // PaletteFade en fa una còpia interna via memcpy, així que alliberem - // la paleta temporal immediatament. - JD8_Palette pal = JD8_LoadPalette("gfx/gameover.gif"); - fade_.startFadeTo(pal); - std::free(pal); + // PaletteFade en fa una còpia interna via memcpy, així que alliberem + // la paleta temporal immediatament. + JD8_Palette pal = JD8_LoadPalette("gfx/gameover.gif"); + fade_.startFadeTo(pal); + std::free(pal); - phase_ = Phase::FadingIn; - remaining_ms_ = 10000; -} - -void MortScene::tick(int delta_ms) { - switch (phase_) { - case Phase::FadingIn: - fade_.tick(delta_ms); - if (fade_.done()) phase_ = Phase::Showing; - break; - - case Phase::Showing: - if (JI_AnyKey()) { - remaining_ms_ = 0; - } else { - remaining_ms_ -= delta_ms; - } - if (remaining_ms_ <= 0) { - // Arrenca música del següent mòdul abans del fade out, - // igual que la versió vella feia al final de doMort(). - playMusic("music/00000003.ogg"); - info::ctx.num_piramide = 0; - fade_.startFadeOut(); - phase_ = Phase::FadingOut; - } - break; - - case Phase::FadingOut: - fade_.tick(delta_ms); - if (fade_.done()) phase_ = Phase::Done; - break; - - case Phase::Done: - break; + phase_ = Phase::FadingIn; + remaining_ms_ = 10000; + } + + void MortScene::tick(int delta_ms) { + switch (phase_) { + case Phase::FadingIn: + fade_.tick(delta_ms); + if (fade_.done()) phase_ = Phase::Showing; + break; + + case Phase::Showing: + if (JI_AnyKey()) { + remaining_ms_ = 0; + } else { + remaining_ms_ -= delta_ms; + } + if (remaining_ms_ <= 0) { + // Arrenca música del següent mòdul abans del fade out, + // igual que la versió vella feia al final de doMort(). + playMusic("music/00000003.ogg"); + info::ctx.num_piramide = 0; + fade_.startFadeOut(); + phase_ = Phase::FadingOut; + } + break; + + case Phase::FadingOut: + fade_.tick(delta_ms); + if (fade_.done()) phase_ = Phase::Done; + break; + + case Phase::Done: + break; + } } -} } // namespace scenes diff --git a/source/scenes/mort_scene.hpp b/source/scenes/mort_scene.hpp index b2f6fe3..efbb061 100644 --- a/source/scenes/mort_scene.hpp +++ b/source/scenes/mort_scene.hpp @@ -6,31 +6,31 @@ 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 { - public: - void onEnter() override; - void tick(int delta_ms) override; - bool done() const override { return phase_ == Phase::Done; } - int nextState() const override { return 1; } + // 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; + bool done() const override { return phase_ == Phase::Done; } + int nextState() const override { return 1; } - private: - enum class Phase { FadingIn, - Showing, - FadingOut, - Done }; + private: + enum class Phase { FadingIn, + Showing, + FadingOut, + Done }; - SurfaceHandle gfx_; - PaletteFade fade_; - Phase phase_{Phase::FadingIn}; - int remaining_ms_{10000}; // 1000 ticks × 10 ms/tick del doMort original -}; + SurfaceHandle gfx_; + PaletteFade fade_; + Phase phase_{Phase::FadingIn}; + int remaining_ms_{10000}; // 1000 ticks × 10 ms/tick del doMort original + }; } // namespace scenes diff --git a/source/scenes/palette_fade.cpp b/source/scenes/palette_fade.cpp index 1633501..4908328 100644 --- a/source/scenes/palette_fade.cpp +++ b/source/scenes/palette_fade.cpp @@ -2,27 +2,27 @@ namespace scenes { -void PaletteFade::startFadeOut() { - JD8_FadeStartOut(); - active_ = true; -} - -void PaletteFade::startFadeTo(JD8_Palette target) { - JD8_FadeStartToPal(target); - active_ = true; -} - -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. - // Si en el futur volem fer-lo genuinament time-based (p.ex. "fade - // de 500ms exactes independent del framerate") podem convertir la - // màquina d'estats de jdraw8 a time-based ací sense tocar cap altre - // call site. - if (JD8_FadeTickStep()) { - active_ = false; + void PaletteFade::startFadeOut() { + JD8_FadeStartOut(); + active_ = true; + } + + void PaletteFade::startFadeTo(JD8_Palette target) { + JD8_FadeStartToPal(target); + active_ = true; + } + + 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. + // Si en el futur volem fer-lo genuinament time-based (p.ex. "fade + // de 500ms exactes independent del framerate") podem convertir la + // màquina d'estats de jdraw8 a time-based ací sense tocar cap altre + // call site. + if (JD8_FadeTickStep()) { + active_ = false; + } } -} } // namespace scenes diff --git a/source/scenes/palette_fade.hpp b/source/scenes/palette_fade.hpp index ddb0f2b..0fde3ea 100644 --- a/source/scenes/palette_fade.hpp +++ b/source/scenes/palette_fade.hpp @@ -4,27 +4,27 @@ 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 { - public: - PaletteFade() = default; + // 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; - void startFadeOut(); - void startFadeTo(JD8_Palette target); + void startFadeOut(); + void startFadeTo(JD8_Palette target); - void tick(int delta_ms); + void tick(int delta_ms); - bool active() const { return active_; } - bool done() const { return !active_; } + bool active() const { return active_; } + bool done() const { return !active_; } - private: - bool active_{false}; -}; + private: + bool active_{false}; + }; } // namespace scenes diff --git a/source/scenes/scene.hpp b/source/scenes/scene.hpp index 5429846..67c3765 100644 --- a/source/scenes/scene.hpp +++ b/source/scenes/scene.hpp @@ -16,22 +16,22 @@ namespace scenes { -class Scene { - public: - virtual ~Scene() = default; + class Scene { + public: + virtual ~Scene() = default; - virtual void onEnter() {} + virtual void onEnter() {} - virtual void tick(int delta_ms) = 0; + virtual void tick(int delta_ms) = 0; - virtual bool done() const = 0; + virtual bool done() const = 0; - // Valor retornat al caller quan l'escena acaba — equivalent al int - // que retornaven les velles funcions `Go()` de ModuleSequence: - // 1 = continuar amb la següent escena segons info::ctx - // 0 = entrar al gameplay (ModuleGame) - // -1 = eixir del joc - virtual int nextState() const { return 1; } -}; + // Valor retornat al caller quan l'escena acaba — equivalent al int + // que retornaven les velles funcions `Go()` de ModuleSequence: + // 1 = continuar amb la següent escena segons info::ctx + // 0 = entrar al gameplay (ModuleGame) + // -1 = eixir del joc + virtual int nextState() const { return 1; } + }; } // namespace scenes diff --git a/source/scenes/scene_registry.cpp b/source/scenes/scene_registry.cpp index c00e7e4..f7d5012 100644 --- a/source/scenes/scene_registry.cpp +++ b/source/scenes/scene_registry.cpp @@ -2,19 +2,19 @@ namespace scenes { -SceneRegistry& SceneRegistry::instance() { - static SceneRegistry inst; - return inst; -} + SceneRegistry& SceneRegistry::instance() { + static SceneRegistry inst; + return inst; + } -void SceneRegistry::registerScene(int state_key, Factory factory) { - factories_[state_key] = std::move(factory); -} + void SceneRegistry::registerScene(int state_key, Factory factory) { + factories_[state_key] = std::move(factory); + } -std::unique_ptr SceneRegistry::tryCreate(int state_key) const { - const auto it = factories_.find(state_key); - if (it == factories_.end()) return nullptr; - return it->second(); -} + std::unique_ptr SceneRegistry::tryCreate(int state_key) const { + const auto it = factories_.find(state_key); + if (it == factories_.end()) return nullptr; + return it->second(); + } } // namespace scenes diff --git a/source/scenes/scene_registry.hpp b/source/scenes/scene_registry.hpp index 1866288..5edb67f 100644 --- a/source/scenes/scene_registry.hpp +++ b/source/scenes/scene_registry.hpp @@ -8,30 +8,30 @@ 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 { - public: - using Factory = std::function()>; + // 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()>; - static SceneRegistry& instance(); + static SceneRegistry& instance(); - void registerScene(int state_key, Factory factory); + void registerScene(int state_key, Factory factory); - // Retorna `nullptr` si no hi ha cap escena registrada per a aquest - // state. El caller hauria de caure al path legacy en aquest cas. - std::unique_ptr tryCreate(int state_key) const; + // Retorna `nullptr` si no hi ha cap escena registrada per a aquest + // state. El caller hauria de caure al path legacy en aquest cas. + std::unique_ptr tryCreate(int state_key) const; - private: - SceneRegistry() = default; - std::unordered_map factories_; -}; + private: + SceneRegistry() = default; + std::unordered_map factories_; + }; } // namespace scenes diff --git a/source/scenes/scene_utils.cpp b/source/scenes/scene_utils.cpp index 1d689f2..a4bd539 100644 --- a/source/scenes/scene_utils.cpp +++ b/source/scenes/scene_utils.cpp @@ -7,15 +7,16 @@ namespace scenes { -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(buffer.size()), filename), - 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(buffer.size()), + filename), + loop); + } } // namespace scenes diff --git a/source/scenes/scene_utils.hpp b/source/scenes/scene_utils.hpp index dab5d2e..e419872 100644 --- a/source/scenes/scene_utils.hpp +++ b/source/scenes/scene_utils.hpp @@ -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 diff --git a/source/scenes/secreta_scene.cpp b/source/scenes/secreta_scene.cpp index 2807dfc..42af3d5 100644 --- a/source/scenes/secreta_scene.cpp +++ b/source/scenes/secreta_scene.cpp @@ -12,194 +12,194 @@ 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() { - if (pal_aux_) std::free(pal_aux_); - // pal_active_ NO s'allibera: propietat de main_palette via SetScreenPalette. -} - -void SecretaScene::onEnter() { - playMusic("music/00000002.ogg"); - - // Fade-out de la paleta anterior. Els assets es carreguen ja - // però no fem SetScreenPalette fins que acabe el fade — així - // el fade opera sobre la paleta del mòdul anterior. - fade_.startFadeOut(); - - gfx_ = SurfaceHandle("gfx/tomba1.gif"); - pal_aux_ = JD8_LoadPalette("gfx/tomba1.gif"); - pal_active_ = static_cast(std::malloc(768)); - std::memcpy(pal_active_, pal_aux_, 768); - - phase_ = Phase::InitialFadeOut; - phase_acc_ms_ = 0; -} - -void SecretaScene::swapToTomba2() { - JD8_ClearScreen(255); - gfx_.reset("gfx/tomba2.gif"); - - std::free(pal_aux_); - pal_aux_ = JD8_LoadPalette("gfx/tomba2.gif"); - // 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() { - JD8_ClearScreen(0); - JD8_SetPaletteColor(254, 12, 11, 11); - JD8_SetPaletteColor(253, 12, 11, 11); -} - -void SecretaScene::beginFinalFade() { - JA_FadeOutMusic(250); - fade_.startFadeOut(); - phase_ = Phase::FinalFadeOut; -} - -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. - if (!skip_triggered_ && phase_ != Phase::InitialFadeOut && JI_AnyKey()) { - skip_triggered_ = true; - beginFinalFade(); + SecretaScene::~SecretaScene() { + if (pal_aux_) std::free(pal_aux_); + // pal_active_ NO s'allibera: propietat de main_palette via SetScreenPalette. } - switch (phase_) { - case Phase::InitialFadeOut: - fade_.tick(delta_ms); - if (fade_.done()) { - // Ara main_palette (la vella) té tots els canals a 0. - // SetScreenPalette allibera la vella i adopta pal_active_ - // — des d'ara main_palette == pal_active_, així que les - // futures escriptures a pal_active_ afecten la pantalla. - JD8_SetScreenPalette(pal_active_); - JD8_ClearScreen(255); - phase_ = Phase::Tomba1ScrollIn; - phase_acc_ms_ = 0; - } - break; + void SecretaScene::onEnter() { + playMusic("music/00000002.ogg"); - case Phase::Tomba1ScrollIn: { - phase_acc_ms_ += delta_ms; - const int contador = std::min(128, phase_acc_ms_ / TICK_MS + 1); - // Dos blits solapats: el primer avança a velocitat completa, - // el segon (contingut de la dreta del src) a meitat (contador>>1). - JD8_Blit(70, 60, gfx_, 0, contador, 178, 70); - JD8_BlitCK(70, 60, gfx_, 178, contador >> 1, 142, 70, 255); - if (phase_acc_ms_ >= TOMBA1_SCROLL_MS) { - phase_ = Phase::Tomba1Hold; - phase_acc_ms_ = 0; - } - break; - } + // Fade-out de la paleta anterior. Els assets es carreguen ja + // però no fem SetScreenPalette fins que acabe el fade — així + // el fade opera sobre la paleta del mòdul anterior. + fade_.startFadeOut(); - case Phase::Tomba1Hold: - phase_acc_ms_ += delta_ms; - if (phase_acc_ms_ >= TOMBA1_HOLD_MS) { - swapToTomba2(); - phase_ = Phase::Tomba2ScrollIn; - phase_acc_ms_ = 0; - } - break; + gfx_ = SurfaceHandle("gfx/tomba1.gif"); + pal_aux_ = JD8_LoadPalette("gfx/tomba1.gif"); + pal_active_ = static_cast(std::malloc(768)); + std::memcpy(pal_active_, pal_aux_, 768); - case Phase::Tomba2ScrollIn: { - phase_acc_ms_ += delta_ms; - const int contador = std::min(94, phase_acc_ms_ / TICK_MS + 1); - JD8_Blit(55, 53, gfx_, 0, 158 - contador, 211, contador); - if (phase_acc_ms_ >= TOMBA2_SCROLL_MS) { - phase_ = Phase::Tomba2Hold; - phase_acc_ms_ = 0; - } - break; - } - - case Phase::Tomba2Hold: - phase_acc_ms_ += delta_ms; - if (phase_acc_ms_ >= TOMBA2_HOLD_MS) { - beginRedPulseSetup(); - phase_ = Phase::Tomba2Reveal; - phase_acc_ms_ = 0; - } - break; - - case Phase::Tomba2Reveal: { - phase_acc_ms_ += delta_ms; - const int contador = std::min(80, phase_acc_ms_ / TICK_MS + 1); - // Revelat horitzontal simètric: l'amplada creix 2px per tick - // i el src_x es desplaça a l'esquerra el mateix. - JD8_Blit(80, 68, gfx_, 160 - (contador * 2), 0, contador * 2, 64); - if (phase_acc_ms_ >= TOMBA2_REVEAL_MS) { - phase_ = Phase::Tomba2RevealHold; - phase_acc_ms_ = 0; - } - break; - } - - case Phase::Tomba2RevealHold: - phase_acc_ms_ += delta_ms; - if (phase_acc_ms_ >= TOMBA2_REVEAL_HOLD_MS) { - phase_ = Phase::RedPulse; - phase_acc_ms_ = 0; - } - break; - - case Phase::RedPulse: { - phase_acc_ms_ += delta_ms; - const int contador = std::min(51, phase_acc_ms_ / TICK_MS); - // Anima el canal R dels índexs 254 i 253 (aquest a la meitat - // de brillantor). Va de (12,11,11) fins a (63,11,11) / (31,11,11). - JD8_SetPaletteColor(254, contador + 12, 11, 11); - JD8_SetPaletteColor(253, (contador + 12) >> 1, 11, 11); - if (phase_acc_ms_ >= RED_PULSE_MS) { - phase_ = Phase::RedPulseHold; - phase_acc_ms_ = 0; - } - break; - } - - case Phase::RedPulseHold: - phase_acc_ms_ += delta_ms; - if (phase_acc_ms_ >= RED_PULSE_HOLD_MS) { - beginFinalFade(); - } - break; - - case Phase::FinalFadeOut: - fade_.tick(delta_ms); - if (fade_.done()) { - phase_ = Phase::Done; - } - break; - - case Phase::Done: - break; + phase_ = Phase::InitialFadeOut; + phase_acc_ms_ = 0; + } + + void SecretaScene::swapToTomba2() { + JD8_ClearScreen(255); + gfx_.reset("gfx/tomba2.gif"); + + std::free(pal_aux_); + pal_aux_ = JD8_LoadPalette("gfx/tomba2.gif"); + // 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() { + JD8_ClearScreen(0); + JD8_SetPaletteColor(254, 12, 11, 11); + JD8_SetPaletteColor(253, 12, 11, 11); + } + + void SecretaScene::beginFinalFade() { + JA_FadeOutMusic(250); + fade_.startFadeOut(); + phase_ = Phase::FinalFadeOut; + } + + 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. + if (!skip_triggered_ && phase_ != Phase::InitialFadeOut && JI_AnyKey()) { + skip_triggered_ = true; + beginFinalFade(); + } + + switch (phase_) { + case Phase::InitialFadeOut: + fade_.tick(delta_ms); + if (fade_.done()) { + // Ara main_palette (la vella) té tots els canals a 0. + // SetScreenPalette allibera la vella i adopta pal_active_ + // — des d'ara main_palette == pal_active_, així que les + // futures escriptures a pal_active_ afecten la pantalla. + JD8_SetScreenPalette(pal_active_); + JD8_ClearScreen(255); + phase_ = Phase::Tomba1ScrollIn; + phase_acc_ms_ = 0; + } + break; + + case Phase::Tomba1ScrollIn: { + phase_acc_ms_ += delta_ms; + const int contador = std::min(128, phase_acc_ms_ / TICK_MS + 1); + // Dos blits solapats: el primer avança a velocitat completa, + // el segon (contingut de la dreta del src) a meitat (contador>>1). + JD8_Blit(70, 60, gfx_, 0, contador, 178, 70); + JD8_BlitCK(70, 60, gfx_, 178, contador >> 1, 142, 70, 255); + if (phase_acc_ms_ >= TOMBA1_SCROLL_MS) { + phase_ = Phase::Tomba1Hold; + phase_acc_ms_ = 0; + } + break; + } + + case Phase::Tomba1Hold: + phase_acc_ms_ += delta_ms; + if (phase_acc_ms_ >= TOMBA1_HOLD_MS) { + swapToTomba2(); + phase_ = Phase::Tomba2ScrollIn; + phase_acc_ms_ = 0; + } + break; + + case Phase::Tomba2ScrollIn: { + phase_acc_ms_ += delta_ms; + const int contador = std::min(94, phase_acc_ms_ / TICK_MS + 1); + JD8_Blit(55, 53, gfx_, 0, 158 - contador, 211, contador); + if (phase_acc_ms_ >= TOMBA2_SCROLL_MS) { + phase_ = Phase::Tomba2Hold; + phase_acc_ms_ = 0; + } + break; + } + + case Phase::Tomba2Hold: + phase_acc_ms_ += delta_ms; + if (phase_acc_ms_ >= TOMBA2_HOLD_MS) { + beginRedPulseSetup(); + phase_ = Phase::Tomba2Reveal; + phase_acc_ms_ = 0; + } + break; + + case Phase::Tomba2Reveal: { + phase_acc_ms_ += delta_ms; + const int contador = std::min(80, phase_acc_ms_ / TICK_MS + 1); + // Revelat horitzontal simètric: l'amplada creix 2px per tick + // i el src_x es desplaça a l'esquerra el mateix. + JD8_Blit(80, 68, gfx_, 160 - (contador * 2), 0, contador * 2, 64); + if (phase_acc_ms_ >= TOMBA2_REVEAL_MS) { + phase_ = Phase::Tomba2RevealHold; + phase_acc_ms_ = 0; + } + break; + } + + case Phase::Tomba2RevealHold: + phase_acc_ms_ += delta_ms; + if (phase_acc_ms_ >= TOMBA2_REVEAL_HOLD_MS) { + phase_ = Phase::RedPulse; + phase_acc_ms_ = 0; + } + break; + + case Phase::RedPulse: { + phase_acc_ms_ += delta_ms; + const int contador = std::min(51, phase_acc_ms_ / TICK_MS); + // Anima el canal R dels índexs 254 i 253 (aquest a la meitat + // de brillantor). Va de (12,11,11) fins a (63,11,11) / (31,11,11). + JD8_SetPaletteColor(254, contador + 12, 11, 11); + JD8_SetPaletteColor(253, (contador + 12) >> 1, 11, 11); + if (phase_acc_ms_ >= RED_PULSE_MS) { + phase_ = Phase::RedPulseHold; + phase_acc_ms_ = 0; + } + break; + } + + case Phase::RedPulseHold: + phase_acc_ms_ += delta_ms; + if (phase_acc_ms_ >= RED_PULSE_HOLD_MS) { + beginFinalFade(); + } + break; + + case Phase::FinalFadeOut: + fade_.tick(delta_ms); + if (fade_.done()) { + phase_ = Phase::Done; + } + break; + + case Phase::Done: + break; + } } -} } // namespace scenes diff --git a/source/scenes/secreta_scene.hpp b/source/scenes/secreta_scene.hpp index 0b67bf4..10534fa 100644 --- a/source/scenes/secreta_scene.hpp +++ b/source/scenes/secreta_scene.hpp @@ -7,60 +7,60 @@ 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 { - public: - SecretaScene() = default; - ~SecretaScene() override; + // 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; - void onEnter() override; - void tick(int delta_ms) override; - bool done() const override { return phase_ == Phase::Done; } - int nextState() const override { return 0; } + void onEnter() override; + void tick(int delta_ms) override; + bool done() const override { return phase_ == Phase::Done; } + int nextState() const override { return 0; } - private: - enum class Phase { - InitialFadeOut, - Tomba1ScrollIn, - Tomba1Hold, - Tomba2ScrollIn, - Tomba2Hold, - Tomba2Reveal, - Tomba2RevealHold, - RedPulse, - RedPulseHold, - FinalFadeOut, - Done, + private: + enum class Phase { + InitialFadeOut, + Tomba1ScrollIn, + Tomba1Hold, + Tomba2ScrollIn, + Tomba2Hold, + Tomba2Reveal, + Tomba2RevealHold, + RedPulse, + RedPulseHold, + FinalFadeOut, + Done, + }; + + void swapToTomba2(); + void beginRedPulseSetup(); + void beginFinalFade(); + + SurfaceHandle gfx_; + JD8_Palette pal_aux_{nullptr}; + JD8_Palette pal_active_{nullptr}; // propietat transferida a main_palette + PaletteFade fade_; + + Phase phase_{Phase::InitialFadeOut}; + int phase_acc_ms_{0}; + bool skip_triggered_{false}; }; - void swapToTomba2(); - void beginRedPulseSetup(); - void beginFinalFade(); - - SurfaceHandle gfx_; - JD8_Palette pal_aux_{nullptr}; - JD8_Palette pal_active_{nullptr}; // propietat transferida a main_palette - PaletteFade fade_; - - Phase phase_{Phase::InitialFadeOut}; - int phase_acc_ms_{0}; - bool skip_triggered_{false}; -}; - } // namespace scenes diff --git a/source/scenes/slides_scene.cpp b/source/scenes/slides_scene.cpp index 528fb12..8596a23 100644 --- a/source/scenes/slides_scene.cpp +++ b/source/scenes/slides_scene.cpp @@ -13,174 +13,180 @@ 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() { - if (pal_aux_) std::free(pal_aux_); - // pal_active_ NO s'allibera: propietat de main_palette via SetScreenPalette. -} - -void SlidesScene::onEnter() { - num_piramide_at_start_ = info::ctx.num_piramide; - - const char* arxiu = nullptr; - if (num_piramide_at_start_ == 7) { - // loop=1 per replicar el vell `play_music("00000005.ogg", 1)`. - playMusic("music/00000005.ogg", 1); - arxiu = (info::ctx.diners < 200) ? "gfx/intro2.gif" : "gfx/intro3.gif"; - } else { - arxiu = "gfx/intro.gif"; + SlidesScene::~SlidesScene() { + if (pal_aux_) std::free(pal_aux_); + // pal_active_ NO s'allibera: propietat de main_palette via SetScreenPalette. } - gfx_ = SurfaceHandle(arxiu); - pal_aux_ = JD8_LoadPalette(arxiu); + void SlidesScene::onEnter() { + num_piramide_at_start_ = info::ctx.num_piramide; - // Còpia editable de la paleta. `pal_active_` comparteix memòria amb - // main_palette després del SetScreenPalette — modificar-la modifica - // main_palette directament. `pal_aux_` es manté intacte per a poder - // restaurar després de cada fade-out intermedi. - pal_active_ = static_cast(std::malloc(768)); - std::memcpy(pal_active_, pal_aux_, 768); - JD8_SetScreenPalette(pal_active_); + const char* arxiu = nullptr; + if (num_piramide_at_start_ == 7) { + // loop=1 per replicar el vell `play_music("00000005.ogg", 1)`. + playMusic("music/00000005.ogg", 1); + arxiu = (info::ctx.diners < 200) ? "gfx/intro2.gif" : "gfx/intro3.gif"; + } else { + arxiu = "gfx/intro.gif"; + } - JD8_ClearScreen(BG_COLOR_INDEX); + gfx_ = SurfaceHandle(arxiu); + pal_aux_ = JD8_LoadPalette(arxiu); - phase_ = Phase::Slide1Enter; - phase_acc_ms_ = 0; - next_state_ = 0; -} + // Còpia editable de la paleta. `pal_active_` comparteix memòria amb + // main_palette després del SetScreenPalette — modificar-la modifica + // main_palette directament. `pal_aux_` es manté intacte per a poder + // restaurar després de cada fade-out intermedi. + pal_active_ = static_cast(std::malloc(768)); + std::memcpy(pal_active_, pal_aux_, 768); + JD8_SetScreenPalette(pal_active_); -void SlidesScene::drawSlide(int slide_idx, int pos_x) { - const int src_y = slide_idx * SLIDE_H; + JD8_ClearScreen(BG_COLOR_INDEX); - // Clipping manual: translada un rect de 320×65 des de (pos_x, SLIDE_Y) - // a l'àrea visible (0..319, SLIDE_Y..SLIDE_Y+64). - int dst_x = pos_x; - int src_x = 0; - int w = 320; - - if (dst_x < 0) { - src_x = -dst_x; - w = 320 + dst_x; - dst_x = 0; - } else if (dst_x > 0) { - w = 320 - dst_x; + phase_ = Phase::Slide1Enter; + phase_acc_ms_ = 0; + next_state_ = 0; } - if (w > 0) { - JD8_Blit(dst_x, SLIDE_Y, gfx_, src_x, src_y, w, SLIDE_H); + void SlidesScene::drawSlide(int slide_idx, int pos_x) { + const int src_y = slide_idx * SLIDE_H; + + // Clipping manual: translada un rect de 320×65 des de (pos_x, SLIDE_Y) + // a l'àrea visible (0..319, SLIDE_Y..SLIDE_Y+64). + int dst_x = pos_x; + int src_x = 0; + int w = 320; + + if (dst_x < 0) { + src_x = -dst_x; + w = 320 + dst_x; + dst_x = 0; + } else if (dst_x > 0) { + w = 320 - dst_x; + } + + if (w > 0) { + JD8_Blit(dst_x, SLIDE_Y, gfx_, src_x, src_y, w, SLIDE_H); + } } -} -void SlidesScene::restorePalette() { - std::memcpy(pal_active_, pal_aux_, 768); -} - -void SlidesScene::beginFinalFade() { - if (num_piramide_at_start_ != 7) { - JA_FadeOutMusic(250); + void SlidesScene::restorePalette() { + std::memcpy(pal_active_, pal_aux_, 768); } - fade_.startFadeOut(); - phase_ = Phase::FadeFinal; -} -void SlidesScene::tick(int delta_ms) { - // Skip: qualsevol tecla salta directament al fade final. Per fidelitat - // al vell doSlides, el skip NO atura la música explícitament — només - // el final natural crida JA_FadeOutMusic (beginFinalFade() distingeix). - if (!skip_triggered_ && JI_AnyKey()) { - skip_triggered_ = true; - if (num_piramide_at_start_ != 7) JA_FadeOutMusic(250); + void SlidesScene::beginFinalFade() { + if (num_piramide_at_start_ != 7) { + JA_FadeOutMusic(250); + } fade_.startFadeOut(); phase_ = Phase::FadeFinal; } - switch (phase_) { - case Phase::Slide1Enter: - case Phase::Slide2Enter: - case Phase::Slide3Enter: { - phase_acc_ms_ += delta_ms; - const int slide_idx = (phase_ == Phase::Slide1Enter ? 0 - : phase_ == Phase::Slide2Enter ? 1 - : 2); - const float t = std::min(1.0f, static_cast(phase_acc_ms_) / - static_cast(SCROLL_MS)); - const float eased = Easing::outCubic(t); - const int pos_x = Easing::lerpInt(SLIDE_START_X[slide_idx], 0, eased); - drawSlide(slide_idx, pos_x); - - if (phase_acc_ms_ >= SCROLL_MS) { - // Garanteix posició final exacta (pos_x=0). - drawSlide(slide_idx, 0); - if (phase_ == Phase::Slide1Enter) phase_ = Phase::Slide1Hold; - else if (phase_ == Phase::Slide2Enter) phase_ = Phase::Slide2Hold; - else phase_ = Phase::Slide3Hold; - phase_acc_ms_ = 0; - } - break; + void SlidesScene::tick(int delta_ms) { + // Skip: qualsevol tecla salta directament al fade final. Per fidelitat + // al vell doSlides, el skip NO atura la música explícitament — només + // el final natural crida JA_FadeOutMusic (beginFinalFade() distingeix). + if (!skip_triggered_ && JI_AnyKey()) { + skip_triggered_ = true; + if (num_piramide_at_start_ != 7) JA_FadeOutMusic(250); + fade_.startFadeOut(); + phase_ = Phase::FadeFinal; } - case Phase::Slide1Hold: - case Phase::Slide2Hold: - phase_acc_ms_ += delta_ms; - if (phase_acc_ms_ >= HOLD_MS) { - fade_.startFadeOut(); - if (phase_ == Phase::Slide1Hold) phase_ = Phase::FadeOut1; - else phase_ = Phase::FadeOut2; - phase_acc_ms_ = 0; - } - break; + switch (phase_) { + case Phase::Slide1Enter: + case Phase::Slide2Enter: + case Phase::Slide3Enter: { + phase_acc_ms_ += delta_ms; + const int slide_idx = (phase_ == Phase::Slide1Enter ? 0 + : phase_ == Phase::Slide2Enter ? 1 + : 2); + const float t = std::min(1.0f, static_cast(phase_acc_ms_) / static_cast(SCROLL_MS)); + const float eased = Easing::outCubic(t); + const int pos_x = Easing::lerpInt(SLIDE_START_X[slide_idx], 0, eased); + drawSlide(slide_idx, pos_x); - case Phase::Slide3Hold: - phase_acc_ms_ += delta_ms; - if (phase_acc_ms_ >= HOLD_MS) { - beginFinalFade(); - } - break; - - case Phase::FadeOut1: - case Phase::FadeOut2: - fade_.tick(delta_ms); - if (fade_.done()) { - restorePalette(); - JD8_ClearScreen(BG_COLOR_INDEX); - if (phase_ == Phase::FadeOut1) phase_ = Phase::Slide2Enter; - else phase_ = Phase::Slide3Enter; - phase_acc_ms_ = 0; - } - break; - - case Phase::FadeFinal: - fade_.tick(delta_ms); - if (fade_.done()) { - if (num_piramide_at_start_ == 7) { - info::ctx.num_piramide = 8; - next_state_ = 1; - } else { - next_state_ = 0; + if (phase_acc_ms_ >= SCROLL_MS) { + // Garanteix posició final exacta (pos_x=0). + drawSlide(slide_idx, 0); + if (phase_ == Phase::Slide1Enter) + phase_ = Phase::Slide1Hold; + else if (phase_ == Phase::Slide2Enter) + phase_ = Phase::Slide2Hold; + else + phase_ = Phase::Slide3Hold; + phase_acc_ms_ = 0; } - phase_ = Phase::Done; + break; } - break; - case Phase::Done: - break; + case Phase::Slide1Hold: + case Phase::Slide2Hold: + phase_acc_ms_ += delta_ms; + if (phase_acc_ms_ >= HOLD_MS) { + fade_.startFadeOut(); + if (phase_ == Phase::Slide1Hold) + phase_ = Phase::FadeOut1; + else + phase_ = Phase::FadeOut2; + phase_acc_ms_ = 0; + } + break; + + case Phase::Slide3Hold: + phase_acc_ms_ += delta_ms; + if (phase_acc_ms_ >= HOLD_MS) { + beginFinalFade(); + } + break; + + case Phase::FadeOut1: + case Phase::FadeOut2: + fade_.tick(delta_ms); + if (fade_.done()) { + restorePalette(); + JD8_ClearScreen(BG_COLOR_INDEX); + if (phase_ == Phase::FadeOut1) + phase_ = Phase::Slide2Enter; + else + phase_ = Phase::Slide3Enter; + phase_acc_ms_ = 0; + } + break; + + case Phase::FadeFinal: + fade_.tick(delta_ms); + if (fade_.done()) { + if (num_piramide_at_start_ == 7) { + info::ctx.num_piramide = 8; + next_state_ = 1; + } else { + next_state_ = 0; + } + phase_ = Phase::Done; + } + break; + + case Phase::Done: + break; + } } -} } // namespace scenes diff --git a/source/scenes/slides_scene.hpp b/source/scenes/slides_scene.hpp index 3497adf..85e740b 100644 --- a/source/scenes/slides_scene.hpp +++ b/source/scenes/slides_scene.hpp @@ -7,73 +7,73 @@ 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 { - public: - SlidesScene() = default; - ~SlidesScene() override; + // 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; - void onEnter() override; - void tick(int delta_ms) override; - bool done() const override { return phase_ == Phase::Done; } - int nextState() const override { return next_state_; } + void onEnter() override; + void tick(int delta_ms) override; + bool done() const override { return phase_ == Phase::Done; } + int nextState() const override { return next_state_; } - private: - enum class Phase { - Slide1Enter, - Slide1Hold, - FadeOut1, - Slide2Enter, - Slide2Hold, - FadeOut2, - Slide3Enter, - Slide3Hold, - FadeFinal, - Done, + private: + enum class Phase { + Slide1Enter, + Slide1Hold, + FadeOut1, + Slide2Enter, + Slide2Hold, + FadeOut2, + Slide3Enter, + Slide3Hold, + FadeFinal, + Done, + }; + + // Pinta un slide amb desplaçament horitzontal. `slide_idx` = 0..2 + // (correspon a la franja 65x65 a y = 0, 65, 130 dins de gfx_). + // `pos_x` = desplaçament, amb clipping manual quan surt de pantalla. + void drawSlide(int slide_idx, int pos_x); + void restorePalette(); + void beginFinalFade(); + + SurfaceHandle gfx_; + JD8_Palette pal_aux_{nullptr}; // còpia "neta" que preservem + JD8_Palette pal_active_{nullptr}; // propietat transferida a main_palette + PaletteFade fade_; + + Phase phase_{Phase::Slide1Enter}; + int phase_acc_ms_{0}; + int num_piramide_at_start_{1}; + int next_state_{0}; + bool skip_triggered_{false}; }; - // Pinta un slide amb desplaçament horitzontal. `slide_idx` = 0..2 - // (correspon a la franja 65x65 a y = 0, 65, 130 dins de gfx_). - // `pos_x` = desplaçament, amb clipping manual quan surt de pantalla. - void drawSlide(int slide_idx, int pos_x); - void restorePalette(); - void beginFinalFade(); - - SurfaceHandle gfx_; - JD8_Palette pal_aux_{nullptr}; // còpia "neta" que preservem - JD8_Palette pal_active_{nullptr}; // propietat transferida a main_palette - PaletteFade fade_; - - Phase phase_{Phase::Slide1Enter}; - int phase_acc_ms_{0}; - int num_piramide_at_start_{1}; - int next_state_{0}; - bool skip_triggered_{false}; -}; - } // namespace scenes diff --git a/source/scenes/sprite_mover.cpp b/source/scenes/sprite_mover.cpp index d09f14b..b700d25 100644 --- a/source/scenes/sprite_mover.cpp +++ b/source/scenes/sprite_mover.cpp @@ -4,43 +4,43 @@ namespace scenes { -void SpriteMover::moveTo(int x0, int y0, int x1, int y1, int duration_ms, EaseFn ease) { - x0_ = x0; - y0_ = y0; - x1_ = x1; - y1_ = y1; - duration_ms_ = std::max(0, duration_ms); - elapsed_ms_ = 0; - ease_ = ease ? ease : Easing::linear; - cur_x_ = x0; - cur_y_ = y0; -} - -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) { - if (duration_ms_ <= 0) { - cur_x_ = x1_; - cur_y_ = y1_; - return; + void SpriteMover::moveTo(int x0, int y0, int x1, int y1, int duration_ms, EaseFn ease) { + x0_ = x0; + y0_ = y0; + x1_ = x1; + y1_ = y1; + duration_ms_ = std::max(0, duration_ms); + elapsed_ms_ = 0; + ease_ = ease ? ease : Easing::linear; + cur_x_ = x0; + cur_y_ = y0; } - elapsed_ms_ = std::min(elapsed_ms_ + delta_ms, duration_ms_); - const float t = static_cast(elapsed_ms_) / static_cast(duration_ms_); - const float eased = ease_(t); - cur_x_ = Easing::lerpInt(x0_, x1_, eased); - cur_y_ = Easing::lerpInt(y0_, y1_, eased); -} -float SpriteMover::progress() const { - if (duration_ms_ <= 0) return 1.0f; - return static_cast(elapsed_ms_) / static_cast(duration_ms_); -} + 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) { + if (duration_ms_ <= 0) { + cur_x_ = x1_; + cur_y_ = y1_; + return; + } + elapsed_ms_ = std::min(elapsed_ms_ + delta_ms, duration_ms_); + const float t = static_cast(elapsed_ms_) / static_cast(duration_ms_); + const float eased = ease_(t); + cur_x_ = Easing::lerpInt(x0_, x1_, eased); + cur_y_ = Easing::lerpInt(y0_, y1_, eased); + } + + float SpriteMover::progress() const { + if (duration_ms_ <= 0) return 1.0f; + return static_cast(elapsed_ms_) / static_cast(duration_ms_); + } } // namespace scenes diff --git a/source/scenes/sprite_mover.hpp b/source/scenes/sprite_mover.hpp index ca53cac..78d913e 100644 --- a/source/scenes/sprite_mover.hpp +++ b/source/scenes/sprite_mover.hpp @@ -4,36 +4,35 @@ 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 { - public: - using EaseFn = float (*)(float); + // 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; + 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); + // 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); - // Posicionament immediat (útil per a "teleportar" entre moviments). - void setPosition(int x, int y); + // Posicionament immediat (útil per a "teleportar" entre moviments). + void setPosition(int x, int y); - void tick(int delta_ms); + void tick(int delta_ms); - int x() const { return cur_x_; } - int y() const { return cur_y_; } - bool done() const { return elapsed_ms_ >= duration_ms_; } - float progress() const; // 0..1 sense easing aplicat + int x() const { return cur_x_; } + int y() const { return cur_y_; } + bool done() const { return elapsed_ms_ >= duration_ms_; } + float progress() const; // 0..1 sense easing aplicat - private: - int x0_{0}, y0_{0}, x1_{0}, y1_{0}; - int duration_ms_{0}; - int elapsed_ms_{0}; - int cur_x_{0}, cur_y_{0}; - EaseFn ease_{Easing::linear}; -}; + private: + int x0_{0}, y0_{0}, x1_{0}, y1_{0}; + int duration_ms_{0}; + int elapsed_ms_{0}; + int cur_x_{0}, cur_y_{0}; + EaseFn ease_{Easing::linear}; + }; } // namespace scenes diff --git a/source/scenes/surface_handle.cpp b/source/scenes/surface_handle.cpp index 5b5a589..d59d45a 100644 --- a/source/scenes/surface_handle.cpp +++ b/source/scenes/surface_handle.cpp @@ -2,41 +2,41 @@ namespace scenes { -SurfaceHandle::SurfaceHandle(const char* file) - : surface_(JD8_LoadSurface(file)) {} + SurfaceHandle::SurfaceHandle(const char* file) + : surface_(JD8_LoadSurface(file)) {} -SurfaceHandle::~SurfaceHandle() { - if (surface_) JD8_FreeSurface(surface_); -} - -SurfaceHandle::SurfaceHandle(SurfaceHandle&& other) noexcept - : surface_(other.surface_) { - other.surface_ = nullptr; -} - -SurfaceHandle& SurfaceHandle::operator=(SurfaceHandle&& other) noexcept { - if (this != &other) { + SurfaceHandle::~SurfaceHandle() { if (surface_) JD8_FreeSurface(surface_); - surface_ = other.surface_; + } + + SurfaceHandle::SurfaceHandle(SurfaceHandle&& other) noexcept + : surface_(other.surface_) { other.surface_ = nullptr; } - return *this; -} -void SurfaceHandle::reset(const char* file) { - if (surface_) JD8_FreeSurface(surface_); - surface_ = file ? JD8_LoadSurface(file) : nullptr; -} + SurfaceHandle& SurfaceHandle::operator=(SurfaceHandle&& other) noexcept { + if (this != &other) { + if (surface_) JD8_FreeSurface(surface_); + surface_ = other.surface_; + other.surface_ = nullptr; + } + return *this; + } -void SurfaceHandle::adopt(JD8_Surface raw) { - if (surface_) JD8_FreeSurface(surface_); - surface_ = raw; -} + void SurfaceHandle::reset(const char* file) { + if (surface_) JD8_FreeSurface(surface_); + surface_ = file ? JD8_LoadSurface(file) : nullptr; + } -JD8_Surface SurfaceHandle::release() { - JD8_Surface r = surface_; - surface_ = nullptr; - return r; -} + void SurfaceHandle::adopt(JD8_Surface raw) { + if (surface_) JD8_FreeSurface(surface_); + surface_ = raw; + } + + JD8_Surface SurfaceHandle::release() { + JD8_Surface r = surface_; + surface_ = nullptr; + return r; + } } // namespace scenes diff --git a/source/scenes/surface_handle.hpp b/source/scenes/surface_handle.hpp index 7c92968..4308d50 100644 --- a/source/scenes/surface_handle.hpp +++ b/source/scenes/surface_handle.hpp @@ -4,46 +4,46 @@ 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 { - public: - SurfaceHandle() = default; - explicit SurfaceHandle(const char* file); - ~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); + ~SurfaceHandle(); - SurfaceHandle(const SurfaceHandle&) = delete; - SurfaceHandle& operator=(const SurfaceHandle&) = delete; + SurfaceHandle(const SurfaceHandle&) = delete; + SurfaceHandle& operator=(const SurfaceHandle&) = delete; - SurfaceHandle(SurfaceHandle&& other) noexcept; - SurfaceHandle& operator=(SurfaceHandle&& other) noexcept; + SurfaceHandle(SurfaceHandle&& other) noexcept; + SurfaceHandle& operator=(SurfaceHandle&& other) noexcept; - // Allibera la surface actual (si n'hi ha) i carrega una nova. - // Usat per escenes que recarreguen assets a mitja cinemàtica - // (p.ex. doSecreta que passa de tomba1 a tomba2). - void reset(const char* file); + // Allibera la surface actual (si n'hi ha) i carrega una nova. + // Usat per escenes que recarreguen assets a mitja cinemàtica + // (p.ex. doSecreta que passa de tomba1 a tomba2). + void reset(const char* file); - // Adopta una surface ja creada (p.ex. amb JD8_NewSurface). Pren ownership - // — la surface adoptada s'allibera al destructor o al següent reset/adopt. - void adopt(JD8_Surface raw); + // Adopta una surface ja creada (p.ex. amb JD8_NewSurface). Pren ownership + // — la surface adoptada s'allibera al destructor o al següent reset/adopt. + void adopt(JD8_Surface raw); - // Allibera ownership sense destruir la surface. Retorna el pointer cru; - // el caller passa a ser responsable d'alliberar-lo (o de passar-lo a un - // altre propietari). Usat quan una escena delega a codi legacy que - // també allibera la mateixa surface — cal "soltar" el ownership per - // evitar double free. - [[nodiscard]] JD8_Surface release(); + // Allibera ownership sense destruir la surface. Retorna el pointer cru; + // el caller passa a ser responsable d'alliberar-lo (o de passar-lo a un + // altre propietari). Usat quan una escena delega a codi legacy que + // també allibera la mateixa surface — cal "soltar" el ownership per + // evitar double free. + [[nodiscard]] JD8_Surface release(); - // Conversió implícita per al confort d'ús: JD8_Blit(handle) - // en lloc de JD8_Blit(handle.get()). - operator JD8_Surface() const { return surface_; } - JD8_Surface get() const { return surface_; } - bool valid() const { return surface_ != nullptr; } + // Conversió implícita per al confort d'ús: JD8_Blit(handle) + // en lloc de JD8_Blit(handle.get()). + operator JD8_Surface() const { return surface_; } + JD8_Surface get() const { return surface_; } + bool valid() const { return surface_ != nullptr; } - private: - JD8_Surface surface_{nullptr}; -}; + private: + JD8_Surface surface_{nullptr}; + }; } // namespace scenes diff --git a/source/scenes/timeline.cpp b/source/scenes/timeline.cpp index a8a290b..d93df3f 100644 --- a/source/scenes/timeline.cpp +++ b/source/scenes/timeline.cpp @@ -4,82 +4,82 @@ namespace scenes { -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::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) { - Step s; - s.duration_ms = 0; - s.oneshot = std::move(fn); - steps_.push_back(std::move(s)); - return *this; -} + 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() { + while (current_ < steps_.size() && steps_[current_].duration_ms == 0) { + auto& s = steps_[current_]; + if (!s.entered) { + s.entered = true; + if (s.oneshot) s.oneshot(); + } + ++current_; + elapsed_in_step_ = 0; + } + } + + void Timeline::tick(int delta_ms) { + if (skipped_) return; + flushOneShots(); + if (current_ >= steps_.size()) return; -void Timeline::flushOneShots() { - while (current_ < steps_.size() && steps_[current_].duration_ms == 0) { auto& s = steps_[current_]; if (!s.entered) { s.entered = true; - if (s.oneshot) s.oneshot(); + // Primer tick dins del pas: cridem amb progress=0 si hi ha callback. + if (s.continuous) s.continuous(0.0f); } - ++current_; + + elapsed_in_step_ += delta_ms; + if (elapsed_in_step_ >= s.duration_ms) { + // Tancament del pas: una crida final amb progress=1. + if (s.continuous) s.continuous(1.0f); + ++current_; + elapsed_in_step_ = 0; + // Pot ser que el següent pas siga una cadena de one-shots. + flushOneShots(); + } else if (s.continuous) { + const float p = static_cast(elapsed_in_step_) / + static_cast(std::max(1, s.duration_ms)); + s.continuous(p); + } + } + + void Timeline::skip() { + skipped_ = true; + current_ = steps_.size(); + } + + void Timeline::reset() { + for (auto& s : steps_) s.entered = false; + current_ = 0; elapsed_in_step_ = 0; - } -} - -void Timeline::tick(int delta_ms) { - if (skipped_) return; - flushOneShots(); - if (current_ >= steps_.size()) return; - - auto& s = steps_[current_]; - if (!s.entered) { - s.entered = true; - // Primer tick dins del pas: cridem amb progress=0 si hi ha callback. - if (s.continuous) s.continuous(0.0f); + skipped_ = false; } - elapsed_in_step_ += delta_ms; - if (elapsed_in_step_ >= s.duration_ms) { - // Tancament del pas: una crida final amb progress=1. - if (s.continuous) s.continuous(1.0f); - ++current_; - elapsed_in_step_ = 0; - // Pot ser que el següent pas siga una cadena de one-shots. - flushOneShots(); - } else if (s.continuous) { - const float p = static_cast(elapsed_in_step_) / - static_cast(std::max(1, s.duration_ms)); - s.continuous(p); + bool Timeline::done() const { + return skipped_ || current_ >= steps_.size(); } -} -void Timeline::skip() { - skipped_ = true; - current_ = steps_.size(); -} - -void Timeline::reset() { - for (auto& s : steps_) s.entered = false; - current_ = 0; - elapsed_in_step_ = 0; - skipped_ = false; -} - -bool Timeline::done() const { - return skipped_ || current_ >= steps_.size(); -} - -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(elapsed_in_step_) / static_cast(s.duration_ms); -} + 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(elapsed_in_step_) / static_cast(s.duration_ms); + } } // namespace scenes diff --git a/source/scenes/timeline.hpp b/source/scenes/timeline.hpp index 23b3fe2..16b3db8 100644 --- a/source/scenes/timeline.hpp +++ b/source/scenes/timeline.hpp @@ -5,53 +5,53 @@ 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 { - public: - using StepFn = std::function; - using OnceFn = std::function; + // 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; + using OnceFn = std::function; - Timeline& step(int duration_ms, StepFn fn = nullptr); - Timeline& once(OnceFn fn); + Timeline& step(int duration_ms, StepFn fn = nullptr); + Timeline& once(OnceFn fn); - void tick(int delta_ms); - void skip(); - void reset(); + void tick(int delta_ms); + void skip(); + void reset(); - bool done() const; - int currentStepIndex() const { return static_cast(current_); } - float currentProgress() const; + bool done() const; + int currentStepIndex() const { return static_cast(current_); } + float currentProgress() const; - private: - struct Step { - int duration_ms{0}; // 0 = one-shot - StepFn continuous; - OnceFn oneshot; - bool entered{false}; + private: + struct Step { + int duration_ms{0}; // 0 = one-shot + StepFn continuous; + OnceFn oneshot; + bool entered{false}; + }; + + // Avança els one-shots consecutius des de `current_` fins a trobar + // un pas amb duració > 0 o l'endoll de la llista. + void flushOneShots(); + + std::vector steps_; + std::size_t current_{0}; + int elapsed_in_step_{0}; + bool skipped_{false}; }; - // Avança els one-shots consecutius des de `current_` fins a trobar - // un pas amb duració > 0 o l'endoll de la llista. - void flushOneShots(); - - std::vector steps_; - std::size_t current_{0}; - int elapsed_in_step_{0}; - bool skipped_{false}; -}; - } // namespace scenes