step 4: intro_new_logo_scene substituix doIntroNewLogo(); doIntroSprites exposat temporalment

This commit is contained in:
2026-04-15 23:28:22 +02:00
parent 8720e775a0
commit ad38fc09cf
8 changed files with 305 additions and 127 deletions

View File

@@ -21,6 +21,7 @@
#include "game/modulesequence.hpp"
#include "game/options.hpp"
#include "scenes/banner_scene.hpp"
#include "scenes/intro_new_logo_scene.hpp"
#include "scenes/menu_scene.hpp"
#include "scenes/mort_scene.hpp"
#include "scenes/scene.hpp"
@@ -111,6 +112,15 @@ void Director::init() {
for (int p = 2; p <= 5; ++p) {
registry.registerScene(p, [] { return std::make_unique<scenes::BannerScene>(); });
}
// 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.
registry.registerScene(255, []() -> std::unique_ptr<scenes::Scene> {
if (Options::game.use_new_logo) {
return std::make_unique<scenes::IntroNewLogoScene>();
}
return nullptr;
});
GameFiber::init(gameFiberEntry);
}

View File

@@ -108,10 +108,9 @@ void play_music(const char* music, bool loop = -1) {
}
void ModuleSequence::doIntro() {
if (Options::game.use_new_logo) {
doIntroNewLogo();
return;
}
// Branca `if (use_new_logo)` eliminada: scenes::IntroNewLogoScene
// agafa eixe camí via el SceneRegistry. Aquest `doIntro()` legacy
// només s'executa quan `use_new_logo == false`.
JG_SetUpdateTicks(1000);
@@ -820,128 +819,7 @@ void ModuleSequence::doIntroSprites(JD8_Surface gfx) {
JD8_FreeSurface(gfx);
}
void ModuleSequence::doIntroNewLogo() {
// Coordenades mesurades del wordmark "Jailgames" dins logo/logo_new.gif
// (imatge 320x200, fons negre = index 0, lletres en index 17 = verd brillant).
// TUNE: ajusta aquests valors si canvies el logo.
constexpr int LOGO_SRC_X = 60; // x d'inici de la 'J' a la imatge font
constexpr int LOGO_SRC_Y = 158; // y del top del wordmark a la imatge font
constexpr int LOGO_DST_Y = 78; // y de destinació en pantalla (igual que logo vell)
constexpr int LOGO_HEIGHT = 28; // alçada del wordmark
// Amplada del crop des de LOGO_SRC_X fins al final de cada lletra
// (J, Ja, Jai, Jail, Jailg, Jailga, Jailgam, Jailgame, Jailgames):
constexpr int LETTER_WIDTHS[9] = {16, 39, 50, 69, 92, 115, 146, 169, 188};
// Cursor horitzontal (subratllat) al peu de la lletra següent que apareixerà.
// x absolut en pantalla, on comença cada cursor (just després de l'última lletra revelada):
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; // peu de la lletra (y=103)
constexpr Uint8 CURSOR_COLOR = 17; // mateix index verd que les lletres (cicla amb el palette)
JG_SetUpdateTicks(1000);
play_music("00000003.ogg");
JD8_Surface gfx = JD8_LoadSurface("logo/logo_new.gif");
JD8_Palette pal = JD8_LoadPalette("logo/logo_new.gif");
JD8_SetScreenPalette(pal);
// Surface auxiliar plena amb el color del cursor, per poder "blittejar" rectangles.
JD8_Surface cursor_surf = JD8_NewSurface();
memset(cursor_surf, CURSOR_COLOR, 64000);
auto cleanup = [&]() {
JD8_FreeSurface(gfx);
JD8_FreeSurface(cursor_surf);
};
auto waitTick = [&]() -> bool {
// Retorna true si cal sortir (tecla o quitting).
while (!JG_ShouldUpdate()) {
JI_Update();
if (JI_AnyKey() || JG_Quitting()) return true;
}
return false;
};
JD8_ClearScreen(0);
JD8_Flip();
if (waitTick()) {
cleanup();
return;
}
JG_SetUpdateTicks(150);
// Revelat progressiu lletra-a-lletra amb cursor parpadejant (subratllat horitzontal).
for (int i = 0; i < 9; i++) {
// Frame amb cursor visible
JD8_ClearScreen(0);
JD8_Blit(LOGO_SRC_X, LOGO_DST_Y, gfx, LOGO_SRC_X, LOGO_SRC_Y, LETTER_WIDTHS[i], LOGO_HEIGHT);
JD8_Blit(CURSOR_X[i], CURSOR_Y, cursor_surf, 0, 0, CURSOR_W, CURSOR_H);
JD8_Flip();
if (waitTick()) {
cleanup();
return;
}
// Frame sense cursor (parpadeig)
JD8_ClearScreen(0);
JD8_Blit(LOGO_SRC_X, LOGO_DST_Y, gfx, LOGO_SRC_X, LOGO_SRC_Y, LETTER_WIDTHS[i], LOGO_HEIGHT);
JD8_Flip();
if (waitTick()) {
cleanup();
return;
}
}
// Mostra el logo complet amb el cursor final fix un moment més.
JG_SetUpdateTicks(200);
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);
JD8_Flip();
if (waitTick()) {
cleanup();
return;
}
// Treu el cursor abans del cicle de paleta (els seus pixels cicla rien amb les lletres).
JD8_ClearScreen(0);
JD8_Blit(LOGO_SRC_X, LOGO_DST_Y, gfx, LOGO_SRC_X, LOGO_SRC_Y, LETTER_WIDTHS[8], LOGO_HEIGHT);
JD8_Flip();
// Cicle de paleta final (mateix efecte que l'intro original, indexs 16-31).
JG_SetUpdateTicks(20);
for (int j = 0; j < 256; j++) {
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--;
}
JD8_Flip();
if (waitTick()) {
cleanup();
return;
}
}
// Espera abans d'entrar a les animacions de sprites (igual que l'intro vella).
if (waitTick()) {
cleanup();
return;
}
// doIntroSprites pren propietat de gfx i el allibera ell mateix.
JD8_FreeSurface(cursor_surf);
doIntroSprites(gfx);
}
// doIntroNewLogo() — migrat a scenes::IntroNewLogoScene (source/scenes/intro_new_logo_scene.cpp)
// doMenu() — migrat a scenes::MenuScene (source/scenes/menu_scene.cpp)

View File

@@ -11,10 +11,15 @@ class ModuleSequence {
int Go();
// Exposat temporalment (public) fins al Step 9 del pla de migració,
// quan `doIntroSprites` es reescriurà com a scenes::IntroSpritesScene.
// Mentrestant, scenes::IntroNewLogoScene el crida al final del seu
// ciclo per delegar la part de les animacions de sprites.
void doIntroSprites(Uint8* gfx);
private:
void doIntro();
void doIntroNewLogo();
void doIntroSprites(Uint8* gfx);
// doMenu() → migrat a scenes::MenuScene
void doSlides();
// doBanner() → migrat a scenes::BannerScene

View File

@@ -0,0 +1,211 @@
#include "scenes/intro_new_logo_scene.hpp"
#include <cstring>
#include "core/jail/jdraw8.hpp"
#include "core/jail/jinput.hpp"
#include "game/modulesequence.hpp"
#include "scenes/scene_utils.hpp"
namespace {
// Coordenades mesurades del wordmark "Jailgames" dins logo/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;
} // namespace
namespace scenes {
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("00000003.ogg");
gfx_ = SurfaceHandle("logo/logo_new.gif");
pal_ = JD8_LoadPalette("logo/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::Delegate:
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 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;
}
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;
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::FinalWait:
phase_acc_ms_ += delta_ms;
render();
if (phase_acc_ms_ >= FINAL_WAIT_MS) {
phase_ = Phase::Delegate;
}
break;
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à.
ModuleSequence legacy;
legacy.doIntroSprites(gfx_.get());
phase_ = Phase::Done;
break;
}
case Phase::Done:
break;
}
}
} // namespace scenes

View File

@@ -0,0 +1,64 @@
#pragma once
#include "core/jail/jdraw8.hpp"
#include "scenes/scene.hpp"
#include "scenes/surface_handle.hpp"
namespace scenes {
// Intro "moderna" del logo Jailgames amb revelat lletra-a-lletra +
// ciclo de paleta final. Reemplaça `ModuleSequence::doIntroNewLogo()`.
//
// Flux:
// 1. Carrega logo/logo_new.gif, arranca música "00000003.ogg" i posa
// la paleta directament (sense fade-in). Mostra pantalla negra 1s.
// 2. Revelat: 9 lletres × 2 frames (amb cursor / sense cursor), 150 ms
// cada frame.
// 3. Logo complet amb cursor fix 200 ms.
// 4. Cicle de paleta de 256 passos modificant índexs 1631 cada 20 ms.
// 5. Espera final 20 ms.
// 6. Delega a la funció legacy `ModuleSequence::doIntroSprites(gfx)`
// (que s'executa dins del mateix fiber i fa els seus propis Flips
// cooperatius). Aquesta delegació desapareixerà al Step 9 del pla,
// quan `doIntroSprites` es reescriga com a `IntroSpritesScene`.
//
// 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; }
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
Delegate, // delega a doIntroSprites legacy i marca done
Done,
};
void render();
void advancePaletteCycle();
SurfaceHandle gfx_;
SurfaceHandle cursor_surf_;
JD8_Palette pal_{nullptr}; // propietat transferida a main_palette via SetScreenPalette
Phase phase_{Phase::Initial};
int phase_acc_ms_{0};
int reveal_letter_{0};
bool reveal_cursor_visible_{true};
int palette_step_{0};
};
} // namespace scenes

View File

@@ -28,4 +28,9 @@ void SurfaceHandle::reset(const char* file) {
surface_ = file ? JD8_LoadSurface(file) : nullptr;
}
void SurfaceHandle::adopt(JD8_Surface raw) {
if (surface_) JD8_FreeSurface(surface_);
surface_ = raw;
}
} // namespace scenes

View File

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