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