clang-format

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,23 +2,23 @@
namespace info { namespace info {
struct GameContext { struct GameContext {
int num_piramide = 0; int num_piramide = 0;
int num_habitacio = 0; int num_habitacio = 0;
int diners = 0; int diners = 0;
int diamants = 0; int diamants = 0;
int vida = 0; int vida = 0;
int momies = 0; int momies = 0;
int engendros = 0; int engendros = 0;
bool nou_personatge = false; bool nou_personatge = false;
bool pepe_activat = 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 // 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) // 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. // es podrà passar per referència als mòduls en lloc d'accedir via singleton.
inline GameContext ctx; inline GameContext ctx;
} // namespace info } // namespace info

View File

@@ -42,16 +42,17 @@ void ModuleGame::onEnter() {
// fade interpolarien cap a una paleta amb pantalla buida. // fade interpolarien cap a una paleta amb pantalla buida.
this->Draw(); this->Draw();
const char* music = info::ctx.num_piramide == 3 ? "music/00000008.ogg" const char* music = info::ctx.num_piramide == 3 ? "music/00000008.ogg"
: info::ctx.num_piramide == 2 ? "music/00000007.ogg" : info::ctx.num_piramide == 2 ? "music/00000007.ogg"
: info::ctx.num_piramide == 6 ? "music/00000002.ogg" : info::ctx.num_piramide == 6 ? "music/00000002.ogg"
: "music/00000006.ogg"; : "music/00000006.ogg";
const char* current_music = JA_GetMusicFilename(); const char* current_music = JA_GetMusicFilename();
if ((JA_GetMusicState() != JA_MUSIC_PLAYING) || !current_music || if ((JA_GetMusicState() != JA_MUSIC_PLAYING) || !current_music ||
strcmp(music, current_music) != 0) { strcmp(music, current_music) != 0) {
auto buffer = ResourceHelper::loadFile(music); auto buffer = ResourceHelper::loadFile(music);
JA_PlayMusic(JA_LoadMusic(buffer.data(), JA_PlayMusic(JA_LoadMusic(buffer.data(),
static_cast<Uint32>(buffer.size()), music)); static_cast<Uint32>(buffer.size()),
music));
} }
// Arranca el fade-in tick-based. El `PaletteFade` avança un pas (de // Arranca el fade-in tick-based. El `PaletteFade` avança un pas (de

View File

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

View File

@@ -141,11 +141,21 @@ namespace Options {
inline std::string crtpi_file_path{}; inline std::string crtpi_file_path{};
inline int current_crtpi_preset{0}; inline int current_crtpi_preset{0};
inline std::string debug_file_path{};
// --- API --- // --- API ---
void setConfigFile(const std::string& path); void setConfigFile(const std::string& path);
auto loadFromFile() -> bool; auto loadFromFile() -> bool;
auto saveToFile() -> 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); void setPostFXFile(const std::string& path);
auto loadPostFXFromFile() -> bool; auto loadPostFXFromFile() -> bool;

View File

@@ -57,6 +57,13 @@ SDL_AppResult SDL_AppInit(void** /*appstate*/, int /*argc*/, char* /*argv*/[]) {
Options::setConfigFile(std::string(file_getconfigfolder()) + "config.yaml"); Options::setConfigFile(std::string(file_getconfigfolder()) + "config.yaml");
Options::loadFromFile(); 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__ #ifdef __EMSCRIPTEN__
// MEMFS no persistix entre recàrregues: força valors sensats per a web. // MEMFS no persistix entre recàrregues: força valors sensats per a web.
Options::window.fullscreen = false; Options::window.fullscreen = false;
@@ -112,6 +119,9 @@ void SDL_AppQuit(void* /*appstate*/, SDL_AppResult /*result*/) {
Director::get()->teardown(); Director::get()->teardown();
Options::saveToFile(); Options::saveToFile();
#ifndef NDEBUG
Options::saveDebugToFile();
#endif
Director::destroy(); Director::destroy();
Menu::destroy(); Menu::destroy();

View File

@@ -10,62 +10,62 @@
namespace scenes { namespace scenes {
void BannerScene::onEnter() { void BannerScene::onEnter() {
playMusic("music/00000004.ogg"); playMusic("music/00000004.ogg");
gfx_ = SurfaceHandle("gfx/ffase.gif"); gfx_ = SurfaceHandle("gfx/ffase.gif");
JD8_ClearScreen(0); JD8_ClearScreen(0);
// Títols superior i inferior del banner (compartits per tots els nivells) // Títols superior i inferior del banner (compartits per tots els nivells)
JD8_Blit(81, 24, gfx_, 81, 155, 168, 21); JD8_Blit(81, 24, gfx_, 81, 155, 168, 21);
JD8_Blit(39, 150, gfx_, 39, 175, 248, 20); JD8_Blit(39, 150, gfx_, 39, 175, 248, 20);
// Número de piràmide: les 4 variants del vell `doBanner` es reduïxen // 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. // a coordenades (sx,sy) calculades a partir de l'índex 0..3.
const int idx = info::ctx.num_piramide - 2; // 2..5 → 0..3 const int idx = info::ctx.num_piramide - 2; // 2..5 → 0..3
if (idx >= 0 && idx <= 3) { if (idx >= 0 && idx <= 3) {
const int sx = (idx % 2) * 160; const int sx = (idx % 2) * 160;
const int sy = (idx / 2) * 75; const int sy = (idx / 2) * 75;
JD8_Blit(82, 60, gfx_, sx, sy, 160, 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. void BannerScene::tick(int delta_ms) {
JD8_Palette pal = JD8_LoadPalette("gfx/ffase.gif"); switch (phase_) {
fade_.startFadeTo(pal); case Phase::FadingIn:
std::free(pal); fade_.tick(delta_ms);
if (fade_.done()) phase_ = Phase::Showing;
break;
phase_ = Phase::FadingIn; case Phase::Showing:
remaining_ms_ = 5000; 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) { case Phase::FadingOut:
switch (phase_) { fade_.tick(delta_ms);
case Phase::FadingIn: if (fade_.done()) phase_ = Phase::Done;
fade_.tick(delta_ms); break;
if (fade_.done()) phase_ = Phase::Showing;
break;
case Phase::Showing: case Phase::Done:
if (JI_AnyKey()) { break;
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;
} }
}
} // namespace scenes } // namespace scenes

View File

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

View File

@@ -11,129 +11,136 @@
namespace { namespace {
// Frames del cotxe: 8 posicions dins del sprite sheet gfx/final.gif. El // Frames del cotxe: 8 posicions dins del sprite sheet gfx/final.gif. El
// vell doCredits tenia aquesta taula inline — la reproduïm idèntica. // vell doCredits tenia aquesta taula inline — la reproduïm idèntica.
struct CocheFrame { struct CocheFrame {
Uint16 x, y; Uint16 x, y;
}; };
constexpr CocheFrame COCHE_FRAMES[8] = { constexpr CocheFrame COCHE_FRAMES[8] = {
{214, 152}, {214, 104}, {214, 56}, {214, 104}, {214, 152}, {214, 8}, {108, 152}, {214, 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 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 TICK_MS = 20; // JG_SetUpdateTicks heretat del doSlides previ
constexpr int BG_INDEX = 255; constexpr int BG_INDEX = 255;
} // namespace } // namespace
namespace scenes { namespace scenes {
CreditsScene::~CreditsScene() { CreditsScene::~CreditsScene() {
// No toquem la paleta activa: SetScreenPalette n'ha pres ownership. // 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");
} }
vaddr2_ = SurfaceHandle("gfx/final.gif"); void CreditsScene::onEnter() {
vaddr3_ = SurfaceHandle("gfx/finals.gif"); // El vell doCredits no tocava música — heretava la del doSlides
// previ ("music/00000005.ogg"). Si l'escena s'arrenca directament (test
JD8_Palette pal = JD8_LoadPalette("gfx/final.gif"); // amb piramide_inicial=8) no hi ha res que heretar, així que
JD8_SetScreenPalette(pal); // arranquem la mateixa pista només si no sona res. Inocu en el
// `pal` passa a ser propietat de main_palette — no l'alliberem. // flux normal: JA_MUSIC_PLAYING fa que no la tornem a tocar.
if (JA_GetMusicState() != JA_MUSIC_PLAYING) {
phase_ = Phase::Rolling; playMusic("music/00000005.ogg");
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: vaddr2_ = SurfaceHandle("gfx/final.gif");
fade_.tick(delta_ms); vaddr3_ = SurfaceHandle("gfx/finals.gif");
if (fade_.done()) {
info::ctx.num_piramide = 255;
phase_ = Phase::Done;
}
break;
case Phase::Done: JD8_Palette pal = JD8_LoadPalette("gfx/final.gif");
break; 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 } // namespace scenes

View File

@@ -8,45 +8,45 @@
namespace scenes { namespace scenes {
// Crèdits finals del joc. Reemplaça `ModuleSequence::doCredits()`. // Crèdits finals del joc. Reemplaça `ModuleSequence::doCredits()`.
// //
// Flux: // Flux:
// 1. Carrega gfx/final.gif (sprites de crèdits) i gfx/finals.gif (fons). // 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 // 2. Mostra els crèdits amb scroll vertical de 2 columnes durant
// ~62 segons (contador 0..3100 × 20 ms). // ~62 segons (contador 0..3100 × 20 ms).
// 3. Si `info::ctx.diamants == 16`, pinta addicionalment un parallax // 3. Si `info::ctx.diamants == 16`, pinta addicionalment un parallax
// de 4 capes amb cotxe animat (8 frames). Si no, 2 blits fixos. // 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` // 4. Al acabar (per tecla o per contador), crea el fitxer `trick.ini`
// i activa `info::ctx.nou_personatge`. // i activa `info::ctx.nou_personatge`.
// 5. Fade-out de paleta. Torna a la intro (num_piramide = 255). // 5. Fade-out de paleta. Torna a la intro (num_piramide = 255).
// //
// Registrada al SceneRegistry amb state_key = 8. // Registrada al SceneRegistry amb state_key = 8.
class CreditsScene : public Scene { class CreditsScene : public Scene {
public: public:
CreditsScene() = default; CreditsScene() = default;
~CreditsScene() override; ~CreditsScene() override;
void onEnter() override; void onEnter() override;
void tick(int delta_ms) override; void tick(int delta_ms) override;
bool done() const override { return phase_ == Phase::Done; } bool done() const override { return phase_ == Phase::Done; }
int nextState() const override { return 1; } int nextState() const override { return 1; }
private: private:
enum class Phase { Rolling, enum class Phase { Rolling,
FadingOut, FadingOut,
Done }; Done };
void render(); void render();
void writeTrickIni(); void writeTrickIni();
SurfaceHandle vaddr2_; // gfx/final.gif (sprites i coches) SurfaceHandle vaddr2_; // gfx/final.gif (sprites i coches)
SurfaceHandle vaddr3_; // gfx/finals.gif (fons / parallax) SurfaceHandle vaddr3_; // gfx/finals.gif (fons / parallax)
PaletteFade fade_; PaletteFade fade_;
FrameAnimator coche_{8, 60, true}; // 8 frames × 60 ms (~3 × 20 ms tick vell) FrameAnimator coche_{8, 60, true}; // 8 frames × 60 ms (~3 × 20 ms tick vell)
Phase phase_{Phase::Rolling}; Phase phase_{Phase::Rolling};
int contador_{1}; int contador_{1};
int contador_acc_ms_{0}; int contador_acc_ms_{0};
}; };
} // namespace scenes } // namespace scenes

View File

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

View File

@@ -2,33 +2,33 @@
namespace scenes { namespace scenes {
// Cicla per un conjunt de frames numerats (0..num_frames-1) avançant un // 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 — // frame cada `frame_ms` mil·lisegons. No carrega ni dibuixa cap sprite —
// només el caller sap quins frames dibuixar a partir de `frame()`. // només el caller sap quins frames dibuixar a partir de `frame()`.
// //
// Usat per animacions periòdiques amb frames subsamplejats: palmeres, // Usat per animacions periòdiques amb frames subsamplejats: palmeres,
// camell, aigua, torxes, Sam caminant amb `(i/5) % fr` del codi original. // camell, aigua, torxes, Sam caminant amb `(i/5) % fr` del codi original.
class FrameAnimator { class FrameAnimator {
public: public:
FrameAnimator() = default; FrameAnimator() = default;
FrameAnimator(int num_frames, int frame_ms, bool loop = true); 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_; } int frame() const { return current_frame_; }
bool done() const { return !loop_ && finished_; } bool done() const { return !loop_ && finished_; }
int numFrames() const { return num_frames_; } int numFrames() const { return num_frames_; }
void reset(); void reset();
void setFrameMs(int frame_ms) { frame_ms_ = frame_ms; } void setFrameMs(int frame_ms) { frame_ms_ = frame_ms; }
private: private:
int num_frames_{1}; int num_frames_{1};
int frame_ms_{100}; int frame_ms_{100};
bool loop_{true}; bool loop_{true};
int current_frame_{0}; int current_frame_{0};
int elapsed_ms_{0}; int elapsed_ms_{0};
bool finished_{false}; bool finished_{false};
}; };
} // namespace scenes } // namespace scenes

View File

@@ -9,210 +9,206 @@
namespace { namespace {
// Coordenades mesurades del wordmark "Jailgames" dins gfx/logo_new.gif. // Coordenades mesurades del wordmark "Jailgames" dins gfx/logo_new.gif.
// Idèntiques a les del doIntroNewLogo vell — si canvies el logo, aquí i // Idèntiques a les del doIntroNewLogo vell — si canvies el logo, aquí i
// al GIF són els únics llocs a tocar. // al GIF són els únics llocs a tocar.
constexpr int LOGO_SRC_X = 60; constexpr int LOGO_SRC_X = 60;
constexpr int LOGO_SRC_Y = 158; constexpr int LOGO_SRC_Y = 158;
constexpr int LOGO_DST_Y = 78; constexpr int LOGO_DST_Y = 78;
constexpr int LOGO_HEIGHT = 28; constexpr int LOGO_HEIGHT = 28;
constexpr int LETTER_WIDTHS[9] = {16, 39, 50, 69, 92, 115, 146, 169, 188}; 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_X[9] = {77, 100, 111, 130, 153, 176, 207, 230, 249};
constexpr int CURSOR_W = 12; constexpr int CURSOR_W = 12;
constexpr int CURSOR_H = 3; constexpr int CURSOR_H = 3;
constexpr int CURSOR_Y = LOGO_DST_Y + LOGO_HEIGHT - CURSOR_H; // y = 103 constexpr int CURSOR_Y = LOGO_DST_Y + LOGO_HEIGHT - CURSOR_H; // y = 103
constexpr Uint8 CURSOR_COLOR = 17; // mateix index verd que les lletres constexpr Uint8 CURSOR_COLOR = 17; // mateix index verd que les lletres
// Timings (ms) — idèntics als de doIntroNewLogo vell. // Timings (ms) — idèntics als de doIntroNewLogo vell.
constexpr int INITIAL_MS = 1000; constexpr int INITIAL_MS = 1000;
constexpr int REVEAL_FRAME_MS = 150; constexpr int REVEAL_FRAME_MS = 150;
constexpr int FULL_LOGO_MS = 200; constexpr int FULL_LOGO_MS = 200;
constexpr int PALETTE_CYCLE_STEP_MS = 20; constexpr int PALETTE_CYCLE_STEP_MS = 20;
constexpr int FINAL_WAIT_MS = 20; constexpr int FINAL_WAIT_MS = 20;
constexpr int PALETTE_CYCLE_STEPS = 256; constexpr int PALETTE_CYCLE_STEPS = 256;
} // namespace } // namespace
namespace scenes { namespace scenes {
IntroNewLogoScene::IntroNewLogoScene() = default; IntroNewLogoScene::IntroNewLogoScene() = default;
IntroNewLogoScene::~IntroNewLogoScene() { IntroNewLogoScene::~IntroNewLogoScene() {
// No alliberem `pal_`: JD8_SetScreenPalette n'ha pres ownership i // No alliberem `pal_`: JD8_SetScreenPalette n'ha pres ownership i
// el proper SetScreenPalette / FadeToPal el lliurarà. Alliberar-lo // el proper SetScreenPalette / FadeToPal el lliurarà. Alliberar-lo
// ací provocaria double free. // 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;
} }
switch (phase_) { void IntroNewLogoScene::onEnter() {
case Phase::Initial: playMusic("music/00000003.ogg");
phase_acc_ms_ += delta_ms;
render();
if (phase_acc_ms_ >= INITIAL_MS) {
phase_ = Phase::Revealing;
phase_acc_ms_ = 0;
}
break;
case Phase::Revealing: gfx_ = SurfaceHandle("gfx/logo_new.gif");
phase_acc_ms_ += delta_ms; pal_ = JD8_LoadPalette("gfx/logo_new.gif");
render(); JD8_SetScreenPalette(pal_);
if (phase_acc_ms_ >= REVEAL_FRAME_MS) {
phase_acc_ms_ = 0; // Surface auxiliar omplida amb el color del cursor — permet pintar
reveal_cursor_visible_ = !reveal_cursor_visible_; // el "subratllat" amb un blit normal.
// Quan acabem els dos frames d'una lletra (cursor on → off), cursor_surf_.adopt(JD8_NewSurface());
// passem a la següent lletra. 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_) { if (reveal_cursor_visible_) {
++reveal_letter_; JD8_Blit(CURSOR_X[reveal_letter_], CURSOR_Y, cursor_surf_, 0, 0, CURSOR_W, CURSOR_H);
if (reveal_letter_ >= 9) { }
phase_ = Phase::FullLogoFlash; break;
reveal_letter_ = 8; }
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: case Phase::FullLogoFlash:
phase_acc_ms_ += delta_ms; phase_acc_ms_ += delta_ms;
render(); render();
if (phase_acc_ms_ >= FULL_LOGO_MS) { if (phase_acc_ms_ >= FULL_LOGO_MS) {
phase_ = Phase::PaletteCycle; phase_ = Phase::PaletteCycle;
phase_acc_ms_ = 0; phase_acc_ms_ = 0;
} }
break; break;
case Phase::PaletteCycle: case Phase::PaletteCycle:
phase_acc_ms_ += delta_ms; phase_acc_ms_ += delta_ms;
// Avancem passos de paleta cada 20 ms. Si el delta és gran, // Avancem passos de paleta cada 20 ms. Si el delta és gran,
// consumim múltiples passos en la mateixa crida. // consumim múltiples passos en la mateixa crida.
while (phase_acc_ms_ >= PALETTE_CYCLE_STEP_MS && while (phase_acc_ms_ >= PALETTE_CYCLE_STEP_MS &&
palette_step_ < PALETTE_CYCLE_STEPS) { palette_step_ < PALETTE_CYCLE_STEPS) {
phase_acc_ms_ -= PALETTE_CYCLE_STEP_MS; phase_acc_ms_ -= PALETTE_CYCLE_STEP_MS;
advancePaletteCycle(); advancePaletteCycle();
++palette_step_; ++palette_step_;
} }
render(); render();
if (palette_step_ >= PALETTE_CYCLE_STEPS) { if (palette_step_ >= PALETTE_CYCLE_STEPS) {
phase_ = Phase::FinalWait; phase_ = Phase::FinalWait;
phase_acc_ms_ = 0; phase_acc_ms_ = 0;
} }
break; break;
case Phase::FinalWait: case Phase::FinalWait:
phase_acc_ms_ += delta_ms; phase_acc_ms_ += delta_ms;
render(); render();
if (phase_acc_ms_ >= FINAL_WAIT_MS) { if (phase_acc_ms_ >= FINAL_WAIT_MS) {
phase_ = Phase::Sprites; phase_ = Phase::Sprites;
} }
break; break;
case Phase::Sprites: case Phase::Sprites:
// Sub-escena construïda al primer tick. Transferim el gfx_ // Sub-escena construïda al primer tick. Transferim el gfx_
// per move — la sub-escena se n'ocupa fins que es destruix. // per move — la sub-escena se n'ocupa fins que es destruix.
// Cada tick successiu delega l'animació dels sprites. // Cada tick successiu delega l'animació dels sprites.
if (!sprites_scene_) { if (!sprites_scene_) {
sprites_scene_ = std::make_unique<IntroSpritesScene>(std::move(gfx_)); sprites_scene_ = std::make_unique<IntroSpritesScene>(std::move(gfx_));
sprites_scene_->onEnter(); sprites_scene_->onEnter();
} }
sprites_scene_->tick(delta_ms); sprites_scene_->tick(delta_ms);
if (sprites_scene_->done()) { if (sprites_scene_->done()) {
// El vell `Go()` post-switch feia `num_piramide = 0` // El vell `Go()` post-switch feia `num_piramide = 0`
// per passar al menú. Sense açò el while del fiber // per passar al menú. Sense açò el while del fiber
// tornaria a crear IntroNewLogoScene infinitament. // tornaria a crear IntroNewLogoScene infinitament.
info::ctx.num_piramide = 0; info::ctx.num_piramide = 0;
phase_ = Phase::Done; phase_ = Phase::Done;
} }
break; break;
case Phase::Done: case Phase::Done:
break; break;
}
} }
}
} // namespace scenes } // namespace scenes

View File

@@ -9,60 +9,60 @@
namespace scenes { namespace scenes {
// Intro "moderna" del logo Jailgames amb revelat lletra-a-lletra + // Intro "moderna" del logo Jailgames amb revelat lletra-a-lletra +
// ciclo de paleta final. Reemplaça `ModuleSequence::doIntroNewLogo()`. // ciclo de paleta final. Reemplaça `ModuleSequence::doIntroNewLogo()`.
// //
// Flux: // Flux:
// 1. Carrega gfx/logo_new.gif, arranca música "music/00000003.ogg" i posa // 1. Carrega gfx/logo_new.gif, arranca música "music/00000003.ogg" i posa
// la paleta directament (sense fade-in). Mostra pantalla negra 1s. // la paleta directament (sense fade-in). Mostra pantalla negra 1s.
// 2. Revelat: 9 lletres × 2 frames (amb cursor / sense cursor), 150 ms // 2. Revelat: 9 lletres × 2 frames (amb cursor / sense cursor), 150 ms
// cada frame. // cada frame.
// 3. Logo complet amb cursor fix 200 ms. // 3. Logo complet amb cursor fix 200 ms.
// 4. Cicle de paleta de 256 passos modificant índexs 1631 cada 20 ms. // 4. Cicle de paleta de 256 passos modificant índexs 1631 cada 20 ms.
// 5. Espera final 20 ms. // 5. Espera final 20 ms.
// 6. Transfereix el gfx_ a una `IntroSpritesScene` com a sub-escena // 6. Transfereix el gfx_ a una `IntroSpritesScene` com a sub-escena
// i li delega els ticks fins que acaba (anima el prota + momia + // i li delega els ticks fins que acaba (anima el prota + momia +
// mapa, amb 3 variants aleatòries). En acabar, setzea num_piramide // mapa, amb 3 variants aleatòries). En acabar, setzea num_piramide
// = 0 per passar al menú. // = 0 per passar al menú.
// //
// Registrada al SceneRegistry amb state_key = 255, amb una factory // Registrada al SceneRegistry amb state_key = 255, amb una factory
// condicional: només s'activa si `Options::game.use_new_logo == true`. // condicional: només s'activa si `Options::game.use_new_logo == true`.
// Si és false, la factory retorna nullptr i el gameFiberEntry cau al // Si és false, la factory retorna nullptr i el gameFiberEntry cau al
// path legacy (`ModuleSequence::doIntro()` vell). // path legacy (`ModuleSequence::doIntro()` vell).
class IntroNewLogoScene : public Scene { class IntroNewLogoScene : public Scene {
public: public:
IntroNewLogoScene(); IntroNewLogoScene();
~IntroNewLogoScene() override; ~IntroNewLogoScene() override;
void onEnter() override; void onEnter() override;
void tick(int delta_ms) override; void tick(int delta_ms) override;
bool done() const override { return phase_ == Phase::Done; } bool done() const override { return phase_ == Phase::Done; }
int nextState() const override { return 1; } int nextState() const override { return 1; }
private: private:
enum class Phase { enum class Phase {
Initial, // pantalla negra 1000 ms Initial, // pantalla negra 1000 ms
Revealing, // 9 × 2 frames × 150 ms cada un Revealing, // 9 × 2 frames × 150 ms cada un
FullLogoFlash, // logo complet + cursor, 200 ms FullLogoFlash, // logo complet + cursor, 200 ms
PaletteCycle, // 256 passos × 20 ms modificant paleta PaletteCycle, // 256 passos × 20 ms modificant paleta
FinalWait, // 20 ms final FinalWait, // 20 ms final
Sprites, // tick delegat a IntroSpritesScene fins que acaba Sprites, // tick delegat a IntroSpritesScene fins que acaba
Done, Done,
};
void render();
void advancePaletteCycle();
SurfaceHandle gfx_;
SurfaceHandle cursor_surf_;
JD8_Palette pal_{nullptr}; // propietat transferida a main_palette via SetScreenPalette
std::unique_ptr<IntroSpritesScene> 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<IntroSpritesScene> 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 } // namespace scenes

View File

@@ -7,212 +7,212 @@
namespace { namespace {
// Timings idèntics als del vell `doIntro()`: el JG_SetUpdateTicks(1000) // 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 // 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 // "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. // (G, A, M, E) i (200) per a la resta fins al cicle de paleta.
constexpr int INITIAL_MS = 1000; constexpr int INITIAL_MS = 1000;
constexpr int PALETTE_CYCLE_STEP_MS = 20; constexpr int PALETTE_CYCLE_STEP_MS = 20;
constexpr int PALETTE_CYCLE_STEPS = 256; constexpr int PALETTE_CYCLE_STEPS = 256;
constexpr int FINAL_WAIT_MS = 200; constexpr int FINAL_WAIT_MS = 200;
// Un pas del revelat. Dos blits configurables (cos del wordmark + avió) // 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. // més una variant per al wordmark sencer i un flag de ClearScreen previ.
struct RevealStep { struct RevealStep {
int duration_ms; int duration_ms;
int body_w; // amplada del blit body (43,78) ← (43,155, body_w, 45); 0 si s'usa wordmark 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ó 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 clear; // fa ClearScreen(0) abans dels blits
bool wordmark; // usa drawWordmark() en lloc del blit body (wordmark complet) bool wordmark; // usa drawWordmark() en lloc del blit body (wordmark complet)
}; };
constexpr RevealStep REVEAL_STEPS[] = { constexpr RevealStep REVEAL_STEPS[] = {
{100, 27, 68, false, false}, // J {100, 27, 68, false, false}, // J
{100, 53, 96, false, false}, // JA {100, 53, 96, false, false}, // JA
{100, 66, 109, false, false}, // JAI {100, 66, 109, false, false}, // JAI
{200, 92, 136, false, false}, // JAIL {200, 92, 136, false, false}, // JAIL
{200, 92, -1, true, false}, // JAIL (clear, sense avió — parpelleig) {200, 92, -1, true, false}, // JAIL (clear, sense avió — parpelleig)
{100, 118, 160, false, false}, // JAILG {100, 118, 160, false, false}, // JAILG
{100, 145, 188, false, false}, // JAILGA {100, 145, 188, false, false}, // JAILGA
{100, 178, 221, false, false}, // JAILGAM {100, 178, 221, false, false}, // JAILGAM
{100, 205, 248, false, false}, // JAILGAME {100, 205, 248, false, false}, // JAILGAME
{200, 0, 274, false, true}, // JAILGAMES (wordmark complet) + avió {200, 0, 274, false, true}, // JAILGAMES (wordmark complet) + avió
{200, 0, -1, true, true}, // JAILGAMES (clear, sense avió) {200, 0, -1, true, true}, // JAILGAMES (clear, sense avió)
{200, 0, 274, false, true}, // JAILGAMES + avió (sense clear) {200, 0, 274, false, true}, // JAILGAMES + avió (sense clear)
{200, 0, -1, true, true}, // JAILGAMES (clear, sense avió) {200, 0, -1, true, true}, // JAILGAMES (clear, sense avió)
{200, 0, 274, false, true}, // JAILGAMES + avió (sense clear) {200, 0, 274, false, true}, // JAILGAMES + avió (sense clear)
{200, 0, -1, true, true}, // JAILGAMES (clear, sense avió) {200, 0, -1, true, true}, // JAILGAMES (clear, sense avió)
}; };
constexpr int REVEAL_COUNT = sizeof(REVEAL_STEPS) / sizeof(REVEAL_STEPS[0]); constexpr int REVEAL_COUNT = sizeof(REVEAL_STEPS) / sizeof(REVEAL_STEPS[0]);
// Branca `!use_new_logo` del drawIntroWordmark de modulesequence.cpp: // Branca `!use_new_logo` del drawIntroWordmark de modulesequence.cpp:
// blit únic del wordmark "JAILGAMES" complet (231×45 al destí 43,78). // 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 // IntroScene només s'activa quan use_new_logo == false, així que la
// branca use_new_logo d'aquell helper aquí no es necessita. // branca use_new_logo d'aquell helper aquí no es necessita.
void drawWordmark(JD8_Surface gfx) { void drawWordmark(JD8_Surface gfx) {
JD8_Blit(43, 78, gfx, 43, 155, 231, 45); JD8_Blit(43, 78, gfx, 43, 155, 231, 45);
} }
} // namespace } // namespace
namespace scenes { namespace scenes {
IntroScene::IntroScene() = default; IntroScene::IntroScene() = default;
IntroScene::~IntroScene() { IntroScene::~IntroScene() {
// No alliberem `pal_`: JD8_SetScreenPalette n'ha pres ownership i el // No alliberem `pal_`: JD8_SetScreenPalette n'ha pres ownership i el
// proper SetScreenPalette / FadeToPal la lliurarà. Alliberar-la ací // proper SetScreenPalette / FadeToPal la lliurarà. Alliberar-la ací
// provocaria double free. // 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;
} }
switch (phase_) { void IntroScene::onEnter() {
case Phase::InitialWait: playMusic("music/00000003.ogg");
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: gfx_ = SurfaceHandle("gfx/logo.gif");
phase_acc_ms_ += delta_ms; pal_ = JD8_LoadPalette("gfx/logo.gif");
render(); JD8_SetScreenPalette(pal_);
if (phase_acc_ms_ >= REVEAL_STEPS[reveal_index_].duration_ms) {
phase_acc_ms_ = 0; JD8_ClearScreen(0);
++reveal_index_;
if (reveal_index_ >= REVEAL_COUNT) { phase_ = Phase::InitialWait;
phase_ = Phase::PaletteCycle; 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: case Phase::PaletteCycle:
phase_acc_ms_ += delta_ms; case Phase::FinalWait:
// Avancem tants passos com permet el delta, per evitar // Wordmark complet fix mentre cicla la paleta — l'últim
// saltar-ne si el frame ha vingut lent. // pas del revelat (PAS 15) deixa la pantalla en aquest mateix
while (phase_acc_ms_ >= PALETTE_CYCLE_STEP_MS && // estat, i el vell doIntro no redibuixava durant el cicle.
palette_step_ < PALETTE_CYCLE_STEPS) { JD8_ClearScreen(0);
phase_acc_ms_ -= PALETTE_CYCLE_STEP_MS; drawWordmark(gfx_);
advancePaletteCycle(); break;
++palette_step_;
}
render();
if (palette_step_ >= PALETTE_CYCLE_STEPS) {
phase_ = Phase::FinalWait;
phase_acc_ms_ = 0;
}
break;
case Phase::FinalWait: case Phase::Sprites:
phase_acc_ms_ += delta_ms; case Phase::Done:
render(); break;
if (phase_acc_ms_ >= FINAL_WAIT_MS) { }
phase_ = Phase::Sprites; }
}
break; void IntroScene::advancePaletteCycle() {
// Replica exacta del cicle del doIntro vell sobre pal[16..31] — el
case Phase::Sprites: // grup del verd brillant del logo. Index 17 s'acosta a blanc mentre
// Sub-escena construïda al vol al primer tick d'aquesta fase. // els altres convergeixen cap al mateix gris mitjà.
// Transferim el gfx_ per move — la sub-escena se n'ocupa for (int i = 16; i < 32; i++) {
// fins que es destruix. Una vegada feta, els ticks delegats if (i == 17) {
// avancen l'animació dels sprites. if (pal_[i].r < 255) pal_[i].r++;
if (!sprites_scene_) { if (pal_[i].g < 255) pal_[i].g++;
sprites_scene_ = std::make_unique<IntroSpritesScene>(std::move(gfx_)); if (pal_[i].b < 255) pal_[i].b++;
sprites_scene_->onEnter(); }
} if (pal_[i].b < pal_[i].g) pal_[i].b++;
sprites_scene_->tick(delta_ms); if (pal_[i].b > pal_[i].g) pal_[i].b--;
if (sprites_scene_->done()) { if (pal_[i].r < pal_[i].g) pal_[i].r++;
// Equivalent al vell `Go()` post-switch: passem al menú. if (pal_[i].r > pal_[i].g) pal_[i].r--;
// 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; void IntroScene::tick(int delta_ms) {
} // Qualsevol tecla durant revelat/paleta salta TOTA la intro
break; // (inclou saltar la fase de sprites). Durant Sprites deixem que
// la sub-escena gestione el seu propi skip internament, que a més
case Phase::Done: // respecta la fase "final" no skippable de la variant 0.
break; if (phase_ != Phase::Sprites && phase_ != Phase::Done && JI_AnyKey()) {
info::ctx.num_piramide = 0;
phase_ = Phase::Done;
return;
}
switch (phase_) {
case Phase::InitialWait:
phase_acc_ms_ += delta_ms;
render();
if (phase_acc_ms_ >= INITIAL_MS) {
phase_ = Phase::Reveal;
phase_acc_ms_ = 0;
reveal_index_ = 0;
}
break;
case Phase::Reveal:
phase_acc_ms_ += delta_ms;
render();
if (phase_acc_ms_ >= REVEAL_STEPS[reveal_index_].duration_ms) {
phase_acc_ms_ = 0;
++reveal_index_;
if (reveal_index_ >= REVEAL_COUNT) {
phase_ = Phase::PaletteCycle;
}
}
break;
case Phase::PaletteCycle:
phase_acc_ms_ += delta_ms;
// Avancem tants passos com permet el delta, per evitar
// saltar-ne si el frame ha vingut lent.
while (phase_acc_ms_ >= PALETTE_CYCLE_STEP_MS &&
palette_step_ < PALETTE_CYCLE_STEPS) {
phase_acc_ms_ -= PALETTE_CYCLE_STEP_MS;
advancePaletteCycle();
++palette_step_;
}
render();
if (palette_step_ >= PALETTE_CYCLE_STEPS) {
phase_ = Phase::FinalWait;
phase_acc_ms_ = 0;
}
break;
case Phase::FinalWait:
phase_acc_ms_ += delta_ms;
render();
if (phase_acc_ms_ >= FINAL_WAIT_MS) {
phase_ = Phase::Sprites;
}
break;
case Phase::Sprites:
// Sub-escena construïda al vol al primer tick d'aquesta fase.
// Transferim el gfx_ per move — la sub-escena se n'ocupa
// fins que es destruix. Una vegada feta, els ticks delegats
// avancen l'animació dels sprites.
if (!sprites_scene_) {
sprites_scene_ = std::make_unique<IntroSpritesScene>(std::move(gfx_));
sprites_scene_->onEnter();
}
sprites_scene_->tick(delta_ms);
if (sprites_scene_->done()) {
// Equivalent al vell `Go()` post-switch: passem al menú.
// Sense açò el while del fiber tornaria a crear IntroScene
// infinitament amb num_piramide encara a 255.
info::ctx.num_piramide = 0;
phase_ = Phase::Done;
}
break;
case Phase::Done:
break;
}
} }
}
} // namespace scenes } // namespace scenes

View File

@@ -9,58 +9,58 @@
namespace scenes { namespace scenes {
// Intro "legacy" del wordmark JAILGAMES lletra a lletra + cicle de paleta. // Intro "legacy" del wordmark JAILGAMES lletra a lletra + cicle de paleta.
// Reemplaça `ModuleSequence::doIntro()`. S'activa quan // Reemplaça `ModuleSequence::doIntro()`. S'activa quan
// `Options::game.use_new_logo == false`; l'alternativa moderna és // `Options::game.use_new_logo == false`; l'alternativa moderna és
// `IntroNewLogoScene`. // `IntroNewLogoScene`.
// //
// Flux: // Flux:
// 1. Carrega gfx/logo.gif, arranca música "music/00000003.ogg", pantalla negra // 1. Carrega gfx/logo.gif, arranca música "music/00000003.ogg", pantalla negra
// 1000 ms. // 1000 ms.
// 2. Revelat: 15 passos (100 o 200 ms) que van acumulant les lletres // 2. Revelat: 15 passos (100 o 200 ms) que van acumulant les lletres
// "JAILGAMES" d'esquerra a dreta amb un avió escombrant al final // "JAILGAMES" d'esquerra a dreta amb un avió escombrant al final
// de la paraula. Els passos 5, 11, 13 i 15 netegen la pantalla // de la paraula. Els passos 5, 11, 13 i 15 netegen la pantalla
// per generar els parpelleigs finals. // per generar els parpelleigs finals.
// 3. Cicle de paleta: 256 passos × 20 ms modificant els índexs 16..31. // 3. Cicle de paleta: 256 passos × 20 ms modificant els índexs 16..31.
// 4. Espera final 200 ms. // 4. Espera final 200 ms.
// 5. Transfereix el gfx_ a una `IntroSpritesScene` com a sub-escena // 5. Transfereix el gfx_ a una `IntroSpritesScene` com a sub-escena
// i li delega els ticks fins que acaba (anima el prota + momia + // i li delega els ticks fins que acaba (anima el prota + momia +
// mapa, amb 3 variants aleatòries). En acabar, setzea num_piramide // mapa, amb 3 variants aleatòries). En acabar, setzea num_piramide
// = 0 per passar al menú. // = 0 per passar al menú.
// //
// Registrada al SceneRegistry amb state_key = 255: la mateixa factory que // Registrada al SceneRegistry amb state_key = 255: la mateixa factory que
// per a IntroNewLogoScene, però retornada quan `use_new_logo == false`. // per a IntroNewLogoScene, però retornada quan `use_new_logo == false`.
class IntroScene : public Scene { class IntroScene : public Scene {
public: public:
IntroScene(); IntroScene();
~IntroScene() override; ~IntroScene() override;
void onEnter() override; void onEnter() override;
void tick(int delta_ms) override; void tick(int delta_ms) override;
bool done() const override { return phase_ == Phase::Done; } bool done() const override { return phase_ == Phase::Done; }
int nextState() const override { return 1; } int nextState() const override { return 1; }
private: private:
enum class Phase { enum class Phase {
InitialWait, // 1000 ms pantalla negra InitialWait, // 1000 ms pantalla negra
Reveal, // 15 passos del wordmark Reveal, // 15 passos del wordmark
PaletteCycle, // 256 × 20 ms mutant pal[16..31] PaletteCycle, // 256 × 20 ms mutant pal[16..31]
FinalWait, // 200 ms abans de la sub-escena de sprites FinalWait, // 200 ms abans de la sub-escena de sprites
Sprites, // tick delegat a IntroSpritesScene fins que acaba Sprites, // tick delegat a IntroSpritesScene fins que acaba
Done, Done,
};
void render();
void advancePaletteCycle();
SurfaceHandle gfx_;
JD8_Palette pal_{nullptr}; // propietat transferida a main_palette via SetScreenPalette
std::unique_ptr<IntroSpritesScene> sprites_scene_;
Phase phase_{Phase::InitialWait};
int phase_acc_ms_{0};
int reveal_index_{0};
int palette_step_{0};
}; };
void render();
void advancePaletteCycle();
SurfaceHandle gfx_;
JD8_Palette pal_{nullptr}; // propietat transferida a main_palette via SetScreenPalette
std::unique_ptr<IntroSpritesScene> sprites_scene_;
Phase phase_{Phase::InitialWait};
int phase_acc_ms_{0};
int reveal_index_{0};
int palette_step_{0};
};
} // namespace scenes } // namespace scenes

View File

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

View File

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

View File

@@ -8,106 +8,106 @@
namespace scenes { namespace scenes {
void MenuScene::onEnter() { void MenuScene::onEnter() {
fondo_ = SurfaceHandle("gfx/menu.gif"); fondo_ = SurfaceHandle("gfx/menu.gif");
gfx_ = SurfaceHandle("gfx/menu2.gif"); gfx_ = SurfaceHandle("gfx/menu2.gif");
// Pintat inicial (congelat durant el fade-in de paleta). El loop // Pintat inicial (congelat durant el fade-in de paleta). El loop
// d'animació repintarà tot des de zero en el primer tick de Showing. // d'animació repintarà tot des de zero en el primer tick de Showing.
JD8_Blit(fondo_); JD8_Blit(fondo_);
JD8_BlitCK(100, 25, gfx_, 0, 74, 124, 68, 255); // logo 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(130, 100, gfx_, 0, 0, 80, 74, 255); // camell (frame 0)
JD8_BlitCK(0, 150, gfx_, 0, 150, 320, 50, 255); // base "jdes" JD8_BlitCK(0, 150, gfx_, 0, 150, 320, 50, 255); // base "jdes"
JD8_Palette pal = JD8_LoadPalette("gfx/menu2.gif"); JD8_Palette pal = JD8_LoadPalette("gfx/menu2.gif");
fade_.startFadeTo(pal); fade_.startFadeTo(pal);
std::free(pal); std::free(pal);
phase_ = Phase::FadingIn; phase_ = Phase::FadingIn;
} }
void MenuScene::render() { void MenuScene::render() {
// Cel estàtic (els primers 100 pixels verticals) // Cel estàtic (els primers 100 pixels verticals)
JD8_Blit(0, 0, fondo_, 0, 0, 320, 100); JD8_Blit(0, 0, fondo_, 0, 0, 320, 100);
// Fondo mòvil (horitzó) amb wrap a 320 // Fondo mòvil (horitzó) amb wrap a 320
JD8_BlitCK(horitzo_, 100, fondo_, 0, 100, 320 - horitzo_, 100, 255); JD8_BlitCK(horitzo_, 100, fondo_, 0, 100, 320 - horitzo_, 100, 255);
JD8_BlitCK(0, 100, fondo_, 320 - horitzo_, 100, horitzo_, 100, 255); JD8_BlitCK(0, 100, fondo_, 320 - horitzo_, 100, horitzo_, 100, 255);
// Logo i camell animat // Logo i camell animat
JD8_BlitCK(100, 25, gfx_, 0, 74, 124, 68, 255); JD8_BlitCK(100, 25, gfx_, 0, 74, 124, 68, 255);
JD8_BlitCK(130, 100, gfx_, camello_.frame() * 80, 0, 80, 74, 255); JD8_BlitCK(130, 100, gfx_, camello_.frame() * 80, 0, 80, 74, 255);
// Palmeres mòvils amb wrap a 320 // Palmeres mòvils amb wrap a 320
JD8_BlitCK(palmeres_, 150, gfx_, 0, 150, 320 - palmeres_, 50, 255); JD8_BlitCK(palmeres_, 150, gfx_, 0, 150, 320 - palmeres_, 50, 255);
JD8_BlitCK(0, 150, gfx_, 320 - palmeres_, 150, 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 // "jdes" estàtic (davant dels scrollers) i versió a la cantonada
JD8_BlitCK(87, 167, gfx_, 127, 124, 150, 24, 255); JD8_BlitCK(87, 167, gfx_, 127, 124, 150, 24, 255);
JD8_BlitCK(303, 193, gfx_, 305, 143, 15, 5, 255); JD8_BlitCK(303, 193, gfx_, 305, 143, 15, 5, 255);
// "Polsa tecla" parpallejant. Al vell `contador % 100 > 30` amb // "Polsa tecla" parpallejant. Al vell `contador % 100 > 30` amb
// updateTicks=20 ms, el cicle són 2000 ms amb un llindar de 600 ms: // 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. // amagat els primers 600 ms, visible els següents 1400 ms.
const bool blink_on = (blink_ms_ % 2000) > 600; const bool blink_on = (blink_ms_ % 2000) > 600;
if (blink_on) { if (blink_on) {
JD8_BlitCK(98, 130, gfx_, 161, 92, 127, 9, 255); JD8_BlitCK(98, 130, gfx_, 161, 92, 127, 9, 255);
if (info::ctx.nou_personatge) { if (info::ctx.nou_personatge) {
JD8_BlitCK(68, 141, gfx_, 128, 105, 189, 9, 255); JD8_BlitCK(68, 141, gfx_, 128, 105, 189, 9, 255);
}
} }
} }
}
void MenuScene::tick(int delta_ms) { void MenuScene::tick(int delta_ms) {
switch (phase_) { switch (phase_) {
case Phase::FadingIn: case Phase::FadingIn:
fade_.tick(delta_ms); fade_.tick(delta_ms);
if (fade_.done()) phase_ = Phase::Showing; if (fade_.done()) phase_ = Phase::Showing;
break; break;
case Phase::Showing: { case Phase::Showing: {
// Palmeres: 1 pixel cada 80 ms (= cada 4 ticks × 20 ms originals) // Palmeres: 1 pixel cada 80 ms (= cada 4 ticks × 20 ms originals)
palmeres_acc_ms_ += delta_ms; palmeres_acc_ms_ += delta_ms;
while (palmeres_acc_ms_ >= 80) { while (palmeres_acc_ms_ >= 80) {
palmeres_acc_ms_ -= 80; palmeres_acc_ms_ -= 80;
if (--palmeres_ < 0) palmeres_ = 319; 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) case Phase::FadingOut:
horitzo_acc_ms_ += delta_ms; fade_.tick(delta_ms);
while (horitzo_acc_ms_ >= 320) { if (fade_.done()) phase_ = Phase::Done;
horitzo_acc_ms_ -= 320; break;
if (--horitzo_ < 0) horitzo_ = 319;
}
camello_.tick(delta_ms); case Phase::Done:
break;
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::FadingOut:
fade_.tick(delta_ms);
if (fade_.done()) phase_ = Phase::Done;
break;
case Phase::Done:
break;
} }
}
} // namespace scenes } // namespace scenes

View File

@@ -7,51 +7,51 @@
namespace scenes { namespace scenes {
// Menú del títol. Reemplaça `ModuleSequence::doMenu()`. // Menú del títol. Reemplaça `ModuleSequence::doMenu()`.
// //
// Flux: // Flux:
// 1. Carrega gfx/menu.gif (fondo) i gfx/menu2.gif (sprites) + paleta. // 1. Carrega gfx/menu.gif (fondo) i gfx/menu2.gif (sprites) + paleta.
// 2. Pintat inicial estàtic (fondo, logo, camell frame 0, base "jdes"), // 2. Pintat inicial estàtic (fondo, logo, camell frame 0, base "jdes"),
// fade-in de paleta. // fade-in de paleta.
// 3. Loop d'animació: escroll parallax de horitzó (cada 320 ms) i // 3. Loop d'animació: escroll parallax de horitzó (cada 320 ms) i
// palmeres (cada 80 ms), cicle del camell (4 frames × 160 ms), // palmeres (cada 80 ms), cicle del camell (4 frames × 160 ms),
// i el text "polsa tecla" parpallejant cada 2 s (visible 1.4 s, // i el text "polsa tecla" parpallejant cada 2 s (visible 1.4 s,
// amagat 0.6 s, igual que el `contador % 100 > 30` original). // amagat 0.6 s, igual que el `contador % 100 > 30` original).
// 4. Quan l'usuari polsa qualsevol tecla — o 'P' per a activar Pepe — // 4. Quan l'usuari polsa qualsevol tecla — o 'P' per a activar Pepe —
// llegim `info::ctx.pepe_activat`, disparem fade-out i marquem // llegim `info::ctx.pepe_activat`, disparem fade-out i marquem
// num_piramide=1 (vas a doSlides). // num_piramide=1 (vas a doSlides).
// //
// Registrat al SceneRegistry amb state_key = 0. // Registrat al SceneRegistry amb state_key = 0.
class MenuScene : public Scene { class MenuScene : public Scene {
public: public:
void onEnter() override; void onEnter() override;
void tick(int delta_ms) override; void tick(int delta_ms) override;
bool done() const override { return phase_ == Phase::Done; } bool done() const override { return phase_ == Phase::Done; }
int nextState() const override { return 1; } int nextState() const override { return 1; }
private: private:
enum class Phase { FadingIn, enum class Phase { FadingIn,
Showing, Showing,
FadingOut, FadingOut,
Done }; Done };
void render(); void render();
SurfaceHandle fondo_; SurfaceHandle fondo_;
SurfaceHandle gfx_; SurfaceHandle gfx_;
PaletteFade fade_; PaletteFade fade_;
FrameAnimator camello_{4, 160, true}; FrameAnimator camello_{4, 160, true};
Phase phase_{Phase::FadingIn}; Phase phase_{Phase::FadingIn};
// Scrollers horizontals. Mouen 1 pixel per pas. // Scrollers horizontals. Mouen 1 pixel per pas.
int palmeres_{0}; int palmeres_{0};
int horitzo_{0}; int horitzo_{0};
int palmeres_acc_ms_{0}; int palmeres_acc_ms_{0};
int horitzo_acc_ms_{0}; int horitzo_acc_ms_{0};
// Acumulador per al parpalleig del text "polsa tecla". // Acumulador per al parpalleig del text "polsa tecla".
int blink_ms_{0}; int blink_ms_{0};
}; };
} // namespace scenes } // namespace scenes

View File

@@ -9,56 +9,56 @@
namespace scenes { namespace scenes {
void MortScene::onEnter() { void MortScene::onEnter() {
playMusic("music/00000001.ogg"); playMusic("music/00000001.ogg");
JI_DisableKeyboard(60); JI_DisableKeyboard(60);
info::ctx.vida = 5; info::ctx.vida = 5;
gfx_ = SurfaceHandle("gfx/gameover.gif"); gfx_ = SurfaceHandle("gfx/gameover.gif");
JD8_ClearScreen(0); JD8_ClearScreen(0);
JD8_Blit(gfx_); JD8_Blit(gfx_);
// PaletteFade en fa una còpia interna via memcpy, així que alliberem // PaletteFade en fa una còpia interna via memcpy, així que alliberem
// la paleta temporal immediatament. // la paleta temporal immediatament.
JD8_Palette pal = JD8_LoadPalette("gfx/gameover.gif"); JD8_Palette pal = JD8_LoadPalette("gfx/gameover.gif");
fade_.startFadeTo(pal); fade_.startFadeTo(pal);
std::free(pal); std::free(pal);
phase_ = Phase::FadingIn; phase_ = Phase::FadingIn;
remaining_ms_ = 10000; remaining_ms_ = 10000;
} }
void MortScene::tick(int delta_ms) { void MortScene::tick(int delta_ms) {
switch (phase_) { switch (phase_) {
case Phase::FadingIn: case Phase::FadingIn:
fade_.tick(delta_ms); fade_.tick(delta_ms);
if (fade_.done()) phase_ = Phase::Showing; if (fade_.done()) phase_ = Phase::Showing;
break; break;
case Phase::Showing: case Phase::Showing:
if (JI_AnyKey()) { if (JI_AnyKey()) {
remaining_ms_ = 0; remaining_ms_ = 0;
} else { } else {
remaining_ms_ -= delta_ms; remaining_ms_ -= delta_ms;
} }
if (remaining_ms_ <= 0) { if (remaining_ms_ <= 0) {
// Arrenca música del següent mòdul abans del fade out, // Arrenca música del següent mòdul abans del fade out,
// igual que la versió vella feia al final de doMort(). // igual que la versió vella feia al final de doMort().
playMusic("music/00000003.ogg"); playMusic("music/00000003.ogg");
info::ctx.num_piramide = 0; info::ctx.num_piramide = 0;
fade_.startFadeOut(); fade_.startFadeOut();
phase_ = Phase::FadingOut; phase_ = Phase::FadingOut;
} }
break; break;
case Phase::FadingOut: case Phase::FadingOut:
fade_.tick(delta_ms); fade_.tick(delta_ms);
if (fade_.done()) phase_ = Phase::Done; if (fade_.done()) phase_ = Phase::Done;
break; break;
case Phase::Done: case Phase::Done:
break; break;
}
} }
}
} // namespace scenes } // namespace scenes

View File

@@ -6,31 +6,31 @@
namespace scenes { namespace scenes {
// Pantalla de "game over". Reemplaça `ModuleSequence::doMort()`. // Pantalla de "game over". Reemplaça `ModuleSequence::doMort()`.
// //
// Flux: // Flux:
// 1. Carrega gfx/gameover.gif, arranca música "music/00000001.ogg", fade-in de paleta. // 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. // 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. // 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 // 4. Marca num_piramide=0 i retorna nextState=1 perquè el Director
// passe a l'escena del menú. // passe a l'escena del menú.
class MortScene : public Scene { class MortScene : public Scene {
public: public:
void onEnter() override; void onEnter() override;
void tick(int delta_ms) override; void tick(int delta_ms) override;
bool done() const override { return phase_ == Phase::Done; } bool done() const override { return phase_ == Phase::Done; }
int nextState() const override { return 1; } int nextState() const override { return 1; }
private: private:
enum class Phase { FadingIn, enum class Phase { FadingIn,
Showing, Showing,
FadingOut, FadingOut,
Done }; Done };
SurfaceHandle gfx_; SurfaceHandle gfx_;
PaletteFade fade_; PaletteFade fade_;
Phase phase_{Phase::FadingIn}; Phase phase_{Phase::FadingIn};
int remaining_ms_{10000}; // 1000 ticks × 10 ms/tick del doMort original int remaining_ms_{10000}; // 1000 ticks × 10 ms/tick del doMort original
}; };
} // namespace scenes } // namespace scenes

View File

@@ -2,27 +2,27 @@
namespace scenes { namespace scenes {
void PaletteFade::startFadeOut() { void PaletteFade::startFadeOut() {
JD8_FadeStartOut(); JD8_FadeStartOut();
active_ = true; active_ = true;
} }
void PaletteFade::startFadeTo(JD8_Palette target) { void PaletteFade::startFadeTo(JD8_Palette target) {
JD8_FadeStartToPal(target); JD8_FadeStartToPal(target);
active_ = true; active_ = true;
} }
void PaletteFade::tick(int /*delta_ms*/) { void PaletteFade::tick(int /*delta_ms*/) {
if (!active_) return; if (!active_) return;
// El fade té 32 passos interns. Amb un tick per frame (~16ms) // El fade té 32 passos interns. Amb un tick per frame (~16ms)
// dura ~512ms — el mateix temps que la versió bloquejant original. // dura ~512ms — el mateix temps que la versió bloquejant original.
// Si en el futur volem fer-lo genuinament time-based (p.ex. "fade // Si en el futur volem fer-lo genuinament time-based (p.ex. "fade
// de 500ms exactes independent del framerate") podem convertir la // de 500ms exactes independent del framerate") podem convertir la
// màquina d'estats de jdraw8 a time-based ací sense tocar cap altre // màquina d'estats de jdraw8 a time-based ací sense tocar cap altre
// call site. // call site.
if (JD8_FadeTickStep()) { if (JD8_FadeTickStep()) {
active_ = false; active_ = false;
}
} }
}
} // namespace scenes } // namespace scenes

View File

@@ -4,27 +4,27 @@
namespace scenes { namespace scenes {
// Embolcall fi damunt de la màquina d'estats de fade de jdraw8 // Embolcall fi damunt de la màquina d'estats de fade de jdraw8
// (`JD8_FadeStart*` / `JD8_FadeTickStep`). Exposa una API time-based // (`JD8_FadeStart*` / `JD8_FadeTickStep`). Exposa una API time-based
// però internament avança un pas del fade per cada crida a `tick()`. // 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 // La raó de tindre-ho com a classe a banda: que una escena no puga
// cridar accidentalment a `JD8_FadeOut`/`JD8_FadeToPal` (els shims // cridar accidentalment a `JD8_FadeOut`/`JD8_FadeToPal` (els shims
// bloquejants vells) i que el `done()` siga consultable com la resta // bloquejants vells) i que el `done()` siga consultable com la resta
// dels helpers. // dels helpers.
class PaletteFade { class PaletteFade {
public: public:
PaletteFade() = default; PaletteFade() = default;
void startFadeOut(); void startFadeOut();
void startFadeTo(JD8_Palette target); void startFadeTo(JD8_Palette target);
void tick(int delta_ms); void tick(int delta_ms);
bool active() const { return active_; } bool active() const { return active_; }
bool done() const { return !active_; } bool done() const { return !active_; }
private: private:
bool active_{false}; bool active_{false};
}; };
} // namespace scenes } // namespace scenes

View File

@@ -16,22 +16,22 @@
namespace scenes { namespace scenes {
class Scene { class Scene {
public: public:
virtual ~Scene() = default; 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 // Valor retornat al caller quan l'escena acaba — equivalent al int
// que retornaven les velles funcions `Go()` de ModuleSequence: // que retornaven les velles funcions `Go()` de ModuleSequence:
// 1 = continuar amb la següent escena segons info::ctx // 1 = continuar amb la següent escena segons info::ctx
// 0 = entrar al gameplay (ModuleGame) // 0 = entrar al gameplay (ModuleGame)
// -1 = eixir del joc // -1 = eixir del joc
virtual int nextState() const { return 1; } virtual int nextState() const { return 1; }
}; };
} // namespace scenes } // namespace scenes

View File

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

View File

@@ -8,30 +8,30 @@
namespace scenes { namespace scenes {
// Mapa de `state_key` (actualment = `info::ctx.num_piramide`) a factory // Mapa de `state_key` (actualment = `info::ctx.num_piramide`) a factory
// d'escena. Permet que el dispatch de `gameFiberEntry` provi primer una // d'escena. Permet que el dispatch de `gameFiberEntry` provi primer una
// Scene nova i caiga al vell `ModuleSequence::Go()` si encara no està // Scene nova i caiga al vell `ModuleSequence::Go()` si encara no està
// migrada. // migrada.
// //
// Registre inicial: `Director::init()` cridarà `instance()` i afegirà // Registre inicial: `Director::init()` cridarà `instance()` i afegirà
// una entrada per cada escena ja portada. A mesura que vagen caient, les // una entrada per cada escena ja portada. A mesura que vagen caient, les
// línies del registre creixen i les funcions `doX()` del modulesequence // línies del registre creixen i les funcions `doX()` del modulesequence
// desapareixen. // desapareixen.
class SceneRegistry { class SceneRegistry {
public: public:
using Factory = std::function<std::unique_ptr<Scene>()>; using Factory = std::function<std::unique_ptr<Scene>()>;
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 // Retorna `nullptr` si no hi ha cap escena registrada per a aquest
// state. El caller hauria de caure al path legacy en aquest cas. // state. El caller hauria de caure al path legacy en aquest cas.
std::unique_ptr<Scene> tryCreate(int state_key) const; std::unique_ptr<Scene> tryCreate(int state_key) const;
private: private:
SceneRegistry() = default; SceneRegistry() = default;
std::unordered_map<int, Factory> factories_; std::unordered_map<int, Factory> factories_;
}; };
} // namespace scenes } // namespace scenes

View File

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

View File

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

View File

@@ -12,194 +12,194 @@
namespace { 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: // Durades per fase, derivades dels contador-thresholds del vell:
// tomba1 scroll: 127 passos (contador 1→128) × 20ms // tomba1 scroll: 127 passos (contador 1→128) × 20ms
// tomba1 hold: 128 passos (contador 128→0) × 20ms // tomba1 hold: 128 passos (contador 128→0) × 20ms
// tomba2 scroll: 94 passos × 20ms // tomba2 scroll: 94 passos × 20ms
// tomba2 hold: 94 passos × 20ms // tomba2 hold: 94 passos × 20ms
// reveal horit: 80 passos × 20ms // reveal horit: 80 passos × 20ms
// reveal hold: 80 passos × 20ms // reveal hold: 80 passos × 20ms
// red pulse: 51 passos × 20ms // red pulse: 51 passos × 20ms
// red pulse hold: 51 passos × 20ms // red pulse hold: 51 passos × 20ms
constexpr int TOMBA1_SCROLL_MS = 127 * TICK_MS; constexpr int TOMBA1_SCROLL_MS = 127 * TICK_MS;
constexpr int TOMBA1_HOLD_MS = 128 * TICK_MS; constexpr int TOMBA1_HOLD_MS = 128 * TICK_MS;
constexpr int TOMBA2_SCROLL_MS = 94 * TICK_MS; constexpr int TOMBA2_SCROLL_MS = 94 * TICK_MS;
constexpr int TOMBA2_HOLD_MS = 94 * TICK_MS; constexpr int TOMBA2_HOLD_MS = 94 * TICK_MS;
constexpr int TOMBA2_REVEAL_MS = 80 * TICK_MS; constexpr int TOMBA2_REVEAL_MS = 80 * TICK_MS;
constexpr int TOMBA2_REVEAL_HOLD_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_MS = 51 * TICK_MS;
constexpr int RED_PULSE_HOLD_MS = 51 * TICK_MS; constexpr int RED_PULSE_HOLD_MS = 51 * TICK_MS;
} // namespace } // namespace
namespace scenes { namespace scenes {
SecretaScene::~SecretaScene() { SecretaScene::~SecretaScene() {
if (pal_aux_) std::free(pal_aux_); if (pal_aux_) std::free(pal_aux_);
// pal_active_ NO s'allibera: propietat de main_palette via SetScreenPalette. // 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<JD8_Palette>(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();
} }
switch (phase_) { void SecretaScene::onEnter() {
case Phase::InitialFadeOut: playMusic("music/00000002.ogg");
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: { // Fade-out de la paleta anterior. Els assets es carreguen ja
phase_acc_ms_ += delta_ms; // però no fem SetScreenPalette fins que acabe el fade — així
const int contador = std::min(128, phase_acc_ms_ / TICK_MS + 1); // el fade opera sobre la paleta del mòdul anterior.
// Dos blits solapats: el primer avança a velocitat completa, fade_.startFadeOut();
// 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: gfx_ = SurfaceHandle("gfx/tomba1.gif");
phase_acc_ms_ += delta_ms; pal_aux_ = JD8_LoadPalette("gfx/tomba1.gif");
if (phase_acc_ms_ >= TOMBA1_HOLD_MS) { pal_active_ = static_cast<JD8_Palette>(std::malloc(768));
swapToTomba2(); std::memcpy(pal_active_, pal_aux_, 768);
phase_ = Phase::Tomba2ScrollIn;
phase_acc_ms_ = 0;
}
break;
case Phase::Tomba2ScrollIn: { phase_ = Phase::InitialFadeOut;
phase_acc_ms_ += delta_ms; phase_acc_ms_ = 0;
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) { void SecretaScene::swapToTomba2() {
phase_ = Phase::Tomba2Hold; JD8_ClearScreen(255);
phase_acc_ms_ = 0; gfx_.reset("gfx/tomba2.gif");
}
break; std::free(pal_aux_);
} pal_aux_ = JD8_LoadPalette("gfx/tomba2.gif");
// pal_active_ continua sent el mateix buffer: només actualitzem
case Phase::Tomba2Hold: // el seu contingut. main_palette ja apunta ací.
phase_acc_ms_ += delta_ms; std::memcpy(pal_active_, pal_aux_, 768);
if (phase_acc_ms_ >= TOMBA2_HOLD_MS) { }
beginRedPulseSetup();
phase_ = Phase::Tomba2Reveal; void SecretaScene::beginRedPulseSetup() {
phase_acc_ms_ = 0; JD8_ClearScreen(0);
} JD8_SetPaletteColor(254, 12, 11, 11);
break; JD8_SetPaletteColor(253, 12, 11, 11);
}
case Phase::Tomba2Reveal: {
phase_acc_ms_ += delta_ms; void SecretaScene::beginFinalFade() {
const int contador = std::min(80, phase_acc_ms_ / TICK_MS + 1); JA_FadeOutMusic(250);
// Revelat horitzontal simètric: l'amplada creix 2px per tick fade_.startFadeOut();
// i el src_x es desplaça a l'esquerra el mateix. phase_ = Phase::FinalFadeOut;
JD8_Blit(80, 68, gfx_, 160 - (contador * 2), 0, contador * 2, 64); }
if (phase_acc_ms_ >= TOMBA2_REVEAL_MS) {
phase_ = Phase::Tomba2RevealHold; void SecretaScene::tick(int delta_ms) {
phase_acc_ms_ = 0; // Skip per tecla (després del fade inicial, no mentre). Salta
} // directament al FinalFadeOut. Mateix patró que el vell, on
break; // qualsevol tecla sortia del loop.
} if (!skip_triggered_ && phase_ != Phase::InitialFadeOut && JI_AnyKey()) {
skip_triggered_ = true;
case Phase::Tomba2RevealHold: beginFinalFade();
phase_acc_ms_ += delta_ms; }
if (phase_acc_ms_ >= TOMBA2_REVEAL_HOLD_MS) {
phase_ = Phase::RedPulse; switch (phase_) {
phase_acc_ms_ = 0; case Phase::InitialFadeOut:
} fade_.tick(delta_ms);
break; if (fade_.done()) {
// Ara main_palette (la vella) té tots els canals a 0.
case Phase::RedPulse: { // SetScreenPalette allibera la vella i adopta pal_active_
phase_acc_ms_ += delta_ms; // — des d'ara main_palette == pal_active_, així que les
const int contador = std::min(51, phase_acc_ms_ / TICK_MS); // futures escriptures a pal_active_ afecten la pantalla.
// Anima el canal R dels índexs 254 i 253 (aquest a la meitat JD8_SetScreenPalette(pal_active_);
// de brillantor). Va de (12,11,11) fins a (63,11,11) / (31,11,11). JD8_ClearScreen(255);
JD8_SetPaletteColor(254, contador + 12, 11, 11); phase_ = Phase::Tomba1ScrollIn;
JD8_SetPaletteColor(253, (contador + 12) >> 1, 11, 11); phase_acc_ms_ = 0;
if (phase_acc_ms_ >= RED_PULSE_MS) { }
phase_ = Phase::RedPulseHold; break;
phase_acc_ms_ = 0;
} case Phase::Tomba1ScrollIn: {
break; 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,
case Phase::RedPulseHold: // el segon (contingut de la dreta del src) a meitat (contador>>1).
phase_acc_ms_ += delta_ms; JD8_Blit(70, 60, gfx_, 0, contador, 178, 70);
if (phase_acc_ms_ >= RED_PULSE_HOLD_MS) { JD8_BlitCK(70, 60, gfx_, 178, contador >> 1, 142, 70, 255);
beginFinalFade(); if (phase_acc_ms_ >= TOMBA1_SCROLL_MS) {
} phase_ = Phase::Tomba1Hold;
break; phase_acc_ms_ = 0;
}
case Phase::FinalFadeOut: break;
fade_.tick(delta_ms); }
if (fade_.done()) {
phase_ = Phase::Done; case Phase::Tomba1Hold:
} phase_acc_ms_ += delta_ms;
break; if (phase_acc_ms_ >= TOMBA1_HOLD_MS) {
swapToTomba2();
case Phase::Done: phase_ = Phase::Tomba2ScrollIn;
break; 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 } // namespace scenes

View File

@@ -7,60 +7,60 @@
namespace scenes { namespace scenes {
// Pre-Secreta. Reemplaça `ModuleSequence::doSecreta()`. // Pre-Secreta. Reemplaça `ModuleSequence::doSecreta()`.
// //
// Flux: // Flux:
// 1. Arranca música "music/00000002.ogg" i fa fade-out de la paleta anterior. // 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 // 2. Carrega gfx/tomba1.gif + paleta i pinta un scroll vertical doble
// (dos blits solapats, un a velocitat meitat que l'altre) durant // (dos blits solapats, un a velocitat meitat que l'altre) durant
// ~2.5 s + ~2.5 s de pausa. // ~2.5 s + ~2.5 s de pausa.
// 3. Swap a gfx/tomba2.gif + reset de paleta, scroll vertical del segon // 3. Swap a gfx/tomba2.gif + reset de paleta, scroll vertical del segon
// asset (~1.9 s + ~1.9 s de pausa). // asset (~1.9 s + ~1.9 s de pausa).
// 4. ClearScreen a 0, set colors 253/254 a vermell fosc (12,11,11) // 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). // 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 // 5. "Red pulse": anima els colors 253/254 incrementant el canal R
// de 12 a 62 durant ~1 s (+ ~1 s de pausa). // de 12 a 62 durant ~1 s (+ ~1 s de pausa).
// 6. FadeOut + JA_FadeOutMusic(250). // 6. FadeOut + JA_FadeOutMusic(250).
// 7. Retorna nextState=0 per entrar al ModuleGame amb num_piramide=6. // 7. Retorna nextState=0 per entrar al ModuleGame amb num_piramide=6.
// //
// Registrada al SceneRegistry amb state_key = 6. // Registrada al SceneRegistry amb state_key = 6.
class SecretaScene : public Scene { class SecretaScene : public Scene {
public: public:
SecretaScene() = default; SecretaScene() = default;
~SecretaScene() override; ~SecretaScene() override;
void onEnter() override; void onEnter() override;
void tick(int delta_ms) override; void tick(int delta_ms) override;
bool done() const override { return phase_ == Phase::Done; } bool done() const override { return phase_ == Phase::Done; }
int nextState() const override { return 0; } int nextState() const override { return 0; }
private: private:
enum class Phase { enum class Phase {
InitialFadeOut, InitialFadeOut,
Tomba1ScrollIn, Tomba1ScrollIn,
Tomba1Hold, Tomba1Hold,
Tomba2ScrollIn, Tomba2ScrollIn,
Tomba2Hold, Tomba2Hold,
Tomba2Reveal, Tomba2Reveal,
Tomba2RevealHold, Tomba2RevealHold,
RedPulse, RedPulse,
RedPulseHold, RedPulseHold,
FinalFadeOut, FinalFadeOut,
Done, 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 } // namespace scenes

View File

@@ -13,174 +13,180 @@
namespace { namespace {
constexpr int SCROLL_MS = 1600; // 80 iters × 20 ms del vell doSlides 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 HOLD_MS = 4600; // 230 iters × 20 ms (80 + 150) del vell
constexpr int SLIDE_Y = 65; constexpr int SLIDE_Y = 65;
constexpr int SLIDE_H = 65; constexpr int SLIDE_H = 65;
constexpr int BG_COLOR_INDEX = 255; constexpr int BG_COLOR_INDEX = 255;
// Desplaçament inicial del slide segons direcció del wipe. // Desplaçament inicial del slide segons direcció del wipe.
// Slide 1 i 3: "scroll in from right" (pos_x va de 320 → 0). // 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 // 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 // estrany del doSlides vell on la imatge es desplaça des de l'esquerra
// però revela primer el lateral dret del src. // però revela primer el lateral dret del src.
constexpr int SLIDE_START_X[3] = {320, -320, 320}; constexpr int SLIDE_START_X[3] = {320, -320, 320};
} // namespace } // namespace
namespace scenes { namespace scenes {
SlidesScene::~SlidesScene() { SlidesScene::~SlidesScene() {
if (pal_aux_) std::free(pal_aux_); if (pal_aux_) std::free(pal_aux_);
// pal_active_ NO s'allibera: propietat de main_palette via SetScreenPalette. // 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";
} }
gfx_ = SurfaceHandle(arxiu); void SlidesScene::onEnter() {
pal_aux_ = JD8_LoadPalette(arxiu); num_piramide_at_start_ = info::ctx.num_piramide;
// Còpia editable de la paleta. `pal_active_` comparteix memòria amb const char* arxiu = nullptr;
// main_palette després del SetScreenPalette — modificar-la modifica if (num_piramide_at_start_ == 7) {
// main_palette directament. `pal_aux_` es manté intacte per a poder // loop=1 per replicar el vell `play_music("00000005.ogg", 1)`.
// restaurar després de cada fade-out intermedi. playMusic("music/00000005.ogg", 1);
pal_active_ = static_cast<JD8_Palette>(std::malloc(768)); arxiu = (info::ctx.diners < 200) ? "gfx/intro2.gif" : "gfx/intro3.gif";
std::memcpy(pal_active_, pal_aux_, 768); } else {
JD8_SetScreenPalette(pal_active_); arxiu = "gfx/intro.gif";
}
JD8_ClearScreen(BG_COLOR_INDEX); gfx_ = SurfaceHandle(arxiu);
pal_aux_ = JD8_LoadPalette(arxiu);
phase_ = Phase::Slide1Enter; // Còpia editable de la paleta. `pal_active_` comparteix memòria amb
phase_acc_ms_ = 0; // main_palette després del SetScreenPalette — modificar-la modifica
next_state_ = 0; // main_palette directament. `pal_aux_` es manté intacte per a poder
} // restaurar després de cada fade-out intermedi.
pal_active_ = static_cast<JD8_Palette>(std::malloc(768));
std::memcpy(pal_active_, pal_aux_, 768);
JD8_SetScreenPalette(pal_active_);
void SlidesScene::drawSlide(int slide_idx, int pos_x) { JD8_ClearScreen(BG_COLOR_INDEX);
const int src_y = slide_idx * SLIDE_H;
// Clipping manual: translada un rect de 320×65 des de (pos_x, SLIDE_Y) phase_ = Phase::Slide1Enter;
// a l'àrea visible (0..319, SLIDE_Y..SLIDE_Y+64). phase_acc_ms_ = 0;
int dst_x = pos_x; next_state_ = 0;
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) { void SlidesScene::drawSlide(int slide_idx, int pos_x) {
JD8_Blit(dst_x, SLIDE_Y, gfx_, src_x, src_y, w, SLIDE_H); 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() { void SlidesScene::restorePalette() {
std::memcpy(pal_active_, pal_aux_, 768); std::memcpy(pal_active_, pal_aux_, 768);
}
void SlidesScene::beginFinalFade() {
if (num_piramide_at_start_ != 7) {
JA_FadeOutMusic(250);
} }
fade_.startFadeOut();
phase_ = Phase::FadeFinal;
}
void SlidesScene::tick(int delta_ms) { void SlidesScene::beginFinalFade() {
// Skip: qualsevol tecla salta directament al fade final. Per fidelitat if (num_piramide_at_start_ != 7) {
// al vell doSlides, el skip NO atura la música explícitament — només JA_FadeOutMusic(250);
// 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(); fade_.startFadeOut();
phase_ = Phase::FadeFinal; phase_ = Phase::FadeFinal;
} }
switch (phase_) { void SlidesScene::tick(int delta_ms) {
case Phase::Slide1Enter: // Skip: qualsevol tecla salta directament al fade final. Per fidelitat
case Phase::Slide2Enter: // al vell doSlides, el skip NO atura la música explícitament — només
case Phase::Slide3Enter: { // el final natural crida JA_FadeOutMusic (beginFinalFade() distingeix).
phase_acc_ms_ += delta_ms; if (!skip_triggered_ && JI_AnyKey()) {
const int slide_idx = (phase_ == Phase::Slide1Enter ? 0 skip_triggered_ = true;
: phase_ == Phase::Slide2Enter ? 1 if (num_piramide_at_start_ != 7) JA_FadeOutMusic(250);
: 2); fade_.startFadeOut();
const float t = std::min(1.0f, static_cast<float>(phase_acc_ms_) / phase_ = Phase::FadeFinal;
static_cast<float>(SCROLL_MS));
const float eased = Easing::outCubic(t);
const int pos_x = Easing::lerpInt(SLIDE_START_X[slide_idx], 0, eased);
drawSlide(slide_idx, pos_x);
if (phase_acc_ms_ >= SCROLL_MS) {
// Garanteix posició final exacta (pos_x=0).
drawSlide(slide_idx, 0);
if (phase_ == Phase::Slide1Enter) phase_ = Phase::Slide1Hold;
else if (phase_ == Phase::Slide2Enter) phase_ = Phase::Slide2Hold;
else phase_ = Phase::Slide3Hold;
phase_acc_ms_ = 0;
}
break;
} }
case Phase::Slide1Hold: switch (phase_) {
case Phase::Slide2Hold: case Phase::Slide1Enter:
phase_acc_ms_ += delta_ms; case Phase::Slide2Enter:
if (phase_acc_ms_ >= HOLD_MS) { case Phase::Slide3Enter: {
fade_.startFadeOut(); phase_acc_ms_ += delta_ms;
if (phase_ == Phase::Slide1Hold) phase_ = Phase::FadeOut1; const int slide_idx = (phase_ == Phase::Slide1Enter ? 0
else phase_ = Phase::FadeOut2; : phase_ == Phase::Slide2Enter ? 1
phase_acc_ms_ = 0; : 2);
} const float t = std::min(1.0f, static_cast<float>(phase_acc_ms_) / static_cast<float>(SCROLL_MS));
break; 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: if (phase_acc_ms_ >= SCROLL_MS) {
phase_acc_ms_ += delta_ms; // Garanteix posició final exacta (pos_x=0).
if (phase_acc_ms_ >= HOLD_MS) { drawSlide(slide_idx, 0);
beginFinalFade(); if (phase_ == Phase::Slide1Enter)
} phase_ = Phase::Slide1Hold;
break; else if (phase_ == Phase::Slide2Enter)
phase_ = Phase::Slide2Hold;
case Phase::FadeOut1: else
case Phase::FadeOut2: phase_ = Phase::Slide3Hold;
fade_.tick(delta_ms); phase_acc_ms_ = 0;
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;
} }
break;
case Phase::Done: case Phase::Slide1Hold:
break; 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 } // namespace scenes

View File

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

View File

@@ -4,43 +4,43 @@
namespace scenes { namespace scenes {
void SpriteMover::moveTo(int x0, int y0, int x1, int y1, int duration_ms, EaseFn ease) { void SpriteMover::moveTo(int x0, int y0, int x1, int y1, int duration_ms, EaseFn ease) {
x0_ = x0; x0_ = x0;
y0_ = y0; y0_ = y0;
x1_ = x1; x1_ = x1;
y1_ = y1; y1_ = y1;
duration_ms_ = std::max(0, duration_ms); duration_ms_ = std::max(0, duration_ms);
elapsed_ms_ = 0; elapsed_ms_ = 0;
ease_ = ease ? ease : Easing::linear; ease_ = ease ? ease : Easing::linear;
cur_x_ = x0; cur_x_ = x0;
cur_y_ = y0; 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;
} }
elapsed_ms_ = std::min(elapsed_ms_ + delta_ms, duration_ms_);
const float t = static_cast<float>(elapsed_ms_) / static_cast<float>(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 { void SpriteMover::setPosition(int x, int y) {
if (duration_ms_ <= 0) return 1.0f; cur_x_ = x;
return static_cast<float>(elapsed_ms_) / static_cast<float>(duration_ms_); 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<float>(elapsed_ms_) / static_cast<float>(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<float>(elapsed_ms_) / static_cast<float>(duration_ms_);
}
} // namespace scenes } // namespace scenes

View File

@@ -4,36 +4,35 @@
namespace scenes { namespace scenes {
// Interpola una posició 2D entre dos punts durant un temps donat amb // 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() // 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/ // i fa el blit ell mateix. Pensat per a scrolls, sprites que entren/
// ixen per pantalla, i moviments d'objectes interpolats. // ixen per pantalla, i moviments d'objectes interpolats.
class SpriteMover { class SpriteMover {
public: public:
using EaseFn = float (*)(float); using EaseFn = float (*)(float);
SpriteMover() = default; SpriteMover() = default;
// Arrenca un moviment nou. Si ja n'hi havia un en curs, es descarta. // 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, void moveTo(int x0, int y0, int x1, int y1, int duration_ms, EaseFn ease = Easing::linear);
EaseFn ease = Easing::linear);
// Posicionament immediat (útil per a "teleportar" entre moviments). // Posicionament immediat (útil per a "teleportar" entre moviments).
void setPosition(int x, int y); void setPosition(int x, int y);
void tick(int delta_ms); void tick(int delta_ms);
int x() const { return cur_x_; } int x() const { return cur_x_; }
int y() const { return cur_y_; } int y() const { return cur_y_; }
bool done() const { return elapsed_ms_ >= duration_ms_; } bool done() const { return elapsed_ms_ >= duration_ms_; }
float progress() const; // 0..1 sense easing aplicat float progress() const; // 0..1 sense easing aplicat
private: private:
int x0_{0}, y0_{0}, x1_{0}, y1_{0}; int x0_{0}, y0_{0}, x1_{0}, y1_{0};
int duration_ms_{0}; int duration_ms_{0};
int elapsed_ms_{0}; int elapsed_ms_{0};
int cur_x_{0}, cur_y_{0}; int cur_x_{0}, cur_y_{0};
EaseFn ease_{Easing::linear}; EaseFn ease_{Easing::linear};
}; };
} // namespace scenes } // namespace scenes

View File

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

View File

@@ -4,46 +4,46 @@
namespace scenes { namespace scenes {
// Wrapper RAII damunt de `JD8_Surface`. Allibera automàticament amb // Wrapper RAII damunt de `JD8_Surface`. Allibera automàticament amb
// `JD8_FreeSurface` al destructor. Move-only per evitar dobles alliberaments. // `JD8_FreeSurface` al destructor. Move-only per evitar dobles alliberaments.
// Converteix implícitament a `JD8_Surface` per a poder passar-lo // Converteix implícitament a `JD8_Surface` per a poder passar-lo
// directament a `JD8_Blit*` sense haver de cridar `.get()`. // directament a `JD8_Blit*` sense haver de cridar `.get()`.
class SurfaceHandle { class SurfaceHandle {
public: public:
SurfaceHandle() = default; SurfaceHandle() = default;
explicit SurfaceHandle(const char* file); explicit SurfaceHandle(const char* file);
~SurfaceHandle(); ~SurfaceHandle();
SurfaceHandle(const SurfaceHandle&) = delete; SurfaceHandle(const SurfaceHandle&) = delete;
SurfaceHandle& operator=(const SurfaceHandle&) = delete; SurfaceHandle& operator=(const SurfaceHandle&) = delete;
SurfaceHandle(SurfaceHandle&& other) noexcept; SurfaceHandle(SurfaceHandle&& other) noexcept;
SurfaceHandle& operator=(SurfaceHandle&& other) noexcept; SurfaceHandle& operator=(SurfaceHandle&& other) noexcept;
// Allibera la surface actual (si n'hi ha) i carrega una nova. // Allibera la surface actual (si n'hi ha) i carrega una nova.
// Usat per escenes que recarreguen assets a mitja cinemàtica // Usat per escenes que recarreguen assets a mitja cinemàtica
// (p.ex. doSecreta que passa de tomba1 a tomba2). // (p.ex. doSecreta que passa de tomba1 a tomba2).
void reset(const char* file); void reset(const char* file);
// Adopta una surface ja creada (p.ex. amb JD8_NewSurface). Pren ownership // 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. // — la surface adoptada s'allibera al destructor o al següent reset/adopt.
void adopt(JD8_Surface raw); void adopt(JD8_Surface raw);
// Allibera ownership sense destruir la surface. Retorna el pointer cru; // 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 // 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 // altre propietari). Usat quan una escena delega a codi legacy que
// també allibera la mateixa surface — cal "soltar" el ownership per // també allibera la mateixa surface — cal "soltar" el ownership per
// evitar double free. // evitar double free.
[[nodiscard]] JD8_Surface release(); [[nodiscard]] JD8_Surface release();
// Conversió implícita per al confort d'ús: JD8_Blit(handle) // Conversió implícita per al confort d'ús: JD8_Blit(handle)
// en lloc de JD8_Blit(handle.get()). // en lloc de JD8_Blit(handle.get()).
operator JD8_Surface() const { return surface_; } operator JD8_Surface() const { return surface_; }
JD8_Surface get() const { return surface_; } JD8_Surface get() const { return surface_; }
bool valid() const { return surface_ != nullptr; } bool valid() const { return surface_ != nullptr; }
private: private:
JD8_Surface surface_{nullptr}; JD8_Surface surface_{nullptr};
}; };
} // namespace scenes } // namespace scenes

View File

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

View File

@@ -5,53 +5,53 @@
namespace scenes { namespace scenes {
// Timeline declaratiu de passos seqüencials. Cada pas té una duració en // Timeline declaratiu de passos seqüencials. Cada pas té una duració en
// ms i un callback. Exemple d'ús: // ms i un callback. Exemple d'ús:
// //
// timeline_ // timeline_
// .once([this] { JD8_ClearScreen(0); fade_.startFadeTo(pal); }) // .once([this] { JD8_ClearScreen(0); fade_.startFadeTo(pal); })
// .step(5000) // espera pura // .step(5000) // espera pura
// .step(1000, [this](float p) { /*...*/ }) // animat amb progress // .step(1000, [this](float p) { /*...*/ }) // animat amb progress
// .once([this] { JA_FadeOutMusic(250); }); // .once([this] { JA_FadeOutMusic(250); });
// //
// `tick(delta_ms)` avança el temps. Els passos one-shot s'executen al // `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ó // 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 // 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 // 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 // com a acabada (no executa res més) — útil per als "polsa una tecla
// per a saltar la cinemàtica". // per a saltar la cinemàtica".
class Timeline { class Timeline {
public: public:
using StepFn = std::function<void(float progress_0_1)>; using StepFn = std::function<void(float progress_0_1)>;
using OnceFn = std::function<void()>; using OnceFn = std::function<void()>;
Timeline& step(int duration_ms, StepFn fn = nullptr); Timeline& step(int duration_ms, StepFn fn = nullptr);
Timeline& once(OnceFn fn); Timeline& once(OnceFn fn);
void tick(int delta_ms); void tick(int delta_ms);
void skip(); void skip();
void reset(); void reset();
bool done() const; bool done() const;
int currentStepIndex() const { return static_cast<int>(current_); } int currentStepIndex() const { return static_cast<int>(current_); }
float currentProgress() const; float currentProgress() const;
private: private:
struct Step { struct Step {
int duration_ms{0}; // 0 = one-shot int duration_ms{0}; // 0 = one-shot
StepFn continuous; StepFn continuous;
OnceFn oneshot; OnceFn oneshot;
bool entered{false}; 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<Step> 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<Step> steps_;
std::size_t current_{0};
int elapsed_in_step_{0};
bool skipped_{false};
};
} // namespace scenes } // namespace scenes