#include "game/scenes/room_fsm.hpp" #include #include #include #include "core/locale/locale.hpp" #include "core/resources/resource_helper.hpp" #include "external/fkyaml_node.hpp" namespace { // Llegeix un sub-camp sencer d'un node YAML. Si manca, retorna el // fallback. Dissenyat per al consum puntual de `loadConfig`. auto readChildInt(const fkyaml::node& parent, const char* key, int fallback) -> int { if (!parent.contains(key)) { return fallback; } return parent[key].get_value(); } } // namespace namespace RoomFsm { auto loadConfig(const std::string& yaml_path) -> Config { Config cfg{}; cfg.text_enter_curve = UiConfig::curveByName("outCubic"); cfg.text_exit_curve = UiConfig::curveByName("inCubic"); auto bytes = Resource::Helper::loadFile(yaml_path); if (bytes.empty()) { std::cout << "RoomFsm: " << yaml_path << " absent — uso fallbacks compilats\n"; return cfg; } const auto* begin = reinterpret_cast(bytes.data()); const auto* end = begin + bytes.size(); auto yaml = fkyaml::node::deserialize(begin, end); if (yaml.contains("room_intro")) { const auto& n = yaml["room_intro"]; cfg.intro_fade_in_ms = readChildInt(n, "fade_in_ms", cfg.intro_fade_in_ms); cfg.intro_text_delay_ms = readChildInt(n, "text_delay_ms", cfg.intro_text_delay_ms); cfg.intro_text_enter_ms = readChildInt(n, "text_enter_ms", cfg.intro_text_enter_ms); cfg.intro_text_hold_ms = readChildInt(n, "text_hold_ms", cfg.intro_text_hold_ms); cfg.intro_text_exit_ms = readChildInt(n, "text_exit_ms", cfg.intro_text_exit_ms); } if (yaml.contains("room_outro")) { const auto& n = yaml["room_outro"]; cfg.outro_text_enter_ms = readChildInt(n, "text_enter_ms", cfg.outro_text_enter_ms); cfg.outro_text_hold_ms = readChildInt(n, "text_hold_ms", cfg.outro_text_hold_ms); cfg.outro_text_exit_ms = readChildInt(n, "text_exit_ms", cfg.outro_text_exit_ms); cfg.outro_fade_out_ms = readChildInt(n, "fade_out_ms", cfg.outro_fade_out_ms); } if (yaml.contains("text")) { const auto& n = yaml["text"]; if (n.contains("enter_curve")) { cfg.text_enter_curve = UiConfig::curveByName(n["enter_curve"].get_value()); } if (n.contains("exit_curve")) { cfg.text_exit_curve = UiConfig::curveByName(n["exit_curve"].get_value()); } } std::cout << "RoomFsm: timings carregats des de " << yaml_path << " (intro fade " << cfg.intro_fade_in_ms << "ms, outro fade " << cfg.outro_fade_out_ms << "ms)\n"; return cfg; } auto advance(State current, Transition& t, const Config& cfg, int delta_ms) -> State { if (current == State::PLAYING) { return current; } t.elapsed_ms += delta_ms; if (current == State::ROOM_INTRO) { const std::array DURS = { cfg.intro_fade_in_ms, cfg.intro_text_delay_ms, cfg.intro_text_enter_ms, cfg.intro_text_hold_ms, cfg.intro_text_exit_ms, }; while (t.phase < static_cast(DURS.size()) && t.elapsed_ms >= DURS[t.phase]) { t.elapsed_ms -= DURS[t.phase]; ++t.phase; } if (t.phase >= static_cast(DURS.size())) { return State::PLAYING; } return current; } // ROOM_OUTRO const std::array DURS = { cfg.outro_text_enter_ms, cfg.outro_text_hold_ms, cfg.outro_text_exit_ms, cfg.outro_fade_out_ms, }; while (t.phase < static_cast(DURS.size()) && t.elapsed_ms >= DURS[t.phase]) { t.elapsed_ms -= DURS[t.phase]; ++t.phase; } // Placeholder fins a l'itinerari de sales: l'OUTRO recau a INTRO. // TODO(itinerari): aquest mateix retorn sortirà cap a una nova // sala (incrementant `room_index_` i recreant tilemap/entitats al // GameScene) abans de tornar a ROOM_INTRO. if (t.phase >= static_cast(DURS.size())) { return State::ROOM_INTRO; } return current; } auto formatRoomTitle(int n) -> std::string { auto tpl = Locale::get().text("game.room_intro"); constexpr std::string_view PLACEHOLDER = "{n}"; auto pos = tpl.find(PLACEHOLDER); if (pos != std::string::npos) { tpl.replace(pos, PLACEHOLDER.size(), std::to_string(n)); } return tpl; } auto stateName(State s) -> const char* { switch (s) { case State::ROOM_INTRO: return "ROOM_INTRO"; case State::PLAYING: return "PLAYING"; case State::ROOM_OUTRO: return "ROOM_OUTRO"; } return "?"; } } // namespace RoomFsm