diff --git a/CMakeLists.txt b/CMakeLists.txt index 4171b0c..b729a2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,7 @@ set(APP_SOURCES source/core/resources/palette_loader.cpp source/utils/easing.cpp source/utils/utils.cpp + source/game/debug_config.cpp source/game/options.cpp source/game/scenes/placeholder_input.cpp source/game/scenes/logo_scene.cpp diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index c8503d4..dd6ebc3 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -21,8 +21,12 @@ #include "core/resources/resource_cache.hpp" #include "core/scene/scene.hpp" #include "core/system/event_buffer.hpp" +#include "game/debug_config.hpp" #include "game/options.hpp" #include "game/scenes/game_scene.hpp" +#include "game/scenes/intro_scene.hpp" +#include "game/scenes/logo_scene.hpp" +#include "game/scenes/title_scene.hpp" #include "game/ui/ui_palette.hpp" #include "utils/defaults.hpp" @@ -91,7 +95,45 @@ Director::Director() { RenderInfo::get().init(Options::buildRenderInfoFrame(UiColor::RENDER_INFO_TEXT, UiColor::RENDER_INFO_SHADOW), hud_text); Notifier::get().init(hud_text, ui_palette); - current_scene_ = std::make_unique(); + // Flux d'escenes LOGO → TITLE → INTRO → GAME. Cada placeholder rep una + // factory que construeix la següent, construïda bottom-up des de GAME. + // + // Release: sempre s'arranca a LOGO (el flux complet, el que veu el + // jugador final). Debug: s'obri `debug.yaml`; si no existeix, el creem + // amb `start_scene: GAME` perquè el dia a dia de gameplay no es mengi + // els 15 s de placeholders. L'usuari pot editar-lo a mà per escollir + // un punt d'entrada intermedi. +#ifdef _DEBUG + const auto DEBUG_YAML = system_folder_ + "debug.yaml"; + DebugConfig::createDefaultIfMissing(DEBUG_YAML); + const auto START = DebugConfig::resolveStartScene(DEBUG_YAML); +#else + const auto START = DebugConfig::StartScene::LOGO; +#endif + std::cout << "Director: start_scene=" << DebugConfig::toString(START) << '\n'; + + auto make_game = []() -> std::unique_ptr { return std::make_unique(); }; + auto make_intro = [hud_text, make_game]() -> std::unique_ptr { + return std::make_unique(hud_text, make_game); + }; + auto make_title = [hud_text, make_intro]() -> std::unique_ptr { + return std::make_unique(hud_text, make_intro); + }; + + switch (START) { + case DebugConfig::StartScene::LOGO: + current_scene_ = std::make_unique(hud_text, make_title); + break; + case DebugConfig::StartScene::TITLE: + current_scene_ = make_title(); + break; + case DebugConfig::StartScene::INTRO: + current_scene_ = make_intro(); + break; + case DebugConfig::StartScene::GAME: + current_scene_ = make_game(); + break; + } last_ticks_ms_ = SDL_GetTicks(); } @@ -143,6 +185,8 @@ void Director::processEvents() { void Director::switchSceneIfRequested() { if (!current_scene_) { return; } if (auto next = current_scene_->nextScene()) { + // TODO: migrar a logger unificat (INC-03 a docs/audit-core-rendering.md). + std::cout << "Director: scene transition\n"; current_scene_ = std::move(next); } } diff --git a/source/game/debug_config.cpp b/source/game/debug_config.cpp new file mode 100644 index 0000000..6070e59 --- /dev/null +++ b/source/game/debug_config.cpp @@ -0,0 +1,98 @@ +#include "game/debug_config.hpp" + +#include +#include +#include +#include +#include +#include + +#include "external/fkyaml_node.hpp" + +namespace DebugConfig { + + namespace { + + auto readFile(const std::string& path) -> std::string { + std::ifstream file(path); + if (!file) { return {}; } + std::stringstream buffer; + buffer << file.rdbuf(); + return buffer.str(); + } + + auto fromName(const std::string& name) -> StartScene { + if (name == "LOGO") { return StartScene::LOGO; } + if (name == "TITLE") { return StartScene::TITLE; } + if (name == "INTRO") { return StartScene::INTRO; } + if (name == "GAME") { return StartScene::GAME; } + return StartScene::GAME; + } + + auto isKnown(const std::string& name) -> bool { + return name == "LOGO" || name == "TITLE" || name == "INTRO" || name == "GAME"; + } + + } // namespace + + auto parseStartScene(const std::string& yaml_content) -> StartScene { + if (yaml_content.empty()) { return StartScene::GAME; } + try { + auto yaml = fkyaml::node::deserialize(yaml_content); + if (!yaml.contains("start_scene")) { return StartScene::GAME; } + const auto RAW = yaml["start_scene"].get_value(); + if (!isKnown(RAW)) { + std::cerr << "debug.yaml: start_scene \"" << RAW << "\" invàlid, arrancant a GAME\n"; + return StartScene::GAME; + } + return fromName(RAW); + } catch (const std::exception& e) { + std::cerr << "debug.yaml: error parsejant: " << e.what() << " — arrancant a GAME\n"; + return StartScene::GAME; + } + } + + auto resolveStartScene(const std::string& file_path) -> StartScene { + std::error_code error; + if (!std::filesystem::exists(file_path, error)) { + // Silenciós: la no-existència és el cas per defecte en una màquina + // neta, no un error. L'usuari crea el fitxer quan el necessita. + return StartScene::GAME; + } + const auto CONTENT = readFile(file_path); + if (CONTENT.empty()) { + std::cerr << "debug.yaml: fitxer buit a " << file_path << ", arrancant a GAME\n"; + return StartScene::GAME; + } + return parseStartScene(CONTENT); + } + + auto toString(StartScene scene) -> const char* { + switch (scene) { + case StartScene::LOGO: + return "LOGO"; + case StartScene::TITLE: + return "TITLE"; + case StartScene::INTRO: + return "INTRO"; + case StartScene::GAME: + return "GAME"; + } + return "GAME"; + } + + void createDefaultIfMissing(const std::string& file_path) { + std::error_code error; + if (std::filesystem::exists(file_path, error)) { return; } + std::ofstream file(file_path); + if (!file) { + std::cerr << "debug.yaml: no s'ha pogut crear " << file_path << '\n'; + return; + } + file << "# debug.yaml — ajust personal de dev (no versionat).\n"; + file << "# Valors vàlids: LOGO, TITLE, INTRO, GAME.\n"; + file << "start_scene: GAME\n"; + std::cout << "debug.yaml: creat a " << file_path << " amb start_scene=GAME\n"; + } + +} // namespace DebugConfig diff --git a/source/game/debug_config.hpp b/source/game/debug_config.hpp new file mode 100644 index 0000000..44dad06 --- /dev/null +++ b/source/game/debug_config.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +// Configuració personal de desenvolupament, carregada de `debug.yaml` dins la +// carpeta de sistema (`$XDG_CONFIG_HOME/jailgames/aee_2026/debug.yaml`). No es +// versiona: cada dev el crea a la seua màquina. Si no existeix, s'aplica el +// valor per defecte (GAME) sense warning de nivell error. +namespace DebugConfig { + + enum class StartScene : std::uint8_t { LOGO, + TITLE, + INTRO, + GAME }; + + // Parseja un contingut YAML en cru. Si no conté `start_scene`, el valor és + // desconegut o el YAML és invàlid, retorna GAME. Pura: no fa I/O. + [[nodiscard]] auto parseStartScene(const std::string& yaml_content) -> StartScene; + + // Llig el fitxer i delega a parseStartScene. Si el fitxer no existeix + // retorna GAME sense warning (és el cas per defecte en una màquina neta); + // si existeix però el valor és invàlid, imprimeix un warning i retorna + // GAME. + [[nodiscard]] auto resolveStartScene(const std::string& file_path) -> StartScene; + + // Nom canònic de l'enum (útil per a logs): "LOGO" / "TITLE" / "INTRO" / "GAME". + [[nodiscard]] auto toString(StartScene scene) -> const char*; + + // Si el fitxer no existeix, el crea amb `start_scene: GAME` (el valor + // per defecte pensat per al dia a dia de dev). Si ja existeix, no el + // toca — preservem qualsevol edició manual de l'usuari. Usat només al + // path de debug del Director (el binari release ignora `debug.yaml`). + void createDefaultIfMissing(const std::string& file_path); + +} // namespace DebugConfig