143 lines
6.8 KiB
C++
143 lines
6.8 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <string>
|
|
|
|
#include "core/ui/ui_config.hpp"
|
|
|
|
// FSM de sala (intro/outro) extreta de GameScene per a testabilitat. La
|
|
// lògica que viu aquí és pura: no toca SDL, ni Audio, ni cap singleton
|
|
// excepte `Locale::get()` (per a la formatació del text). El consumidor
|
|
// actual és `GameScene`, que cridarà `RoomFsm::advance` cada frame.
|
|
namespace RoomFsm {
|
|
|
|
enum class State : std::uint8_t {
|
|
ROOM_INTRO, // Entrada de sala: fade in + text d'entrada.
|
|
PLAYING, // Joc normal.
|
|
ROOM_OUTRO, // Sortida de sala: text de sortida + fade out.
|
|
};
|
|
|
|
// Sub-fase i temps acumulat dins de l'estat actual. Es reseteja a
|
|
// {0, 0} quan es canvia d'estat. Significat de `phase` per estat:
|
|
// ROOM_INTRO: 0=fade_in, 1=text_delay, 2=text_enter, 3=text_hold,
|
|
// 4=text_exit, 5=music_tail
|
|
// ROOM_OUTRO: 0=text_enter, 1=text_hold, 2=text_exit, 3=music_tail,
|
|
// 4=fade_out, 5=black_hold
|
|
// PLAYING: no s'usa.
|
|
// Les fases `music_tail` són la cua silenciosa que sincronitza el
|
|
// timeline visual amb el final del .ogg de música corresponent: el
|
|
// GameScene les calcula a `enterRoomIntro` / `setState(ROOM_OUTRO)`
|
|
// a partir de la durada precarregada del .ogg menys el sumatori de
|
|
// les fases de text. Si la música no arrenca, la cua és 0 i la fase
|
|
// es consumeix instantàniament (la transició no s'embussa mai).
|
|
struct Transition {
|
|
int phase{0};
|
|
int elapsed_ms{0};
|
|
};
|
|
|
|
// Timings i corbes carregats de `data/scenes/game.yaml`. Els valors
|
|
// per defecte coincideixen amb els del YAML versionat: si manca el
|
|
// fitxer, la FSM funciona igualment amb aquests fallbacks.
|
|
struct Config {
|
|
int intro_fade_in_ms{500};
|
|
int intro_text_delay_ms{500};
|
|
int intro_text_enter_ms{500};
|
|
int intro_text_hold_ms{1000};
|
|
int intro_text_exit_ms{500};
|
|
|
|
int outro_text_enter_ms{500};
|
|
int outro_text_hold_ms{1000};
|
|
int outro_text_exit_ms{500};
|
|
int outro_fade_out_ms{500};
|
|
// Pausa en negre opac entre el final de l'OUTRO i el començament
|
|
// de l'INTRO de la sala següent. 0 = sense pausa (transició
|
|
// immediata, comportament històric). El YAML pot sobreescriure
|
|
// sense recompilar — útil per donar respir abans del nou rètol.
|
|
int outro_black_hold_ms{0};
|
|
|
|
// Cua silenciosa entre el text d'INTRO/OUTRO i la fi del .ogg de
|
|
// música corresponent. NO es llig del YAML — el GameScene la
|
|
// calcula a `enterRoomIntro` / `setState(ROOM_OUTRO)` com
|
|
// `max(0, music_duration - text_total)`. Així el timeline acaba
|
|
// exactament quan la música drena i no cal cap callback de fi
|
|
// de música. 0 quan no hi ha música o quan el text dura més que
|
|
// la pista.
|
|
int intro_music_tail_ms{0};
|
|
int outro_music_tail_ms{0};
|
|
|
|
// Mil·lisegons des del callback de fi de la música d'INTRO
|
|
// (`room_intro.ogg`) en què cau l'spawn aeri del primer slot
|
|
// actiu. 0 = cau al moment exacte que la música acaba. El
|
|
// respawn post-mort tindrà el seu propi timing quan toqui
|
|
// dissenyar-lo.
|
|
int player_spawn_delay_ms{0};
|
|
|
|
// Delay independent per al segon jugador, **només quan els dos
|
|
// slots estan actius** (mode BOTH). En single-player (P1 o P2
|
|
// sols) s'usa sempre `player_spawn_delay_ms`. Si el YAML no
|
|
// fixa aquest valor, `loadConfig` el copia de `player_spawn_delay_ms`
|
|
// perquè els dos players caiguin alhora — comportament neutre.
|
|
int player2_spawn_delay_ms{0};
|
|
|
|
// Píxels extra entre les dues línies del text d'INTRO (piràmide
|
|
// damunt, sala davall). Es sumen al `lineHeight` natural del
|
|
// font per separar visualment les dues línies. 0 = stack
|
|
// immediat (com l'antic single-line). El YAML pot afinar-lo
|
|
// sense recompilar.
|
|
int intro_text_line_gap_px{8};
|
|
|
|
// Equivalent per a l'OUTRO ("fase" damunt, "completada" davall).
|
|
// Es manté independent del de l'INTRO per si el ritme visual
|
|
// demana un espaiat distint en cada banda de la transició.
|
|
int outro_text_line_gap_px{8};
|
|
|
|
UiConfig::CurveFn text_enter_curve{nullptr};
|
|
UiConfig::CurveFn text_exit_curve{nullptr};
|
|
|
|
// Preset de reverb (catàleg a `data/sounds.yaml`) que GameScene
|
|
// arma al jugador al spawn aeri d'INTRO. Es consumeix al primer
|
|
// aterratge cinemàtic; els salts posteriors del joc i els
|
|
// respawns post-mort cauen al so sec. Buit = sense reverb (la
|
|
// cinemàtica d'entrada sona igual que un salt qualsevol).
|
|
std::string intro_land_reverb_preset;
|
|
|
|
// Panel translúcid sota el rètol de transició (INTRO i OUTRO).
|
|
// Centrat verticalment al centre del playfield, ocupa tota
|
|
// l'amplada del canvas. Creix d'altura 0 → height_px durant la
|
|
// fase d'enter del text (amb `text_enter_curve`) i decreix
|
|
// durant la fase d'exit (amb `text_exit_curve`). 0 d'alpha o
|
|
// 0 d'alçada el deixa invisible. Color RGB (sense alpha — la
|
|
// transparència viu al camp `alpha`).
|
|
std::uint32_t text_panel_color{0x000000U};
|
|
std::uint8_t text_panel_alpha{128};
|
|
int text_panel_height_px{40};
|
|
};
|
|
|
|
// Llegeix la configuració des del YAML. Si el fitxer manca o falten
|
|
// claus, els camps no presents conserven els seus fallbacks. Les
|
|
// corbes es resolen amb `UiConfig::curveByName` (mateix mecanisme
|
|
// que la pausa); si el nom és desconegut cau a `Easing::linear`.
|
|
[[nodiscard]] auto loadConfig(const std::string& yaml_path) -> Config;
|
|
|
|
// Avança la transició consumint `delta_ms` des del temps real.
|
|
// Retorna l'estat resultant (potser el mateix que l'entrant si
|
|
// encara queden sub-fases). Si retorna un estat diferent, el caller
|
|
// ha de resetejar la `Transition` a {0, 0} abans del següent frame
|
|
// (això ho fa `GameScene::setState`). Funció pura sobre l'argument.
|
|
[[nodiscard]] auto advance(State current, Transition& t, const Config& cfg, int delta_ms) -> State;
|
|
|
|
// Substitueix `{p}` de la clau "game.room_intro_pyramid" del Locale
|
|
// per l'enter passat. Pensat per a la primera línia del text d'INTRO.
|
|
// Si la clau no està traduïda, retorna la pròpia clau (fallback de
|
|
// Locale::text).
|
|
[[nodiscard]] auto formatIntroPyramidLine(int pyramid) -> std::string;
|
|
|
|
// Equivalent per a `game.room_intro_room` amb el placeholder `{r}`.
|
|
// Es renderitza com a segona línia del text d'INTRO.
|
|
[[nodiscard]] auto formatIntroRoomLine(int room) -> std::string;
|
|
|
|
// Nom textual per a logs i tests. No es fa servir per a lògica.
|
|
[[nodiscard]] auto stateName(State s) -> const char*;
|
|
|
|
} // namespace RoomFsm
|