#include "game/scenes/intro_sprites_scene.hpp" #include #include #include "core/jail/jdraw8.hpp" #include "core/jail/jinput.hpp" #include "game/options.hpp" namespace { // Duració d'un pas. El vell doIntroSprites feia JG_SetUpdateTicks(20); // cada iteració del seu for (i) consumia un tick de 20 ms. constexpr int TICK_MS = 20; // Taules de frames. Ubicacions de cada sprite dins el gfx de la intro // (gfx/logo.gif o gfx/logo_new.gif — el layout de sprites és el mateix). // Cada sprite ocupa 15×15 px, disposats horitzontalment per fila. // Els valors són els offsets x (la y la posa l'invocador al src_y). // Derivats dels `fr_ani_N[i] = ...` del vell doIntroSprites. constexpr Uint16 fr1[] = {0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180}; // camina dreta (y=0) constexpr Uint16 fr2[] = {0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180}; // camina esquerra (y=15) constexpr Uint16 fr3[] = {0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150}; // trau mapa dreta (y=30) constexpr Uint16 fr4[] = {0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150}; // trau mapa esquerra (y=45) constexpr Uint16 fr5[] = {165, 180, 195, 210, 225, 240, 255, 270, 285, 300, 300, 285, 270, 255, 240, 225, 210, 195, 180, 165}; // bot de susto (y=45, mirror) constexpr Uint16 fr6[] = {0, 15, 30, 45, 60, 75, 90, 105}; // momia (y=60) constexpr Uint16 fr7[] = {75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 255, 270, // paper (y=75, idx 0..13) 0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210}; // sombra (y=105, idx 14..28) constexpr Uint16 fr8[] = {15, 30, 45, 60}; // pedra (y=75) constexpr Uint16 fr9[] = {0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225}; // prota ball (y=120) constexpr Uint16 fr10[] = {0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225}; // momia ball (y=135) constexpr Uint16 fr11[] = {15, 30, 45, 60, 75, 60}; // altaveu (y=90, [5]=[3] pel loop de 4) constexpr Uint16 CREU = 75; // src_y de la creu (overlay) constexpr Uint16 INTERROGANT = 90; // src_y del signe d'interrogant // Equivalent de la funció `drawIntroWordmark` de modulesequence.cpp. // Branqueja segons use_new_logo perquè la mateixa sub-escena es // reutilitza des de IntroScene (logo vell) i IntroNewLogoScene (logo // nou) amb arxius diferents però mateix layout de sprites. void drawWordmark(const Uint8* gfx) { if (Options::game.use_new_logo) { // Centrat: (320 − 188) / 2 = 66 (IntroNewLogoScene usa la mateixa x). Jd8::blit(66, 78, gfx, 60, 158, 188, 28); } else { Jd8::blit(43, 78, gfx, 43, 155, 231, 45); } } using RenderFn = void (*)(const Uint8*, int); // Una fase — rang [start_i..end_i] inclusive (direcció implícita per // signe), funció de render, i flag d'skippable. Totes les fases actuals // són skippables; el flag es conserva per si alguna futura ha de ser // no interrompuda (p.ex. un logo fatídic que cal veure sencer). struct SpritePhase { int start_i; int end_i; RenderFn render; bool skippable; }; // ========================================================================= // Variant 0 — Interrogant / Momia // ========================================================================= void v0_walk_right(const Uint8* gfx, int i) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(i, 150, gfx, fr1[(i / 5) % 13], 0, 15, 15, 0); } void v0_pull_map_right(const Uint8* gfx, int i) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(200, 150, gfx, fr3[std::min(i / 5, 10)], 30, 15, 15, 0); } void v0_walk_left_to_80(const Uint8* gfx, int i) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(i, 150, gfx, fr2[(i / 5) % 13], 15, 15, 15, 0); } void v0_pull_map_left(const Uint8* gfx, int i) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(80, 150, gfx, fr4[std::min(i / 5, 10)], 45, 15, 15, 0); } void v0_momia_left(const Uint8* gfx, int i) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(i, 150, gfx, fr6[(i / 5) % 8], 60, 15, 15, 0); Jd8::blitCK(80, 150, gfx, fr4[10], 45, 15, 15, 0); } void v0_turn(const Uint8* gfx, int /*i*/) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(80, 150, gfx, fr1[1], 0, 15, 15, 0); Jd8::blitCK(95, 150, gfx, fr6[4], 60, 15, 15, 0); Jd8::blitCK(80, 133, gfx, 0, INTERROGANT, 15, 15, 0); } void v0_jump1(const Uint8* gfx, int i) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(80, 150 - ((i % 50) / 5), gfx, fr5[std::min(i / 5, 19)], 45, 15, 15, 0); Jd8::blitCK(95, 150, gfx, fr6[4], 60, 15, 15, 0); } void v0_jump2(const Uint8* gfx, int i) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(80, 140 + ((i % 50) / 5), gfx, fr5[std::min(i / 5, 19)], 45, 15, 15, 0); Jd8::blitCK(95, 150, gfx, fr6[4], 60, 15, 15, 0); } void v0_walk_final(const Uint8* gfx, int i) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(i, 150, gfx, fr2[(i / 5) % 13], 15, 15, 15, 0); Jd8::blitCK(95, 150, gfx, fr6[4], 60, 15, 15, 0); } void v0_final(const Uint8* gfx, int /*i*/) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(95, 150, gfx, fr6[4], 60, 15, 15, 0); Jd8::blitCK(95, 133, gfx, 0, INTERROGANT, 15, 15, 0); } constexpr SpritePhase variant_0[] = { {.start_i = 0, .end_i = 200, .render = v0_walk_right, .skippable = true}, {.start_i = 0, .end_i = 200, .render = v0_pull_map_right, .skippable = true}, {.start_i = 200, .end_i = 0, .render = v0_pull_map_right, .skippable = true}, // guarda el mapa (reprodueix inversament) {.start_i = 200, .end_i = 80, .render = v0_walk_left_to_80, .skippable = true}, {.start_i = 0, .end_i = 200, .render = v0_pull_map_left, .skippable = true}, {.start_i = 300, .end_i = 95, .render = v0_momia_left, .skippable = true}, {.start_i = 0, .end_i = 50, .render = v0_turn, .skippable = true}, {.start_i = 0, .end_i = 49, .render = v0_jump1, .skippable = true}, {.start_i = 50, .end_i = 99, .render = v0_jump2, .skippable = true}, {.start_i = 80, .end_i = 0, .render = v0_walk_final, .skippable = true}, {.start_i = 0, .end_i = 150, .render = v0_final, .skippable = true}, }; // ========================================================================= // Variant 1 — Creu / Pedra // ========================================================================= void v1_walk_right(const Uint8* gfx, int i) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(200, 155, gfx, 0, CREU, 15, 15, 255); Jd8::blitCK(i, 150, gfx, fr1[(i / 5) % 13], 0, 15, 15, 255); } void v1_pull_map(const Uint8* gfx, int i) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(200, 155, gfx, 0, CREU, 15, 15, 255); Jd8::blitCK(200, 150, gfx, fr3[std::min(i / 5, 10)], 30, 15, 15, 255); } void v1_interrogant(const Uint8* gfx, int /*i*/) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(200, 155, gfx, 0, CREU, 15, 15, 255); Jd8::blitCK(200, 134, gfx, 0, INTERROGANT, 15, 15, 255); Jd8::blitCK(200, 150, gfx, fr3[10], 30, 15, 15, 255); } void v1_drop_map(const Uint8* gfx, int i) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(200, 155, gfx, 0, CREU, 15, 15, 255); const int idx = std::min(i / 5, 28); // fr7 té 29 frames dividits en dos grups: paper (idx 0..13, src_y=75) // i sombra (idx 14..28, src_y=105). El vell feia una branca al bucle. if (idx <= 13) { Jd8::blitCK(200, 150, gfx, fr7[idx], 75, 15, 15, 255); } else { Jd8::blitCK(200, 150, gfx, fr7[idx], 105, 15, 15, 255); } } void v1_stone_fall(const Uint8* gfx, int i) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(200, 155, gfx, 0, CREU, 15, 15, 255); Jd8::blitCK(200, 150, gfx, fr7[28], 105, 15, 15, 255); Jd8::blitCK(200, i * 2, gfx, fr8[0], 75, 15, 15, 255); } void v1_stone_break(const Uint8* gfx, int i) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(200, 155, gfx, 0, CREU, 15, 15, 255); Jd8::blitCK(200, 150, gfx, fr8[i / 10], 75, 15, 15, 255); } void v1_final(const Uint8* gfx, int /*i*/) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(200, 155, gfx, 0, CREU, 15, 15, 255); Jd8::blitCK(200, 150, gfx, fr8[1], 75, 15, 15, 255); Jd8::blitCK(185, 150, gfx, fr8[2], 75, 15, 15, 255); Jd8::blitCK(215, 150, gfx, fr8[3], 75, 15, 15, 255); } constexpr SpritePhase variant_1[] = { {.start_i = 0, .end_i = 200, .render = v1_walk_right, .skippable = true}, {.start_i = 0, .end_i = 300, .render = v1_pull_map, .skippable = true}, {.start_i = 0, .end_i = 100, .render = v1_interrogant, .skippable = true}, {.start_i = 0, .end_i = 200, .render = v1_drop_map, .skippable = true}, {.start_i = 0, .end_i = 75, .render = v1_stone_fall, .skippable = true}, {.start_i = 0, .end_i = 19, .render = v1_stone_break, .skippable = true}, {.start_i = 0, .end_i = 200, .render = v1_final, .skippable = true}, }; // ========================================================================= // Variant 2 — Ball de carnaval // ========================================================================= void v2_approach(const Uint8* gfx, int i) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(i, 150, gfx, fr1[(i / 5) % 13], 0, 15, 15, 255); Jd8::blitCK(304 - i, 150, gfx, fr6[(i / 10) % 8], 60, 15, 15, 255); } void v2_still(const Uint8* gfx, int /*i*/) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(145, 150, gfx, fr1[1], 0, 15, 15, 255); Jd8::blitCK(160, 150, gfx, fr6[1], 60, 15, 15, 255); } void v2_horn(const Uint8* gfx, int i) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(125, 150, gfx, fr11[(i / 10) % 2], 90, 15, 15, 255); Jd8::blitCK(145, 150, gfx, fr1[1], 0, 15, 15, 255); Jd8::blitCK(160, 150, gfx, fr6[1], 60, 15, 15, 255); } void v2_ball(const Uint8* gfx, int i) { Jd8::clearScreen(0); drawWordmark(gfx); Jd8::blitCK(145, 150, gfx, fr9[(i / 10) % 16], 120, 15, 15, 255); Jd8::blitCK(160, 150, gfx, fr10[(i / 10) % 16], 135, 15, 15, 255); Jd8::blitCK(125, 150, gfx, fr11[((i / 5) % 4) + 2], 90, 15, 15, 255); } constexpr SpritePhase variant_2[] = { {.start_i = 0, .end_i = 145, .render = v2_approach, .skippable = true}, {.start_i = 0, .end_i = 100, .render = v2_still, .skippable = true}, {.start_i = 0, .end_i = 50, .render = v2_horn, .skippable = true}, {.start_i = 0, .end_i = 800, .render = v2_ball, .skippable = true}, }; // ========================================================================= // Dispatch per variant // ========================================================================= auto variant_table(int variant) -> const SpritePhase* { switch (variant) { case 0: return variant_0; case 1: return variant_1; case 2: return variant_2; default: return variant_0; } } auto variant_length(int variant) -> int { switch (variant) { case 0: return sizeof(variant_0) / sizeof(variant_0[0]); case 1: return sizeof(variant_1) / sizeof(variant_1[0]); case 2: return sizeof(variant_2) / sizeof(variant_2[0]); default: return 0; } } auto phase_step_count(const SpritePhase& p) -> int { return std::abs(p.end_i - p.start_i) + 1; } auto phase_current_i(const SpritePhase& p, int step) -> int { return p.end_i >= p.start_i ? p.start_i + step : p.start_i - step; } } // namespace namespace scenes { IntroSpritesScene::IntroSpritesScene(SurfaceHandle&& gfx) : gfx_(std::move(gfx)) {} void IntroSpritesScene::onEnter() { // El vell doIntroSprites feia `rand() % 3` al principi. El seed ve // establert per `srand(time(0))` al boot del joc (info.cpp / main), // així que la variant canvia entre execucions. variant_ = std::rand() % 3; phase_ = 0; phase_step_ = 0; step_acc_ms_ = 0; done_ = false; // Renderitzem ja el primer frame (step 0 de la primera fase) perquè // el Jd8::flip del mini-loop del fiber el pinte al primer cicle. const SpritePhase* phases = variant_table(variant_); phases[0].render(gfx_.get(), phase_current_i(phases[0], 0)); } void IntroSpritesScene::tick(int delta_ms) { if (done_) { return; } const SpritePhase* phases = variant_table(variant_); const int num_phases = variant_length(variant_); // Skip per tecla. Durant la fase marcada com a no skippable (només // v0_final al vell codi) s'ignora — preserva la semàntica del vell // bucle final de la variant 0 que no cridava wait_frame_or_skip. if (phases[phase_].skippable && JI_AnyKey()) { done_ = true; return; } step_acc_ms_ += delta_ms; while (step_acc_ms_ >= TICK_MS && !done_) { step_acc_ms_ -= TICK_MS; ++phase_step_; if (phase_step_ >= phase_step_count(phases[phase_])) { ++phase_; phase_step_ = 0; if (phase_ >= num_phases) { done_ = true; return; } } } phases[phase_].render(gfx_.get(), phase_current_i(phases[phase_], phase_step_)); } } // namespace scenes