168 lines
7.1 KiB
C++
168 lines
7.1 KiB
C++
#include "game/scenes/game_over_fsm.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
|
|
#include "core/resources/resource_helper.hpp"
|
|
#include "external/fkyaml_node.hpp"
|
|
#include "utils/log.hpp"
|
|
|
|
namespace {
|
|
|
|
// Llegeix un sub-camp sencer d'un node YAML. Si manca, retorna el
|
|
// fallback. Mateix patró que `RoomFsm::loadConfig`.
|
|
auto readChildInt(const fkyaml::node& parent, const char* key, int fallback) -> int {
|
|
if (!parent.contains(key)) { return fallback; }
|
|
return parent[key].get_value<int>();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace GameOverFsm {
|
|
|
|
auto loadConfig(const std::string& yaml_path) -> Config {
|
|
Config cfg{};
|
|
auto bytes = Resource::Helper::loadFile(yaml_path);
|
|
if (bytes.empty()) {
|
|
Log::warn("GameOverFsm", "{} absent — uso fallbacks compilats", yaml_path);
|
|
return cfg;
|
|
}
|
|
const auto* begin = reinterpret_cast<const char*>(bytes.data());
|
|
const auto* end = begin + bytes.size();
|
|
auto yaml = fkyaml::node::deserialize(begin, end);
|
|
|
|
if (yaml.contains("game_over")) {
|
|
const auto& n = yaml["game_over"];
|
|
cfg.continue_text_enter_ms = readChildInt(n, "continue_text_enter_ms", cfg.continue_text_enter_ms);
|
|
cfg.continue_text_hold_ms = readChildInt(n, "continue_text_hold_ms", cfg.continue_text_hold_ms);
|
|
cfg.continue_text_exit_ms = readChildInt(n, "continue_text_exit_ms", cfg.continue_text_exit_ms);
|
|
cfg.continue_count_start = readChildInt(n, "continue_count_start", cfg.continue_count_start);
|
|
cfg.game_over_text_hold_ms = readChildInt(n, "game_over_text_hold_ms", cfg.game_over_text_hold_ms);
|
|
cfg.game_over_text_exit_ms = readChildInt(n, "game_over_text_exit_ms", cfg.game_over_text_exit_ms);
|
|
cfg.game_over_wait_ms = readChildInt(n, "game_over_wait_ms", cfg.game_over_wait_ms);
|
|
cfg.fade_out_ms = readChildInt(n, "fade_out_ms", cfg.fade_out_ms);
|
|
cfg.black_hold_ms = readChildInt(n, "black_hold_ms", cfg.black_hold_ms);
|
|
if (n.contains("continue_music_speed")) {
|
|
cfg.continue_music_speed = n["continue_music_speed"].get_value<float>();
|
|
}
|
|
if (n.contains("game_over_music_track")) {
|
|
cfg.game_over_music_track = n["game_over_music_track"].get_value<std::string>();
|
|
}
|
|
if (n.contains("countdown_sound_key")) {
|
|
cfg.countdown_sound_key = n["countdown_sound_key"].get_value<std::string>();
|
|
}
|
|
cfg.skip_below_or_equal = readChildInt(n, "skip_below_or_equal", cfg.skip_below_or_equal);
|
|
}
|
|
|
|
Log::info("GameOverFsm", "timings carregats des de {} (continue hold {}ms, fade {}ms)", yaml_path, cfg.continue_text_hold_ms, cfg.fade_out_ms);
|
|
return cfg;
|
|
}
|
|
|
|
auto advance(State current, Transition& t, const Config& cfg, int delta_ms) -> State {
|
|
if (current == State::OFF || current == State::DONE) { return current; }
|
|
|
|
t.elapsed_ms += delta_ms;
|
|
|
|
if (current == State::CONTINUE_PROMPT) {
|
|
const std::array<int, 3> DURS = {
|
|
cfg.continue_text_enter_ms,
|
|
cfg.continue_text_hold_ms,
|
|
cfg.continue_text_exit_ms,
|
|
};
|
|
while (t.phase < static_cast<int>(DURS.size()) && t.elapsed_ms >= DURS[t.phase]) {
|
|
t.elapsed_ms -= DURS[t.phase];
|
|
// Si acabem la fase 1 (hold) sense cancel·lació, saltem
|
|
// directament a GAME_OVER — el panel queda amunt i no
|
|
// es reprodueix l'exit anim. La fase 2 (exit) només
|
|
// s'executa si el GameScene l'ha forçada amb
|
|
// `cancelContinuePrompt`.
|
|
if (t.phase == 1) {
|
|
return State::GAME_OVER;
|
|
}
|
|
++t.phase;
|
|
}
|
|
if (t.phase >= static_cast<int>(DURS.size())) { return State::OFF; }
|
|
return current;
|
|
}
|
|
|
|
if (current == State::GAME_OVER) {
|
|
const std::array<int, 3> DURS = {
|
|
cfg.game_over_text_hold_ms,
|
|
cfg.game_over_text_exit_ms,
|
|
cfg.game_over_wait_ms,
|
|
};
|
|
while (t.phase < static_cast<int>(DURS.size()) && t.elapsed_ms >= DURS[t.phase]) {
|
|
t.elapsed_ms -= DURS[t.phase];
|
|
++t.phase;
|
|
}
|
|
if (t.phase >= static_cast<int>(DURS.size())) { return State::FADE_OUT; }
|
|
return current;
|
|
}
|
|
|
|
// FADE_OUT
|
|
const std::array<int, 2> DURS = {
|
|
cfg.fade_out_ms,
|
|
cfg.black_hold_ms,
|
|
};
|
|
while (t.phase < static_cast<int>(DURS.size()) && t.elapsed_ms >= DURS[t.phase]) {
|
|
t.elapsed_ms -= DURS[t.phase];
|
|
++t.phase;
|
|
}
|
|
if (t.phase >= static_cast<int>(DURS.size())) { return State::DONE; }
|
|
return current;
|
|
}
|
|
|
|
void cancelContinuePrompt(State current, Transition& t) {
|
|
if (current != State::CONTINUE_PROMPT) { return; }
|
|
if (t.phase >= 2) { return; } // ja en exit anim, no-op
|
|
t.phase = 2;
|
|
t.elapsed_ms = 0;
|
|
}
|
|
|
|
void skipToNextCountdown(State current, Transition& t, const Config& cfg) {
|
|
if (current != State::CONTINUE_PROMPT) { return; }
|
|
if (t.phase != 1) { return; }
|
|
const int START = std::max(0, cfg.continue_count_start);
|
|
const int STEP_COUNT = START + 1;
|
|
const int STEP_MS = std::max(1, cfg.continue_text_hold_ms / STEP_COUNT);
|
|
const int CONSUMED = t.elapsed_ms / STEP_MS;
|
|
const int CURRENT = std::clamp(START - CONSUMED, 0, START);
|
|
// No saltem si ja som al 0: el següent "número" és la transició
|
|
// a GAME_OVER, que `advance` ja gestiona quan el hold s'esgota.
|
|
if (CURRENT <= 0) { return; }
|
|
t.elapsed_ms = (CONSUMED + 1) * STEP_MS;
|
|
}
|
|
|
|
auto countdownSecondsRemaining(State current, const Transition& t, const Config& cfg) -> int {
|
|
if (current != State::CONTINUE_PROMPT) { return 0; }
|
|
if (t.phase != 1) { return 0; }
|
|
// La sèrie és [start, start-1, ..., 1, 0]: en total `start+1`
|
|
// graons. El primer número visible és `start` i l'últim 0 es
|
|
// manté durant el seu graó abans que la fase 1 acabi i la FSM
|
|
// salti a GAME_OVER. Si `start == 0` només es mostra "0"
|
|
// durant tot el hold.
|
|
const int START = std::max(0, cfg.continue_count_start);
|
|
const int STEP_COUNT = START + 1;
|
|
const int STEP_MS = std::max(1, cfg.continue_text_hold_ms / STEP_COUNT);
|
|
const int CONSUMED = t.elapsed_ms / STEP_MS;
|
|
return std::clamp(START - CONSUMED, 0, START);
|
|
}
|
|
|
|
auto stateName(State s) -> const char* {
|
|
switch (s) {
|
|
case State::OFF:
|
|
return "OFF";
|
|
case State::CONTINUE_PROMPT:
|
|
return "CONTINUE_PROMPT";
|
|
case State::GAME_OVER:
|
|
return "GAME_OVER";
|
|
case State::FADE_OUT:
|
|
return "FADE_OUT";
|
|
case State::DONE:
|
|
return "DONE";
|
|
}
|
|
return "?";
|
|
}
|
|
|
|
} // namespace GameOverFsm
|