refactor: extreure helpers per reduir complexitat cognitiva (tidy net)
This commit is contained in:
@@ -195,97 +195,86 @@ namespace Gamepad {
|
||||
SDL_PushEvent(&e);
|
||||
}
|
||||
|
||||
// Estat agregat d'un frame: D-pad i stick combinats, més botons frontals.
|
||||
struct PadState {
|
||||
bool up;
|
||||
bool down;
|
||||
bool left;
|
||||
bool right;
|
||||
bool south;
|
||||
bool east;
|
||||
bool west;
|
||||
bool north;
|
||||
bool start;
|
||||
bool back;
|
||||
};
|
||||
|
||||
static auto readPadState() -> PadState {
|
||||
const Sint16 LX = SDL_GetGamepadAxis(pad, SDL_GAMEPAD_AXIS_LEFTX);
|
||||
const Sint16 LY = SDL_GetGamepadAxis(pad, SDL_GAMEPAD_AXIS_LEFTY);
|
||||
return PadState{
|
||||
.up = SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_DPAD_UP) || LY < -STICK_DEADZONE,
|
||||
.down = SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_DPAD_DOWN) || LY > STICK_DEADZONE,
|
||||
.left = SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_DPAD_LEFT) || LX < -STICK_DEADZONE,
|
||||
.right = SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT) || LX > STICK_DEADZONE,
|
||||
.south = SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_SOUTH),
|
||||
.east = SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_EAST),
|
||||
.west = SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_WEST),
|
||||
.north = SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_NORTH),
|
||||
.start = SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_START),
|
||||
.back = SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_BACK),
|
||||
};
|
||||
}
|
||||
|
||||
static void handleMenuNavigation(const PadState& s) {
|
||||
if (s.up && !prev_up) { pushKey(SDL_SCANCODE_UP); }
|
||||
if (s.down && !prev_down) { pushKey(SDL_SCANCODE_DOWN); }
|
||||
if (s.left && !prev_left) { pushKey(SDL_SCANCODE_LEFT); }
|
||||
if (s.right && !prev_right) { pushKey(SDL_SCANCODE_RIGHT); }
|
||||
if (s.east && !prev_east) { pushKey(SDL_SCANCODE_RETURN); }
|
||||
if (s.south && !prev_south) { pushKey(SDL_SCANCODE_BACKSPACE); }
|
||||
// Mentre el menú està obert, el joc no ha de rebre moviment.
|
||||
Ji::setVirtualKey(SDL_SCANCODE_UP, Ji::VirtualSource::GAMEPAD, false);
|
||||
Ji::setVirtualKey(SDL_SCANCODE_DOWN, Ji::VirtualSource::GAMEPAD, false);
|
||||
Ji::setVirtualKey(SDL_SCANCODE_LEFT, Ji::VirtualSource::GAMEPAD, false);
|
||||
Ji::setVirtualKey(SDL_SCANCODE_RIGHT, Ji::VirtualSource::GAMEPAD, false);
|
||||
}
|
||||
|
||||
static void handleGameInput(const PadState& s) {
|
||||
Ji::setVirtualKey(SDL_SCANCODE_UP, Ji::VirtualSource::GAMEPAD, s.up);
|
||||
Ji::setVirtualKey(SDL_SCANCODE_DOWN, Ji::VirtualSource::GAMEPAD, s.down);
|
||||
Ji::setVirtualKey(SDL_SCANCODE_LEFT, Ji::VirtualSource::GAMEPAD, s.left);
|
||||
Ji::setVirtualKey(SDL_SCANCODE_RIGHT, Ji::VirtualSource::GAMEPAD, s.right);
|
||||
const bool ANY_FRONT_EDGE = (s.south && !prev_south) || (s.east && !prev_east) ||
|
||||
(s.west && !prev_west) || (s.north && !prev_north);
|
||||
if (ANY_FRONT_EDGE) {
|
||||
pushKey(SDL_SCANCODE_RETURN);
|
||||
}
|
||||
}
|
||||
|
||||
void update() {
|
||||
if (pad == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// D-pad
|
||||
bool dup = SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_DPAD_UP);
|
||||
bool ddn = SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
|
||||
bool dlt = SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
|
||||
bool drt = SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
|
||||
|
||||
// Stick esquerre amb dead-zone
|
||||
Sint16 lx = SDL_GetGamepadAxis(pad, SDL_GAMEPAD_AXIS_LEFTX);
|
||||
Sint16 ly = SDL_GetGamepadAxis(pad, SDL_GAMEPAD_AXIS_LEFTY);
|
||||
bool sup = ly < -STICK_DEADZONE;
|
||||
bool sdn = ly > STICK_DEADZONE;
|
||||
bool slt = lx < -STICK_DEADZONE;
|
||||
bool srt = lx > STICK_DEADZONE;
|
||||
|
||||
bool up = dup || sup;
|
||||
bool dn = ddn || sdn;
|
||||
bool lt = dlt || slt;
|
||||
bool rt = drt || srt;
|
||||
|
||||
// Botons frontals (layout SDL: SOUTH=A/Cross, EAST=B/Circle, WEST=X/Square, NORTH=Y/Triangle)
|
||||
bool south = SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_SOUTH);
|
||||
bool east = SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_EAST);
|
||||
bool west = SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_WEST);
|
||||
bool north = SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_NORTH);
|
||||
bool start = SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_START);
|
||||
bool back = SDL_GetGamepadButton(pad, SDL_GAMEPAD_BUTTON_BACK);
|
||||
|
||||
// Select (Back) → obre/tanca menú de servei (flanc)
|
||||
if (back && !prev_back) {
|
||||
pushKey(KeyConfig::scancode("menu_toggle"));
|
||||
}
|
||||
// Start → pausa (flanc)
|
||||
if (start && !prev_start) {
|
||||
pushKey(KeyConfig::scancode("pause_toggle"));
|
||||
}
|
||||
|
||||
const PadState S = readPadState();
|
||||
// Flancs globals: Select i Start sempre operen.
|
||||
if (S.back && !prev_back) { pushKey(KeyConfig::scancode("menu_toggle")); }
|
||||
if (S.start && !prev_start) { pushKey(KeyConfig::scancode("pause_toggle")); }
|
||||
if (Menu::isOpen()) {
|
||||
// Navegació del menú per flanc
|
||||
if (up && !prev_up) {
|
||||
pushKey(SDL_SCANCODE_UP);
|
||||
}
|
||||
if (dn && !prev_down) {
|
||||
pushKey(SDL_SCANCODE_DOWN);
|
||||
}
|
||||
if (lt && !prev_left) {
|
||||
pushKey(SDL_SCANCODE_LEFT);
|
||||
}
|
||||
if (rt && !prev_right) {
|
||||
pushKey(SDL_SCANCODE_RIGHT);
|
||||
}
|
||||
// EAST accepta, SOUTH cancela / endarrere
|
||||
if (east && !prev_east) {
|
||||
pushKey(SDL_SCANCODE_RETURN);
|
||||
}
|
||||
if (south && !prev_south) {
|
||||
pushKey(SDL_SCANCODE_BACKSPACE);
|
||||
}
|
||||
|
||||
// Assegura que el joc no rep tecles de moviment mentre el menú està obert
|
||||
Ji::setVirtualKey(SDL_SCANCODE_UP, Ji::VirtualSource::GAMEPAD, false);
|
||||
Ji::setVirtualKey(SDL_SCANCODE_DOWN, Ji::VirtualSource::GAMEPAD, false);
|
||||
Ji::setVirtualKey(SDL_SCANCODE_LEFT, Ji::VirtualSource::GAMEPAD, false);
|
||||
Ji::setVirtualKey(SDL_SCANCODE_RIGHT, Ji::VirtualSource::GAMEPAD, false);
|
||||
handleMenuNavigation(S);
|
||||
} else {
|
||||
// Moviment al joc — level-triggered (polling)
|
||||
Ji::setVirtualKey(SDL_SCANCODE_UP, Ji::VirtualSource::GAMEPAD, up);
|
||||
Ji::setVirtualKey(SDL_SCANCODE_DOWN, Ji::VirtualSource::GAMEPAD, dn);
|
||||
Ji::setVirtualKey(SDL_SCANCODE_LEFT, Ji::VirtualSource::GAMEPAD, lt);
|
||||
Ji::setVirtualKey(SDL_SCANCODE_RIGHT, Ji::VirtualSource::GAMEPAD, rt);
|
||||
// Qualsevol dels 4 botons frontals avança escenes (Ji::anyKey via Enter sintètic)
|
||||
if ((south && !prev_south) || (east && !prev_east) ||
|
||||
(west && !prev_west) || (north && !prev_north)) {
|
||||
pushKey(SDL_SCANCODE_RETURN);
|
||||
}
|
||||
handleGameInput(S);
|
||||
}
|
||||
|
||||
prev_up = up;
|
||||
prev_down = dn;
|
||||
prev_left = lt;
|
||||
prev_right = rt;
|
||||
prev_south = south;
|
||||
prev_east = east;
|
||||
prev_west = west;
|
||||
prev_north = north;
|
||||
prev_start = start;
|
||||
prev_back = back;
|
||||
prev_up = S.up;
|
||||
prev_down = S.down;
|
||||
prev_left = S.left;
|
||||
prev_right = S.right;
|
||||
prev_south = S.south;
|
||||
prev_east = S.east;
|
||||
prev_west = S.west;
|
||||
prev_north = S.north;
|
||||
prev_start = S.start;
|
||||
prev_back = S.back;
|
||||
}
|
||||
|
||||
} // namespace Gamepad
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "core/input/global_inputs.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include "core/input/key_config.hpp"
|
||||
@@ -23,131 +24,71 @@ namespace GlobalInputs {
|
||||
static bool texture_filter_prev = false;
|
||||
static bool render_info_prev = false;
|
||||
|
||||
// Patró comú: lectura amb detecció de flanc + acumulació al flag "consumed".
|
||||
// `on_press` només s'executa al flanc puja; `prev` es manté actualitzat.
|
||||
static auto edgeTrigger(const char* key_id, bool& prev, const std::function<void()>& on_press) -> bool {
|
||||
const bool PRESSED = Ji::keyPressed(KeyConfig::scancode(key_id));
|
||||
if (PRESSED && !prev) {
|
||||
on_press();
|
||||
}
|
||||
prev = PRESSED;
|
||||
return PRESSED;
|
||||
}
|
||||
|
||||
auto handle() -> bool {
|
||||
bool consumed = false;
|
||||
|
||||
// F1 — Reduir zoom
|
||||
bool dec_zoom = Ji::keyPressed(KeyConfig::scancode("dec_zoom"));
|
||||
if (dec_zoom && !dec_zoom_prev) {
|
||||
consumed |= edgeTrigger("dec_zoom", dec_zoom_prev, [] {
|
||||
Screen::get()->decZoom();
|
||||
char msg[32];
|
||||
snprintf(msg, sizeof(msg), Locale::get("notifications.zoom_fmt"), Screen::get()->getZoom());
|
||||
Overlay::showNotification(msg);
|
||||
}
|
||||
if (dec_zoom) {
|
||||
consumed = true;
|
||||
}
|
||||
dec_zoom_prev = dec_zoom;
|
||||
|
||||
// F2 — Augmentar zoom
|
||||
bool inc_zoom = Ji::keyPressed(KeyConfig::scancode("inc_zoom"));
|
||||
if (inc_zoom && !inc_zoom_prev) {
|
||||
});
|
||||
consumed |= edgeTrigger("inc_zoom", inc_zoom_prev, [] {
|
||||
Screen::get()->incZoom();
|
||||
char msg[32];
|
||||
snprintf(msg, sizeof(msg), Locale::get("notifications.zoom_fmt"), Screen::get()->getZoom());
|
||||
Overlay::showNotification(msg);
|
||||
}
|
||||
if (inc_zoom) {
|
||||
consumed = true;
|
||||
}
|
||||
inc_zoom_prev = inc_zoom;
|
||||
|
||||
// F3 — Toggle pantalla completa
|
||||
bool fullscreen = Ji::keyPressed(KeyConfig::scancode("fullscreen"));
|
||||
if (fullscreen && !fullscreen_prev) {
|
||||
});
|
||||
consumed |= edgeTrigger("fullscreen", fullscreen_prev, [] {
|
||||
Screen::get()->toggleFullscreen();
|
||||
Overlay::showNotification(Screen::get()->isFullscreen() ? Locale::get("notifications.fullscreen") : Locale::get("notifications.windowed"));
|
||||
}
|
||||
if (fullscreen) {
|
||||
consumed = true;
|
||||
}
|
||||
fullscreen_prev = fullscreen;
|
||||
|
||||
// F4 — Toggle shaders
|
||||
bool shader = Ji::keyPressed(KeyConfig::scancode("toggle_shader"));
|
||||
if (shader && !shader_prev) {
|
||||
});
|
||||
consumed |= edgeTrigger("toggle_shader", shader_prev, [] {
|
||||
Screen::get()->toggleShaders();
|
||||
Overlay::showNotification(Options::video.shader_enabled ? Locale::get("notifications.shader_on") : Locale::get("notifications.shader_off"));
|
||||
}
|
||||
if (shader) {
|
||||
consumed = true;
|
||||
}
|
||||
shader_prev = shader;
|
||||
|
||||
// F5 — Toggle aspect ratio 4:3
|
||||
bool aspect = Ji::keyPressed(KeyConfig::scancode("toggle_aspect_ratio"));
|
||||
if (aspect && !aspect_prev) {
|
||||
});
|
||||
consumed |= edgeTrigger("toggle_aspect_ratio", aspect_prev, [] {
|
||||
Screen::get()->toggleAspectRatio();
|
||||
Overlay::showNotification(Options::video.aspect_ratio_4_3 ? Locale::get("notifications.aspect_43") : Locale::get("notifications.aspect_square"));
|
||||
}
|
||||
if (aspect) {
|
||||
consumed = true;
|
||||
}
|
||||
aspect_prev = aspect;
|
||||
|
||||
// F6 — Toggle supersampling
|
||||
bool ss = Ji::keyPressed(KeyConfig::scancode("toggle_supersampling"));
|
||||
if (ss && !ss_prev) {
|
||||
});
|
||||
consumed |= edgeTrigger("toggle_supersampling", ss_prev, [] {
|
||||
if (Screen::get()->toggleSupersampling()) {
|
||||
Overlay::showNotification(Options::video.supersampling ? Locale::get("notifications.ss_on") : Locale::get("notifications.ss_off"));
|
||||
}
|
||||
}
|
||||
if (ss) {
|
||||
consumed = true;
|
||||
}
|
||||
ss_prev = ss;
|
||||
|
||||
// F7 — Canviar tipus de shader (PostFX ↔ CrtPi)
|
||||
bool next_shader = Ji::keyPressed(KeyConfig::scancode("next_shader"));
|
||||
if (next_shader && !next_shader_prev) {
|
||||
});
|
||||
consumed |= edgeTrigger("next_shader", next_shader_prev, [] {
|
||||
if (Screen::get()->nextShaderType()) {
|
||||
char msg[64];
|
||||
snprintf(msg, sizeof(msg), "%s: %s", Screen::get()->getActiveShaderName(), Screen::get()->getCurrentPresetName());
|
||||
Overlay::showNotification(msg);
|
||||
}
|
||||
}
|
||||
if (next_shader) {
|
||||
consumed = true;
|
||||
}
|
||||
next_shader_prev = next_shader;
|
||||
|
||||
// F8 — Pròxim preset del shader actiu
|
||||
bool next_preset = Ji::keyPressed(KeyConfig::scancode("next_shader_preset"));
|
||||
if (next_preset && !next_preset_prev) {
|
||||
});
|
||||
consumed |= edgeTrigger("next_shader_preset", next_preset_prev, [] {
|
||||
if (Screen::get()->nextPreset()) {
|
||||
char msg[64];
|
||||
snprintf(msg, sizeof(msg), Locale::get("notifications.preset_fmt"), Screen::get()->getCurrentPresetName());
|
||||
Overlay::showNotification(msg);
|
||||
}
|
||||
}
|
||||
if (next_preset) {
|
||||
consumed = true;
|
||||
}
|
||||
next_preset_prev = next_preset;
|
||||
|
||||
// F9 — Cicla filtre de textura (NEAREST ↔ LINEAR), sempre aplicat
|
||||
bool texture_filter = Ji::keyPressed(KeyConfig::scancode("cycle_texture_filter"));
|
||||
if (texture_filter && !texture_filter_prev) {
|
||||
});
|
||||
consumed |= edgeTrigger("cycle_texture_filter", texture_filter_prev, [] {
|
||||
Screen::get()->cycleTextureFilter(+1);
|
||||
Overlay::showNotification(Options::video.texture_filter == Options::TextureFilter::LINEAR
|
||||
? Locale::get("notifications.filter_linear")
|
||||
: Locale::get("notifications.filter_nearest"));
|
||||
}
|
||||
if (texture_filter) {
|
||||
consumed = true;
|
||||
}
|
||||
texture_filter_prev = texture_filter;
|
||||
|
||||
// F10 — Toggle render info (FPS, driver, shader)
|
||||
bool render_info = Ji::keyPressed(KeyConfig::scancode("toggle_render_info"));
|
||||
if (render_info && !render_info_prev) {
|
||||
});
|
||||
consumed |= edgeTrigger("toggle_render_info", render_info_prev, [] {
|
||||
Overlay::toggleRenderInfo();
|
||||
}
|
||||
if (render_info) {
|
||||
consumed = true;
|
||||
}
|
||||
render_info_prev = render_info;
|
||||
|
||||
});
|
||||
return consumed;
|
||||
}
|
||||
|
||||
|
||||
+126
-100
@@ -60,10 +60,10 @@ namespace Menu {
|
||||
const char* label;
|
||||
ItemKind kind;
|
||||
std::function<std::string()> get_value; // opcional
|
||||
std::function<void(int dir)> change; // per TOGGLE/CYCLE/INT_RANGE
|
||||
std::function<void()> enter; // per SUBMENU i ACTION
|
||||
SDL_Scancode* scancode{nullptr}; // per KEY_BIND
|
||||
std::function<bool()> visible; // nullptr ⇒ sempre visible
|
||||
std::function<void(int dir)> change; // per TOGGLE/CYCLE/INT_RANGE
|
||||
std::function<void()> enter; // per SUBMENU i ACTION
|
||||
SDL_Scancode* scancode{nullptr}; // per KEY_BIND
|
||||
std::function<bool()> visible; // nullptr ⇒ sempre visible
|
||||
};
|
||||
|
||||
struct Page {
|
||||
@@ -448,27 +448,30 @@ namespace Menu {
|
||||
capturing = nullptr;
|
||||
}
|
||||
|
||||
void handleKey(SDL_Scancode sc) {
|
||||
if (!isOpen()) {
|
||||
return;
|
||||
static void backOrClose() {
|
||||
if (stack.size() > 1) {
|
||||
popPage();
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
Page& page = stack.back();
|
||||
if (page.items.empty()) {
|
||||
// Pàgina buida — només backspace surt
|
||||
if (sc == SDL_SCANCODE_BACKSPACE) {
|
||||
if (stack.size() > 1) {
|
||||
popPage();
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
// Activació d'un ítem (RETURN/KP_ENTER): SUBMENU/ACTION criden enter,
|
||||
// KEY_BIND inicia captura, la resta avança change(+1).
|
||||
static void activateItem(Item& item) {
|
||||
if (item.kind == ItemKind::SUBMENU || item.kind == ItemKind::ACTION) {
|
||||
if (item.enter) {
|
||||
item.enter();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Si el cursor està sobre un ítem ocultat (p. ex. una acció anterior el va ocultar),
|
||||
// reubica'l al pròxim visible abans de processar l'entrada.
|
||||
if (!isVisible(page.items[page.cursor])) {
|
||||
page.cursor = nextVisibleCursor(page, page.cursor, +1);
|
||||
} else if (item.kind == ItemKind::KEY_BIND) {
|
||||
capturing = item.scancode;
|
||||
} else if (item.change) {
|
||||
item.change(+1);
|
||||
}
|
||||
}
|
||||
|
||||
static void applyKeyToItem(Page& page, SDL_Scancode sc) {
|
||||
Item& item = page.items[page.cursor];
|
||||
switch (sc) {
|
||||
case SDL_SCANCODE_UP:
|
||||
page.cursor = nextVisibleCursor(page, page.cursor, -1);
|
||||
@@ -477,43 +480,44 @@ namespace Menu {
|
||||
page.cursor = nextVisibleCursor(page, page.cursor, +1);
|
||||
break;
|
||||
case SDL_SCANCODE_LEFT:
|
||||
if (page.items[page.cursor].kind != ItemKind::SUBMENU &&
|
||||
page.items[page.cursor].change) {
|
||||
page.items[page.cursor].change(-1);
|
||||
if (item.kind != ItemKind::SUBMENU && item.change) {
|
||||
item.change(-1);
|
||||
}
|
||||
break;
|
||||
case SDL_SCANCODE_RIGHT:
|
||||
if (page.items[page.cursor].kind != ItemKind::SUBMENU &&
|
||||
page.items[page.cursor].change) {
|
||||
page.items[page.cursor].change(+1);
|
||||
if (item.kind != ItemKind::SUBMENU && item.change) {
|
||||
item.change(+1);
|
||||
}
|
||||
break;
|
||||
case SDL_SCANCODE_RETURN:
|
||||
case SDL_SCANCODE_KP_ENTER:
|
||||
if (page.items[page.cursor].kind == ItemKind::SUBMENU ||
|
||||
page.items[page.cursor].kind == ItemKind::ACTION) {
|
||||
if (page.items[page.cursor].enter) {
|
||||
page.items[page.cursor].enter();
|
||||
}
|
||||
} else if (page.items[page.cursor].kind == ItemKind::KEY_BIND) {
|
||||
capturing = page.items[page.cursor].scancode;
|
||||
} else if (page.items[page.cursor].change) {
|
||||
page.items[page.cursor].change(+1);
|
||||
}
|
||||
activateItem(item);
|
||||
break;
|
||||
case SDL_SCANCODE_BACKSPACE:
|
||||
if (stack.size() > 1) {
|
||||
popPage();
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
backOrClose();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Després de qualsevol acció, si el cursor quedara sobre un ítem ocult
|
||||
// (possible si una acció ha canviat la visibilitat pròpia de l'ítem actual,
|
||||
// edge case defensiu), salta al següent visible.
|
||||
}
|
||||
|
||||
void handleKey(SDL_Scancode sc) {
|
||||
if (!isOpen()) {
|
||||
return;
|
||||
}
|
||||
Page& page = stack.back();
|
||||
if (page.items.empty()) {
|
||||
if (sc == SDL_SCANCODE_BACKSPACE) {
|
||||
backOrClose();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!isVisible(page.items[page.cursor])) {
|
||||
page.cursor = nextVisibleCursor(page, page.cursor, +1);
|
||||
}
|
||||
applyKeyToItem(page, sc);
|
||||
// Defensa: si una acció ha amagat l'ítem que tenim sota el cursor,
|
||||
// saltem al pròxim visible.
|
||||
if (!stack.empty()) {
|
||||
Page& top = stack.back();
|
||||
if (!top.items.empty() && !isVisible(top.items[top.cursor])) {
|
||||
@@ -522,6 +526,9 @@ namespace Menu {
|
||||
}
|
||||
}
|
||||
|
||||
// Forward decl: renderOneItem viu sota renderPageContent però aquest l'ha de cridar.
|
||||
static void renderOneItem(Uint32* pixel_data, const Item& item, bool selected, int box_x, int y, int x_offset, int clip_x_min, int clip_x_max, int clip_y_min, int clip_y_max);
|
||||
|
||||
// Dibuixa el contingut d'una pàgina amb un offset horitzontal i un rang de clip.
|
||||
// box_x/box_y són les coordenades de la caixa (per calcular posicions relatives);
|
||||
// clip_x_min/clip_x_max limiten on es dibuixa text i la línia separadora.
|
||||
@@ -560,69 +567,88 @@ namespace Menu {
|
||||
return;
|
||||
}
|
||||
|
||||
int y_slot = 0; // índex de fila visible (independent de l'índex real de l'ítem)
|
||||
int y_slot = 0;
|
||||
for (size_t i = 0; i < page.items.size(); i++) {
|
||||
const Item& item = page.items[i];
|
||||
if (!isVisible(item)) {
|
||||
continue;
|
||||
}
|
||||
int y = items_y + (y_slot * ITEM_SPACING);
|
||||
const int Y = items_y + (y_slot * ITEM_SPACING);
|
||||
++y_slot;
|
||||
bool selected = (static_cast<int>(i) == page.cursor);
|
||||
Uint32 label_color = selected ? CURSOR_COLOR : LABEL_COLOR;
|
||||
|
||||
// ACTION: sense valor a la dreta — centrem el label amb el cursor just a l'esquerra.
|
||||
if (item.kind == ItemKind::ACTION) {
|
||||
int lw = font->width(item.label);
|
||||
int lx = box_x + ((BOX_W - lw) / 2) + x_offset;
|
||||
if (selected) {
|
||||
font->drawClipped(pixel_data, lx - font->width("> "), y, ">", CURSOR_COLOR, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
}
|
||||
font->drawClipped(pixel_data, lx, y, item.label, label_color, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
font->drawClipped(pixel_data, box_x + 4 + x_offset, y, ">", CURSOR_COLOR, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
}
|
||||
|
||||
font->drawClipped(pixel_data, box_x + ITEM_PAD_X + x_offset, y, item.label, label_color, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
|
||||
if (item.kind == ItemKind::SUBMENU) {
|
||||
const char* arrow = ">>";
|
||||
int aw = font->width(arrow);
|
||||
Uint32 ac = selected ? CURSOR_COLOR : VALUE_COLOR;
|
||||
font->drawClipped(pixel_data, box_x + BOX_W - ITEM_PAD_X - aw + x_offset, y, arrow, ac, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
} else if (item.kind == ItemKind::KEY_BIND) {
|
||||
bool this_capturing = (capturing == item.scancode);
|
||||
const char* text = nullptr;
|
||||
if (this_capturing) {
|
||||
text = Locale::get("menu.values.press_key");
|
||||
} else if (item.scancode != nullptr) {
|
||||
text = SDL_GetScancodeName(*item.scancode);
|
||||
} else {
|
||||
text = "";
|
||||
}
|
||||
if ((text == nullptr) || (*text == 0)) {
|
||||
text = Locale::get("menu.values.unknown");
|
||||
}
|
||||
int tw = font->width(text);
|
||||
Uint32 tc = 0;
|
||||
if (this_capturing) {
|
||||
tc = 0xFF00FFFF;
|
||||
} else {
|
||||
tc = selected ? CURSOR_COLOR : VALUE_COLOR;
|
||||
}
|
||||
font->drawClipped(pixel_data, box_x + BOX_W - ITEM_PAD_X - tw + x_offset, y, text, tc, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
} else if (item.get_value) {
|
||||
std::string value = item.get_value();
|
||||
int value_w = font->width(value.c_str());
|
||||
Uint32 value_color = selected ? CURSOR_COLOR : VALUE_COLOR;
|
||||
font->drawClipped(pixel_data, box_x + BOX_W - ITEM_PAD_X - value_w + x_offset, y, value.c_str(), value_color, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
}
|
||||
const bool SELECTED = (static_cast<int>(i) == page.cursor);
|
||||
renderOneItem(pixel_data, item, SELECTED, box_x, Y, x_offset, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
}
|
||||
}
|
||||
|
||||
static auto keybindText(const Item& item, bool this_capturing) -> const char* {
|
||||
const char* text = nullptr;
|
||||
if (this_capturing) {
|
||||
text = Locale::get("menu.values.press_key");
|
||||
} else if (item.scancode != nullptr) {
|
||||
text = SDL_GetScancodeName(*item.scancode);
|
||||
} else {
|
||||
text = "";
|
||||
}
|
||||
if ((text == nullptr) || (*text == 0)) {
|
||||
text = Locale::get("menu.values.unknown");
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
static void renderActionItem(Uint32* pixel_data, const Item& item, bool selected, int box_x, int y, int x_offset, int clip_x_min, int clip_x_max, int clip_y_min, int clip_y_max) {
|
||||
const Uint32 LABEL_COLOR_LOCAL = selected ? CURSOR_COLOR : LABEL_COLOR;
|
||||
const int LW = font->width(item.label);
|
||||
const int LX = box_x + ((BOX_W - LW) / 2) + x_offset;
|
||||
if (selected) {
|
||||
font->drawClipped(pixel_data, LX - font->width("> "), y, ">", CURSOR_COLOR, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
}
|
||||
font->drawClipped(pixel_data, LX, y, item.label, LABEL_COLOR_LOCAL, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
}
|
||||
|
||||
static auto keybindColor(bool this_capturing, bool selected) -> Uint32 {
|
||||
if (this_capturing) {
|
||||
return 0xFF00FFFF;
|
||||
}
|
||||
return selected ? CURSOR_COLOR : VALUE_COLOR;
|
||||
}
|
||||
|
||||
static void renderItemValue(Uint32* pixel_data, const Item& item, bool selected, int box_x, int y, int x_offset, int clip_x_min, int clip_x_max, int clip_y_min, int clip_y_max) {
|
||||
if (item.kind == ItemKind::SUBMENU) {
|
||||
const char* arrow = ">>";
|
||||
const int AW = font->width(arrow);
|
||||
const Uint32 AC = selected ? CURSOR_COLOR : VALUE_COLOR;
|
||||
font->drawClipped(pixel_data, box_x + BOX_W - ITEM_PAD_X - AW + x_offset, y, arrow, AC, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
return;
|
||||
}
|
||||
if (item.kind == ItemKind::KEY_BIND) {
|
||||
const bool THIS_CAPTURING = (capturing == item.scancode);
|
||||
const char* text = keybindText(item, THIS_CAPTURING);
|
||||
const int TW = font->width(text);
|
||||
const Uint32 TC = keybindColor(THIS_CAPTURING, selected);
|
||||
font->drawClipped(pixel_data, box_x + BOX_W - ITEM_PAD_X - TW + x_offset, y, text, TC, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
return;
|
||||
}
|
||||
if (item.get_value) {
|
||||
const std::string VALUE = item.get_value();
|
||||
const int VALUE_W = font->width(VALUE.c_str());
|
||||
const Uint32 VALUE_COLOR_LOCAL = selected ? CURSOR_COLOR : VALUE_COLOR;
|
||||
font->drawClipped(pixel_data, box_x + BOX_W - ITEM_PAD_X - VALUE_W + x_offset, y, VALUE.c_str(), VALUE_COLOR_LOCAL, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
}
|
||||
}
|
||||
|
||||
static void renderOneItem(Uint32* pixel_data, const Item& item, bool selected, int box_x, int y, int x_offset, int clip_x_min, int clip_x_max, int clip_y_min, int clip_y_max) {
|
||||
if (item.kind == ItemKind::ACTION) {
|
||||
renderActionItem(pixel_data, item, selected, box_x, y, x_offset, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
return;
|
||||
}
|
||||
const Uint32 LABEL_COLOR_LOCAL = selected ? CURSOR_COLOR : LABEL_COLOR;
|
||||
if (selected) {
|
||||
font->drawClipped(pixel_data, box_x + 4 + x_offset, y, ">", CURSOR_COLOR, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
}
|
||||
font->drawClipped(pixel_data, box_x + ITEM_PAD_X + x_offset, y, item.label, LABEL_COLOR_LOCAL, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
renderItemValue(pixel_data, item, selected, box_x, y, x_offset, clip_x_min, clip_x_max, clip_y_min, clip_y_max);
|
||||
}
|
||||
|
||||
void render(Uint32* pixel_data) {
|
||||
if (!isVisible() || !font || (pixel_data == nullptr)) {
|
||||
return;
|
||||
|
||||
+218
-235
@@ -114,258 +114,241 @@ namespace Overlay {
|
||||
}
|
||||
}
|
||||
|
||||
static void updateNotifFsm(Notification& notif, float dt) {
|
||||
switch (notif.status) {
|
||||
case Status::RISING:
|
||||
notif.anim += SLIDE_SPEED * dt;
|
||||
if (notif.anim >= 1.0F) {
|
||||
notif.anim = 1.0F;
|
||||
notif.status = Status::STAY;
|
||||
notif.timer = 0.0F;
|
||||
}
|
||||
break;
|
||||
case Status::STAY:
|
||||
notif.timer += dt;
|
||||
if (notif.timer >= notif.duration) {
|
||||
notif.status = Status::VANISHING;
|
||||
}
|
||||
break;
|
||||
case Status::VANISHING:
|
||||
notif.anim -= SLIDE_SPEED * dt;
|
||||
if (notif.anim <= 0.0F) {
|
||||
notif.status = Status::FINISHED;
|
||||
}
|
||||
break;
|
||||
case Status::FINISHED:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void computeNotifBoxPos(const Notification& notif, int& box_x, int& box_y) {
|
||||
switch (notif.pos) {
|
||||
case NotifPosition::TOP_LEFT_SLIDE:
|
||||
box_x = NOTIF_MARGIN_X - static_cast<int>((1.0F - notif.anim) * (notif.box_w + NOTIF_MARGIN_X));
|
||||
box_y = NOTIF_MARGIN_Y;
|
||||
break;
|
||||
case NotifPosition::TOP_CENTER_DROP:
|
||||
box_x = (SCREEN_W - notif.box_w) / 2;
|
||||
box_y = NOTIF_MARGIN_Y - static_cast<int>((1.0F - notif.anim) * (notif.box_h + NOTIF_MARGIN_Y));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void drawNotifTextLine(Uint32* pixel_data, const std::string& line, int line_x, int line_y, const Notification& notif) {
|
||||
if (notif.style == NotifStyle::SHADOW) {
|
||||
font->draw(pixel_data, line_x + 1, line_y + 1, line.c_str(), notif.accent_color);
|
||||
} else if (notif.style == NotifStyle::OUTLINE) {
|
||||
font->draw(pixel_data, line_x, line_y - 1, line.c_str(), notif.accent_color);
|
||||
font->draw(pixel_data, line_x, line_y + 1, line.c_str(), notif.accent_color);
|
||||
font->draw(pixel_data, line_x - 1, line_y, line.c_str(), notif.accent_color);
|
||||
font->draw(pixel_data, line_x + 1, line_y, line.c_str(), notif.accent_color);
|
||||
}
|
||||
font->draw(pixel_data, line_x, line_y, line.c_str(), notif.text_color);
|
||||
}
|
||||
|
||||
static void renderOneNotification(Uint32* pixel_data, const Notification& notif) {
|
||||
int box_x = 0;
|
||||
int box_y = 0;
|
||||
computeNotifBoxPos(notif, box_x, box_y);
|
||||
if (notif.style == NotifStyle::BOX) {
|
||||
drawRect(pixel_data, box_x, box_y, notif.box_w, notif.box_h, notif.accent_color);
|
||||
}
|
||||
const int LINE_H = font->charHeight();
|
||||
int line_y = box_y + NOTIF_PADDING_V;
|
||||
for (const auto& line : notif.lines) {
|
||||
const int LINE_W = font->width(line.c_str());
|
||||
const int LINE_X = box_x + ((notif.box_w - LINE_W) / 2);
|
||||
drawNotifTextLine(pixel_data, line, LINE_X, line_y, notif);
|
||||
line_y += LINE_H + 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void updateRenderInfoFsm(float dt) {
|
||||
const auto DESIRED = Options::render_info.position;
|
||||
if (DESIRED == info_visible_pos) {
|
||||
if (info_anim < 1.0F) {
|
||||
info_anim = std::min(info_anim + (INFO_SLIDE_SPEED * dt), 1.0F);
|
||||
}
|
||||
} else if (info_visible_pos == Options::RenderInfoPosition::OFF) {
|
||||
info_visible_pos = DESIRED;
|
||||
info_anim = 0.0F;
|
||||
} else {
|
||||
info_anim -= INFO_SLIDE_SPEED * dt;
|
||||
if (info_anim <= 0.0F) {
|
||||
info_anim = 0.0F;
|
||||
info_visible_pos = DESIRED;
|
||||
}
|
||||
}
|
||||
for (auto& seg : info_segments) {
|
||||
const float TARGET = seg.visible ? 1.0F : 0.0F;
|
||||
if (seg.anim < TARGET) {
|
||||
seg.anim = std::min(seg.anim + (SEG_SPEED * dt), TARGET);
|
||||
} else if (seg.anim > TARGET) {
|
||||
seg.anim = std::max(seg.anim - (SEG_SPEED * dt), TARGET);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static auto computeInfoTotalWidth(int digit_cell) -> float {
|
||||
float total_w = 0.0F;
|
||||
for (const auto& seg : info_segments) {
|
||||
if (seg.anim > 0.0F && !seg.text.empty()) {
|
||||
const int W = seg.mono_digits
|
||||
? font->widthMonoDigits(seg.text.c_str(), digit_cell)
|
||||
: font->width(seg.text.c_str());
|
||||
total_w += static_cast<float>(W) * Easing::outQuad(seg.anim);
|
||||
}
|
||||
}
|
||||
return total_w;
|
||||
}
|
||||
|
||||
static void drawInfoSegment(Uint32* pixel_data, const InfoSegment& seg, int xi, int info_y, int digit_cell) {
|
||||
if (seg.mono_digits) {
|
||||
font->drawMonoDigits(pixel_data, xi + 1, info_y + 1, seg.text.c_str(), Options::render_info.shadow_color, digit_cell);
|
||||
font->drawMonoDigits(pixel_data, xi, info_y, seg.text.c_str(), Options::render_info.text_color, digit_cell);
|
||||
} else {
|
||||
font->draw(pixel_data, xi + 1, info_y + 1, seg.text.c_str(), Options::render_info.shadow_color);
|
||||
font->draw(pixel_data, xi, info_y, seg.text.c_str(), Options::render_info.text_color);
|
||||
}
|
||||
}
|
||||
|
||||
static void renderRenderInfo(Uint32* pixel_data) {
|
||||
if (info_visible_pos == Options::RenderInfoPosition::OFF || info_anim <= 0.0F) {
|
||||
return;
|
||||
}
|
||||
const int DIGIT_CELL = font->charBoxWidth() - 1;
|
||||
const float TOTAL_W = computeInfoTotalWidth(DIGIT_CELL);
|
||||
if (TOTAL_W <= 0.0F) {
|
||||
return;
|
||||
}
|
||||
const float EASED_Y = Easing::outQuad(info_anim);
|
||||
const int CH = font->charHeight();
|
||||
const int FINAL_Y = (info_visible_pos == Options::RenderInfoPosition::TOP) ? 1 : SCREEN_H - CH - 1;
|
||||
const int START_Y = (info_visible_pos == Options::RenderInfoPosition::TOP) ? -CH - 1 : SCREEN_H;
|
||||
const int INFO_Y = START_Y + static_cast<int>(static_cast<float>(FINAL_Y - START_Y) * EASED_Y);
|
||||
float cur_x = (SCREEN_W - TOTAL_W) / 2.0F;
|
||||
for (const auto& seg : info_segments) {
|
||||
if (seg.anim <= 0.01F || seg.text.empty()) {
|
||||
continue;
|
||||
}
|
||||
const int XI = static_cast<int>(cur_x);
|
||||
const int SEG_W = seg.mono_digits
|
||||
? font->widthMonoDigits(seg.text.c_str(), DIGIT_CELL)
|
||||
: font->width(seg.text.c_str());
|
||||
drawInfoSegment(pixel_data, seg, XI, INFO_Y, DIGIT_CELL);
|
||||
cur_x += static_cast<float>(SEG_W) * Easing::outQuad(seg.anim);
|
||||
}
|
||||
}
|
||||
|
||||
static void renderPauseIndicator(Uint32* pixel_data) {
|
||||
if ((Director::get() == nullptr) || !Director::get()->isPaused()) {
|
||||
return;
|
||||
}
|
||||
const char* pause_text = Locale::get("notifications.pause");
|
||||
const int W = font->width(pause_text);
|
||||
const int X = SCREEN_W - W - 4;
|
||||
const int Y = 4;
|
||||
font->draw(pixel_data, X, Y - 1, pause_text, 0xFFFFFFFF);
|
||||
font->draw(pixel_data, X, Y + 1, pause_text, 0xFFFFFFFF);
|
||||
font->draw(pixel_data, X - 1, Y, pause_text, 0xFFFFFFFF);
|
||||
font->draw(pixel_data, X + 1, Y, pause_text, 0xFFFFFFFF);
|
||||
font->draw(pixel_data, X, Y, pause_text, 0xFF0000FF);
|
||||
}
|
||||
|
||||
static void emitCreditsLines(const char* role_key, const char* name_key) {
|
||||
showNotification(
|
||||
{std::string(Locale::get(role_key)), std::string(Locale::get(name_key))},
|
||||
CREDITS_HOLD,
|
||||
NotifPosition::TOP_CENTER_DROP,
|
||||
NotifStyle::OUTLINE,
|
||||
CREDITS_BG,
|
||||
CREDITS_FG);
|
||||
}
|
||||
|
||||
static void advanceCredits(float dt) {
|
||||
if (credits_phase == CreditsPhase::IDLE) {
|
||||
return;
|
||||
}
|
||||
credits_timer += dt;
|
||||
switch (credits_phase) {
|
||||
case CreditsPhase::DELAY:
|
||||
if (credits_timer >= CREDITS_DELAY) {
|
||||
emitCreditsLines("credits.port_role", "credits.port_name");
|
||||
credits_phase = CreditsPhase::PLAYING_1;
|
||||
credits_timer = 0.0F;
|
||||
}
|
||||
break;
|
||||
case CreditsPhase::PLAYING_1:
|
||||
if (notifications.empty()) {
|
||||
credits_phase = CreditsPhase::GAP;
|
||||
credits_timer = 0.0F;
|
||||
}
|
||||
break;
|
||||
case CreditsPhase::GAP:
|
||||
if (credits_timer >= CREDITS_GAP) {
|
||||
emitCreditsLines("credits.modern_role", "credits.modern_name");
|
||||
credits_phase = CreditsPhase::PLAYING_2;
|
||||
credits_timer = 0.0F;
|
||||
}
|
||||
break;
|
||||
case CreditsPhase::PLAYING_2:
|
||||
if (notifications.empty()) {
|
||||
credits_phase = CreditsPhase::IDLE;
|
||||
credits_timer = 0.0F;
|
||||
}
|
||||
break;
|
||||
case CreditsPhase::IDLE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void render(Uint32* pixel_data) {
|
||||
if (!font || (pixel_data == nullptr)) {
|
||||
return;
|
||||
}
|
||||
const Uint32 NOW = SDL_GetTicks();
|
||||
const float DT = static_cast<float>(NOW - last_ticks) / 1000.0F;
|
||||
last_ticks = NOW;
|
||||
|
||||
// Calcula delta time
|
||||
Uint32 now = SDL_GetTicks();
|
||||
float dt = static_cast<float>(now - last_ticks) / 1000.0F;
|
||||
last_ticks = now;
|
||||
|
||||
// Actualitza i pinta cada notificació
|
||||
for (auto& notif : notifications) {
|
||||
switch (notif.status) {
|
||||
case Status::RISING:
|
||||
notif.anim += SLIDE_SPEED * dt;
|
||||
if (notif.anim >= 1.0F) {
|
||||
notif.anim = 1.0F;
|
||||
notif.status = Status::STAY;
|
||||
notif.timer = 0.0F;
|
||||
}
|
||||
break;
|
||||
|
||||
case Status::STAY:
|
||||
notif.timer += dt;
|
||||
if (notif.timer >= notif.duration) {
|
||||
notif.status = Status::VANISHING;
|
||||
}
|
||||
break;
|
||||
|
||||
case Status::VANISHING:
|
||||
notif.anim -= SLIDE_SPEED * dt;
|
||||
if (notif.anim <= 0.0F) {
|
||||
notif.status = Status::FINISHED;
|
||||
}
|
||||
break;
|
||||
|
||||
case Status::FINISHED:
|
||||
break;
|
||||
}
|
||||
|
||||
if (notif.status == Status::FINISHED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Posició segons el tipus
|
||||
int box_x = 0;
|
||||
int box_y = 0;
|
||||
switch (notif.pos) {
|
||||
case NotifPosition::TOP_LEFT_SLIDE: {
|
||||
int target_x = NOTIF_MARGIN_X;
|
||||
int target_y = NOTIF_MARGIN_Y;
|
||||
box_x = target_x - static_cast<int>((1.0F - notif.anim) * (notif.box_w + NOTIF_MARGIN_X));
|
||||
box_y = target_y;
|
||||
break;
|
||||
}
|
||||
case NotifPosition::TOP_CENTER_DROP: {
|
||||
int target_y = NOTIF_MARGIN_Y;
|
||||
box_x = (SCREEN_W - notif.box_w) / 2;
|
||||
// Baixa des de sobre de la pantalla fins a target_y
|
||||
box_y = target_y - static_cast<int>((1.0F - notif.anim) * (notif.box_h + NOTIF_MARGIN_Y));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Pinta fons (si BOX)
|
||||
if (notif.style == NotifStyle::BOX) {
|
||||
drawRect(pixel_data, box_x, box_y, notif.box_w, notif.box_h, notif.accent_color);
|
||||
}
|
||||
|
||||
// Pinta el text línia a línia (amb ombra o contorn segons style)
|
||||
int line_h = font->charHeight();
|
||||
int line_y = box_y + NOTIF_PADDING_V;
|
||||
for (const auto& line : notif.lines) {
|
||||
int line_w = font->width(line.c_str());
|
||||
int line_x = box_x + ((notif.box_w - line_w) / 2); // centrat dins la caixa
|
||||
if (notif.style == NotifStyle::SHADOW) {
|
||||
font->draw(pixel_data, line_x + 1, line_y + 1, line.c_str(), notif.accent_color);
|
||||
} else if (notif.style == NotifStyle::OUTLINE) {
|
||||
// Contorn 4-direccional (N, S, E, W)
|
||||
font->draw(pixel_data, line_x, line_y - 1, line.c_str(), notif.accent_color);
|
||||
font->draw(pixel_data, line_x, line_y + 1, line.c_str(), notif.accent_color);
|
||||
font->draw(pixel_data, line_x - 1, line_y, line.c_str(), notif.accent_color);
|
||||
font->draw(pixel_data, line_x + 1, line_y, line.c_str(), notif.accent_color);
|
||||
}
|
||||
font->draw(pixel_data, line_x, line_y, line.c_str(), notif.text_color);
|
||||
line_y += line_h + 1;
|
||||
updateNotifFsm(notif, DT);
|
||||
if (notif.status != Status::FINISHED) {
|
||||
renderOneNotification(pixel_data, notif);
|
||||
}
|
||||
}
|
||||
|
||||
// Render info (FPS, driver, shader) — animat amb slide vertical
|
||||
// State machine: visible_pos s'actualitza cap a DESIRED quan anim arriba a 0
|
||||
{
|
||||
const auto DESIRED = Options::render_info.position;
|
||||
if (DESIRED == info_visible_pos) {
|
||||
// Mateix lloc: entra fins a 1
|
||||
if (info_anim < 1.0F) {
|
||||
info_anim += INFO_SLIDE_SPEED * dt;
|
||||
info_anim = std::min(info_anim, 1.0F);
|
||||
}
|
||||
} else {
|
||||
// Canvi: si visible_pos està OFF, commuta directament
|
||||
if (info_visible_pos == Options::RenderInfoPosition::OFF) {
|
||||
info_visible_pos = DESIRED;
|
||||
info_anim = 0.0F;
|
||||
} else {
|
||||
// Ix del lloc actual
|
||||
info_anim -= INFO_SLIDE_SPEED * dt;
|
||||
if (info_anim <= 0.0F) {
|
||||
info_anim = 0.0F;
|
||||
info_visible_pos = DESIRED;
|
||||
}
|
||||
}
|
||||
}
|
||||
updateRenderInfoFsm(DT);
|
||||
renderRenderInfo(pixel_data);
|
||||
|
||||
// Actualitza animacions individuals dels segments
|
||||
for (auto& seg : info_segments) {
|
||||
float target = seg.visible ? 1.0F : 0.0F;
|
||||
if (seg.anim < target) {
|
||||
seg.anim += SEG_SPEED * dt;
|
||||
seg.anim = std::min(seg.anim, target);
|
||||
} else if (seg.anim > target) {
|
||||
seg.anim -= SEG_SPEED * dt;
|
||||
seg.anim = std::max(seg.anim, target);
|
||||
}
|
||||
}
|
||||
|
||||
// Render si hi ha alguna cosa visible
|
||||
if (info_visible_pos != Options::RenderInfoPosition::OFF && info_anim > 0.0F) {
|
||||
const int DIGIT_CELL = font->charBoxWidth() - 1; // amplada uniforme per dígit
|
||||
|
||||
// Calcula amplada total interpolant cada segment per la seva anim
|
||||
float total_w = 0.0F;
|
||||
for (const auto& seg : info_segments) {
|
||||
if (seg.anim > 0.0F && !seg.text.empty()) {
|
||||
int w = seg.mono_digits
|
||||
? font->widthMonoDigits(seg.text.c_str(), DIGIT_CELL)
|
||||
: font->width(seg.text.c_str());
|
||||
total_w += w * Easing::outQuad(seg.anim);
|
||||
}
|
||||
}
|
||||
if (total_w > 0.0F) {
|
||||
float eased_y = Easing::outQuad(info_anim);
|
||||
int ch = font->charHeight();
|
||||
int final_y;
|
||||
int start_y;
|
||||
if (info_visible_pos == Options::RenderInfoPosition::TOP) {
|
||||
final_y = 1;
|
||||
start_y = -ch - 1;
|
||||
} else {
|
||||
final_y = SCREEN_H - ch - 1;
|
||||
start_y = SCREEN_H;
|
||||
}
|
||||
int info_y = start_y + static_cast<int>((final_y - start_y) * eased_y);
|
||||
|
||||
// Dibuixa cada segment en la seva posició x acumulada
|
||||
float cur_x = (SCREEN_W - total_w) / 2.0F;
|
||||
for (const auto& seg : info_segments) {
|
||||
if (seg.anim > 0.01F && !seg.text.empty()) {
|
||||
int xi = static_cast<int>(cur_x);
|
||||
int seg_w = seg.mono_digits
|
||||
? font->widthMonoDigits(seg.text.c_str(), DIGIT_CELL)
|
||||
: font->width(seg.text.c_str());
|
||||
if (seg.mono_digits) {
|
||||
font->drawMonoDigits(pixel_data, xi + 1, info_y + 1, seg.text.c_str(), Options::render_info.shadow_color, DIGIT_CELL);
|
||||
font->drawMonoDigits(pixel_data, xi, info_y, seg.text.c_str(), Options::render_info.text_color, DIGIT_CELL);
|
||||
} else {
|
||||
font->draw(pixel_data, xi + 1, info_y + 1, seg.text.c_str(), Options::render_info.shadow_color);
|
||||
font->draw(pixel_data, xi, info_y, seg.text.c_str(), Options::render_info.text_color);
|
||||
}
|
||||
cur_x += seg_w * Easing::outQuad(seg.anim);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Elimina les acabades
|
||||
std::erase_if(notifications, [](const Notification& n) { return n.status == Status::FINISHED; });
|
||||
|
||||
// Si la notificació d'ESC ha desaparegut, reseteja l'estat
|
||||
if (esc_waiting && notifications.empty()) {
|
||||
esc_waiting = false;
|
||||
}
|
||||
|
||||
// Indicador de pausa persistent (cantó superior dret)
|
||||
if ((Director::get() != nullptr) && Director::get()->isPaused()) {
|
||||
const char* pause_text = Locale::get("notifications.pause");
|
||||
int w = font->width(pause_text);
|
||||
int x = SCREEN_W - w - 4;
|
||||
int y = 4;
|
||||
// Contorn blanc 4-direccional
|
||||
font->draw(pixel_data, x, y - 1, pause_text, 0xFFFFFFFF);
|
||||
font->draw(pixel_data, x, y + 1, pause_text, 0xFFFFFFFF);
|
||||
font->draw(pixel_data, x - 1, y, pause_text, 0xFFFFFFFF);
|
||||
font->draw(pixel_data, x + 1, y, pause_text, 0xFFFFFFFF);
|
||||
// Text en roig
|
||||
font->draw(pixel_data, x, y, pause_text, 0xFF0000FF);
|
||||
}
|
||||
renderPauseIndicator(pixel_data);
|
||||
advanceCredits(DT);
|
||||
|
||||
// Crèdits seqüencials — dispara notificacions TOP_CENTER_DROP una darrere l'altra.
|
||||
if (credits_phase != CreditsPhase::IDLE) {
|
||||
credits_timer += dt;
|
||||
switch (credits_phase) {
|
||||
case CreditsPhase::DELAY:
|
||||
if (credits_timer >= CREDITS_DELAY) {
|
||||
showNotification(
|
||||
{std::string(Locale::get("credits.port_role")),
|
||||
std::string(Locale::get("credits.port_name"))},
|
||||
CREDITS_HOLD,
|
||||
NotifPosition::TOP_CENTER_DROP,
|
||||
NotifStyle::OUTLINE,
|
||||
CREDITS_BG,
|
||||
CREDITS_FG);
|
||||
credits_phase = CreditsPhase::PLAYING_1;
|
||||
credits_timer = 0.0F;
|
||||
}
|
||||
break;
|
||||
case CreditsPhase::PLAYING_1:
|
||||
if (notifications.empty()) {
|
||||
credits_phase = CreditsPhase::GAP;
|
||||
credits_timer = 0.0F;
|
||||
}
|
||||
break;
|
||||
case CreditsPhase::GAP:
|
||||
if (credits_timer >= CREDITS_GAP) {
|
||||
showNotification(
|
||||
{std::string(Locale::get("credits.modern_role")),
|
||||
std::string(Locale::get("credits.modern_name"))},
|
||||
CREDITS_HOLD,
|
||||
NotifPosition::TOP_CENTER_DROP,
|
||||
NotifStyle::OUTLINE,
|
||||
CREDITS_BG,
|
||||
CREDITS_FG);
|
||||
credits_phase = CreditsPhase::PLAYING_2;
|
||||
credits_timer = 0.0F;
|
||||
}
|
||||
break;
|
||||
case CreditsPhase::PLAYING_2:
|
||||
if (notifications.empty()) {
|
||||
credits_phase = CreditsPhase::IDLE;
|
||||
credits_timer = 0.0F;
|
||||
}
|
||||
break;
|
||||
case CreditsPhase::IDLE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Neteja notificacions finalitzades
|
||||
std::erase_if(notifications, [](const Notification& n) { return n.status == Status::FINISHED; });
|
||||
|
||||
// Menú flotant per damunt de tot (isVisible inclou l'animació de tancament)
|
||||
if (Menu::isVisible()) {
|
||||
Menu::render(pixel_data);
|
||||
}
|
||||
|
||||
+65
-177
@@ -1,5 +1,6 @@
|
||||
#include "core/rendering/text.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@@ -158,60 +159,57 @@ void Text::loadBitmap(const char* gif_file) {
|
||||
|
||||
// --- Renderitzat ---
|
||||
|
||||
auto Text::resolveGlyph(uint32_t cp) const -> const GlyphInfo* {
|
||||
auto it = glyphs_.find(cp);
|
||||
if (it != glyphs_.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
it = glyphs_.find('?');
|
||||
return (it != glyphs_.end()) ? &it->second : nullptr;
|
||||
}
|
||||
|
||||
void Text::blitGlyph(Uint32* pixel_data, int dst_x, int dst_y, const GlyphInfo& glyph, Uint32 color, int clip_x_min, int clip_x_max, int clip_y_min, int clip_y_max) const {
|
||||
const int GY_START = std::max(0, clip_y_min - dst_y);
|
||||
const int GY_END = std::min(box_height_, clip_y_max - dst_y);
|
||||
const int GX_START = std::max(0, clip_x_min - dst_x);
|
||||
const int GX_END = std::min(glyph.w, clip_x_max - dst_x);
|
||||
for (int gy = GY_START; gy < GY_END; gy++) {
|
||||
const int SRC_Y = glyph.y + gy;
|
||||
if (SRC_Y >= bitmap_height_) {
|
||||
continue;
|
||||
}
|
||||
const int DST_ROW = dst_y + gy;
|
||||
for (int gx = GX_START; gx < GX_END; gx++) {
|
||||
const int SRC_X = glyph.x + gx;
|
||||
if (SRC_X >= bitmap_width_) {
|
||||
continue;
|
||||
}
|
||||
const Uint8 PIXEL = bitmap_[SRC_X + (SRC_Y * bitmap_width_)];
|
||||
if (PIXEL != 0) {
|
||||
pixel_data[(dst_x + gx) + (DST_ROW * SCREEN_WIDTH)] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Text::draw(Uint32* pixel_data, int x, int y, const char* text, Uint32 color) const {
|
||||
if (bitmap_.empty() || (pixel_data == nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* ptr = text;
|
||||
int cursor_x = x;
|
||||
|
||||
while (*ptr != 0) {
|
||||
uint32_t cp = nextCodepoint(ptr);
|
||||
if (cp == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto it = glyphs_.find(cp);
|
||||
if (it == glyphs_.end()) {
|
||||
it = glyphs_.find('?');
|
||||
if (it == glyphs_.end()) {
|
||||
cursor_x += box_width_;
|
||||
continue;
|
||||
}
|
||||
const GlyphInfo* glyph = resolveGlyph(cp);
|
||||
if (glyph == nullptr) {
|
||||
cursor_x += box_width_;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& glyph = it->second;
|
||||
|
||||
// Pinta glifo pixel a pixel
|
||||
for (int gy = 0; gy < box_height_; gy++) {
|
||||
int dst_y = y + gy;
|
||||
if (dst_y < 0 || dst_y >= SCREEN_HEIGHT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int gx = 0; gx < glyph.w; gx++) {
|
||||
int dst_x = cursor_x + gx;
|
||||
if (dst_x < 0 || dst_x >= SCREEN_WIDTH) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int src_x = glyph.x + gx;
|
||||
int src_y = glyph.y + gy;
|
||||
|
||||
if (src_x >= bitmap_width_ || src_y >= bitmap_height_) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Uint8 pixel = bitmap_[src_x + (src_y * bitmap_width_)];
|
||||
// Píxel no transparent (índex 0 és fons típicament)
|
||||
if (pixel != 0) {
|
||||
pixel_data[dst_x + (dst_y * SCREEN_WIDTH)] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cursor_x += glyph.w + 1; // +1 kerning
|
||||
blitGlyph(pixel_data, cursor_x, y, *glyph, color, 0, SCREEN_WIDTH, 0, SCREEN_HEIGHT);
|
||||
cursor_x += glyph->w + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,70 +223,29 @@ void Text::drawClipped(Uint32* pixel_data, int x, int y, const char* text, Uint3
|
||||
if (bitmap_.empty() || (pixel_data == nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Descart ràpid si el glifo sencer cau fora verticalment
|
||||
if (y + box_height_ <= clip_y_min || y >= clip_y_max) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int X_MIN = std::max(0, clip_x_min);
|
||||
const int X_MAX = std::min(SCREEN_WIDTH, clip_x_max);
|
||||
const int Y_MIN = std::max(0, clip_y_min);
|
||||
const int Y_MAX = std::min(SCREEN_HEIGHT, clip_y_max);
|
||||
const char* ptr = text;
|
||||
int cursor_x = x;
|
||||
|
||||
while (*ptr != 0) {
|
||||
uint32_t cp = nextCodepoint(ptr);
|
||||
if (cp == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto it = glyphs_.find(cp);
|
||||
if (it == glyphs_.end()) {
|
||||
it = glyphs_.find('?');
|
||||
if (it == glyphs_.end()) {
|
||||
cursor_x += box_width_;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const auto& glyph = it->second;
|
||||
|
||||
// Si el glifo està completament fora del clip horitzontal, salta
|
||||
if (cursor_x + glyph.w <= clip_x_min || cursor_x >= clip_x_max) {
|
||||
cursor_x += glyph.w + 1;
|
||||
const GlyphInfo* glyph = resolveGlyph(cp);
|
||||
if (glyph == nullptr) {
|
||||
cursor_x += box_width_;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int gy = 0; gy < box_height_; gy++) {
|
||||
int dst_y = y + gy;
|
||||
if (dst_y < 0 || dst_y >= SCREEN_HEIGHT) {
|
||||
continue;
|
||||
}
|
||||
if (dst_y < clip_y_min || dst_y >= clip_y_max) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int gx = 0; gx < glyph.w; gx++) {
|
||||
int dst_x = cursor_x + gx;
|
||||
if (dst_x < 0 || dst_x >= SCREEN_WIDTH) {
|
||||
continue;
|
||||
}
|
||||
if (dst_x < clip_x_min || dst_x >= clip_x_max) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int src_x = glyph.x + gx;
|
||||
int src_y = glyph.y + gy;
|
||||
if (src_x >= bitmap_width_ || src_y >= bitmap_height_) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Uint8 pixel = bitmap_[src_x + (src_y * bitmap_width_)];
|
||||
if (pixel != 0) {
|
||||
pixel_data[dst_x + (dst_y * SCREEN_WIDTH)] = color;
|
||||
}
|
||||
}
|
||||
if (cursor_x + glyph->w > X_MIN && cursor_x < X_MAX) {
|
||||
blitGlyph(pixel_data, cursor_x, y, *glyph, color, X_MIN, X_MAX, Y_MIN, Y_MAX);
|
||||
}
|
||||
|
||||
cursor_x += glyph.w + 1;
|
||||
cursor_x += glyph->w + 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,54 +253,20 @@ void Text::drawMono(Uint32* pixel_data, int x, int y, const char* text, Uint32 c
|
||||
if (bitmap_.empty() || (pixel_data == nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* ptr = text;
|
||||
int cursor_x = x;
|
||||
|
||||
while (*ptr != 0) {
|
||||
uint32_t cp = nextCodepoint(ptr);
|
||||
if (cp == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto it = glyphs_.find(cp);
|
||||
if (it == glyphs_.end()) {
|
||||
it = glyphs_.find('?');
|
||||
if (it == glyphs_.end()) {
|
||||
cursor_x += cell_w;
|
||||
continue;
|
||||
}
|
||||
const GlyphInfo* glyph = resolveGlyph(cp);
|
||||
if (glyph == nullptr) {
|
||||
cursor_x += cell_w;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& glyph = it->second;
|
||||
// Centra el glif dins la cel·la
|
||||
int glyph_x = cursor_x + ((cell_w - glyph.w) / 2);
|
||||
|
||||
for (int gy = 0; gy < box_height_; gy++) {
|
||||
int dst_y = y + gy;
|
||||
if (dst_y < 0 || dst_y >= SCREEN_HEIGHT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int gx = 0; gx < glyph.w; gx++) {
|
||||
int dst_x = glyph_x + gx;
|
||||
if (dst_x < 0 || dst_x >= SCREEN_WIDTH) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int src_x = glyph.x + gx;
|
||||
int src_y = glyph.y + gy;
|
||||
if (src_x >= bitmap_width_ || src_y >= bitmap_height_) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Uint8 pixel = bitmap_[src_x + (src_y * bitmap_width_)];
|
||||
if (pixel != 0) {
|
||||
pixel_data[dst_x + (dst_y * SCREEN_WIDTH)] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int GLYPH_X = cursor_x + ((cell_w - glyph->w) / 2);
|
||||
blitGlyph(pixel_data, GLYPH_X, y, *glyph, color, 0, SCREEN_WIDTH, 0, SCREEN_HEIGHT);
|
||||
cursor_x += cell_w;
|
||||
}
|
||||
}
|
||||
@@ -352,62 +275,27 @@ void Text::drawMonoDigits(Uint32* pixel_data, int x, int y, const char* text, Ui
|
||||
if (bitmap_.empty() || (pixel_data == nullptr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* ptr = text;
|
||||
int cursor_x = x;
|
||||
bool first = true;
|
||||
|
||||
while (*ptr != 0) {
|
||||
uint32_t cp = nextCodepoint(ptr);
|
||||
if (cp == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto it = glyphs_.find(cp);
|
||||
if (it == glyphs_.end()) {
|
||||
it = glyphs_.find('?');
|
||||
if (it == glyphs_.end()) {
|
||||
if (!first) {
|
||||
cursor_x += 1;
|
||||
}
|
||||
cursor_x += box_width_;
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const auto& glyph = it->second;
|
||||
bool is_digit = (cp >= '0' && cp <= '9');
|
||||
|
||||
if (!first) {
|
||||
cursor_x += 1; // kerning
|
||||
}
|
||||
|
||||
int glyph_x = is_digit ? cursor_x + ((digit_cell_w - glyph.w) / 2) : cursor_x;
|
||||
|
||||
for (int gy = 0; gy < box_height_; gy++) {
|
||||
int dst_y = y + gy;
|
||||
if (dst_y < 0 || dst_y >= SCREEN_HEIGHT) {
|
||||
continue;
|
||||
}
|
||||
for (int gx = 0; gx < glyph.w; gx++) {
|
||||
int dst_x = glyph_x + gx;
|
||||
if (dst_x < 0 || dst_x >= SCREEN_WIDTH) {
|
||||
continue;
|
||||
}
|
||||
int src_x = glyph.x + gx;
|
||||
int src_y = glyph.y + gy;
|
||||
if (src_x >= bitmap_width_ || src_y >= bitmap_height_) {
|
||||
continue;
|
||||
}
|
||||
Uint8 pixel = bitmap_[src_x + (src_y * bitmap_width_)];
|
||||
if (pixel != 0) {
|
||||
pixel_data[dst_x + (dst_y * SCREEN_WIDTH)] = color;
|
||||
}
|
||||
}
|
||||
const GlyphInfo* glyph = resolveGlyph(cp);
|
||||
if (glyph == nullptr) {
|
||||
cursor_x += box_width_;
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
cursor_x += is_digit ? digit_cell_w : glyph.w;
|
||||
const bool IS_DIGIT = (cp >= '0' && cp <= '9');
|
||||
const int GLYPH_X = IS_DIGIT ? cursor_x + ((digit_cell_w - glyph->w) / 2) : cursor_x;
|
||||
blitGlyph(pixel_data, GLYPH_X, y, *glyph, color, 0, SCREEN_WIDTH, 0, SCREEN_HEIGHT);
|
||||
cursor_x += IS_DIGIT ? digit_cell_w : glyph->w;
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,13 @@ class Text {
|
||||
void loadFont(const char* fnt_file);
|
||||
void loadBitmap(const char* gif_file);
|
||||
|
||||
// Resolt un codepoint al GlyphInfo corresponent o al fallback '?'.
|
||||
// Retorna nullptr si ni el codepoint ni el fallback existeixen.
|
||||
[[nodiscard]] auto resolveGlyph(uint32_t cp) const -> const GlyphInfo*;
|
||||
// Pinta un glif a (dst_x, dst_y) amb clipping per finestra.
|
||||
// Si la finestra és tota la pantalla, passar clip_x_min=0, clip_x_max=SCREEN_WIDTH, idem y.
|
||||
void blitGlyph(Uint32* pixel_data, int dst_x, int dst_y, const GlyphInfo& glyph, Uint32 color, int clip_x_min, int clip_x_max, int clip_y_min, int clip_y_max) const;
|
||||
|
||||
static constexpr int SCREEN_WIDTH = 320;
|
||||
static constexpr int SCREEN_HEIGHT = 200;
|
||||
};
|
||||
|
||||
@@ -105,6 +105,36 @@ namespace Resource {
|
||||
std::cout << "Resource::Cache: precarregant " << total_count_ << " assets\n";
|
||||
}
|
||||
|
||||
void Cache::stepEachInList(List::Type type, const std::function<void()>& clear_fn, LoadStage next, const std::function<void(size_t)>& load_fn) {
|
||||
auto items = List::get()->getListByType(type);
|
||||
if (stage_index_ == 0) {
|
||||
clear_fn();
|
||||
}
|
||||
if (stage_index_ >= items.size()) {
|
||||
stage_ = next;
|
||||
stage_index_ = 0;
|
||||
return;
|
||||
}
|
||||
load_fn(stage_index_++);
|
||||
}
|
||||
|
||||
void Cache::stepTextFiles() {
|
||||
auto data_items = List::get()->getListByType(List::Type::DATA);
|
||||
auto font_items = List::get()->getListByType(List::Type::FONT);
|
||||
auto items = data_items;
|
||||
items.insert(items.end(), font_items.begin(), font_items.end());
|
||||
if (stage_index_ == 0) {
|
||||
text_files_.clear();
|
||||
}
|
||||
if (stage_index_ >= items.size()) {
|
||||
stage_ = LoadStage::DONE;
|
||||
stage_index_ = 0;
|
||||
std::cout << "Resource::Cache: precarrega completada (" << loaded_count_ << "/" << total_count_ << ")\n";
|
||||
return;
|
||||
}
|
||||
loadOneTextFile(stage_index_++);
|
||||
}
|
||||
|
||||
auto Cache::loadStep(int budget_ms) -> bool {
|
||||
if (stage_ == LoadStage::DONE) {
|
||||
return true;
|
||||
@@ -112,66 +142,21 @@ namespace Resource {
|
||||
|
||||
const Uint64 START_NS = SDL_GetTicksNS();
|
||||
const Uint64 BUDGET_NS = static_cast<Uint64>(budget_ms) * 1'000'000ULL;
|
||||
const auto* list = List::get();
|
||||
|
||||
while (stage_ != LoadStage::DONE) {
|
||||
switch (stage_) {
|
||||
case LoadStage::MUSICS: {
|
||||
auto items = list->getListByType(List::Type::MUSIC);
|
||||
if (stage_index_ == 0) {
|
||||
musics_.clear();
|
||||
}
|
||||
if (stage_index_ >= items.size()) {
|
||||
stage_ = LoadStage::SOUNDS;
|
||||
stage_index_ = 0;
|
||||
break;
|
||||
}
|
||||
loadOneMusic(stage_index_++);
|
||||
case LoadStage::MUSICS:
|
||||
stepEachInList(List::Type::MUSIC, [this] { musics_.clear(); }, LoadStage::SOUNDS, [this](size_t i) { loadOneMusic(i); });
|
||||
break;
|
||||
}
|
||||
case LoadStage::SOUNDS: {
|
||||
auto items = list->getListByType(List::Type::SOUND);
|
||||
if (stage_index_ == 0) {
|
||||
sounds_.clear();
|
||||
}
|
||||
if (stage_index_ >= items.size()) {
|
||||
stage_ = LoadStage::BITMAPS;
|
||||
stage_index_ = 0;
|
||||
break;
|
||||
}
|
||||
loadOneSound(stage_index_++);
|
||||
case LoadStage::SOUNDS:
|
||||
stepEachInList(List::Type::SOUND, [this] { sounds_.clear(); }, LoadStage::BITMAPS, [this](size_t i) { loadOneSound(i); });
|
||||
break;
|
||||
}
|
||||
case LoadStage::BITMAPS: {
|
||||
auto items = list->getListByType(List::Type::BITMAP);
|
||||
if (stage_index_ == 0) {
|
||||
surfaces_.clear();
|
||||
}
|
||||
if (stage_index_ >= items.size()) {
|
||||
stage_ = LoadStage::TEXT_FILES;
|
||||
stage_index_ = 0;
|
||||
break;
|
||||
}
|
||||
loadOneBitmap(stage_index_++);
|
||||
case LoadStage::BITMAPS:
|
||||
stepEachInList(List::Type::BITMAP, [this] { surfaces_.clear(); }, LoadStage::TEXT_FILES, [this](size_t i) { loadOneBitmap(i); });
|
||||
break;
|
||||
}
|
||||
case LoadStage::TEXT_FILES: {
|
||||
auto data_items = list->getListByType(List::Type::DATA);
|
||||
auto font_items = list->getListByType(List::Type::FONT);
|
||||
auto items = data_items;
|
||||
items.insert(items.end(), font_items.begin(), font_items.end());
|
||||
if (stage_index_ == 0) {
|
||||
text_files_.clear();
|
||||
}
|
||||
if (stage_index_ >= items.size()) {
|
||||
stage_ = LoadStage::DONE;
|
||||
stage_index_ = 0;
|
||||
std::cout << "Resource::Cache: precarrega completada (" << loaded_count_ << "/" << total_count_ << ")\n";
|
||||
break;
|
||||
}
|
||||
loadOneTextFile(stage_index_++);
|
||||
case LoadStage::TEXT_FILES:
|
||||
stepTextFiles();
|
||||
break;
|
||||
}
|
||||
case LoadStage::DONE:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "core/resources/resource_list.hpp"
|
||||
#include "core/resources/resource_types.hpp"
|
||||
|
||||
namespace Resource {
|
||||
@@ -56,6 +58,8 @@ namespace Resource {
|
||||
void loadOneSound(size_t index);
|
||||
void loadOneBitmap(size_t index);
|
||||
void loadOneTextFile(size_t index);
|
||||
void stepEachInList(List::Type type, const std::function<void()>& clear_fn, LoadStage next, const std::function<void(size_t)>& load_fn);
|
||||
void stepTextFiles();
|
||||
|
||||
std::vector<MusicResource> musics_;
|
||||
std::vector<SoundResource> sounds_;
|
||||
|
||||
+107
-137
@@ -136,120 +136,100 @@ void Director::setup() {
|
||||
has_frame_ = false;
|
||||
}
|
||||
|
||||
void Director::applyRestart() {
|
||||
restart_requested_ = false;
|
||||
Audio::get()->stopMusic();
|
||||
Audio::get()->stopAllSounds();
|
||||
initGameContext();
|
||||
Info::ctx.num_piramide = 255;
|
||||
current_scene_.reset();
|
||||
game_state_ = 1;
|
||||
has_frame_ = false;
|
||||
Menu::close();
|
||||
Ji::setInputBlocked(false);
|
||||
}
|
||||
|
||||
void Director::maybeStartTitleCredits() {
|
||||
static bool credits_triggered_ = false;
|
||||
if (credits_triggered_ || Info::ctx.num_piramide != 0) {
|
||||
return;
|
||||
}
|
||||
if (Options::game.show_title_credits) {
|
||||
Overlay::startCredits();
|
||||
}
|
||||
credits_triggered_ = true;
|
||||
}
|
||||
|
||||
auto Director::tickActiveScene() -> bool {
|
||||
if (current_scene_ && (current_scene_->done() || Jg::quitting())) {
|
||||
game_state_ = current_scene_->nextState();
|
||||
current_scene_.reset();
|
||||
}
|
||||
if (!current_scene_) {
|
||||
if (game_state_ == -1 || Jg::quitting()) {
|
||||
return false;
|
||||
}
|
||||
current_scene_ = createNextScene();
|
||||
if (!current_scene_) {
|
||||
return false;
|
||||
}
|
||||
current_scene_->onEnter();
|
||||
last_tick_ms_ = SDL_GetTicks();
|
||||
}
|
||||
|
||||
Ji::update();
|
||||
const Uint32 NOW = SDL_GetTicks();
|
||||
const int DELTA_MS = static_cast<int>(NOW - last_tick_ms_);
|
||||
last_tick_ms_ = NOW;
|
||||
current_scene_->tick(DELTA_MS);
|
||||
|
||||
Jd8::flip();
|
||||
std::memcpy(game_frame_, Jd8::getFramebuffer(), sizeof(game_frame_));
|
||||
has_frame_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Director::iterate() -> bool {
|
||||
if (quit_requested_) {
|
||||
Jg::quitSignal();
|
||||
current_scene_.reset(); // destrueix l'escena actual ordenadament
|
||||
current_scene_.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reinici "suau": processat al començament del frame per no manipular
|
||||
// l'escena des d'una lambda del menú mentre encara s'està executant.
|
||||
if (restart_requested_) {
|
||||
restart_requested_ = false;
|
||||
Audio::get()->stopMusic();
|
||||
Audio::get()->stopAllSounds();
|
||||
// Reinicialitza Info::ctx des d'Options (vides, diners, diamants...)
|
||||
// en lloc de ctx.reset() pla que deixaria vida=0 → jugador mort.
|
||||
initGameContext();
|
||||
// Força l'intro independentment de `piramide_inicial` (que pot estar
|
||||
// configurat a una piràmide intermèdia per a proves ràpides).
|
||||
Info::ctx.num_piramide = 255;
|
||||
current_scene_.reset();
|
||||
game_state_ = 1; // 1 = dispatch via SceneRegistry per num_piramide
|
||||
has_frame_ = false;
|
||||
Menu::close();
|
||||
Ji::setInputBlocked(false); // el menú ho havia bloquejat — cal desfer-ho
|
||||
applyRestart();
|
||||
}
|
||||
|
||||
if (!context_initialized_) {
|
||||
initGameContext();
|
||||
context_initialized_ = true;
|
||||
}
|
||||
|
||||
constexpr Uint32 FRAME_MS_VSYNC = 16; // ~60 FPS amb VSync
|
||||
constexpr Uint32 FRAME_MS_NO_VSYNC = 4; // ~250 FPS sense VSync (límit superior)
|
||||
|
||||
constexpr Uint32 FRAME_MS_VSYNC = 16;
|
||||
constexpr Uint32 FRAME_MS_NO_VSYNC = 4;
|
||||
const Uint32 FRAME_START = SDL_GetTicks();
|
||||
|
||||
Gamepad::update();
|
||||
KeyRemap::update();
|
||||
GlobalInputs::handle();
|
||||
Mouse::updateCursorVisibility();
|
||||
|
||||
// Bombeig de l'àudio: reomple l'stream de música i para els canals
|
||||
// drenats. Substituïx el callback de SDL_AddTimer de la versió
|
||||
// antiga — imprescindible per al port a emscripten.
|
||||
Audio::update();
|
||||
|
||||
// Dispara els crèdits cinematogràfics la primera vegada que el joc
|
||||
// arriba al menú del títol (Info::ctx.num_piramide == 0).
|
||||
static bool credits_triggered_ = false;
|
||||
if (!credits_triggered_ && Info::ctx.num_piramide == 0) {
|
||||
if (Options::game.show_title_credits) {
|
||||
Overlay::startCredits();
|
||||
}
|
||||
credits_triggered_ = true;
|
||||
}
|
||||
maybeStartTitleCredits();
|
||||
|
||||
// Si l'overlay ja no bloqueja ESC (timeout), desbloquegem
|
||||
if (esc_blocked_ && !Overlay::isEscConsumed()) {
|
||||
esc_blocked_ = false;
|
||||
}
|
||||
|
||||
// Avança l'escena (si no estem pausats). En pausa, es manté l'escena
|
||||
// congelada i re-presentem l'últim frame amb l'overlay fresc per
|
||||
// damunt.
|
||||
if (!paused_) {
|
||||
// Transicions: si l'escena actual ha acabat (o s'ha senyalat
|
||||
// quit), llegim el seu next state i la destruïm per crear la
|
||||
// següent a continuació.
|
||||
if (current_scene_ && (current_scene_->done() || Jg::quitting())) {
|
||||
game_state_ = current_scene_->nextState();
|
||||
current_scene_.reset();
|
||||
if (!tickActiveScene()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Si no hi ha escena activa, construeix la pròxima segons
|
||||
// game_state_ i Info::ctx. Si és impossible (game_state_ == -1,
|
||||
// quit, o state no registrat), eixim del loop.
|
||||
if (!current_scene_) {
|
||||
if (game_state_ == -1 || Jg::quitting()) {
|
||||
return false;
|
||||
}
|
||||
current_scene_ = createNextScene();
|
||||
if (!current_scene_) {
|
||||
return false;
|
||||
}
|
||||
current_scene_->onEnter();
|
||||
last_tick_ms_ = SDL_GetTicks();
|
||||
}
|
||||
|
||||
// Tick de l'escena. Ji::update refresca key_pressed/any_key; el
|
||||
// DELTA_MS és el temps real transcorregut des de l'últim tick.
|
||||
Ji::update();
|
||||
const Uint32 NOW = SDL_GetTicks();
|
||||
const int DELTA_MS = static_cast<int>(NOW - last_tick_ms_);
|
||||
last_tick_ms_ = NOW;
|
||||
current_scene_->tick(DELTA_MS);
|
||||
|
||||
// Converteix `screen` indexat → `pixel_data` ARGB amb la paleta
|
||||
// actual. Jd8::flip ja no fa yield (Phase B.2 eliminà els fibers);
|
||||
// ara només omple el framebuffer perquè el Director l'aprofite.
|
||||
Jd8::flip();
|
||||
std::memcpy(game_frame_, Jd8::getFramebuffer(), sizeof(game_frame_));
|
||||
has_frame_ = true;
|
||||
}
|
||||
|
||||
// Presenta sempre: parteix del frame net del joc, afegeix overlay i envia
|
||||
if (has_frame_) {
|
||||
std::memcpy(presentation_buffer_, game_frame_, sizeof(presentation_buffer_));
|
||||
Screen::get()->present(presentation_buffer_);
|
||||
}
|
||||
|
||||
// Límit de framerate segons VSync.
|
||||
// Nota: quan el runtime posseïx el main loop (SDL_AppIterate /
|
||||
// emscripten), aquest SDL_Delay no és ideal. Fase 7 afegirà un mode
|
||||
// que es basa en el timing intern de SDL en lloc del delay explícit.
|
||||
const Uint32 TARGET_MS = Options::video.vsync ? FRAME_MS_VSYNC : FRAME_MS_NO_VSYNC;
|
||||
const Uint32 ELAPSED = SDL_GetTicks() - FRAME_START;
|
||||
if (ELAPSED < TARGET_MS) {
|
||||
@@ -285,107 +265,97 @@ void Director::pollAllEvents() {
|
||||
}
|
||||
}
|
||||
|
||||
void Director::handleEvent(const SDL_Event& event) {
|
||||
if (event.type == SDL_EVENT_QUIT) {
|
||||
Jg::quitSignal();
|
||||
requestQuit();
|
||||
}
|
||||
// Hot-plug de gamepad (a Emscripten els dispositius web entren com
|
||||
// JOYSTICK_ADDED/REMOVED perquè SDL no reconeix el GUID)
|
||||
if (event.type == SDL_EVENT_GAMEPAD_ADDED || event.type == SDL_EVENT_GAMEPAD_REMOVED ||
|
||||
event.type == SDL_EVENT_JOYSTICK_ADDED || event.type == SDL_EVENT_JOYSTICK_REMOVED) {
|
||||
Gamepad::handleEvent(event);
|
||||
return;
|
||||
}
|
||||
// Empassar-se el KEY_UP de qualsevol tecla que el menú va consumir en KEY_DOWN
|
||||
auto Director::handleMenuEvent(const SDL_Event& event) -> bool {
|
||||
// Empassar-se el KEY_UP d'una tecla que el menú va consumir en KEY_DOWN.
|
||||
if (event.type == SDL_EVENT_KEY_UP && event.key.scancode >= 0 &&
|
||||
event.key.scancode < SDL_SCANCODE_COUNT && menu_keys_held_[event.key.scancode]) {
|
||||
menu_keys_held_[event.key.scancode] = false;
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// Captura de tecla (remapeig al menú): intercepta KEY_DOWN abans de tot
|
||||
if (Menu::isCapturing() && event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) {
|
||||
const bool KEY_DOWN = event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat;
|
||||
// Captura de tecla (remapeig al menú): intercepta KEY_DOWN abans de tot.
|
||||
if (Menu::isCapturing() && KEY_DOWN) {
|
||||
Menu::captureKey(event.key.scancode);
|
||||
menu_keys_held_[event.key.scancode] = true;
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// Pausa: F11 (o tecla configurada) pausa/reprén la simulació.
|
||||
// No mostrem notificació — l'indicador persistent "Pausa" a la cantonada
|
||||
// superior dreta (pintat per Overlay) ja comunica l'estat.
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
|
||||
event.key.scancode == KeyConfig::scancode("pause_toggle")) {
|
||||
// Pausa / menú toggle.
|
||||
if (KEY_DOWN && event.key.scancode == KeyConfig::scancode("pause_toggle")) {
|
||||
togglePause();
|
||||
menu_keys_held_[event.key.scancode] = true;
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// Menú: F12 (o tecla configurada) obre/tanca el menú flotant
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat &&
|
||||
event.key.scancode == KeyConfig::scancode("menu_toggle")) {
|
||||
if (KEY_DOWN && event.key.scancode == KeyConfig::scancode("menu_toggle")) {
|
||||
Menu::toggle();
|
||||
Ji::setInputBlocked(Menu::isOpen());
|
||||
menu_keys_held_[event.key.scancode] = true;
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// Si el menú està obert, consumeix tot l'input de teclat
|
||||
if (Menu::isOpen() && event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) {
|
||||
// Si el menú està obert, consumeix tot l'input de teclat.
|
||||
if (Menu::isOpen() && KEY_DOWN) {
|
||||
if (event.key.scancode == SDL_SCANCODE_ESCAPE) {
|
||||
Menu::close();
|
||||
Ji::setInputBlocked(false);
|
||||
// Empassa l'ESC fins al release perquè el joc no la veja per polling
|
||||
esc_swallow_until_release_ = true;
|
||||
} else {
|
||||
Menu::handleKey(event.key.scancode);
|
||||
// El menú pot haver-se tancat (p.ex. Backspace al nivell arrel)
|
||||
if (!Menu::isOpen()) {
|
||||
Ji::setInputBlocked(false);
|
||||
}
|
||||
}
|
||||
menu_keys_held_[event.key.scancode] = true;
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
if (Menu::isOpen() && event.type == SDL_EVENT_KEY_UP) {
|
||||
return; // no deixem passar KEY_UP al joc tampoc
|
||||
return true;
|
||||
}
|
||||
// Salta els crèdits amb qualsevol tecla que arribe al joc. Es fa DESPRÉS
|
||||
// del toggle del menú/pausa i del handling del menú obert — així F12 i
|
||||
// SELECT (gamepad) obrin el menú sense cancel·lar els crèdits, i la
|
||||
// navegació per dins del menú tampoc els anul·la.
|
||||
return false;
|
||||
}
|
||||
|
||||
auto Director::handleEscapeEvent(const SDL_Event& event) -> bool {
|
||||
// Salta els crèdits amb qualsevol tecla que arribe al joc.
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat && Overlay::creditsActive()) {
|
||||
Overlay::cancelCredits();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// Allibera el bloqueig d'ESC quan l'usuari la deixa anar
|
||||
// Allibera el bloqueig d'ESC quan l'usuari la deixa anar.
|
||||
if (event.type == SDL_EVENT_KEY_UP && event.key.scancode == SDL_SCANCODE_ESCAPE && esc_swallow_until_release_) {
|
||||
esc_swallow_until_release_ = false;
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// ESC: interceptem KEY_DOWN per bloquejar-la ABANS que el joc la veja per polling
|
||||
// ESC KEY_DOWN: bloqueja per polling i decideix notificació vs eixida.
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.scancode == SDL_SCANCODE_ESCAPE && !event.key.repeat) {
|
||||
esc_blocked_ = true; // Bloqueja ESC per polling immediatament
|
||||
esc_blocked_ = true;
|
||||
if (!Overlay::isEscConsumed()) {
|
||||
// Primera pulsació: mostra notificació
|
||||
Overlay::handleEscape();
|
||||
} else {
|
||||
// Segona pulsació: senyal d'eixida al joc
|
||||
esc_blocked_ = false;
|
||||
key_pressed_ = true;
|
||||
Jg::quitSignal();
|
||||
// Si estem en pausa, la desactivem: el fiber del joc està
|
||||
// congelat i necessita ser reprès per veure la senyal de
|
||||
// quit i poder tornar de forma natural.
|
||||
paused_ = false;
|
||||
}
|
||||
return; // no processa més aquest event
|
||||
return true;
|
||||
}
|
||||
if (event.type == SDL_EVENT_KEY_UP) {
|
||||
if (event.key.scancode == SDL_SCANCODE_ESCAPE) {
|
||||
// Ja processat a KEY_DOWN, només deixem netejar el bloqueig
|
||||
// quan l'overlay faça timeout
|
||||
return;
|
||||
} // Comprova si és una tecla d'UI registrada (no passa al joc).
|
||||
// KeyConfig::isGuiKey cobreix totes les tecles GUI a la vegada,
|
||||
// incloent pause_toggle i menu_toggle (defensa en profunditat:
|
||||
// aquestes ja s'haurien hagut de menjar al swallow d'amunt).
|
||||
return false;
|
||||
}
|
||||
|
||||
void Director::handleEvent(const SDL_Event& event) {
|
||||
if (event.type == SDL_EVENT_QUIT) {
|
||||
Jg::quitSignal();
|
||||
requestQuit();
|
||||
}
|
||||
if (event.type == SDL_EVENT_GAMEPAD_ADDED || event.type == SDL_EVENT_GAMEPAD_REMOVED ||
|
||||
event.type == SDL_EVENT_JOYSTICK_ADDED || event.type == SDL_EVENT_JOYSTICK_REMOVED) {
|
||||
Gamepad::handleEvent(event);
|
||||
return;
|
||||
}
|
||||
if (handleMenuEvent(event)) {
|
||||
return;
|
||||
}
|
||||
if (handleEscapeEvent(event)) {
|
||||
return;
|
||||
}
|
||||
if (event.type == SDL_EVENT_KEY_UP && event.key.scancode != SDL_SCANCODE_ESCAPE) {
|
||||
const auto SC = event.key.scancode;
|
||||
if (!KeyConfig::isGuiKey(SC)) {
|
||||
key_pressed_ = true;
|
||||
|
||||
@@ -67,6 +67,14 @@ class Director {
|
||||
// Construeix l'escena apropiada segons game_state_ i Info::ctx.
|
||||
// Retorna nullptr si l'state actual no té escena registrada (bug).
|
||||
[[nodiscard]] auto createNextScene() const -> std::unique_ptr<Scenes::Scene>;
|
||||
// Helpers d'iterate() — extrets per reduir complexitat cognitiva.
|
||||
void applyRestart();
|
||||
static void maybeStartTitleCredits();
|
||||
auto tickActiveScene() -> bool; // true = continuar; false = sortir del loop
|
||||
|
||||
// Helpers d'handleEvent() — cada un retorna true si l'event s'ha consumit.
|
||||
auto handleMenuEvent(const SDL_Event& event) -> bool;
|
||||
auto handleEscapeEvent(const SDL_Event& event) -> bool;
|
||||
|
||||
// Buffers persistents entre iteracions. Abans eren locals a run(),
|
||||
// ara són membres perquè iterate() els pot reutilitzar sense tornar-los
|
||||
|
||||
Vendored
+2
@@ -1,3 +1,4 @@
|
||||
// NOLINTBEGIN(clang-analyzer-unix.Malloc) — codi extern de tercers, no l'auditem
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -510,3 +511,4 @@ unsigned char* LoadGif(unsigned char *buffer, unsigned short* w, unsigned short*
|
||||
|
||||
fclose( gif_file );
|
||||
}*/
|
||||
// NOLINTEND(clang-analyzer-unix.Malloc)
|
||||
|
||||
+12
-12
@@ -6,7 +6,7 @@
|
||||
|
||||
Bola::Bola(Jd8::Surface gfx, Prota* sam)
|
||||
: Sprite(gfx) {
|
||||
this->sam = sam;
|
||||
this->sam_ = sam;
|
||||
|
||||
entitat.frames.reserve(2);
|
||||
entitat.frames.push_back({30, 155, 15, 15});
|
||||
@@ -17,28 +17,28 @@ Bola::Bola(Jd8::Surface gfx, Prota* sam)
|
||||
|
||||
this->cur_frame = 0;
|
||||
this->o = 0;
|
||||
this->cycles_per_frame = 4;
|
||||
this->cycles_per_frame_ = 4;
|
||||
this->x = 20;
|
||||
this->y = 100;
|
||||
this->contador = 0;
|
||||
this->contador_ = 0;
|
||||
}
|
||||
|
||||
void Bola::draw() {
|
||||
if (this->contador == 0) {
|
||||
if (this->contador_ == 0) {
|
||||
Sprite::draw();
|
||||
}
|
||||
}
|
||||
|
||||
void Bola::update() {
|
||||
if (this->contador == 0) {
|
||||
if (this->contador_ == 0) {
|
||||
// Augmentem la x
|
||||
this->x++;
|
||||
if (this->x == 280) {
|
||||
this->contador = 200;
|
||||
this->contador_ = 200;
|
||||
}
|
||||
|
||||
// Augmentem el frame
|
||||
if (Jg::getCycleCounter() % this->cycles_per_frame == 0) {
|
||||
if (Jg::getCycleCounter() % this->cycles_per_frame_ == 0) {
|
||||
this->cur_frame++;
|
||||
if (this->cur_frame == entitat.animacions[this->o].frames.size()) {
|
||||
this->cur_frame = 0;
|
||||
@@ -46,16 +46,16 @@ void Bola::update() {
|
||||
}
|
||||
|
||||
// Comprovem si ha tocat a Sam
|
||||
if (this->x > (this->sam->x - 7) && this->x < (this->sam->x + 7) && this->y > (this->sam->y - 7) && this->y < (this->sam->y + 7)) {
|
||||
this->contador = 200;
|
||||
if (this->x > (this->sam_->x - 7) && this->x < (this->sam_->x + 7) && this->y > (this->sam_->y - 7) && this->y < (this->sam_->y + 7)) {
|
||||
this->contador_ = 200;
|
||||
Info::ctx.vida--;
|
||||
if (Info::ctx.vida == 0) {
|
||||
this->sam->o = 5;
|
||||
this->sam_->o = 5;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this->contador--;
|
||||
if (this->contador == 0) {
|
||||
this->contador_--;
|
||||
if (this->contador_ == 0) {
|
||||
this->x = 20;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,6 @@ class Bola : public Sprite {
|
||||
void update();
|
||||
|
||||
protected:
|
||||
Uint8 contador;
|
||||
Prota* sam;
|
||||
Uint8 contador_;
|
||||
Prota* sam_;
|
||||
};
|
||||
|
||||
@@ -22,25 +22,25 @@ Engendro::Engendro(Jd8::Surface gfx, Uint16 x, Uint16 y)
|
||||
entitat.animacions[0].frames = {0, 1, 2, 3, 2, 1};
|
||||
|
||||
this->cur_frame = 0;
|
||||
this->vida = 18;
|
||||
this->vida_ = 18;
|
||||
this->x = x;
|
||||
this->y = y;
|
||||
this->o = 0;
|
||||
this->cycles_per_frame = 30;
|
||||
this->cycles_per_frame_ = 30;
|
||||
}
|
||||
|
||||
auto Engendro::update() -> bool {
|
||||
bool mort = false;
|
||||
|
||||
if (Jg::getCycleCounter() % this->cycles_per_frame == 0) {
|
||||
if (Jg::getCycleCounter() % this->cycles_per_frame_ == 0) {
|
||||
this->cur_frame++;
|
||||
if (this->cur_frame == entitat.animacions[this->o].frames.size()) {
|
||||
this->cur_frame = 0;
|
||||
}
|
||||
this->vida--;
|
||||
this->vida_--;
|
||||
}
|
||||
|
||||
if (vida == 0) {
|
||||
if (vida_ == 0) {
|
||||
mort = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,5 +9,5 @@ class Engendro : public Sprite {
|
||||
auto update() -> bool;
|
||||
|
||||
protected:
|
||||
Uint8 vida;
|
||||
Uint8 vida_;
|
||||
};
|
||||
|
||||
@@ -247,7 +247,7 @@ void Mapa::comprovaCaixa(Uint8 num) {
|
||||
}
|
||||
|
||||
// Si algun costat encara no està passat, no hi ha res que fer
|
||||
if (std::any_of(std::begin(this->tombes[num].costat), std::end(this->tombes[num].costat), [](bool c) { return !c; })) {
|
||||
if (std::ranges::any_of(this->tombes[num].costat, [](bool c) { return !c; })) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+16
-16
@@ -1,8 +1,8 @@
|
||||
#include "game/marcador.hpp"
|
||||
|
||||
Marcador::Marcador(Jd8::Surface gfx, Prota* sam) {
|
||||
this->gfx = gfx;
|
||||
this->sam = sam;
|
||||
this->gfx_ = gfx;
|
||||
this->sam_ = sam;
|
||||
}
|
||||
|
||||
void Marcador::draw() {
|
||||
@@ -15,47 +15,47 @@ void Marcador::draw() {
|
||||
this->pintaNumero(156, 2, (Info::ctx.diners % 100) / 10);
|
||||
this->pintaNumero(163, 2, Info::ctx.diners % 10);
|
||||
|
||||
if (this->sam->pergami) {
|
||||
Jd8::blitCK(190, 1, this->gfx, 209, 185, 15, 14, 255);
|
||||
if (this->sam_->pergami) {
|
||||
Jd8::blitCK(190, 1, this->gfx_, 209, 185, 15, 14, 255);
|
||||
}
|
||||
|
||||
Jd8::blitCK(271, 1, this->gfx, 0, 20, 15, Info::ctx.vida * 3, 255);
|
||||
Jd8::blitCK(271, 1, this->gfx_, 0, 20, 15, Info::ctx.vida * 3, 255);
|
||||
if (Info::ctx.vida < 5) {
|
||||
Jd8::blitCK(271, 1 + (Info::ctx.vida * 3), this->gfx, 75, 20, 15, 15 - (Info::ctx.vida * 3), 255);
|
||||
Jd8::blitCK(271, 1 + (Info::ctx.vida * 3), this->gfx_, 75, 20, 15, 15 - (Info::ctx.vida * 3), 255);
|
||||
}
|
||||
}
|
||||
|
||||
void Marcador::pintaNumero(Uint16 x, Uint16 y, Uint8 num) {
|
||||
switch (num) {
|
||||
case 0:
|
||||
Jd8::blitCK(x, y, this->gfx, 141, 193, 10, 7, 255);
|
||||
Jd8::blitCK(x, y, this->gfx_, 141, 193, 10, 7, 255);
|
||||
break;
|
||||
case 1:
|
||||
Jd8::blitCK(x, y, this->gfx, 100, 185, 10, 7, 255);
|
||||
Jd8::blitCK(x, y, this->gfx_, 100, 185, 10, 7, 255);
|
||||
break;
|
||||
case 2:
|
||||
Jd8::blitCK(x, y, this->gfx, 110, 185, 10, 7, 255);
|
||||
Jd8::blitCK(x, y, this->gfx_, 110, 185, 10, 7, 255);
|
||||
break;
|
||||
case 3:
|
||||
Jd8::blitCK(x, y, this->gfx, 120, 185, 10, 7, 255);
|
||||
Jd8::blitCK(x, y, this->gfx_, 120, 185, 10, 7, 255);
|
||||
break;
|
||||
case 4:
|
||||
Jd8::blitCK(x, y, this->gfx, 130, 185, 10, 7, 255);
|
||||
Jd8::blitCK(x, y, this->gfx_, 130, 185, 10, 7, 255);
|
||||
break;
|
||||
case 5:
|
||||
Jd8::blitCK(x, y, this->gfx, 140, 185, 10, 7, 255);
|
||||
Jd8::blitCK(x, y, this->gfx_, 140, 185, 10, 7, 255);
|
||||
break;
|
||||
case 6:
|
||||
Jd8::blitCK(x, y, this->gfx, 101, 193, 10, 7, 255);
|
||||
Jd8::blitCK(x, y, this->gfx_, 101, 193, 10, 7, 255);
|
||||
break;
|
||||
case 7:
|
||||
Jd8::blitCK(x, y, this->gfx, 111, 193, 10, 7, 255);
|
||||
Jd8::blitCK(x, y, this->gfx_, 111, 193, 10, 7, 255);
|
||||
break;
|
||||
case 8:
|
||||
Jd8::blitCK(x, y, this->gfx, 121, 193, 10, 7, 255);
|
||||
Jd8::blitCK(x, y, this->gfx_, 121, 193, 10, 7, 255);
|
||||
break;
|
||||
case 9:
|
||||
Jd8::blitCK(x, y, this->gfx, 131, 193, 10, 7, 255);
|
||||
Jd8::blitCK(x, y, this->gfx_, 131, 193, 10, 7, 255);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -14,6 +14,6 @@ class Marcador {
|
||||
protected:
|
||||
void pintaNumero(Uint16 x, Uint16 y, Uint8 num);
|
||||
|
||||
Jd8::Surface gfx;
|
||||
Prota* sam;
|
||||
Jd8::Surface gfx_;
|
||||
Prota* sam_;
|
||||
};
|
||||
|
||||
+101
-88
@@ -7,7 +7,7 @@
|
||||
Momia::Momia(Jd8::Surface gfx, bool dimoni, Uint16 x, Uint16 y, Prota* sam)
|
||||
: Sprite(gfx) {
|
||||
this->dimoni = dimoni;
|
||||
this->sam = sam;
|
||||
this->sam_ = sam;
|
||||
|
||||
entitat.frames.reserve(20);
|
||||
for (int row = 0; row < 4; row++) {
|
||||
@@ -43,7 +43,7 @@ Momia::Momia(Jd8::Surface gfx, bool dimoni, Uint16 x, Uint16 y, Prota* sam)
|
||||
|
||||
this->cur_frame = 0;
|
||||
this->o = rand() % 4;
|
||||
this->cycles_per_frame = 4;
|
||||
this->cycles_per_frame_ = 4;
|
||||
|
||||
if (this->dimoni) {
|
||||
if (x == 0) {
|
||||
@@ -52,7 +52,7 @@ Momia::Momia(Jd8::Surface gfx, bool dimoni, Uint16 x, Uint16 y, Prota* sam)
|
||||
this->x = x;
|
||||
}
|
||||
if (y == 0) {
|
||||
if (this->sam->y > 100) {
|
||||
if (this->sam_->y > 100) {
|
||||
this->y = 30;
|
||||
} else {
|
||||
this->y = 170;
|
||||
@@ -60,7 +60,7 @@ Momia::Momia(Jd8::Surface gfx, bool dimoni, Uint16 x, Uint16 y, Prota* sam)
|
||||
} else {
|
||||
this->y = y;
|
||||
}
|
||||
this->engendro = std::make_unique<Engendro>(gfx, this->x, this->y);
|
||||
this->engendro_ = std::make_unique<Engendro>(gfx, this->x, this->y);
|
||||
} else {
|
||||
this->x = x;
|
||||
this->y = y;
|
||||
@@ -68,104 +68,117 @@ Momia::Momia(Jd8::Surface gfx, bool dimoni, Uint16 x, Uint16 y, Prota* sam)
|
||||
}
|
||||
|
||||
void Momia::draw() {
|
||||
if (this->engendro) {
|
||||
this->engendro->draw();
|
||||
if (this->engendro_) {
|
||||
this->engendro_->draw();
|
||||
} else {
|
||||
Sprite::draw();
|
||||
|
||||
if (Info::ctx.num_piramide == 4) {
|
||||
if ((Jg::getCycleCounter() % 40) < 20) {
|
||||
Jd8::blitCK(this->x, this->y, this->gfx, 220, 80, 15, 15, 255);
|
||||
Jd8::blitCK(this->x, this->y, this->gfx_, 220, 80, 15, 15, 255);
|
||||
} else {
|
||||
Jd8::blitCK(this->x, this->y, this->gfx, 235, 80, 15, 15, 255);
|
||||
Jd8::blitCK(this->x, this->y, this->gfx_, 235, 80, 15, 15, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Momia::pickHorizontalThenVertical() {
|
||||
if (this->x > this->sam_->x) {
|
||||
this->o = 3;
|
||||
} else if (this->x < this->sam_->x) {
|
||||
this->o = 2;
|
||||
} else if (this->y < this->sam_->y) {
|
||||
this->o = 0;
|
||||
} else if (this->y > this->sam_->y) {
|
||||
this->o = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void Momia::pickVerticalThenHorizontal() {
|
||||
if (this->y < this->sam_->y) {
|
||||
this->o = 0;
|
||||
} else if (this->y > this->sam_->y) {
|
||||
this->o = 1;
|
||||
} else if (this->x > this->sam_->x) {
|
||||
this->o = 3;
|
||||
} else if (this->x < this->sam_->x) {
|
||||
this->o = 2;
|
||||
}
|
||||
}
|
||||
|
||||
void Momia::pickDirection() {
|
||||
if (!this->dimoni) {
|
||||
this->o = rand() % 4;
|
||||
return;
|
||||
}
|
||||
if (rand() % 2 == 0) {
|
||||
pickHorizontalThenVertical();
|
||||
} else {
|
||||
pickVerticalThenHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
void Momia::stepInDirection() {
|
||||
switch (this->o) {
|
||||
case 0:
|
||||
if (y < 170) { this->y++; }
|
||||
break;
|
||||
case 1:
|
||||
if (y > 30) { this->y--; }
|
||||
break;
|
||||
case 2:
|
||||
if (x < 280) { this->x++; }
|
||||
break;
|
||||
case 3:
|
||||
if (x > 20) { this->x--; }
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto Momia::collidesWithSam() const -> bool {
|
||||
return this->x > (this->sam_->x - 7) && this->x < (this->sam_->x + 7) &&
|
||||
this->y > (this->sam_->y - 7) && this->y < (this->sam_->y + 7);
|
||||
}
|
||||
|
||||
void Momia::applyCollisionWithSam() {
|
||||
if (this->sam_->pergami) {
|
||||
this->sam_->pergami = false;
|
||||
return;
|
||||
}
|
||||
Info::ctx.vida--;
|
||||
if (Info::ctx.vida == 0) {
|
||||
this->sam_->o = 5;
|
||||
}
|
||||
}
|
||||
|
||||
auto Momia::update() -> bool {
|
||||
bool morta = false;
|
||||
|
||||
if (this->engendro) {
|
||||
if (this->engendro->update()) {
|
||||
this->engendro.reset();
|
||||
if (this->engendro_) {
|
||||
if (this->engendro_->update()) {
|
||||
this->engendro_.reset();
|
||||
}
|
||||
return morta;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->sam->o < 4 && (this->dimoni || Info::ctx.num_piramide == 5 || Jg::getCycleCounter() % 2 == 0)) {
|
||||
if ((this->x - 20) % 65 == 0 && (this->y - 30) % 35 == 0) {
|
||||
if (this->dimoni) {
|
||||
if (rand() % 2 == 0) {
|
||||
if (this->x > this->sam->x) {
|
||||
this->o = 3;
|
||||
} else if (this->x < this->sam->x) {
|
||||
this->o = 2;
|
||||
} else if (this->y < this->sam->y) {
|
||||
this->o = 0;
|
||||
} else if (this->y > this->sam->y) {
|
||||
this->o = 1;
|
||||
}
|
||||
} else {
|
||||
if (this->y < this->sam->y) {
|
||||
this->o = 0;
|
||||
} else if (this->y > this->sam->y) {
|
||||
this->o = 1;
|
||||
} else if (this->x > this->sam->x) {
|
||||
this->o = 3;
|
||||
} else if (this->x < this->sam->x) {
|
||||
this->o = 2;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this->o = rand() % 4;
|
||||
}
|
||||
}
|
||||
|
||||
switch (this->o) {
|
||||
case 0:
|
||||
if (y < 170) {
|
||||
this->y++;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (y > 30) {
|
||||
this->y--;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (x < 280) {
|
||||
this->x++;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (x > 20) {
|
||||
this->x--;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (Jg::getCycleCounter() % this->cycles_per_frame == 0) {
|
||||
this->cur_frame++;
|
||||
if (this->cur_frame == entitat.animacions[this->o].frames.size()) {
|
||||
this->cur_frame = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->x > (this->sam->x - 7) && this->x < (this->sam->x + 7) && this->y > (this->sam->y - 7) && this->y < (this->sam->y + 7)) {
|
||||
morta = true;
|
||||
if (this->sam->pergami) {
|
||||
this->sam->pergami = false;
|
||||
} else {
|
||||
Info::ctx.vida--;
|
||||
if (Info::ctx.vida == 0) {
|
||||
this->sam->o = 5;
|
||||
}
|
||||
}
|
||||
const bool SAM_ALIVE = this->sam_->o < 4;
|
||||
const bool MAY_STEP = this->dimoni || Info::ctx.num_piramide == 5 || Jg::getCycleCounter() % 2 == 0;
|
||||
if (!SAM_ALIVE || !MAY_STEP) {
|
||||
return false;
|
||||
}
|
||||
if ((this->x - 20) % 65 == 0 && (this->y - 30) % 35 == 0) {
|
||||
pickDirection();
|
||||
}
|
||||
stepInDirection();
|
||||
if (Jg::getCycleCounter() % this->cycles_per_frame_ == 0) {
|
||||
this->cur_frame++;
|
||||
if (this->cur_frame == entitat.animacions[this->o].frames.size()) {
|
||||
this->cur_frame = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return morta;
|
||||
if (collidesWithSam()) {
|
||||
applyCollisionWithSam();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,13 @@ class Momia : public Sprite {
|
||||
bool dimoni;
|
||||
|
||||
protected:
|
||||
Prota* sam;
|
||||
std::unique_ptr<Engendro> engendro;
|
||||
Prota* sam_;
|
||||
std::unique_ptr<Engendro> engendro_;
|
||||
|
||||
void pickDirection();
|
||||
void pickHorizontalThenVertical();
|
||||
void pickVerticalThenHorizontal();
|
||||
void stepInDirection();
|
||||
[[nodiscard]] auto collidesWithSam() const -> bool;
|
||||
void applyCollisionWithSam();
|
||||
};
|
||||
|
||||
+27
-37
@@ -437,7 +437,7 @@ namespace Options {
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- Helper per a parsejar floats de YAML ---
|
||||
// --- Helpers per a parsejar camps de YAML ---
|
||||
static void parseFloatField(const fkyaml::node& node, const std::string& key, float& target) {
|
||||
if (node.contains(key)) {
|
||||
try {
|
||||
@@ -448,6 +448,26 @@ namespace Options {
|
||||
}
|
||||
}
|
||||
|
||||
static void parseIntField(const fkyaml::node& node, const std::string& key, int& target) {
|
||||
if (node.contains(key)) {
|
||||
try {
|
||||
target = node[key].get_value<int>();
|
||||
} catch (...) {
|
||||
// @INTENTIONAL: camp YAML no és int vàlid, mantenim valor per defecte.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void parseBoolField(const fkyaml::node& node, const std::string& key, bool& target) {
|
||||
if (node.contains(key)) {
|
||||
try {
|
||||
target = node[key].get_value<bool>();
|
||||
} catch (...) {
|
||||
// @INTENTIONAL: camp YAML no és bool vàlid, mantenim valor per defecte.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Presets PostFX ---
|
||||
|
||||
void setPostFXFile(const std::string& path) { postfx_file_path = path; }
|
||||
@@ -561,42 +581,12 @@ namespace Options {
|
||||
parseFloatField(p, "mask_brightness", preset.mask_brightness);
|
||||
parseFloatField(p, "curvature_x", preset.curvature_x);
|
||||
parseFloatField(p, "curvature_y", preset.curvature_y);
|
||||
if (p.contains("mask_type")) {
|
||||
try {
|
||||
preset.mask_type = p["mask_type"].get_value<int>();
|
||||
} catch (...) { /* @INTENTIONAL: camp YAML invàlid, mantenim el valor per defecte. */
|
||||
}
|
||||
}
|
||||
if (p.contains("enable_scanlines")) {
|
||||
try {
|
||||
preset.enable_scanlines = p["enable_scanlines"].get_value<bool>();
|
||||
} catch (...) { /* @INTENTIONAL: camp YAML invàlid, mantenim el valor per defecte. */
|
||||
}
|
||||
}
|
||||
if (p.contains("enable_multisample")) {
|
||||
try {
|
||||
preset.enable_multisample = p["enable_multisample"].get_value<bool>();
|
||||
} catch (...) { /* @INTENTIONAL: camp YAML invàlid, mantenim el valor per defecte. */
|
||||
}
|
||||
}
|
||||
if (p.contains("enable_gamma")) {
|
||||
try {
|
||||
preset.enable_gamma = p["enable_gamma"].get_value<bool>();
|
||||
} catch (...) { /* @INTENTIONAL: camp YAML invàlid, mantenim el valor per defecte. */
|
||||
}
|
||||
}
|
||||
if (p.contains("enable_curvature")) {
|
||||
try {
|
||||
preset.enable_curvature = p["enable_curvature"].get_value<bool>();
|
||||
} catch (...) { /* @INTENTIONAL: camp YAML invàlid, mantenim el valor per defecte. */
|
||||
}
|
||||
}
|
||||
if (p.contains("enable_sharper")) {
|
||||
try {
|
||||
preset.enable_sharper = p["enable_sharper"].get_value<bool>();
|
||||
} catch (...) { /* @INTENTIONAL: camp YAML invàlid, mantenim el valor per defecte. */
|
||||
}
|
||||
}
|
||||
parseIntField(p, "mask_type", preset.mask_type);
|
||||
parseBoolField(p, "enable_scanlines", preset.enable_scanlines);
|
||||
parseBoolField(p, "enable_multisample", preset.enable_multisample);
|
||||
parseBoolField(p, "enable_gamma", preset.enable_gamma);
|
||||
parseBoolField(p, "enable_curvature", preset.enable_curvature);
|
||||
parseBoolField(p, "enable_sharper", preset.enable_sharper);
|
||||
crtpi_presets.push_back(preset);
|
||||
}
|
||||
}
|
||||
|
||||
+132
-148
@@ -5,84 +5,75 @@
|
||||
#include "core/jail/jgame.hpp"
|
||||
#include "core/jail/jinput.hpp"
|
||||
|
||||
Prota::Prota(Jd8::Surface gfx)
|
||||
: Sprite(gfx) {
|
||||
entitat.frames.reserve(82);
|
||||
|
||||
for (int y = 0; y < 4; y++) {
|
||||
for (int x = 0; x < 5; x++) {
|
||||
Frame f;
|
||||
f.w = 15;
|
||||
f.h = 15;
|
||||
if (Info::ctx.num_piramide == 4) {
|
||||
f.h -= 5;
|
||||
}
|
||||
f.x = x * 15;
|
||||
f.y = 20 + (y * 15);
|
||||
entitat.frames.push_back(f);
|
||||
}
|
||||
namespace {
|
||||
// Atura el frame.h a 10 quan piràmide 4 (sprite "petit" amb pijama de presoner).
|
||||
auto adjustedHeight(int base_h) -> int {
|
||||
return (Info::ctx.num_piramide == 4) ? base_h - 5 : base_h;
|
||||
}
|
||||
for (int y = 95; y < 185; y += 30) {
|
||||
for (int x = 60; x < 315; x += 15) {
|
||||
if (x != 300 || y != 155) {
|
||||
Frame f;
|
||||
f.w = 15;
|
||||
f.h = 30;
|
||||
if (Info::ctx.num_piramide == 4) {
|
||||
f.h -= 5;
|
||||
|
||||
void addFrameGrid(Entitat& entitat, int x0, int x1, int x_step, int y0, int y1, int y_step, int w, int h, int skip_x = -1, int skip_y = -1) {
|
||||
for (int yy = y0; yy < y1; yy += y_step) {
|
||||
for (int xx = x0; xx < x1; xx += x_step) {
|
||||
if (xx == skip_x && yy == skip_y) {
|
||||
continue;
|
||||
}
|
||||
f.x = x;
|
||||
f.y = y;
|
||||
Frame f;
|
||||
f.w = w;
|
||||
f.h = adjustedHeight(h);
|
||||
f.x = xx;
|
||||
f.y = yy;
|
||||
entitat.frames.push_back(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int y = 20; y < 50; y += 15) {
|
||||
for (int x = 225; x < 315; x += 15) {
|
||||
Frame f;
|
||||
f.w = 15;
|
||||
f.h = 15;
|
||||
if (Info::ctx.num_piramide == 4) {
|
||||
f.h -= 5;
|
||||
}
|
||||
f.x = x;
|
||||
f.y = y;
|
||||
entitat.frames.push_back(f);
|
||||
|
||||
void buildProtaFrames(Entitat& entitat) {
|
||||
entitat.frames.reserve(82);
|
||||
// Cara/quatre direccions (4×5 sprites de 15×15 a y=20..)
|
||||
addFrameGrid(entitat, 0, 75, 15, 20, 80, 15, 15, 15);
|
||||
// Animació de mort (15×30 a y=95..; salta x=300/y=155)
|
||||
addFrameGrid(entitat, 60, 315, 15, 95, 185, 30, 15, 30, 300, 155);
|
||||
// Animació de victòria (15×15 a y=20.., x=225..)
|
||||
addFrameGrid(entitat, 225, 315, 15, 20, 50, 15, 15, 15);
|
||||
}
|
||||
|
||||
void buildProtaAnimations(Entitat& entitat) {
|
||||
entitat.animacions.resize(6);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
entitat.animacions[i].frames = {
|
||||
static_cast<Uint8>(0 + (i * 5)),
|
||||
static_cast<Uint8>(1 + (i * 5)),
|
||||
static_cast<Uint8>(2 + (i * 5)),
|
||||
static_cast<Uint8>(1 + (i * 5)),
|
||||
static_cast<Uint8>(0 + (i * 5)),
|
||||
static_cast<Uint8>(3 + (i * 5)),
|
||||
static_cast<Uint8>(4 + (i * 5)),
|
||||
static_cast<Uint8>(3 + (i * 5)),
|
||||
};
|
||||
}
|
||||
entitat.animacions[4].frames.resize(50);
|
||||
for (int i = 0; i < 50; i++) {
|
||||
entitat.animacions[4].frames[i] = i + 20;
|
||||
}
|
||||
entitat.animacions[5].frames.resize(48);
|
||||
for (int i = 0; i < 12; i++) {
|
||||
entitat.animacions[5].frames[i] = i + 70;
|
||||
}
|
||||
for (int i = 12; i < 48; i++) {
|
||||
entitat.animacions[5].frames[i] = 81;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
entitat.animacions.resize(6);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
entitat.animacions[i].frames = {
|
||||
static_cast<Uint8>(0 + (i * 5)),
|
||||
static_cast<Uint8>(1 + (i * 5)),
|
||||
static_cast<Uint8>(2 + (i * 5)),
|
||||
static_cast<Uint8>(1 + (i * 5)),
|
||||
static_cast<Uint8>(0 + (i * 5)),
|
||||
static_cast<Uint8>(3 + (i * 5)),
|
||||
static_cast<Uint8>(4 + (i * 5)),
|
||||
static_cast<Uint8>(3 + (i * 5)),
|
||||
};
|
||||
}
|
||||
|
||||
entitat.animacions[4].frames.resize(50);
|
||||
for (int i = 0; i < 50; i++) {
|
||||
entitat.animacions[4].frames[i] = i + 20;
|
||||
}
|
||||
|
||||
entitat.animacions[5].frames.resize(48);
|
||||
for (int i = 0; i < 12; i++) {
|
||||
entitat.animacions[5].frames[i] = i + 70;
|
||||
}
|
||||
for (int i = 12; i < 48; i++) {
|
||||
entitat.animacions[5].frames[i] = 81;
|
||||
}
|
||||
|
||||
Prota::Prota(Jd8::Surface gfx)
|
||||
: Sprite(gfx) {
|
||||
buildProtaFrames(entitat);
|
||||
buildProtaAnimations(entitat);
|
||||
cur_frame = 0;
|
||||
x = 150;
|
||||
y = 30;
|
||||
o = 0;
|
||||
cycles_per_frame = 4;
|
||||
cycles_per_frame_ = 4;
|
||||
pergami = false;
|
||||
frame_pejades = 0;
|
||||
}
|
||||
@@ -92,94 +83,87 @@ void Prota::draw() {
|
||||
|
||||
if (Info::ctx.num_piramide == 4 && this->o != 4) {
|
||||
if ((Jg::getCycleCounter() % 40) < 20) {
|
||||
Jd8::blitCK(this->x, this->y, this->gfx, 220, 80, 15, 15, 255);
|
||||
Jd8::blitCK(this->x, this->y, this->gfx_, 220, 80, 15, 15, 255);
|
||||
} else {
|
||||
Jd8::blitCK(this->x, this->y, this->gfx, 235, 80, 15, 15, 255);
|
||||
Jd8::blitCK(this->x, this->y, this->gfx_, 235, 80, 15, 15, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Prota::readDirection() -> Uint8 {
|
||||
Uint8 dir = 4;
|
||||
if (Ji::keyPressed(SDL_SCANCODE_DOWN)) {
|
||||
if ((this->x - 20) % 65 == 0) { this->o = 0; }
|
||||
dir = this->o;
|
||||
}
|
||||
if (Ji::keyPressed(SDL_SCANCODE_UP)) {
|
||||
if ((this->x - 20) % 65 == 0) { this->o = 1; }
|
||||
dir = this->o;
|
||||
}
|
||||
if (Ji::keyPressed(SDL_SCANCODE_RIGHT)) {
|
||||
if ((this->y - 30) % 35 == 0) { this->o = 2; }
|
||||
dir = this->o;
|
||||
}
|
||||
if (Ji::keyPressed(SDL_SCANCODE_LEFT)) {
|
||||
if ((this->y - 30) % 35 == 0) { this->o = 3; }
|
||||
dir = this->o;
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
void Prota::stepInDirection(Uint8 dir) {
|
||||
switch (dir) {
|
||||
case 0:
|
||||
if (this->y < 170) { this->y++; }
|
||||
break;
|
||||
case 1:
|
||||
if (this->y > 30) { this->y--; }
|
||||
break;
|
||||
case 2:
|
||||
if (this->x < 280) { this->x++; }
|
||||
break;
|
||||
case 3:
|
||||
if (this->x > 20) { this->x--; }
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Prota::advanceWalkingFrame(Uint8 dir) {
|
||||
if (dir == 4) {
|
||||
this->cur_frame = 0;
|
||||
return;
|
||||
}
|
||||
this->frame_pejades++;
|
||||
if (this->frame_pejades == 15) {
|
||||
this->frame_pejades = 0;
|
||||
}
|
||||
if (Jg::getCycleCounter() % this->cycles_per_frame_ == 0) {
|
||||
this->cur_frame++;
|
||||
if (this->cur_frame == entitat.animacions[this->o].frames.size()) {
|
||||
this->cur_frame = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto Prota::advanceFinalAnimation() -> Uint8 {
|
||||
if (Jg::getCycleCounter() % this->cycles_per_frame_ != 0) {
|
||||
return 0;
|
||||
}
|
||||
this->cur_frame++;
|
||||
if (this->cur_frame != entitat.animacions[this->o].frames.size()) {
|
||||
return 0;
|
||||
}
|
||||
return (this->o == 4) ? 1 : 2;
|
||||
}
|
||||
|
||||
auto Prota::update() -> Uint8 {
|
||||
Uint8 eixir = 0;
|
||||
|
||||
if (this->o < 4) {
|
||||
Uint8 dir = 4;
|
||||
if (Ji::keyPressed(SDL_SCANCODE_DOWN)) {
|
||||
if ((this->x - 20) % 65 == 0) {
|
||||
this->o = 0;
|
||||
}
|
||||
dir = this->o;
|
||||
}
|
||||
if (Ji::keyPressed(SDL_SCANCODE_UP)) {
|
||||
if ((this->x - 20) % 65 == 0) {
|
||||
this->o = 1;
|
||||
}
|
||||
dir = this->o;
|
||||
}
|
||||
if (Ji::keyPressed(SDL_SCANCODE_RIGHT)) {
|
||||
if ((this->y - 30) % 35 == 0) {
|
||||
this->o = 2;
|
||||
}
|
||||
dir = this->o;
|
||||
}
|
||||
if (Ji::keyPressed(SDL_SCANCODE_LEFT)) {
|
||||
if ((this->y - 30) % 35 == 0) {
|
||||
this->o = 3;
|
||||
}
|
||||
dir = this->o;
|
||||
}
|
||||
|
||||
switch (dir) {
|
||||
case 0:
|
||||
if (this->y < 170) {
|
||||
this->y++;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (this->y > 30) {
|
||||
this->y--;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (this->x < 280) {
|
||||
this->x++;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (this->x > 20) {
|
||||
this->x--;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (dir == 4) {
|
||||
this->cur_frame = 0;
|
||||
} else {
|
||||
this->frame_pejades++;
|
||||
if (this->frame_pejades == 15) {
|
||||
this->frame_pejades = 0;
|
||||
}
|
||||
if (Jg::getCycleCounter() % this->cycles_per_frame == 0) {
|
||||
this->cur_frame++;
|
||||
if (this->cur_frame == entitat.animacions[this->o].frames.size()) {
|
||||
this->cur_frame = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
eixir = 0U;
|
||||
} else {
|
||||
if (Jg::getCycleCounter() % this->cycles_per_frame == 0) {
|
||||
this->cur_frame++;
|
||||
if (this->cur_frame == entitat.animacions[this->o].frames.size()) {
|
||||
if (this->o == 4) {
|
||||
eixir = 1;
|
||||
} else {
|
||||
eixir = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this->o >= 4) {
|
||||
return advanceFinalAnimation();
|
||||
}
|
||||
return eixir;
|
||||
const Uint8 DIR = readDirection();
|
||||
stepInDirection(DIR);
|
||||
advanceWalkingFrame(DIR);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -14,4 +14,8 @@ class Prota : public Sprite {
|
||||
bool pergami;
|
||||
|
||||
protected:
|
||||
auto readDirection() -> Uint8;
|
||||
void stepInDirection(Uint8 dir);
|
||||
void advanceWalkingFrame(Uint8 dir);
|
||||
auto advanceFinalAnimation() -> Uint8;
|
||||
};
|
||||
|
||||
@@ -132,17 +132,59 @@ namespace Scenes {
|
||||
}
|
||||
}
|
||||
|
||||
void IntroNewLogoScene::advanceRevealing(int delta_ms) {
|
||||
phase_acc_ms_ += delta_ms;
|
||||
render();
|
||||
if (phase_acc_ms_ < REVEAL_FRAME_MS) {
|
||||
return;
|
||||
}
|
||||
phase_acc_ms_ = 0;
|
||||
reveal_cursor_visible_ = !reveal_cursor_visible_;
|
||||
if (!reveal_cursor_visible_) {
|
||||
return;
|
||||
}
|
||||
++reveal_letter_;
|
||||
if (reveal_letter_ >= 9) {
|
||||
phase_ = Phase::FULL_LOGO_FLASH;
|
||||
reveal_letter_ = 8;
|
||||
}
|
||||
}
|
||||
|
||||
void IntroNewLogoScene::advancePaletteStep(int delta_ms) {
|
||||
phase_acc_ms_ += delta_ms;
|
||||
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::FINAL_WAIT;
|
||||
phase_acc_ms_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void IntroNewLogoScene::advanceSpritesPhase(int delta_ms) {
|
||||
if (!sprites_scene_) {
|
||||
sprites_scene_ = std::make_unique<IntroSpritesScene>(std::move(gfx_));
|
||||
sprites_scene_->onEnter();
|
||||
}
|
||||
sprites_scene_->tick(delta_ms);
|
||||
if (sprites_scene_->done()) {
|
||||
Info::ctx.num_piramide = 0;
|
||||
phase_ = Phase::DONE;
|
||||
}
|
||||
}
|
||||
|
||||
void IntroNewLogoScene::tick(int delta_ms) {
|
||||
// Qualsevol tecla durant el revelat o el ciclo de paleta salta
|
||||
// TOTA la intro (inclou saltar la fase de sprites). Durant SPRITES
|
||||
// deixem que la sub-escena gestione el seu propi skip (que a més
|
||||
// respecta la fase "final" no skippable de la variant 0).
|
||||
// deixem que la sub-escena gestione el seu propi skip.
|
||||
if (phase_ != Phase::SPRITES && phase_ != Phase::DONE && Ji::anyKey()) {
|
||||
Info::ctx.num_piramide = 0;
|
||||
phase_ = Phase::DONE;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (phase_) {
|
||||
case Phase::INITIAL:
|
||||
phase_acc_ms_ += delta_ms;
|
||||
@@ -152,25 +194,9 @@ namespace Scenes {
|
||||
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::FULL_LOGO_FLASH;
|
||||
reveal_letter_ = 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
advanceRevealing(delta_ms);
|
||||
break;
|
||||
|
||||
case Phase::FULL_LOGO_FLASH:
|
||||
phase_acc_ms_ += delta_ms;
|
||||
render();
|
||||
@@ -179,24 +205,9 @@ namespace Scenes {
|
||||
phase_acc_ms_ = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case Phase::PALETTE_CYCLE:
|
||||
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::FINAL_WAIT;
|
||||
phase_acc_ms_ = 0;
|
||||
}
|
||||
advancePaletteStep(delta_ms);
|
||||
break;
|
||||
|
||||
case Phase::FINAL_WAIT:
|
||||
phase_acc_ms_ += delta_ms;
|
||||
render();
|
||||
@@ -204,25 +215,9 @@ namespace Scenes {
|
||||
phase_ = Phase::SPRITES;
|
||||
}
|
||||
break;
|
||||
|
||||
case Phase::SPRITES:
|
||||
// Sub-escena construïda al primer tick. Transferim el gfx_
|
||||
// per move — la sub-escena se n'ocupa fins que es destruix.
|
||||
// Cada tick successiu delega 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()) {
|
||||
// El vell `Go()` post-switch feia `num_piramide = 0`
|
||||
// per passar al menú. Sense açò el while del fiber
|
||||
// tornaria a crear IntroNewLogoScene infinitament.
|
||||
Info::ctx.num_piramide = 0;
|
||||
phase_ = Phase::DONE;
|
||||
}
|
||||
advanceSpritesPhase(delta_ms);
|
||||
break;
|
||||
|
||||
case Phase::DONE:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -41,17 +41,21 @@ namespace Scenes {
|
||||
|
||||
private:
|
||||
enum class Phase : std::uint8_t {
|
||||
INITIAL, // pantalla negra 1000 ms
|
||||
REVEALING, // 9 × 2 frames × 150 ms cada un
|
||||
INITIAL, // pantalla negra 1000 ms
|
||||
REVEALING, // 9 × 2 frames × 150 ms cada un
|
||||
FULL_LOGO_FLASH, // logo complet + cursor, 200 ms
|
||||
PALETTE_CYCLE, // 256 passos × 20 ms modificant paleta
|
||||
FINAL_WAIT, // 20 ms final
|
||||
SPRITES, // tick delegat a IntroSpritesScene fins que acaba
|
||||
PALETTE_CYCLE, // 256 passos × 20 ms modificant paleta
|
||||
FINAL_WAIT, // 20 ms final
|
||||
SPRITES, // tick delegat a IntroSpritesScene fins que acaba
|
||||
DONE,
|
||||
};
|
||||
|
||||
void render();
|
||||
void advancePaletteCycle();
|
||||
// Helpers per a `tick()` — extrets per reduir complexitat cognitiva.
|
||||
void advanceRevealing(int delta_ms);
|
||||
void advancePaletteStep(int delta_ms);
|
||||
void advanceSpritesPhase(int delta_ms);
|
||||
|
||||
SurfaceHandle gfx_;
|
||||
SurfaceHandle cursor_surf_;
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
namespace Scenes {
|
||||
|
||||
auto SceneRegistry::instance() -> SceneRegistry& {
|
||||
static SceneRegistry inst;
|
||||
return inst;
|
||||
static SceneRegistry instance_;
|
||||
return instance_;
|
||||
}
|
||||
|
||||
void SceneRegistry::registerScene(int state_key, Factory factory) {
|
||||
|
||||
@@ -65,12 +65,12 @@ namespace Scenes {
|
||||
next_state_ = 0;
|
||||
}
|
||||
|
||||
void SlidesScene::drawSlide(int slide_idx, int POS_X) {
|
||||
void SlidesScene::drawSlide(int slide_idx, int pos_x) {
|
||||
const int SRC_Y = slide_idx * SLIDE_H;
|
||||
|
||||
// Clipping manual: translada un rect de 320×65 des de (POS_X, SLIDE_Y)
|
||||
// a l'àrea visible (0..319, SLIDE_Y..SLIDE_Y+64).
|
||||
int dst_x = POS_X;
|
||||
int dst_x = pos_x;
|
||||
int src_x = 0;
|
||||
int w = 320;
|
||||
|
||||
@@ -99,99 +99,106 @@ namespace Scenes {
|
||||
phase_ = Phase::FADE_FINAL;
|
||||
}
|
||||
|
||||
void SlidesScene::tick(int delta_ms) {
|
||||
// Skip: qualsevol tecla salta directament al fade final. Per fidelitat
|
||||
// al vell doSlides, el skip NO atura la música explícitament — només
|
||||
// el final natural crida Ja::fadeOutMusic (beginFinalFade() distingeix).
|
||||
if (!skip_triggered_ && Ji::anyKey()) {
|
||||
skip_triggered_ = true;
|
||||
if (num_piramide_at_start_ != 7) {
|
||||
Audio::get()->fadeOutMusic(250);
|
||||
}
|
||||
fade_.startFadeOut();
|
||||
phase_ = Phase::FADE_FINAL;
|
||||
void SlidesScene::triggerSkip() {
|
||||
skip_triggered_ = true;
|
||||
if (num_piramide_at_start_ != 7) {
|
||||
Audio::get()->fadeOutMusic(250);
|
||||
}
|
||||
fade_.startFadeOut();
|
||||
phase_ = Phase::FADE_FINAL;
|
||||
}
|
||||
|
||||
void SlidesScene::tickSlideEnter(int delta_ms) {
|
||||
phase_acc_ms_ += delta_ms;
|
||||
int slide_idx = 2;
|
||||
if (phase_ == Phase::SLIDE1_ENTER) {
|
||||
slide_idx = 0;
|
||||
} else if (phase_ == Phase::SLIDE2_ENTER) {
|
||||
slide_idx = 1;
|
||||
}
|
||||
const float T = std::min(1.0F, static_cast<float>(phase_acc_ms_) / static_cast<float>(SCROLL_MS));
|
||||
const float EASED = Easing::outCubic(T);
|
||||
drawSlide(slide_idx, Easing::lerpInt(SLIDE_START_X[slide_idx], 0, EASED));
|
||||
if (phase_acc_ms_ < SCROLL_MS) {
|
||||
return;
|
||||
}
|
||||
drawSlide(slide_idx, 0);
|
||||
switch (phase_) {
|
||||
case Phase::SLIDE1_ENTER:
|
||||
phase_ = Phase::SLIDE1_HOLD;
|
||||
break;
|
||||
case Phase::SLIDE2_ENTER:
|
||||
phase_ = Phase::SLIDE2_HOLD;
|
||||
break;
|
||||
default:
|
||||
phase_ = Phase::SLIDE3_HOLD;
|
||||
break;
|
||||
}
|
||||
phase_acc_ms_ = 0;
|
||||
}
|
||||
|
||||
void SlidesScene::tickHoldIntermediate(int delta_ms) {
|
||||
phase_acc_ms_ += delta_ms;
|
||||
if (phase_acc_ms_ < HOLD_MS) {
|
||||
return;
|
||||
}
|
||||
fade_.startFadeOut();
|
||||
phase_ = (phase_ == Phase::SLIDE1_HOLD) ? Phase::FADE_OUT1 : Phase::FADE_OUT2;
|
||||
phase_acc_ms_ = 0;
|
||||
}
|
||||
|
||||
void SlidesScene::tickFadeOutIntermediate(int delta_ms) {
|
||||
fade_.tick(delta_ms);
|
||||
if (!fade_.done()) {
|
||||
return;
|
||||
}
|
||||
restorePalette();
|
||||
Jd8::clearScreen(BG_COLOR_INDEX);
|
||||
phase_ = (phase_ == Phase::FADE_OUT1) ? Phase::SLIDE2_ENTER : Phase::SLIDE3_ENTER;
|
||||
phase_acc_ms_ = 0;
|
||||
}
|
||||
|
||||
void SlidesScene::tickFinalFade(int delta_ms) {
|
||||
fade_.tick(delta_ms);
|
||||
if (!fade_.done()) {
|
||||
return;
|
||||
}
|
||||
if (num_piramide_at_start_ == 7) {
|
||||
Info::ctx.num_piramide = 8;
|
||||
next_state_ = 1;
|
||||
} else {
|
||||
next_state_ = 0;
|
||||
}
|
||||
phase_ = Phase::DONE;
|
||||
}
|
||||
|
||||
void SlidesScene::tick(int delta_ms) {
|
||||
if (!skip_triggered_ && Ji::anyKey()) {
|
||||
triggerSkip();
|
||||
}
|
||||
switch (phase_) {
|
||||
case Phase::SLIDE1_ENTER:
|
||||
case Phase::SLIDE2_ENTER:
|
||||
case Phase::SLIDE3_ENTER: {
|
||||
phase_acc_ms_ += delta_ms;
|
||||
int slide_idx = 2;
|
||||
if (phase_ == Phase::SLIDE1_ENTER) {
|
||||
slide_idx = 0;
|
||||
} else if (phase_ == Phase::SLIDE2_ENTER) {
|
||||
slide_idx = 1;
|
||||
}
|
||||
const float T = std::min(1.0F, static_cast<float>(phase_acc_ms_) / static_cast<float>(SCROLL_MS));
|
||||
const float EASED = Easing::outCubic(T);
|
||||
const int POS_X = Easing::lerpInt(SLIDE_START_X[slide_idx], 0, EASED);
|
||||
drawSlide(slide_idx, POS_X);
|
||||
|
||||
if (phase_acc_ms_ >= SCROLL_MS) {
|
||||
// Garanteix posició final exacta (POS_X=0).
|
||||
drawSlide(slide_idx, 0);
|
||||
if (phase_ == Phase::SLIDE1_ENTER) {
|
||||
phase_ = Phase::SLIDE1_HOLD;
|
||||
} else if (phase_ == Phase::SLIDE2_ENTER) {
|
||||
phase_ = Phase::SLIDE2_HOLD;
|
||||
} else {
|
||||
phase_ = Phase::SLIDE3_HOLD;
|
||||
}
|
||||
phase_acc_ms_ = 0;
|
||||
}
|
||||
case Phase::SLIDE3_ENTER:
|
||||
tickSlideEnter(delta_ms);
|
||||
break;
|
||||
}
|
||||
|
||||
case Phase::SLIDE1_HOLD:
|
||||
case Phase::SLIDE2_HOLD:
|
||||
phase_acc_ms_ += delta_ms;
|
||||
if (phase_acc_ms_ >= HOLD_MS) {
|
||||
fade_.startFadeOut();
|
||||
if (phase_ == Phase::SLIDE1_HOLD) {
|
||||
phase_ = Phase::FADE_OUT1;
|
||||
} else {
|
||||
phase_ = Phase::FADE_OUT2;
|
||||
}
|
||||
phase_acc_ms_ = 0;
|
||||
}
|
||||
tickHoldIntermediate(delta_ms);
|
||||
break;
|
||||
|
||||
case Phase::SLIDE3_HOLD:
|
||||
phase_acc_ms_ += delta_ms;
|
||||
if (phase_acc_ms_ >= HOLD_MS) {
|
||||
beginFinalFade();
|
||||
}
|
||||
break;
|
||||
|
||||
case Phase::FADE_OUT1:
|
||||
case Phase::FADE_OUT2:
|
||||
fade_.tick(delta_ms);
|
||||
if (fade_.done()) {
|
||||
restorePalette();
|
||||
Jd8::clearScreen(BG_COLOR_INDEX);
|
||||
if (phase_ == Phase::FADE_OUT1) {
|
||||
phase_ = Phase::SLIDE2_ENTER;
|
||||
} else {
|
||||
phase_ = Phase::SLIDE3_ENTER;
|
||||
}
|
||||
phase_acc_ms_ = 0;
|
||||
}
|
||||
tickFadeOutIntermediate(delta_ms);
|
||||
break;
|
||||
|
||||
case Phase::FADE_FINAL:
|
||||
fade_.tick(delta_ms);
|
||||
if (fade_.done()) {
|
||||
if (num_piramide_at_start_ == 7) {
|
||||
Info::ctx.num_piramide = 8;
|
||||
next_state_ = 1;
|
||||
} else {
|
||||
next_state_ = 0;
|
||||
}
|
||||
phase_ = Phase::DONE;
|
||||
}
|
||||
tickFinalFade(delta_ms);
|
||||
break;
|
||||
|
||||
case Phase::DONE:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -65,6 +65,12 @@ namespace Scenes {
|
||||
void drawSlide(int slide_idx, int pos_x);
|
||||
void restorePalette();
|
||||
void beginFinalFade();
|
||||
// Helpers per a `tick()` — extrets per reduir complexitat cognitiva.
|
||||
void triggerSkip();
|
||||
void tickSlideEnter(int delta_ms);
|
||||
void tickHoldIntermediate(int delta_ms);
|
||||
void tickFadeOutIntermediate(int delta_ms);
|
||||
void tickFinalFade(int delta_ms);
|
||||
|
||||
SurfaceHandle gfx_;
|
||||
Jd8::Palette pal_aux_{nullptr}; // còpia "neta" que preservem
|
||||
|
||||
@@ -63,9 +63,9 @@ namespace Scenes {
|
||||
// Pot ser que el següent pas siga una cadena de one-shots.
|
||||
flushOneShots();
|
||||
} else if (s.continuous) {
|
||||
const float p = static_cast<float>(elapsed_in_step_) /
|
||||
const float P = static_cast<float>(elapsed_in_step_) /
|
||||
static_cast<float>(std::max(1, s.duration_ms));
|
||||
s.continuous(p);
|
||||
s.continuous(P);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#include "game/sprite.hpp"
|
||||
|
||||
Sprite::Sprite(Jd8::Surface gfx)
|
||||
: gfx(gfx) {}
|
||||
: gfx_(gfx) {}
|
||||
|
||||
void Sprite::draw() {
|
||||
const Frame& f = entitat.frames[entitat.animacions[o].frames[cur_frame]];
|
||||
Jd8::blitCK(x, y, gfx, f.x, f.y, f.w, f.h, 255);
|
||||
Jd8::blitCK(x, y, gfx_, f.x, f.y, f.w, f.h, 255);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,6 @@ class Sprite {
|
||||
Uint16 o = 0;
|
||||
|
||||
protected:
|
||||
Jd8::Surface gfx;
|
||||
Uint8 cycles_per_frame = 1;
|
||||
Jd8::Surface gfx_;
|
||||
Uint8 cycles_per_frame_ = 1;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user