step 7: secreta_scene amb swap de tomba1→tomba2 i red pulse animat

This commit is contained in:
2026-04-16 00:13:02 +02:00
parent 829d7431c1
commit 6063b1c606
9 changed files with 283 additions and 91 deletions

View File

@@ -27,6 +27,7 @@
#include "scenes/mort_scene.hpp"
#include "scenes/scene.hpp"
#include "scenes/scene_registry.hpp"
#include "scenes/secreta_scene.hpp"
#include "scenes/slides_scene.hpp"
// Cheats del joc original — declarats a jinput.cpp
@@ -43,7 +44,7 @@ namespace {
void gameFiberEntry() {
info::ctx.num_habitacio = Options::game.habitacio_inicial;
info::ctx.num_piramide = Options::game.piramide_inicial;
info::ctx.diners = 0;
info::ctx.diners = Options::game.diners_inicial;
info::ctx.diamants = Options::game.diamants_inicial;
info::ctx.vida = Options::game.vides;
info::ctx.momies = 0;
@@ -130,6 +131,7 @@ void Director::init() {
// l'usuari no té prou diners per a la Secreta)
registry.registerScene(1, [] { return std::make_unique<scenes::SlidesScene>(); });
registry.registerScene(7, [] { return std::make_unique<scenes::SlidesScene>(); });
registry.registerScene(6, [] { return std::make_unique<scenes::SecretaScene>(); });
registry.registerScene(8, [] { return std::make_unique<scenes::CreditsScene>(); });
// IntroNewLogoScene només es registra quan `use_new_logo` està actiu;
// si no, la factory retorna nullptr i el gameFiberEntry cau al vell

View File

@@ -58,6 +58,7 @@ namespace Defaults::Game {
constexpr int PIRAMIDE_INICIAL = 255;
constexpr int VIDES = 5;
constexpr int DIAMANTS_INICIAL = 0;
constexpr int DINERS_INICIAL = 0;
constexpr bool USE_NEW_LOGO = true;
constexpr bool SHOW_TITLE_CREDITS = true;
} // namespace Defaults::Game

View File

@@ -41,9 +41,7 @@ int ModuleSequence::Go() {
// case 0 (Menú) → migrat a scenes::MenuScene.
// case 1 i 7 (Slides) → migrat a scenes::SlidesScene.
// case 2..5 (Pre-piràmide) → migrat a scenes::BannerScene.
case 6: // Pre-Secreta
doSecreta();
break;
// case 6 (Pre-Secreta) → migrat a scenes::SecretaScene.
// case 8 (Credits) → migrat a scenes::CreditsScene.
// case 100 (Mort) → migrat a scenes::MortScene, dispatch via SceneRegistry.
}
@@ -822,92 +820,7 @@ void ModuleSequence::doIntroSprites(JD8_Surface gfx) {
// doBanner() — migrat a scenes::BannerScene (source/scenes/banner_scene.cpp)
void ModuleSequence::doSecreta() {
play_music("00000002.ogg");
JG_SetUpdateTicks(20);
JD8_FadeOut();
JD8_Surface gfx = JD8_LoadSurface("tomba1.gif");
JD8_Palette pal_aux = JD8_LoadPalette("tomba1.gif");
JD8_Palette pal = (JD8_Palette)malloc(768);
memcpy(pal, pal_aux, 768);
JD8_ClearScreen(255);
JD8_SetScreenPalette(pal);
bool exit = false;
int step = 0;
contador = 1;
while (!exit && !JG_Quitting()) {
if (JG_ShouldUpdate()) {
JI_Update();
if (JI_AnyKey()) {
exit = true;
}
switch (step) {
case 0:
JD8_Blit(70, 60, gfx, 0, contador, 178, 70); // Put_Sprite(from, where, 0 + (320 * i), 178, 70, 70, 60);
JD8_BlitCK(70, 60, gfx, 178, contador >> 1, 142, 70, 255); // Put_Sprite(from, where, 178 + (320 * (i div 2)), 142, 70, 70, 60);
JD8_Flip();
contador++;
if (contador == 128) step++;
break;
case 1:
case 4:
case 7:
case 9:
contador--;
if (contador == 0) step++;
break;
case 2:
JD8_ClearScreen(255);
JD8_Flip();
gfx = JD8_LoadSurface("tomba2.gif");
pal_aux = JD8_LoadPalette("tomba2.gif");
memcpy(pal, pal_aux, 768);
JD8_SetScreenPalette(pal);
step++;
break;
case 3:
JD8_Blit(55, 53, gfx, 0, 158 - contador, 211, contador); // Put_Sprite(from, where, 0 + ((158 - i) * 320), 211, i, 55, 53);
JD8_Flip();
contador++;
if (contador == 94) step++;
break;
case 5:
JD8_ClearScreen(0);
JD8_Flip();
JD8_SetPaletteColor(254, 12, 11, 11);
JD8_SetPaletteColor(253, 12, 11, 11);
step++;
break;
case 6:
JD8_Blit(80, 68, gfx, 160 - (contador * 2), 0, contador * 2, 64); // Put_Sprite(from, where, 160 - (i * 2), (i * 2), 64, 80, 68);
JD8_Flip();
contador++;
if (contador == 80) step++;
break;
case 8:
JD8_SetPaletteColor(254, contador + 12, 11, 11);
JD8_SetPaletteColor(253, (contador + 12) >> 1, 11, 11);
JD8_Flip();
contador++;
if (contador == 51) step++;
break;
case 10:
JD8_FadeOut();
memcpy(pal, pal_aux, 768);
JD8_ClearScreen(255);
JA_FadeOutMusic(250);
exit = true;
break;
}
}
}
JD8_FreeSurface(gfx);
free(pal_aux);
}
// doSecreta() — migrat a scenes::SecretaScene (source/scenes/secreta_scene.cpp)
// doCredits() — migrat a scenes::CreditsScene (source/scenes/credits_scene.cpp)

View File

@@ -23,7 +23,7 @@ class ModuleSequence {
// doMenu() → migrat a scenes::MenuScene
// doSlides() → migrat a scenes::SlidesScene
// doBanner() → migrat a scenes::BannerScene
void doSecreta();
// doSecreta() → migrat a scenes::SecretaScene
// doCredits() → migrat a scenes::CreditsScene
// doMort() → migrat a scenes::MortScene

View File

@@ -146,6 +146,8 @@ namespace Options {
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"))
@@ -281,6 +283,7 @@ namespace Options {
file << " piramide_inicial: " << game.piramide_inicial << "\n";
file << " vides: " << game.vides << "\n";
file << " diamants_inicial: " << game.diamants_inicial << "\n";
file << " diners_inicial: " << game.diners_inicial << "\n";
file << " use_new_logo: " << (game.use_new_logo ? "true" : "false") << "\n";
file << " show_title_credits: " << (game.show_title_credits ? "true" : "false") << "\n";
file << "\n";

View File

@@ -84,6 +84,7 @@ namespace Options {
int piramide_inicial{Defaults::Game::PIRAMIDE_INICIAL};
int vides{Defaults::Game::VIDES};
int diamants_inicial{Defaults::Game::DIAMANTS_INICIAL};
int diners_inicial{Defaults::Game::DINERS_INICIAL};
bool use_new_logo{Defaults::Game::USE_NEW_LOGO};
bool show_title_credits{Defaults::Game::SHOW_TITLE_CREDITS};
};

View File

@@ -0,0 +1,205 @@
#include "scenes/secreta_scene.hpp"
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include "core/jail/jail_audio.hpp"
#include "core/jail/jdraw8.hpp"
#include "core/jail/jinput.hpp"
#include "game/info.hpp"
#include "scenes/scene_utils.hpp"
namespace {
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;
} // 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("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("tomba1.gif");
pal_aux_ = JD8_LoadPalette("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("tomba2.gif");
std::free(pal_aux_);
pal_aux_ = JD8_LoadPalette("tomba2.gif");
// pal_active_ continua sent el mateix buffer: només actualitzem
// el seu contingut. main_palette ja apunta ací.
std::memcpy(pal_active_, pal_aux_, 768);
}
void SecretaScene::beginRedPulseSetup() {
JD8_ClearScreen(0);
JD8_SetPaletteColor(254, 12, 11, 11);
JD8_SetPaletteColor(253, 12, 11, 11);
}
void SecretaScene::beginFinalFade() {
JA_FadeOutMusic(250);
fade_.startFadeOut();
phase_ = Phase::FinalFadeOut;
}
void SecretaScene::tick(int delta_ms) {
// Skip per tecla (després del fade inicial, no mentre). Salta
// directament al FinalFadeOut. Mateix patró que el vell, on
// qualsevol tecla sortia del loop.
if (!skip_triggered_ && phase_ != Phase::InitialFadeOut && JI_AnyKey()) {
skip_triggered_ = true;
beginFinalFade();
}
switch (phase_) {
case Phase::InitialFadeOut:
fade_.tick(delta_ms);
if (fade_.done()) {
// Ara main_palette (la vella) té tots els canals a 0.
// SetScreenPalette allibera la vella i adopta pal_active_
// — des d'ara main_palette == pal_active_, així que les
// futures escriptures a pal_active_ afecten la pantalla.
JD8_SetScreenPalette(pal_active_);
JD8_ClearScreen(255);
phase_ = Phase::Tomba1ScrollIn;
phase_acc_ms_ = 0;
}
break;
case Phase::Tomba1ScrollIn: {
phase_acc_ms_ += delta_ms;
const int contador = std::min(128, phase_acc_ms_ / TICK_MS + 1);
// Dos blits solapats: el primer avança a velocitat completa,
// el segon (contingut de la dreta del src) a meitat (contador>>1).
JD8_Blit(70, 60, gfx_, 0, contador, 178, 70);
JD8_BlitCK(70, 60, gfx_, 178, contador >> 1, 142, 70, 255);
if (phase_acc_ms_ >= TOMBA1_SCROLL_MS) {
phase_ = Phase::Tomba1Hold;
phase_acc_ms_ = 0;
}
break;
}
case Phase::Tomba1Hold:
phase_acc_ms_ += delta_ms;
if (phase_acc_ms_ >= TOMBA1_HOLD_MS) {
swapToTomba2();
phase_ = Phase::Tomba2ScrollIn;
phase_acc_ms_ = 0;
}
break;
case Phase::Tomba2ScrollIn: {
phase_acc_ms_ += delta_ms;
const int contador = std::min(94, phase_acc_ms_ / TICK_MS + 1);
JD8_Blit(55, 53, gfx_, 0, 158 - contador, 211, contador);
if (phase_acc_ms_ >= TOMBA2_SCROLL_MS) {
phase_ = Phase::Tomba2Hold;
phase_acc_ms_ = 0;
}
break;
}
case Phase::Tomba2Hold:
phase_acc_ms_ += delta_ms;
if (phase_acc_ms_ >= TOMBA2_HOLD_MS) {
beginRedPulseSetup();
phase_ = Phase::Tomba2Reveal;
phase_acc_ms_ = 0;
}
break;
case Phase::Tomba2Reveal: {
phase_acc_ms_ += delta_ms;
const int contador = std::min(80, phase_acc_ms_ / TICK_MS + 1);
// Revelat horitzontal simètric: l'amplada creix 2px per tick
// i el src_x es desplaça a l'esquerra el mateix.
JD8_Blit(80, 68, gfx_, 160 - (contador * 2), 0, contador * 2, 64);
if (phase_acc_ms_ >= TOMBA2_REVEAL_MS) {
phase_ = Phase::Tomba2RevealHold;
phase_acc_ms_ = 0;
}
break;
}
case Phase::Tomba2RevealHold:
phase_acc_ms_ += delta_ms;
if (phase_acc_ms_ >= TOMBA2_REVEAL_HOLD_MS) {
phase_ = Phase::RedPulse;
phase_acc_ms_ = 0;
}
break;
case Phase::RedPulse: {
phase_acc_ms_ += delta_ms;
const int contador = std::min(51, phase_acc_ms_ / TICK_MS);
// Anima el canal R dels índexs 254 i 253 (aquest a la meitat
// de brillantor). Va de (12,11,11) fins a (63,11,11) / (31,11,11).
JD8_SetPaletteColor(254, contador + 12, 11, 11);
JD8_SetPaletteColor(253, (contador + 12) >> 1, 11, 11);
if (phase_acc_ms_ >= RED_PULSE_MS) {
phase_ = Phase::RedPulseHold;
phase_acc_ms_ = 0;
}
break;
}
case Phase::RedPulseHold:
phase_acc_ms_ += delta_ms;
if (phase_acc_ms_ >= RED_PULSE_HOLD_MS) {
beginFinalFade();
}
break;
case Phase::FinalFadeOut:
fade_.tick(delta_ms);
if (fade_.done()) {
phase_ = Phase::Done;
}
break;
case Phase::Done:
break;
}
}
} // namespace scenes

View File

@@ -0,0 +1,66 @@
#pragma once
#include "core/jail/jdraw8.hpp"
#include "scenes/palette_fade.hpp"
#include "scenes/scene.hpp"
#include "scenes/surface_handle.hpp"
namespace scenes {
// Pre-Secreta. Reemplaça `ModuleSequence::doSecreta()`.
//
// Flux:
// 1. Arranca música "00000002.ogg" i fa fade-out de la paleta anterior.
// 2. Carrega 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 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; }
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};
};
} // namespace scenes