step 5: slides_scene amb wipe suau per easing (substituix doSlides)

This commit is contained in:
2026-04-15 23:50:59 +02:00
parent ad38fc09cf
commit 605c273173
11 changed files with 323 additions and 98 deletions

View File

@@ -4,6 +4,7 @@
#include "core/jail/jdraw8.hpp"
#include "core/jail/jinput.hpp"
#include "game/info.hpp"
#include "game/modulesequence.hpp"
#include "scenes/scene_utils.hpp"
@@ -120,11 +121,14 @@ void IntroNewLogoScene::advancePaletteCycle() {
}
void IntroNewLogoScene::tick(int delta_ms) {
// Qualsevol tecla salta tota la intro del logo i va directament
// a la delegació final — el mini-while del fiber no ha pintat
// res encara, però doIntroSprites fa la seua pròpia intro.
if (JI_AnyKey()) {
phase_ = Phase::Delegate;
// Qualsevol tecla durant el revelat o el ciclo de paleta salta
// TOTA la intro (inclou saltar doIntroSprites). Aquest era el
// comportament del vell `doIntroNewLogo`: a cada `waitTick()`
// retornava abans de cridar `doIntroSprites`.
if (phase_ != Phase::Delegate && phase_ != Phase::Done && JI_AnyKey()) {
info::ctx.num_piramide = 0;
phase_ = Phase::Done;
return;
}
switch (phase_) {
@@ -192,13 +196,18 @@ void IntroNewLogoScene::tick(int delta_ms) {
case Phase::Delegate: {
// Delegació temporal al codi legacy: crea un ModuleSequence
// instància i li crida `doIntroSprites(gfx)`. La funció
// legacy fa els seus propis `JD8_Flip()` (que cedeixen al
// Director via GameFiber::yield) i torna quan la cinemàtica
// de sprites ha acabat. Step 9 d'aquesta migració la
// reescriurà com a scenes::IntroSpritesScene i aquesta
// delegació desapareixerà.
// legacy *sempre* allibera `gfx` ella mateixa (al final o en
// els paths de skip amb JI_AnyKey), així que necessitem
// transferir-li ownership via `release()` per evitar un
// double free al destructor de SurfaceHandle. Step 9
// d'aquesta migració la reescriurà com a IntroSpritesScene
// i la delegació desapareixerà.
ModuleSequence legacy;
legacy.doIntroSprites(gfx_.get());
legacy.doIntroSprites(gfx_.release());
// El vell `Go()` post-switch feia `num_piramide = 0` per a
// passar al menú. Ho reproduïm ací — si no, el while extern
// del fiber tornaria a crear IntroNewLogoScene infinitament.
info::ctx.num_piramide = 0;
phase_ = Phase::Done;
break;
}

View File

@@ -7,7 +7,7 @@
namespace scenes {
void playMusic(const char* filename) {
void playMusic(const char* filename, int loop) {
if (!filename) return;
int size = 0;
char* buffer = file_getfilebuffer(filename, size);
@@ -15,7 +15,7 @@ void playMusic(const char* filename) {
// JA_LoadMusic fa una còpia del OGG comprimit (SDL_malloc), així que
// el `buffer` original es queda huérfano. Leak conegut heredat del
// codi original — es tractarà quan jfile tinga una API std::vector.
JA_PlayMusic(JA_LoadMusic(reinterpret_cast<Uint8*>(buffer), size, filename));
JA_PlayMusic(JA_LoadMusic(reinterpret_cast<Uint8*>(buffer), size, filename), loop);
}
} // namespace scenes

View File

@@ -7,6 +7,7 @@ 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.
void playMusic(const char* filename);
// `loop`: -1 = infinit (per defecte), 0 = una sola vegada, N = N+1 passades.
void playMusic(const char* filename, int loop = -1);
} // namespace scenes

View File

@@ -0,0 +1,186 @@
#include "scenes/slides_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"
#include "utils/easing.hpp"
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;
// 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("00000005.ogg", 1);
arxiu = (info::ctx.diners < 200) ? "intro2.gif" : "intro3.gif";
} else {
arxiu = "intro.gif";
}
gfx_ = SurfaceHandle(arxiu);
pal_aux_ = JD8_LoadPalette(arxiu);
// 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_);
JD8_ClearScreen(BG_COLOR_INDEX);
phase_ = Phase::Slide1Enter;
phase_acc_ms_ = 0;
next_state_ = 0;
}
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);
}
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);
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;
}
case Phase::Slide1Hold:
case Phase::Slide2Hold:
phase_acc_ms_ += delta_ms;
if (phase_acc_ms_ >= HOLD_MS) {
fade_.startFadeOut();
if (phase_ == Phase::Slide1Hold) phase_ = Phase::FadeOut1;
else phase_ = Phase::FadeOut2;
phase_acc_ms_ = 0;
}
break;
case Phase::Slide3Hold:
phase_acc_ms_ += delta_ms;
if (phase_acc_ms_ >= HOLD_MS) {
beginFinalFade();
}
break;
case Phase::FadeOut1:
case Phase::FadeOut2:
fade_.tick(delta_ms);
if (fade_.done()) {
restorePalette();
JD8_ClearScreen(BG_COLOR_INDEX);
if (phase_ == Phase::FadeOut1) phase_ = Phase::Slide2Enter;
else phase_ = Phase::Slide3Enter;
phase_acc_ms_ = 0;
}
break;
case Phase::FadeFinal:
fade_.tick(delta_ms);
if (fade_.done()) {
if (num_piramide_at_start_ == 7) {
info::ctx.num_piramide = 8;
next_state_ = 1;
} else {
next_state_ = 0;
}
phase_ = Phase::Done;
}
break;
case Phase::Done:
break;
}
}
} // namespace scenes

View File

@@ -0,0 +1,79 @@
#pragma once
#include "core/jail/jdraw8.hpp"
#include "scenes/palette_fade.hpp"
#include "scenes/scene.hpp"
#include "scenes/surface_handle.hpp"
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: intro2.gif + música "00000005.ogg"
// - num_piramide == 7 i diners >= 200: intro3.gif + música "00000005.ogg"
// - altre cas (num_piramide == 1): 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_; }
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};
};
} // namespace scenes

View File

@@ -33,4 +33,10 @@ void SurfaceHandle::adopt(JD8_Surface raw) {
surface_ = raw;
}
JD8_Surface SurfaceHandle::release() {
JD8_Surface r = surface_;
surface_ = nullptr;
return r;
}
} // namespace scenes

View File

@@ -29,6 +29,13 @@ class SurfaceHandle {
// — 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();
// Conversió implícita per al confort d'ús: JD8_Blit(handle)
// en lloc de JD8_Blit(handle.get()).
operator JD8_Surface() const { return surface_; }