Files
aee/source/scenes/intro_new_logo_scene.cpp

221 lines
7.4 KiB
C++

#include "scenes/intro_new_logo_scene.hpp"
#include <cstring>
#include "core/jail/jdraw8.hpp"
#include "core/jail/jinput.hpp"
#include "game/info.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 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_) {
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 *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_.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;
}
case Phase::Done:
break;
}
}
} // namespace scenes