Files
aee-arcade/source/game/scenes/room_fsm.hpp
T

136 lines
6.5 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
// ROOM_OUTRO: 0=text_enter, 1=text_hold, 2=text_exit, 3=fade_out, 4=black_hold
// PLAYING: no s'usa.
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};
// Mil·lisegons des de l'inici de ROOM_INTRO en què cau la
// cinemàtica d'spawn aeri dels jugadors actius. Específic per a
// l'entrada de sala — el respawn post-mort tindrà el seu propi
// timing quan toqui dissenyar-lo. Un valor habitual: la suma de
// fade_in + text_delay + text_enter + text_hold, perquè els
// players caiguin just quan el rètol comença a sortir. El
// fallback compilat (500 ms) deixa el spawn al final del fade-in
// si el YAML no defineix res — només pensat com a degradació.
int player_spawn_delay_ms{500};
// 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` per al jugador
// actiu. Si el YAML no fixa aquest valor, el `loadConfig` el
// copia de `player_spawn_delay_ms` perquè els dos players caiguin
// alhora — comportament històric del joc.
int player2_spawn_delay_ms{500};
// 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;
// Clau d'un so puntual (data/sounds/<key>) que GameScene
// dispara just quan la cinemàtica d'INTRO arriba a la fase
// d'entrada del text (phase == 2). Buit o absent = sense so.
// Convenció Resource::Cache: el basename val tant de clau com
// de path (loadSound ho fa servir igual que el land_sound).
std::string intro_text_sound;
// 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