219 lines
8.3 KiB
C++
219 lines
8.3 KiB
C++
#include "scenes/intro_scene.hpp"
|
||
|
||
#include "core/jail/jdraw8.hpp"
|
||
#include "core/jail/jinput.hpp"
|
||
#include "game/info.hpp"
|
||
#include "scenes/scene_utils.hpp"
|
||
|
||
namespace {
|
||
|
||
// Timings idèntics als del vell `doIntro()`: el JG_SetUpdateTicks(1000)
|
||
// inicial, (100) per a les 3 primeres lletres (J, A, I), (200) per a
|
||
// "JAIL" i el seu clear, (100) per a les 4 lletres centrals
|
||
// (G, A, M, E) i (200) per a la resta fins al cicle de paleta.
|
||
constexpr int INITIAL_MS = 1000;
|
||
constexpr int PALETTE_CYCLE_STEP_MS = 20;
|
||
constexpr int PALETTE_CYCLE_STEPS = 256;
|
||
constexpr int FINAL_WAIT_MS = 200;
|
||
|
||
// Un pas del revelat. Dos blits configurables (cos del wordmark + avió)
|
||
// més una variant per al wordmark sencer i un flag de ClearScreen previ.
|
||
struct RevealStep {
|
||
int duration_ms;
|
||
int body_w; // amplada del blit body (43,78) ← (43,155, body_w, 45); 0 si s'usa wordmark
|
||
int plane_x; // x del blit de l'avió (274,155, 27×45); -1 = no avió
|
||
bool clear; // fa ClearScreen(0) abans dels blits
|
||
bool wordmark; // usa drawWordmark() en lloc del blit body (wordmark complet)
|
||
};
|
||
|
||
constexpr RevealStep REVEAL_STEPS[] = {
|
||
{100, 27, 68, false, false}, // J
|
||
{100, 53, 96, false, false}, // JA
|
||
{100, 66, 109, false, false}, // JAI
|
||
{200, 92, 136, false, false}, // JAIL
|
||
{200, 92, -1, true, false}, // JAIL (clear, sense avió — parpelleig)
|
||
{100, 118, 160, false, false}, // JAILG
|
||
{100, 145, 188, false, false}, // JAILGA
|
||
{100, 178, 221, false, false}, // JAILGAM
|
||
{100, 205, 248, false, false}, // JAILGAME
|
||
{200, 0, 274, false, true}, // JAILGAMES (wordmark complet) + avió
|
||
{200, 0, -1, true, true}, // JAILGAMES (clear, sense avió)
|
||
{200, 0, 274, false, true}, // JAILGAMES + avió (sense clear)
|
||
{200, 0, -1, true, true}, // JAILGAMES (clear, sense avió)
|
||
{200, 0, 274, false, true}, // JAILGAMES + avió (sense clear)
|
||
{200, 0, -1, true, true}, // JAILGAMES (clear, sense avió)
|
||
};
|
||
constexpr int REVEAL_COUNT = sizeof(REVEAL_STEPS) / sizeof(REVEAL_STEPS[0]);
|
||
|
||
// Branca `!use_new_logo` del drawIntroWordmark de modulesequence.cpp:
|
||
// blit únic del wordmark "JAILGAMES" complet (231×45 al destí 43,78).
|
||
// IntroScene només s'activa quan use_new_logo == false, així que la
|
||
// branca use_new_logo d'aquell helper aquí no es necessita.
|
||
void drawWordmark(JD8_Surface gfx) {
|
||
JD8_Blit(43, 78, gfx, 43, 155, 231, 45);
|
||
}
|
||
|
||
} // namespace
|
||
|
||
namespace scenes {
|
||
|
||
IntroScene::IntroScene() = default;
|
||
|
||
IntroScene::~IntroScene() {
|
||
// No alliberem `pal_`: JD8_SetScreenPalette n'ha pres ownership i el
|
||
// proper SetScreenPalette / FadeToPal la lliurarà. Alliberar-la ací
|
||
// provocaria double free.
|
||
}
|
||
|
||
void IntroScene::onEnter() {
|
||
playMusic("music/menu.ogg");
|
||
|
||
gfx_ = SurfaceHandle("gfx/logo.gif");
|
||
pal_ = JD8_LoadPalette("gfx/logo.gif");
|
||
JD8_SetScreenPalette(pal_);
|
||
|
||
JD8_ClearScreen(0);
|
||
|
||
phase_ = Phase::InitialWait;
|
||
phase_acc_ms_ = 0;
|
||
reveal_index_ = 0;
|
||
palette_step_ = 0;
|
||
}
|
||
|
||
void IntroScene::render() {
|
||
switch (phase_) {
|
||
case Phase::InitialWait:
|
||
JD8_ClearScreen(0);
|
||
break;
|
||
|
||
case Phase::Reveal: {
|
||
const RevealStep& s = REVEAL_STEPS[reveal_index_];
|
||
if (s.clear) JD8_ClearScreen(0);
|
||
if (s.wordmark) {
|
||
drawWordmark(gfx_);
|
||
} else if (s.body_w > 0) {
|
||
JD8_Blit(43, 78, gfx_, 43, 155, s.body_w, 45);
|
||
}
|
||
if (s.plane_x >= 0) {
|
||
JD8_Blit(s.plane_x, 78, gfx_, 274, 155, 27, 45);
|
||
}
|
||
break;
|
||
}
|
||
|
||
case Phase::PaletteCycle:
|
||
case Phase::FinalWait:
|
||
// Wordmark complet fix mentre cicla la paleta — l'últim
|
||
// pas del revelat (PAS 15) deixa la pantalla en aquest mateix
|
||
// estat, i el vell doIntro no redibuixava durant el cicle.
|
||
JD8_ClearScreen(0);
|
||
drawWordmark(gfx_);
|
||
break;
|
||
|
||
case Phase::Sprites:
|
||
case Phase::Done:
|
||
break;
|
||
}
|
||
}
|
||
|
||
void IntroScene::advancePaletteCycle() {
|
||
// Replica exacta del cicle del doIntro vell sobre pal[16..31] — el
|
||
// grup del verd brillant del logo. Index 17 s'acosta a blanc mentre
|
||
// els altres convergeixen cap al mateix gris mitjà.
|
||
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 IntroScene::tick(int delta_ms) {
|
||
// Qualsevol tecla durant revelat/paleta salta TOTA la intro
|
||
// (inclou saltar la fase de sprites). Durant Sprites deixem que
|
||
// la sub-escena gestione el seu propi skip internament, que a més
|
||
// respecta la fase "final" no skippable de la variant 0.
|
||
if (phase_ != Phase::Sprites && phase_ != Phase::Done && JI_AnyKey()) {
|
||
info::ctx.num_piramide = 0;
|
||
phase_ = Phase::Done;
|
||
return;
|
||
}
|
||
|
||
switch (phase_) {
|
||
case Phase::InitialWait:
|
||
phase_acc_ms_ += delta_ms;
|
||
render();
|
||
if (phase_acc_ms_ >= INITIAL_MS) {
|
||
phase_ = Phase::Reveal;
|
||
phase_acc_ms_ = 0;
|
||
reveal_index_ = 0;
|
||
}
|
||
break;
|
||
|
||
case Phase::Reveal:
|
||
phase_acc_ms_ += delta_ms;
|
||
render();
|
||
if (phase_acc_ms_ >= REVEAL_STEPS[reveal_index_].duration_ms) {
|
||
phase_acc_ms_ = 0;
|
||
++reveal_index_;
|
||
if (reveal_index_ >= REVEAL_COUNT) {
|
||
phase_ = Phase::PaletteCycle;
|
||
}
|
||
}
|
||
break;
|
||
|
||
case Phase::PaletteCycle:
|
||
phase_acc_ms_ += delta_ms;
|
||
// Avancem tants passos com permet el delta, per evitar
|
||
// saltar-ne si el frame ha vingut lent.
|
||
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::Sprites;
|
||
}
|
||
break;
|
||
|
||
case Phase::Sprites:
|
||
// Sub-escena construïda al vol al primer tick d'aquesta fase.
|
||
// Transferim el gfx_ per move — la sub-escena se n'ocupa
|
||
// fins que es destruix. Una vegada feta, els ticks delegats
|
||
// avancen l'animació dels sprites.
|
||
if (!sprites_scene_) {
|
||
sprites_scene_ = std::make_unique<IntroSpritesScene>(std::move(gfx_));
|
||
sprites_scene_->onEnter();
|
||
}
|
||
sprites_scene_->tick(delta_ms);
|
||
if (sprites_scene_->done()) {
|
||
// Equivalent al vell `Go()` post-switch: passem al menú.
|
||
// Sense açò el while del fiber tornaria a crear IntroScene
|
||
// infinitament amb num_piramide encara a 255.
|
||
info::ctx.num_piramide = 0;
|
||
phase_ = Phase::Done;
|
||
}
|
||
break;
|
||
|
||
case Phase::Done:
|
||
break;
|
||
}
|
||
}
|
||
|
||
} // namespace scenes
|