clang-format

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,129 +11,136 @@
namespace {
// Frames del cotxe: 8 posicions dins del sprite sheet gfx/final.gif. El
// vell doCredits tenia aquesta taula inline — la reproduïm idèntica.
struct CocheFrame {
Uint16 x, y;
};
// Frames del cotxe: 8 posicions dins del sprite sheet gfx/final.gif. El
// vell doCredits tenia aquesta taula inline — la reproduïm idèntica.
struct CocheFrame {
Uint16 x, y;
};
constexpr CocheFrame COCHE_FRAMES[8] = {
{214, 152}, {214, 104}, {214, 56}, {214, 104}, {214, 152}, {214, 8}, {108, 152}, {214, 8},
};
constexpr CocheFrame COCHE_FRAMES[8] = {
{214, 152},
{214, 104},
{214, 56},
{214, 104},
{214, 152},
{214, 8},
{108, 152},
{214, 8},
};
constexpr int CONTADOR_MAX = 3100; // ~62 s de crèdits a 20 ms/tick
constexpr int TICK_MS = 20; // JG_SetUpdateTicks heretat del doSlides previ
constexpr int BG_INDEX = 255;
constexpr int CONTADOR_MAX = 3100; // ~62 s de crèdits a 20 ms/tick
constexpr int TICK_MS = 20; // JG_SetUpdateTicks heretat del doSlides previ
constexpr int BG_INDEX = 255;
} // namespace
namespace scenes {
CreditsScene::~CreditsScene() {
// No toquem la paleta activa: SetScreenPalette n'ha pres ownership.
}
void CreditsScene::onEnter() {
// El vell doCredits no tocava música — heretava la del doSlides
// previ ("music/00000005.ogg"). Si l'escena s'arrenca directament (test
// amb piramide_inicial=8) no hi ha res que heretar, així que
// arranquem la mateixa pista només si no sona res. Inocu en el
// flux normal: JA_MUSIC_PLAYING fa que no la tornem a tocar.
if (JA_GetMusicState() != JA_MUSIC_PLAYING) {
playMusic("music/00000005.ogg");
CreditsScene::~CreditsScene() {
// No toquem la paleta activa: SetScreenPalette n'ha pres ownership.
}
vaddr2_ = SurfaceHandle("gfx/final.gif");
vaddr3_ = SurfaceHandle("gfx/finals.gif");
JD8_Palette pal = JD8_LoadPalette("gfx/final.gif");
JD8_SetScreenPalette(pal);
// `pal` passa a ser propietat de main_palette — no l'alliberem.
phase_ = Phase::Rolling;
contador_ = 1;
contador_acc_ms_ = 0;
}
void CreditsScene::render() {
JD8_ClearScreen(BG_INDEX);
// Columna 1: scroll vertical del bloc (0,0,80,200) pujant des de
// y=200 fins que el contador supera 2750.
if (contador_ < 2750) {
JD8_BlitCKCut(115, 200 - (contador_ / 6), vaddr2_, 0, 0, 80, 200, 0);
}
// Columna 2: scroll vertical del bloc (85,0,120,140), arrenca
// a contador 1200 i s'atura (fix en y=20) a partir de 2250.
if ((contador_ > 1200) && (contador_ < 2280)) {
JD8_BlitCKCut(100, 200 - ((contador_ - 1200) / 6), vaddr2_, 85, 0, 120, 140, 0);
} else if (contador_ >= 2250) {
JD8_BlitCK(100, 20, vaddr2_, 85, 0, 120, 140, 0);
}
// Fons: 4 capes parallax + cotxe només si l'usuari ha aconseguit
// tots els diamants (final "bo"). Altrament fons estàtic.
if (info::ctx.diamants == 16) {
JD8_BlitCKScroll(50, vaddr3_, ((contador_ >> 3) % 320) + 1, 0, 50, 255);
JD8_BlitCKScroll(50, vaddr3_, ((contador_ >> 2) % 320) + 1, 50, 50, 255);
JD8_BlitCKScroll(50, vaddr3_, ((contador_ >> 1) % 320) + 1, 100, 50, 255);
JD8_BlitCKScroll(50, vaddr3_, (contador_ % 320) + 1, 150, 50, 255);
const CocheFrame& cf = COCHE_FRAMES[coche_.frame()];
JD8_BlitCK(100, 50, vaddr2_, cf.x, cf.y, 106, 48, 255);
} else {
JD8_BlitCK(0, 50, vaddr3_, 0, 0, 320, 50, 255);
JD8_BlitCK(0, 50, vaddr3_, 0, 50, 320, 50, 255);
}
// Barres de marc que cobreixen els extrems del scroll vertical.
JD8_FillSquare(0, 50, BG_INDEX);
JD8_FillSquare(100, 10, BG_INDEX);
}
void CreditsScene::writeTrickIni() {
FILE* ini = std::fopen("trick.ini", "wb");
if (ini) {
std::fwrite("1", 1, 1, ini);
std::fclose(ini);
}
info::ctx.nou_personatge = true;
}
void CreditsScene::tick(int delta_ms) {
switch (phase_) {
case Phase::Rolling: {
// Avancem el contador en passos discrets de 20 ms, igual
// que feia JG_ShouldUpdate(20) al vell doCredits.
contador_acc_ms_ += delta_ms;
while (contador_acc_ms_ >= TICK_MS) {
contador_acc_ms_ -= TICK_MS;
++contador_;
}
coche_.tick(delta_ms);
render();
if (JI_AnyKey() || contador_ >= CONTADOR_MAX) {
writeTrickIni();
fade_.startFadeOut();
phase_ = Phase::FadingOut;
}
break;
void CreditsScene::onEnter() {
// El vell doCredits no tocava música — heretava la del doSlides
// previ ("music/00000005.ogg"). Si l'escena s'arrenca directament (test
// amb piramide_inicial=8) no hi ha res que heretar, així que
// arranquem la mateixa pista només si no sona res. Inocu en el
// flux normal: JA_MUSIC_PLAYING fa que no la tornem a tocar.
if (JA_GetMusicState() != JA_MUSIC_PLAYING) {
playMusic("music/00000005.ogg");
}
case Phase::FadingOut:
fade_.tick(delta_ms);
if (fade_.done()) {
info::ctx.num_piramide = 255;
phase_ = Phase::Done;
}
break;
vaddr2_ = SurfaceHandle("gfx/final.gif");
vaddr3_ = SurfaceHandle("gfx/finals.gif");
case Phase::Done:
break;
JD8_Palette pal = JD8_LoadPalette("gfx/final.gif");
JD8_SetScreenPalette(pal);
// `pal` passa a ser propietat de main_palette — no l'alliberem.
phase_ = Phase::Rolling;
contador_ = 1;
contador_acc_ms_ = 0;
}
void CreditsScene::render() {
JD8_ClearScreen(BG_INDEX);
// Columna 1: scroll vertical del bloc (0,0,80,200) pujant des de
// y=200 fins que el contador supera 2750.
if (contador_ < 2750) {
JD8_BlitCKCut(115, 200 - (contador_ / 6), vaddr2_, 0, 0, 80, 200, 0);
}
// Columna 2: scroll vertical del bloc (85,0,120,140), arrenca
// a contador 1200 i s'atura (fix en y=20) a partir de 2250.
if ((contador_ > 1200) && (contador_ < 2280)) {
JD8_BlitCKCut(100, 200 - ((contador_ - 1200) / 6), vaddr2_, 85, 0, 120, 140, 0);
} else if (contador_ >= 2250) {
JD8_BlitCK(100, 20, vaddr2_, 85, 0, 120, 140, 0);
}
// Fons: 4 capes parallax + cotxe només si l'usuari ha aconseguit
// tots els diamants (final "bo"). Altrament fons estàtic.
if (info::ctx.diamants == 16) {
JD8_BlitCKScroll(50, vaddr3_, ((contador_ >> 3) % 320) + 1, 0, 50, 255);
JD8_BlitCKScroll(50, vaddr3_, ((contador_ >> 2) % 320) + 1, 50, 50, 255);
JD8_BlitCKScroll(50, vaddr3_, ((contador_ >> 1) % 320) + 1, 100, 50, 255);
JD8_BlitCKScroll(50, vaddr3_, (contador_ % 320) + 1, 150, 50, 255);
const CocheFrame& cf = COCHE_FRAMES[coche_.frame()];
JD8_BlitCK(100, 50, vaddr2_, cf.x, cf.y, 106, 48, 255);
} else {
JD8_BlitCK(0, 50, vaddr3_, 0, 0, 320, 50, 255);
JD8_BlitCK(0, 50, vaddr3_, 0, 50, 320, 50, 255);
}
// Barres de marc que cobreixen els extrems del scroll vertical.
JD8_FillSquare(0, 50, BG_INDEX);
JD8_FillSquare(100, 10, BG_INDEX);
}
void CreditsScene::writeTrickIni() {
FILE* ini = std::fopen("trick.ini", "wb");
if (ini) {
std::fwrite("1", 1, 1, ini);
std::fclose(ini);
}
info::ctx.nou_personatge = true;
}
void CreditsScene::tick(int delta_ms) {
switch (phase_) {
case Phase::Rolling: {
// Avancem el contador en passos discrets de 20 ms, igual
// que feia JG_ShouldUpdate(20) al vell doCredits.
contador_acc_ms_ += delta_ms;
while (contador_acc_ms_ >= TICK_MS) {
contador_acc_ms_ -= TICK_MS;
++contador_;
}
coche_.tick(delta_ms);
render();
if (JI_AnyKey() || contador_ >= CONTADOR_MAX) {
writeTrickIni();
fade_.startFadeOut();
phase_ = Phase::FadingOut;
}
break;
}
case Phase::FadingOut:
fade_.tick(delta_ms);
if (fade_.done()) {
info::ctx.num_piramide = 255;
phase_ = Phase::Done;
}
break;
case Phase::Done:
break;
}
}
}
} // namespace scenes

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,73 +7,73 @@
namespace scenes {
// 3 slides narratius amb scroll d'entrada + espera + transició amb
// fade-out. Reemplaça `ModuleSequence::doSlides()`.
//
// Tria d'asset segons context:
// - num_piramide == 7 i diners < 200: gfx/intro2.gif + música "music/00000005.ogg"
// - num_piramide == 7 i diners >= 200: gfx/intro3.gif + música "music/00000005.ogg"
// - altre cas (num_piramide == 1): gfx/intro.gif, sense música nova
//
// Flux:
// Slide1Enter (1600 ms scroll dreta→centre, easing outCubic)
// → Slide1Hold (4600 ms)
// → FadeOut1 + clear + reset paleta
// → Slide2Enter (1600 ms scroll esquerra→centre)
// → Slide2Hold (4600 ms)
// → FadeOut2 + clear + reset paleta
// → Slide3Enter (1600 ms scroll dreta→centre)
// → Slide3Hold (4600 ms)
// → FadeFinal (JA_FadeOutMusic si num_piramide != 7 + fade paleta)
// → Done
//
// Qualsevol tecla salta directament a FadeFinal (sense cortar la música
// si hem entrat per num_piramide==7, per fidelitat al vell).
//
// NextState:
// - num_piramide==7 al entrar → num_piramide=8 + return 1 (a Credits)
// - altre cas → return 0 (entra al ModuleGame)
class SlidesScene : public Scene {
public:
SlidesScene() = default;
~SlidesScene() override;
// 3 slides narratius amb scroll d'entrada + espera + transició amb
// fade-out. Reemplaça `ModuleSequence::doSlides()`.
//
// Tria d'asset segons context:
// - num_piramide == 7 i diners < 200: gfx/intro2.gif + música "music/00000005.ogg"
// - num_piramide == 7 i diners >= 200: gfx/intro3.gif + música "music/00000005.ogg"
// - altre cas (num_piramide == 1): gfx/intro.gif, sense música nova
//
// Flux:
// Slide1Enter (1600 ms scroll dreta→centre, easing outCubic)
// → Slide1Hold (4600 ms)
// → FadeOut1 + clear + reset paleta
// → Slide2Enter (1600 ms scroll esquerra→centre)
// → Slide2Hold (4600 ms)
// → FadeOut2 + clear + reset paleta
// → Slide3Enter (1600 ms scroll dreta→centre)
// → Slide3Hold (4600 ms)
// → FadeFinal (JA_FadeOutMusic si num_piramide != 7 + fade paleta)
// → Done
//
// Qualsevol tecla salta directament a FadeFinal (sense cortar la música
// si hem entrat per num_piramide==7, per fidelitat al vell).
//
// NextState:
// - num_piramide==7 al entrar → num_piramide=8 + return 1 (a Credits)
// - altre cas → return 0 (entra al ModuleGame)
class SlidesScene : public Scene {
public:
SlidesScene() = default;
~SlidesScene() override;
void onEnter() override;
void tick(int delta_ms) override;
bool done() const override { return phase_ == Phase::Done; }
int nextState() const override { return next_state_; }
void onEnter() override;
void tick(int delta_ms) override;
bool done() const override { return phase_ == Phase::Done; }
int nextState() const override { return next_state_; }
private:
enum class Phase {
Slide1Enter,
Slide1Hold,
FadeOut1,
Slide2Enter,
Slide2Hold,
FadeOut2,
Slide3Enter,
Slide3Hold,
FadeFinal,
Done,
private:
enum class Phase {
Slide1Enter,
Slide1Hold,
FadeOut1,
Slide2Enter,
Slide2Hold,
FadeOut2,
Slide3Enter,
Slide3Hold,
FadeFinal,
Done,
};
// Pinta un slide amb desplaçament horitzontal. `slide_idx` = 0..2
// (correspon a la franja 65x65 a y = 0, 65, 130 dins de gfx_).
// `pos_x` = desplaçament, amb clipping manual quan surt de pantalla.
void drawSlide(int slide_idx, int pos_x);
void restorePalette();
void beginFinalFade();
SurfaceHandle gfx_;
JD8_Palette pal_aux_{nullptr}; // còpia "neta" que preservem
JD8_Palette pal_active_{nullptr}; // propietat transferida a main_palette
PaletteFade fade_;
Phase phase_{Phase::Slide1Enter};
int phase_acc_ms_{0};
int num_piramide_at_start_{1};
int next_state_{0};
bool skip_triggered_{false};
};
// Pinta un slide amb desplaçament horitzontal. `slide_idx` = 0..2
// (correspon a la franja 65x65 a y = 0, 65, 130 dins de gfx_).
// `pos_x` = desplaçament, amb clipping manual quan surt de pantalla.
void drawSlide(int slide_idx, int pos_x);
void restorePalette();
void beginFinalFade();
SurfaceHandle gfx_;
JD8_Palette pal_aux_{nullptr}; // còpia "neta" que preservem
JD8_Palette pal_active_{nullptr}; // propietat transferida a main_palette
PaletteFade fade_;
Phase phase_{Phase::Slide1Enter};
int phase_acc_ms_{0};
int num_piramide_at_start_{1};
int next_state_{0};
bool skip_triggered_{false};
};
} // namespace scenes

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,82 +4,82 @@
namespace scenes {
Timeline& Timeline::step(int duration_ms, StepFn fn) {
Step s;
s.duration_ms = duration_ms;
s.continuous = std::move(fn);
steps_.push_back(std::move(s));
return *this;
}
Timeline& Timeline::step(int duration_ms, StepFn fn) {
Step s;
s.duration_ms = duration_ms;
s.continuous = std::move(fn);
steps_.push_back(std::move(s));
return *this;
}
Timeline& Timeline::once(OnceFn fn) {
Step s;
s.duration_ms = 0;
s.oneshot = std::move(fn);
steps_.push_back(std::move(s));
return *this;
}
Timeline& Timeline::once(OnceFn fn) {
Step s;
s.duration_ms = 0;
s.oneshot = std::move(fn);
steps_.push_back(std::move(s));
return *this;
}
void Timeline::flushOneShots() {
while (current_ < steps_.size() && steps_[current_].duration_ms == 0) {
auto& s = steps_[current_];
if (!s.entered) {
s.entered = true;
if (s.oneshot) s.oneshot();
}
++current_;
elapsed_in_step_ = 0;
}
}
void Timeline::tick(int delta_ms) {
if (skipped_) return;
flushOneShots();
if (current_ >= steps_.size()) return;
void Timeline::flushOneShots() {
while (current_ < steps_.size() && steps_[current_].duration_ms == 0) {
auto& s = steps_[current_];
if (!s.entered) {
s.entered = true;
if (s.oneshot) s.oneshot();
// Primer tick dins del pas: cridem amb progress=0 si hi ha callback.
if (s.continuous) s.continuous(0.0f);
}
++current_;
elapsed_in_step_ += delta_ms;
if (elapsed_in_step_ >= s.duration_ms) {
// Tancament del pas: una crida final amb progress=1.
if (s.continuous) s.continuous(1.0f);
++current_;
elapsed_in_step_ = 0;
// Pot ser que el següent pas siga una cadena de one-shots.
flushOneShots();
} else if (s.continuous) {
const float p = static_cast<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;
}
}
void Timeline::tick(int delta_ms) {
if (skipped_) return;
flushOneShots();
if (current_ >= steps_.size()) return;
auto& s = steps_[current_];
if (!s.entered) {
s.entered = true;
// Primer tick dins del pas: cridem amb progress=0 si hi ha callback.
if (s.continuous) s.continuous(0.0f);
skipped_ = false;
}
elapsed_in_step_ += delta_ms;
if (elapsed_in_step_ >= s.duration_ms) {
// Tancament del pas: una crida final amb progress=1.
if (s.continuous) s.continuous(1.0f);
++current_;
elapsed_in_step_ = 0;
// Pot ser que el següent pas siga una cadena de one-shots.
flushOneShots();
} else if (s.continuous) {
const float p = static_cast<float>(elapsed_in_step_) /
static_cast<float>(std::max(1, s.duration_ms));
s.continuous(p);
bool Timeline::done() const {
return skipped_ || current_ >= steps_.size();
}
}
void Timeline::skip() {
skipped_ = true;
current_ = steps_.size();
}
void Timeline::reset() {
for (auto& s : steps_) s.entered = false;
current_ = 0;
elapsed_in_step_ = 0;
skipped_ = false;
}
bool Timeline::done() const {
return skipped_ || current_ >= steps_.size();
}
float Timeline::currentProgress() const {
if (current_ >= steps_.size()) return 1.0f;
const auto& s = steps_[current_];
if (s.duration_ms <= 0) return 0.0f;
return static_cast<float>(elapsed_in_step_) / static_cast<float>(s.duration_ms);
}
float Timeline::currentProgress() const {
if (current_ >= steps_.size()) return 1.0f;
const auto& s = steps_[current_];
if (s.duration_ms <= 0) return 0.0f;
return static_cast<float>(elapsed_in_step_) / static_cast<float>(s.duration_ms);
}
} // namespace scenes

View File

@@ -5,53 +5,53 @@
namespace scenes {
// Timeline declaratiu de passos seqüencials. Cada pas té una duració en
// ms i un callback. Exemple d'ús:
//
// timeline_
// .once([this] { JD8_ClearScreen(0); fade_.startFadeTo(pal); })
// .step(5000) // espera pura
// .step(1000, [this](float p) { /*...*/ }) // animat amb progress
// .once([this] { JA_FadeOutMusic(250); });
//
// `tick(delta_ms)` avança el temps. Els passos one-shot s'executen al
// moment d'entrar-hi i avancen immediatament. Els passos amb duració
// criden el seu callback cada tick amb el progress [0..1] i passen al
// següent quan s'exhaureix el temps. `skip()` marca tota la timeline
// com a acabada (no executa res més) — útil per als "polsa una tecla
// per a saltar la cinemàtica".
class Timeline {
public:
using StepFn = std::function<void(float progress_0_1)>;
using OnceFn = std::function<void()>;
// Timeline declaratiu de passos seqüencials. Cada pas té una duració en
// ms i un callback. Exemple d'ús:
//
// timeline_
// .once([this] { JD8_ClearScreen(0); fade_.startFadeTo(pal); })
// .step(5000) // espera pura
// .step(1000, [this](float p) { /*...*/ }) // animat amb progress
// .once([this] { JA_FadeOutMusic(250); });
//
// `tick(delta_ms)` avança el temps. Els passos one-shot s'executen al
// moment d'entrar-hi i avancen immediatament. Els passos amb duració
// criden el seu callback cada tick amb el progress [0..1] i passen al
// següent quan s'exhaureix el temps. `skip()` marca tota la timeline
// com a acabada (no executa res més) — útil per als "polsa una tecla
// per a saltar la cinemàtica".
class Timeline {
public:
using StepFn = std::function<void(float progress_0_1)>;
using OnceFn = std::function<void()>;
Timeline& step(int duration_ms, StepFn fn = nullptr);
Timeline& once(OnceFn fn);
Timeline& step(int duration_ms, StepFn fn = nullptr);
Timeline& once(OnceFn fn);
void tick(int delta_ms);
void skip();
void reset();
void tick(int delta_ms);
void skip();
void reset();
bool done() const;
int currentStepIndex() const { return static_cast<int>(current_); }
float currentProgress() const;
bool done() const;
int currentStepIndex() const { return static_cast<int>(current_); }
float currentProgress() const;
private:
struct Step {
int duration_ms{0}; // 0 = one-shot
StepFn continuous;
OnceFn oneshot;
bool entered{false};
private:
struct Step {
int duration_ms{0}; // 0 = one-shot
StepFn continuous;
OnceFn oneshot;
bool entered{false};
};
// Avança els one-shots consecutius des de `current_` fins a trobar
// un pas amb duració > 0 o l'endoll de la llista.
void flushOneShots();
std::vector<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