step 6: credits_scene substituix doCredits() (scroll vertical + parallax condicional)

This commit is contained in:
2026-04-16 00:03:25 +02:00
parent 605c273173
commit 829d7431c1
9 changed files with 203 additions and 70 deletions

View File

@@ -54,6 +54,7 @@ set(APP_SOURCES
source/scenes/menu_scene.cpp
source/scenes/intro_new_logo_scene.cpp
source/scenes/slides_scene.cpp
source/scenes/credits_scene.cpp
# Game
source/game/options.cpp

View File

@@ -21,6 +21,7 @@
#include "game/modulesequence.hpp"
#include "game/options.hpp"
#include "scenes/banner_scene.hpp"
#include "scenes/credits_scene.hpp"
#include "scenes/intro_new_logo_scene.hpp"
#include "scenes/menu_scene.hpp"
#include "scenes/mort_scene.hpp"
@@ -43,7 +44,7 @@ void gameFiberEntry() {
info::ctx.num_habitacio = Options::game.habitacio_inicial;
info::ctx.num_piramide = Options::game.piramide_inicial;
info::ctx.diners = 0;
info::ctx.diamants = 0;
info::ctx.diamants = Options::game.diamants_inicial;
info::ctx.vida = Options::game.vides;
info::ctx.momies = 0;
info::ctx.nou_personatge = false;
@@ -129,6 +130,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(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
// ModuleSequence::doIntro() legacy que no ha sigut migrat encara.

View File

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

View File

@@ -44,9 +44,7 @@ int ModuleSequence::Go() {
case 6: // Pre-Secreta
doSecreta();
break;
case 8: // Credits
doCredits();
break;
// case 8 (Credits) → migrat a scenes::CreditsScene.
// case 100 (Mort) → migrat a scenes::MortScene, dispatch via SceneRegistry.
}
@@ -911,70 +909,6 @@ void ModuleSequence::doSecreta() {
free(pal_aux);
}
void ModuleSequence::doCredits() {
struct {
Uint16 x, y;
} frames_coche[8] = {{214, 152}, {214, 104}, {214, 56}, {214, 104}, {214, 152}, {214, 8}, {108, 152}, {214, 8}};
const Uint32 n_frames_coche = 8;
const Uint32 velocitat_coche = 3;
JD8_Surface vaddr2 = JD8_LoadSurface("final.gif");
JD8_Surface vaddr3 = JD8_LoadSurface("finals.gif");
JD8_Palette pal = JD8_LoadPalette("final.gif");
JD8_SetScreenPalette(pal);
int contador = 0;
bool exit = false;
contador = 1;
while (!exit && !JG_Quitting()) {
if (JG_ShouldUpdate()) {
JI_Update();
if (JI_AnyKey() || contador >= 3100) {
exit = true;
}
JD8_ClearScreen(255);
if (contador < 2750) {
JD8_BlitCKCut(115, 200 - (contador / 6), vaddr2, 0, 0, 80, 200, 0);
}
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);
}
if (info::ctx.diamants == 16) {
// scroll_final_joc(vaddr3, vaddr, contador_final);
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);
JD8_BlitCK(100, 50, vaddr2, frames_coche[(contador / velocitat_coche) % n_frames_coche].x, frames_coche[(contador / velocitat_coche) % n_frames_coche].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);
}
JD8_FillSquare(0, 50, 255);
JD8_FillSquare(100, 10, 255);
JD8_Flip();
contador++;
}
}
FILE* ini = fopen("trick.ini", "wb");
fwrite("1", 1, 1, ini);
fclose(ini);
info::ctx.nou_personatge = true;
JD8_FreeSurface(vaddr3);
JD8_FreeSurface(vaddr2);
}
// doCredits() — migrat a scenes::CreditsScene (source/scenes/credits_scene.cpp)
// doMort() — migrat a scenes::MortScene (source/scenes/mort_scene.cpp)

View File

@@ -24,7 +24,7 @@ class ModuleSequence {
// doSlides() → migrat a scenes::SlidesScene
// doBanner() → migrat a scenes::BannerScene
void doSecreta();
void doCredits();
// doCredits() → migrat a scenes::CreditsScene
// doMort() → migrat a scenes::MortScene
int contador;

View File

@@ -144,6 +144,8 @@ namespace Options {
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("use_new_logo"))
game.use_new_logo = node["use_new_logo"].get_value<bool>();
if (node.contains("show_title_credits"))
@@ -278,6 +280,7 @@ namespace Options {
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 << " 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

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

View File

@@ -0,0 +1,139 @@
#include "scenes/credits_scene.hpp"
#include <cstdio>
#include <cstdlib>
#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 {
// Frames del cotxe: 8 posicions dins del sprite sheet 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 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 ("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("00000005.ogg");
}
vaddr2_ = SurfaceHandle("final.gif");
vaddr3_ = SurfaceHandle("finals.gif");
JD8_Palette pal = JD8_LoadPalette("final.gif");
JD8_SetScreenPalette(pal);
// `pal` passa a ser propietat de main_palette — no l'alliberem.
phase_ = Phase::Rolling;
contador_ = 1;
contador_acc_ms_ = 0;
}
void CreditsScene::render() {
JD8_ClearScreen(BG_INDEX);
// Columna 1: scroll vertical del bloc (0,0,80,200) pujant des de
// y=200 fins que el contador supera 2750.
if (contador_ < 2750) {
JD8_BlitCKCut(115, 200 - (contador_ / 6), vaddr2_, 0, 0, 80, 200, 0);
}
// Columna 2: scroll vertical del bloc (85,0,120,140), arrenca
// a contador 1200 i s'atura (fix en y=20) a partir de 2250.
if ((contador_ > 1200) && (contador_ < 2280)) {
JD8_BlitCKCut(100, 200 - ((contador_ - 1200) / 6), vaddr2_, 85, 0, 120, 140, 0);
} else if (contador_ >= 2250) {
JD8_BlitCK(100, 20, vaddr2_, 85, 0, 120, 140, 0);
}
// Fons: 4 capes parallax + cotxe només si l'usuari ha aconseguit
// tots els diamants (final "bo"). Altrament fons estàtic.
if (info::ctx.diamants == 16) {
JD8_BlitCKScroll(50, vaddr3_, ((contador_ >> 3) % 320) + 1, 0, 50, 255);
JD8_BlitCKScroll(50, vaddr3_, ((contador_ >> 2) % 320) + 1, 50, 50, 255);
JD8_BlitCKScroll(50, vaddr3_, ((contador_ >> 1) % 320) + 1, 100, 50, 255);
JD8_BlitCKScroll(50, vaddr3_, (contador_ % 320) + 1, 150, 50, 255);
const CocheFrame& cf = COCHE_FRAMES[coche_.frame()];
JD8_BlitCK(100, 50, vaddr2_, cf.x, cf.y, 106, 48, 255);
} else {
JD8_BlitCK(0, 50, vaddr3_, 0, 0, 320, 50, 255);
JD8_BlitCK(0, 50, vaddr3_, 0, 50, 320, 50, 255);
}
// Barres de marc que cobreixen els extrems del scroll vertical.
JD8_FillSquare(0, 50, BG_INDEX);
JD8_FillSquare(100, 10, BG_INDEX);
}
void CreditsScene::writeTrickIni() {
FILE* ini = std::fopen("trick.ini", "wb");
if (ini) {
std::fwrite("1", 1, 1, ini);
std::fclose(ini);
}
info::ctx.nou_personatge = true;
}
void CreditsScene::tick(int delta_ms) {
switch (phase_) {
case Phase::Rolling: {
// Avancem el contador en passos discrets de 20 ms, igual
// que feia JG_ShouldUpdate(20) al vell doCredits.
contador_acc_ms_ += delta_ms;
while (contador_acc_ms_ >= TICK_MS) {
contador_acc_ms_ -= TICK_MS;
++contador_;
}
coche_.tick(delta_ms);
render();
if (JI_AnyKey() || contador_ >= CONTADOR_MAX) {
writeTrickIni();
fade_.startFadeOut();
phase_ = Phase::FadingOut;
}
break;
}
case Phase::FadingOut:
fade_.tick(delta_ms);
if (fade_.done()) {
info::ctx.num_piramide = 255;
phase_ = Phase::Done;
}
break;
case Phase::Done:
break;
}
}
} // namespace scenes

View File

@@ -0,0 +1,52 @@
#pragma once
#include "core/jail/jdraw8.hpp"
#include "scenes/frame_animator.hpp"
#include "scenes/palette_fade.hpp"
#include "scenes/scene.hpp"
#include "scenes/surface_handle.hpp"
namespace scenes {
// Crèdits finals del joc. Reemplaça `ModuleSequence::doCredits()`.
//
// Flux:
// 1. Carrega final.gif (sprites de crèdits) i 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; }
private:
enum class Phase { Rolling,
FadingOut,
Done };
void render();
void writeTrickIni();
SurfaceHandle vaddr2_; // final.gif (sprites i coches)
SurfaceHandle vaddr3_; // 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};
};
} // namespace scenes