#include "game/ui/console_commands.hpp" #include #include // Para ranges::transform, ranges::find #include // Para array #include // Para cout #include // Para ostringstream #include // Para string #include // Para vector #include "core/audio/audio.hpp" // Para Audio #include "core/locale/locale.hpp" // Para Locale #include "core/rendering/render_info.hpp" // Para RenderInfo #include "core/rendering/screen.hpp" // Para Screen #include "core/resources/resource_helper.hpp" // Para Resource::Helper #include "external/fkyaml_node.hpp" // Para fkyaml::node #include "game/game_control.hpp" // Para GameControl #include "game/options.hpp" // Para Options #include "game/scene_manager.hpp" // Para SceneManager #include "game/ui/notifier.hpp" // Para Notifier #include "utils/utils.hpp" // Para toUpper, prettyName #ifdef _DEBUG #include "core/system/debug.hpp" // Para Debug #endif // ── Helpers ────────────────────────────────────────────────────────────────── // Toggle genérico para comandos booleanos ON/OFF (reemplaza macro BOOL_TOGGLE_CMD) static auto boolToggle( const std::string& label, bool& option, const std::function& toggle_fn, const std::vector& args) -> std::string { if (args.empty()) { toggle_fn(); return label + " " + (option ? "ON" : "OFF"); } if (args[0] == "ON") { if (option) { return label + " already ON"; } toggle_fn(); return label + " ON"; } if (args[0] == "OFF") { if (!option) { return label + " already OFF"; } toggle_fn(); return label + " OFF"; } return "usage: " + label + " [on|off]"; } // ── Command handlers ───────────────────────────────────────────────────────── // SS [ON|OFF|SIZE|UPSCALE [NEAREST|LINEAR]|DOWNSCALE [BILINEAR|LANCZOS2|LANCZOS3]] static auto cmd_ss(const std::vector& args) -> std::string { if (!Screen::get()->isHardwareAccelerated()) { return "No GPU acceleration"; } static const std::array DOWNSCALE_NAMES = {"Bilinear", "Lanczos2", "Lanczos3"}; if (!args.empty() && args[0] == "SIZE") { if (!Options::video.supersampling.enabled) { return "Supersampling is OFF: no texture"; } const auto [w, h] = Screen::get()->getSsTextureSize(); if (w == 0) { return "SS texture: not active"; } return "SS texture: " + std::to_string(w) + "x" + std::to_string(h); } if (!args.empty() && args[0] == "UPSCALE") { if (args.size() == 1) { Screen::get()->setLinearUpscale(!Options::video.supersampling.linear_upscale); return std::string("Upscale: ") + (Options::video.supersampling.linear_upscale ? "Linear" : "Nearest"); } if (args[1] == "NEAREST") { if (!Options::video.supersampling.linear_upscale) { return "Upscale already Nearest"; } Screen::get()->setLinearUpscale(false); return "Upscale: Nearest"; } if (args[1] == "LINEAR") { if (Options::video.supersampling.linear_upscale) { return "Upscale already Linear"; } Screen::get()->setLinearUpscale(true); return "Upscale: Linear"; } return "usage: ss upscale [nearest|linear]"; } if (!args.empty() && args[0] == "DOWNSCALE") { if (args.size() == 1) { return std::string("Downscale: ") + std::string(DOWNSCALE_NAMES[static_cast(Options::video.supersampling.downscale_algo)]); } int algo = -1; if (args[1] == "BILINEAR") { algo = 0; } if (args[1] == "LANCZOS2") { algo = 1; } if (args[1] == "LANCZOS3") { algo = 2; } if (algo == -1) { return "usage: ss downscale [bilinear|lanczos2|lanczos3]"; } if (Options::video.supersampling.downscale_algo == algo) { return std::string("Downscale already ") + std::string(DOWNSCALE_NAMES[static_cast(algo)]); } Screen::get()->setDownscaleAlgo(algo); return std::string("Downscale: ") + std::string(DOWNSCALE_NAMES[static_cast(algo)]); } if (args.empty()) { Screen::get()->toggleSupersampling(); return std::string("PostFX Supersampling ") + (Options::video.supersampling.enabled ? "ON" : "OFF"); } if (args[0] == "ON") { if (Options::video.supersampling.enabled) { return "Supersampling already ON"; } Screen::get()->toggleSupersampling(); return "PostFX Supersampling ON"; } if (args[0] == "OFF") { if (!Options::video.supersampling.enabled) { return "Supersampling already OFF"; } Screen::get()->toggleSupersampling(); return "PostFX Supersampling OFF"; } return "usage: ss [on|off|size|upscale [nearest|linear]|downscale [bilinear|lanczos2|lanczos3]]"; } // Helper: aplica un preset por dirección (NEXT/PREV) o nombre; devuelve mensaje static auto applyPreset(const std::vector& args) -> std::string { const bool IS_CRTPI = Options::video.shader.current_shader == Rendering::ShaderType::CRTPI; const auto& presets_postfx = Options::postfx_presets; const auto& presets_crtpi = Options::crtpi_presets; const std::string shader_label = IS_CRTPI ? "CrtPi" : "PostFX"; auto& current_idx = IS_CRTPI ? Options::video.shader.current_crtpi_preset : Options::video.shader.current_postfx_preset; const int count = static_cast(IS_CRTPI ? presets_crtpi.size() : presets_postfx.size()); if (count == 0) { return "No " + shader_label + " presets available"; } const auto presetName = [&]() -> std::string { const auto& name = IS_CRTPI ? presets_crtpi[static_cast(current_idx)].name : presets_postfx[static_cast(current_idx)].name; return prettyName(name); }; if (args.empty()) { return shader_label + " preset: " + presetName(); } if (args[0] == "NEXT") { current_idx = (current_idx + 1) % count; } else if (args[0] == "PREV") { current_idx = (current_idx - 1 + count) % count; } else { // Buscar por nombre (case-insensitive, con guiones) std::string search = args[0]; std::ranges::transform(search, search.begin(), ::toupper); bool found = false; for (int i = 0; i < count; ++i) { const auto& name = IS_CRTPI ? presets_crtpi[static_cast(i)].name : presets_postfx[static_cast(i)].name; if (toUpper(name) == search) { current_idx = i; found = true; break; } } if (!found) { std::string name_lower = args[0]; std::ranges::transform(name_lower, name_lower.begin(), ::tolower); return "Unknown preset: " + name_lower; } } if (IS_CRTPI) { Screen::get()->reloadCrtPi(); } else { Screen::get()->reloadPostFX(); } return shader_label + " preset: " + presetName(); } // SHADER [ON|OFF|NEXT|POSTFX|CRTPI|PRESET [NEXT|PREV|]] static auto cmd_shader(const std::vector& args) -> std::string { if (!Screen::get()->isHardwareAccelerated()) { return "No GPU acceleration"; } if (args.empty()) { Screen::get()->toggleShaders(); return std::string("Shader ") + (Options::video.shader.enabled ? "ON" : "OFF"); } if (args[0] == "ON") { if (Options::video.shader.enabled) { return "Shader already ON"; } Screen::get()->toggleShaders(); return "Shader ON"; } if (args[0] == "OFF") { if (!Options::video.shader.enabled) { return "Shader already OFF"; } Screen::get()->toggleShaders(); return "Shader OFF"; } if (args[0] == "POSTFX") { Screen::get()->setActiveShader(Rendering::ShaderType::POSTFX); return "Shader: PostFX"; } if (args[0] == "CRTPI") { Screen::get()->setActiveShader(Rendering::ShaderType::CRTPI); return "Shader: CrtPi"; } if (args[0] == "NEXT") { Screen::get()->nextShader(); return std::string("Shader: ") + (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI ? "CrtPi" : "PostFX"); } if (args[0] == "PRESET") { const std::vector rest(args.begin() + 1, args.end()); return applyPreset(rest); } return "usage: shader [on|off|next|postfx|crtpi|preset [next|prev|]]"; } // BORDER [ON|OFF] static auto cmd_border(const std::vector& args) -> std::string { return boolToggle("Border", Options::video.border.enabled, [] { Screen::get()->toggleBorder(); }, args); } // FULLSCREEN [ON|OFF [PLEASE]] static auto cmd_fullscreen(const std::vector& args) -> std::string { const bool EXPLICIT_ON = !args.empty() && args[0] == "ON"; const bool EXPLICIT_OFF = !args.empty() && args[0] == "OFF"; const bool WITH_PLEASE = !args.empty() && args.back() == "PLEASE"; const bool IS_TOGGLE = args.empty(); const bool TURNING_OFF = EXPLICIT_OFF || (IS_TOGGLE && Options::video.fullscreen); if (TURNING_OFF && Options::kiosk.enabled && !WITH_PLEASE) { return "Not allowed in kiosk mode"; } if (EXPLICIT_ON) { if (Options::video.fullscreen) { return "Fullscreen already ON"; } Screen::get()->toggleVideoMode(); return "Fullscreen ON"; } if (EXPLICIT_OFF) { if (!Options::video.fullscreen) { return "Fullscreen already OFF"; } Screen::get()->toggleVideoMode(); return "Fullscreen OFF"; } if (IS_TOGGLE) { Screen::get()->toggleVideoMode(); return std::string("Fullscreen ") + (Options::video.fullscreen ? "ON" : "OFF"); } return "usage: fullscreen [on|off]"; } // ZOOM UP/DOWN/ static auto cmd_zoom(const std::vector& args) -> std::string { if (args.empty()) { return "usage: zoom [up|down|<1-" + std::to_string(Screen::get()->getMaxZoom()) + ">]"; } if (args[0] == "UP") { if (!Screen::get()->incWindowZoom()) { return "Max zoom reached"; } return "Zoom " + std::to_string(Options::window.zoom); } if (args[0] == "DOWN") { if (!Screen::get()->decWindowZoom()) { return "Min zoom reached"; } return "Zoom " + std::to_string(Options::window.zoom); } try { const int N = std::stoi(args[0]); const int MAX = Screen::get()->getMaxZoom(); if (N < 1 || N > MAX) { return "Zoom must be between 1 and " + std::to_string(MAX); } if (N == Options::window.zoom) { return "Zoom already " + std::to_string(N); } Screen::get()->setWindowZoom(N); return "Zoom " + std::to_string(Options::window.zoom); } catch (...) {} return "usage: zoom [up|down|<1-" + std::to_string(Screen::get()->getMaxZoom()) + ">]"; } // INTSCALE [ON|OFF] static auto cmd_intscale(const std::vector& args) -> std::string { const bool ON = args.empty() ? !Options::video.integer_scale : (args[0] == "ON"); if (!args.empty() && args[0] != "ON" && args[0] != "OFF") { return "usage: intscale [on|off]"; } if (ON == Options::video.integer_scale) { return std::string("IntScale already ") + (ON ? "ON" : "OFF"); } Screen::get()->toggleIntegerScale(); Screen::get()->setVideoMode(Options::video.fullscreen); return std::string("IntScale ") + (Options::video.integer_scale ? "ON" : "OFF"); } // VSYNC [ON|OFF] static auto cmd_vsync(const std::vector& args) -> std::string { return boolToggle("VSync", Options::video.vertical_sync, [] { Screen::get()->toggleVSync(); }, args); } // DRIVER [LIST|AUTO|NONE|] static auto cmd_driver(const std::vector& args) -> std::string { if (args.empty()) { const auto& driver = Screen::get()->getGPUDriver(); return "GPU: " + (driver.empty() ? std::string("sdl") : driver); } if (args[0] == "LIST") { const int COUNT = SDL_GetNumGPUDrivers(); if (COUNT <= 0) { return "No GPU drivers found"; } const std::string& active = Screen::get()->getGPUDriver(); std::string result = "Drivers:"; for (int i = 0; i < COUNT; ++i) { const char* name = SDL_GetGPUDriver(i); if (name != nullptr) { result += ' '; result += name; if (active == name) { result += '*'; } } } SDL_Log("SDL GPU drivers: %s", result.c_str()); return result; } const bool HAS_PLEASE = std::ranges::find(args, std::string("PLEASE")) != args.end(); if (Options::kiosk.enabled && !HAS_PLEASE) { return "Not allowed in kiosk mode"; } if (args[0] == "AUTO") { Options::video.gpu.preferred_driver.clear(); Options::saveToFile(); return "Driver: auto (restart)"; } if (args[0] == "NONE") { Options::video.gpu.preferred_driver = "none"; Options::saveToFile(); return "Driver: none (SDL fallback, restart)"; } std::string driver_lower = args[0]; std::ranges::transform(driver_lower, driver_lower.begin(), ::tolower); const int COUNT = SDL_GetNumGPUDrivers(); bool found = false; for (int i = 0; i < COUNT && !found; ++i) { const char* name = SDL_GetGPUDriver(i); if (name != nullptr && driver_lower == name) { found = true; } } if (!found) { return "Unknown driver: " + driver_lower + ". Use DRIVER LIST or NONE"; } Options::video.gpu.preferred_driver = driver_lower; Options::saveToFile(); return "Driver: " + driver_lower + " (restart)"; } // PALETTE NEXT/PREV/ static auto cmd_palette(const std::vector& args) -> std::string { const auto palName = []() -> std::string { return Screen::get()->getPalettePrettyName(); }; if (args.empty()) { return "usage: palette [next|prev|]"; } if (args[0] == "NEXT") { Screen::get()->nextPalette(); return "Palette: " + palName(); } if (args[0] == "PREV") { Screen::get()->previousPalette(); return "Palette: " + palName(); } if (!Screen::get()->setPaletteByName(args[0])) { std::string arg_lower = args[0]; std::ranges::transform(arg_lower, arg_lower.begin(), ::tolower); return "Unknown palette: " + arg_lower; } return "Palette: " + palName(); } // AUDIO [ON|OFF|VOL <0-100>] static auto cmd_audio(const std::vector& args) -> std::string { if (args.empty()) { const int VOL = static_cast(Options::audio.volume * 100.0F); return std::string("Audio ") + (Options::audio.enabled ? "ON" : "OFF") + " vol:" + std::to_string(VOL); } if (args[0] == "ON") { if (Options::audio.enabled) { return "Audio already ON"; } Options::audio.enabled = true; Audio::get()->enable(true); return "Audio ON"; } if (args[0] == "OFF") { if (!Options::audio.enabled) { return "Audio already OFF"; } Options::audio.enabled = false; Audio::get()->enable(false); return "Audio OFF"; } if (args[0] == "VOL" && args.size() >= 2) { try { const int VAL = std::stoi(args[1]); if (VAL < 0 || VAL > 100) { return "Vol must be 0-100"; } Options::audio.volume = static_cast(VAL) / 100.0F; Audio::get()->enable(Options::audio.enabled); return "Audio vol:" + std::to_string(VAL); } catch (...) { return "usage: audio vol <0-100>"; } } return "usage: audio [on|off|vol n]"; } // MUSIC [ON|OFF|VOL <0-100>] static auto cmd_music(const std::vector& args) -> std::string { if (args.empty()) { const int VOL = static_cast(Options::audio.music.volume * 100.0F); return std::string("Music ") + (Options::audio.music.enabled ? "ON" : "OFF") + " vol:" + std::to_string(VOL); } if (args[0] == "ON") { if (Options::audio.music.enabled) { return "Music already ON"; } Options::audio.music.enabled = true; Audio::get()->enableMusic(true); Audio::get()->setMusicVolume(Options::audio.music.volume); return "Music ON"; } if (args[0] == "OFF") { if (!Options::audio.music.enabled) { return "Music already OFF"; } Audio::get()->setMusicVolume(0.0F); Audio::get()->enableMusic(false); Options::audio.music.enabled = false; return "Music OFF"; } if (args[0] == "VOL" && args.size() >= 2) { try { const int VAL = std::stoi(args[1]); if (VAL < 0 || VAL > 100) { return "Vol must be 0-100"; } Options::audio.music.volume = static_cast(VAL) / 100.0F; if (Options::audio.music.enabled) { Audio::get()->setMusicVolume(Options::audio.music.volume); } return "Music vol:" + std::to_string(VAL); } catch (...) { return "usage: music vol <0-100>"; } } return "usage: music [on|off|vol n]"; } // SOUND [ON|OFF|VOL <0-100>] static auto cmd_sound(const std::vector& args) -> std::string { if (args.empty()) { const int VOL = static_cast(Options::audio.sound.volume * 100.0F); return std::string("Sound ") + (Options::audio.sound.enabled ? "ON" : "OFF") + " vol:" + std::to_string(VOL); } if (args[0] == "ON") { if (Options::audio.sound.enabled) { return "Sound already ON"; } Options::audio.sound.enabled = true; Audio::get()->enableSound(true); Audio::get()->setSoundVolume(Options::audio.sound.volume); return "Sound ON"; } if (args[0] == "OFF") { if (!Options::audio.sound.enabled) { return "Sound already OFF"; } Audio::get()->setSoundVolume(0.0F); Audio::get()->enableSound(false); Options::audio.sound.enabled = false; return "Sound OFF"; } if (args[0] == "VOL" && args.size() >= 2) { try { const int VAL = std::stoi(args[1]); if (VAL < 0 || VAL > 100) { return "Vol must be 0-100"; } Options::audio.sound.volume = static_cast(VAL) / 100.0F; if (Options::audio.sound.enabled) { Audio::get()->setSoundVolume(Options::audio.sound.volume); } return "Sound vol:" + std::to_string(VAL); } catch (...) { return "usage: sound vol <0-100>"; } } return "usage: sound [on|off|vol n]"; } #ifdef _DEBUG // DEBUG [MODE [ON|OFF]|START [HERE|ROOM|POS|SCENE ]] static auto cmd_debug(const std::vector& args) -> std::string { // --- START subcommands (START SCENE works from any scene) --- if (!args.empty() && args[0] == "START") { // START SCENE [] — works from any scene if (args.size() >= 2 && args[1] == "SCENE") { SceneManager::Scene target = SceneManager::current; std::string name = "current"; if (args.size() >= 3) { if (args[2] == "GAME") { target = SceneManager::Scene::GAME; name = "game"; } else if (args[2] == "LOGO") { target = SceneManager::Scene::LOGO; name = "logo"; } else if (args[2] == "LOADING") { target = SceneManager::Scene::LOADING_SCREEN; name = "loading"; } else if (args[2] == "TITLE") { target = SceneManager::Scene::TITLE; name = "title"; } else if (args[2] == "CREDITS") { target = SceneManager::Scene::CREDITS; name = "credits"; } else if (args[2] == "ENDING") { target = SceneManager::Scene::ENDING; name = "ending"; } else if (args[2] == "ENDING2") { target = SceneManager::Scene::ENDING2; name = "ending2"; } else { std::string scene_lower = args[2]; std::ranges::transform(scene_lower, scene_lower.begin(), ::tolower); return "Unknown scene: " + scene_lower; } } Debug::get()->setInitialScene(target); Debug::get()->saveToFile(); return "Initial scene set to: " + name; } // START HERE|ROOM|POS — requires GAME scene if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; } if (!GameControl::set_initial_room || !GameControl::set_initial_pos) { return "Game not initialized"; } const bool DO_ROOM = args.size() == 1 || args[1] == "HERE" || args[1] == "ROOM"; const bool DO_POS = args.size() == 1 || args[1] == "HERE" || args[1] == "POS"; if (!DO_ROOM && !DO_POS) { return "usage: debug start [here|room|pos|scene ]"; } std::string result; if (DO_ROOM) { result = GameControl::set_initial_room(); } if (DO_POS) { if (!result.empty()) { result += ", "; } result += GameControl::set_initial_pos(); } return result; } // --- MODE [ON|OFF] toggle (requires GAME scene) --- if (!args.empty() && args[0] == "MODE") { if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; } if (!GameControl::toggle_debug_mode) { return "Game not initialized"; } const bool ENABLED = Debug::get()->isEnabled(); if (args.size() >= 2 && args[1] == "ON") { if (ENABLED) { return "Debug mode already ON"; } GameControl::toggle_debug_mode(); return "Debug mode ON"; } if (args.size() >= 2 && args[1] == "OFF") { if (!ENABLED) { return "Debug mode already OFF"; } GameControl::toggle_debug_mode(); return "Debug mode OFF"; } if (args.size() >= 2) { return "usage: debug mode [on|off]"; } GameControl::toggle_debug_mode(); return std::string("Debug mode ") + (Debug::get()->isEnabled() ? "ON" : "OFF"); } return "usage: debug [mode [on|off]|start [here|room|pos|scene ]]"; } // ROOM |NEXT|PREV static auto cmd_room(const std::vector& args) -> std::string { if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; } if (args.empty()) { return "usage: room <1-60>|next|prev"; } int num = 0; if (args[0] == "NEXT" || args[0] == "PREV") { if (!GameControl::get_current_room) { return "Game not initialized"; } const std::string current = GameControl::get_current_room(); try { num = std::stoi(current.substr(0, current.find('.'))); } catch (...) { return "Cannot determine current room"; } num += (args[0] == "NEXT") ? 1 : -1; } else { try { num = std::stoi(args[0]); } catch (...) { return "usage: room <1-60>|next|prev"; } } if (num < 1 || num > 60) { return "Room must be between 1 and 60"; } char buf[16]; std::snprintf(buf, sizeof(buf), "%02d.yaml", num); if (GameControl::change_room && GameControl::change_room(buf)) { return std::string("Room: ") + buf; } return std::string("Room not found: ") + buf; } // ITEMS <0-200> static auto cmd_items(const std::vector& args) -> std::string { if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; } if (args.empty()) { return "usage: items <0-200>"; } int count = 0; try { count = std::stoi(args[0]); } catch (...) { return "usage: items <0-200>"; } if (count < 0 || count > 200) { return "Items must be between 0 and 200"; } if (!GameControl::set_items) { return "Game not initialized"; } GameControl::set_items(count); return "Items: " + std::to_string(count); } // SCENE [LOGO|LOADING|TITLE|CREDITS|GAME|ENDING|ENDING2|RESTART] static auto cmd_scene(const std::vector& args) -> std::string { if (Options::kiosk.enabled) { return "Not allowed in kiosk mode"; } if (args.empty()) { return "usage: scene [logo|loading|title|credits|game|ending|ending2|restart]"; } if (args[0] == "RESTART") { SceneManager::scene_before_restart = SceneManager::current; SceneManager::current = SceneManager::Scene::RESTART_CURRENT; return "Restarting..."; } const auto GO_TO = [](SceneManager::Scene target, const std::string& label) -> std::string { if (SceneManager::current == target) { SceneManager::scene_before_restart = target; SceneManager::current = SceneManager::Scene::RESTART_CURRENT; } else { SceneManager::current = target; } return "Scene: " + label; }; if (args[0] == "LOGO") { return GO_TO(SceneManager::Scene::LOGO, "Logo"); } if (args[0] == "LOADING") { return GO_TO(SceneManager::Scene::LOADING_SCREEN, "Loading"); } if (args[0] == "TITLE") { return GO_TO(SceneManager::Scene::TITLE, "Title"); } if (args[0] == "CREDITS") { return GO_TO(SceneManager::Scene::CREDITS, "Credits"); } if (args[0] == "GAME") { return GO_TO(SceneManager::Scene::GAME, "Game"); } if (args[0] == "ENDING") { return GO_TO(SceneManager::Scene::ENDING, "Ending"); } if (args[0] == "ENDING2") { return GO_TO(SceneManager::Scene::ENDING2, "Ending 2"); } return "Unknown scene: " + args[0]; } #endif // SHOW [INFO|NOTIFICATION|CHEEVO] static auto cmd_show(const std::vector& args) -> std::string { #ifdef _DEBUG if (!args.empty() && args[0] == "NOTIFICATION") { Notifier::get()->show({"NOTIFICATION"}); return "Notification shown"; } if (!args.empty() && args[0] == "CHEEVO") { Notifier::get()->show({Locale::get()->get("achievements.header"), Locale::get()->get("achievements.c1")}, Notifier::Style::CHEEVO, -1, false); // NOLINT(readability-static-accessed-through-instance) return "Cheevo notification shown"; } if (args.empty() || args[0] != "INFO") { return "usage: show [info|notification|cheevo]"; } #else if (args.empty() || args[0] != "INFO") { return "usage: show [info]"; } #endif if (RenderInfo::get()->isActive()) { return "Info overlay already ON"; } RenderInfo::get()->toggle(); return "Info overlay ON"; } // HIDE [INFO] static auto cmd_hide(const std::vector& args) -> std::string { if (args.empty() || args[0] != "INFO") { return "usage: hide [info]"; } if (!RenderInfo::get()->isActive()) { return "Info overlay already OFF"; } RenderInfo::get()->toggle(); return "Info overlay OFF"; } // CHEAT [subcomando] static auto cmd_cheat(const std::vector& args) -> std::string { if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; } if (args.empty()) { return "usage: cheat [infinite lives|invincibility|open the jail|close the jail]"; } // CHEAT INFINITE LIVES [ON|OFF] if (args[0] == "INFINITE") { if (args.size() < 2 || args[1] != "LIVES") { return "usage: cheat infinite lives [on|off]"; } auto& cheat = Options::cheats.infinite_lives; using State = Options::Cheat::State; const std::vector REST(args.begin() + 2, args.end()); if (REST.empty()) { cheat = (cheat == State::ENABLED) ? State::DISABLED : State::ENABLED; } else if (REST[0] == "ON") { if (cheat == State::ENABLED) { return "Infinite lives already ON"; } cheat = State::ENABLED; } else if (REST[0] == "OFF") { if (cheat == State::DISABLED) { return "Infinite lives already OFF"; } cheat = State::DISABLED; } else { return "usage: cheat infinite lives [on|off]"; } return std::string("Infinite lives ") + (cheat == State::ENABLED ? "ON" : "OFF"); } // CHEAT INVINCIBILITY [ON|OFF] if (args[0] == "INVINCIBILITY" || args[0] == "INVENCIBILITY") { auto& cheat = Options::cheats.invincible; using State = Options::Cheat::State; if (args.size() == 1) { cheat = (cheat == State::ENABLED) ? State::DISABLED : State::ENABLED; } else if (args[1] == "ON") { if (cheat == State::ENABLED) { return "Invincibility already ON"; } cheat = State::ENABLED; } else if (args[1] == "OFF") { if (cheat == State::DISABLED) { return "Invincibility already OFF"; } cheat = State::DISABLED; } else { return "usage: cheat invincibility [on|off]"; } return std::string("Invincibility ") + (cheat == State::ENABLED ? "ON" : "OFF"); } // CHEAT OPEN THE JAIL if (args[0] == "OPEN") { if (args.size() < 3 || args[1] != "THE" || args[2] != "JAIL") { return "usage: cheat open the jail"; } if (Options::cheats.jail_is_open == Options::Cheat::State::ENABLED) { return "Jail already open"; } Options::cheats.jail_is_open = Options::Cheat::State::ENABLED; return "Jail opened"; } // CHEAT CLOSE THE JAIL if (args[0] == "CLOSE") { if (args.size() < 3 || args[1] != "THE" || args[2] != "JAIL") { return "usage: cheat close the jail"; } if (Options::cheats.jail_is_open == Options::Cheat::State::DISABLED) { return "Jail already closed"; } Options::cheats.jail_is_open = Options::Cheat::State::DISABLED; return "Jail closed"; } return "usage: cheat [infinite lives|invincibility|open the jail|close the jail]"; } // PLAYER SKIN / PLAYER COLOR static auto cmd_player(const std::vector& args) -> std::string { if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; } // PLAYER SKIN if (args.size() >= 2 && args[0] == "SKIN") { if (!GameControl::change_player_skin) { return "Game not initialized"; } std::string skin_name = args[1]; std::ranges::transform(skin_name, skin_name.begin(), ::tolower); GameControl::change_player_skin(skin_name); return "Player skin: " + skin_name; } // PLAYER COLOR DEFAULT if (args.size() >= 2 && args[0] == "COLOR" && args[1] == "DEFAULT") { if (!GameControl::change_player_color) { return "Game not initialized"; } GameControl::change_player_color(-1); return "Player color: default"; } // PLAYER COLOR <0-15> if (args.size() >= 2 && args[0] == "COLOR") { int color = -1; try { color = std::stoi(args[1]); } catch (...) {} if (color < 0 || color > 15) { return "usage: player color <0-15>|default"; } if (!GameControl::change_player_color) { return "Game not initialized"; } GameControl::change_player_color(color); return "Player color: " + std::to_string(color); } return "usage: player skin | player color <0-15>|default"; } // RESTART static auto cmd_restart(const std::vector&) -> std::string { SceneManager::current = SceneManager::Scene::LOGO; Audio::get()->stopMusic(); return "Restarting..."; } // KIOSK [ON|OFF PLEASE|PLEASE] static auto cmd_kiosk(const std::vector& args) -> std::string { const bool DISABLE = (!args.empty() && args[0] == "PLEASE") || (args.size() >= 2 && args[0] == "OFF" && args[1] == "PLEASE"); if (DISABLE) { Options::kiosk.enabled = false; return "Kiosk mode OFF"; } if (!args.empty() && args[0] == "OFF") { return "Not allowed in kiosk mode"; } if (args.empty() || args[0] == "ON") { if (Options::kiosk.enabled) { return "Kiosk mode already ON"; } Options::kiosk.enabled = true; if (!Options::video.fullscreen) { Screen::get()->toggleVideoMode(); } return "Kiosk mode ON"; } return "usage: kiosk [on]"; } // EXIT / QUIT static auto cmd_exit(const std::vector& args) -> std::string { if (Options::kiosk.enabled && (args.empty() || args[0] != "PLEASE")) { return "Not allowed in kiosk mode"; } SceneManager::current = SceneManager::Scene::QUIT; return "Quitting..."; } // SIZE static auto cmd_size(const std::vector&) -> std::string { int w = 0; int h = 0; SDL_GetWindowSize(SDL_GetRenderWindow(Screen::get()->getRenderer()), &w, &h); return std::to_string(w) + "x" + std::to_string(h); } // ── CommandRegistry ────────────────────────────────────────────────────────── void CommandRegistry::registerHandlers() { handlers_["cmd_ss"] = cmd_ss; handlers_["cmd_shader"] = cmd_shader; handlers_["cmd_border"] = cmd_border; handlers_["cmd_fullscreen"] = cmd_fullscreen; handlers_["cmd_zoom"] = cmd_zoom; handlers_["cmd_intscale"] = cmd_intscale; handlers_["cmd_vsync"] = cmd_vsync; handlers_["cmd_driver"] = cmd_driver; handlers_["cmd_palette"] = cmd_palette; handlers_["cmd_audio"] = cmd_audio; handlers_["cmd_music"] = cmd_music; handlers_["cmd_sound"] = cmd_sound; handlers_["cmd_show"] = cmd_show; handlers_["cmd_hide"] = cmd_hide; handlers_["cmd_cheat"] = cmd_cheat; handlers_["cmd_player"] = cmd_player; handlers_["cmd_restart"] = cmd_restart; handlers_["cmd_kiosk"] = cmd_kiosk; handlers_["cmd_exit"] = cmd_exit; handlers_["cmd_quit"] = cmd_exit; // QUIT usa el mismo handler que EXIT handlers_["cmd_size"] = cmd_size; #ifdef _DEBUG handlers_["cmd_debug"] = cmd_debug; handlers_["cmd_items"] = cmd_items; handlers_["cmd_room"] = cmd_room; handlers_["cmd_scene"] = cmd_scene; #endif // HELP se registra en load() como lambda que captura this // Proveedores de completions dinámicas // PALETTE: NEXT, PREV + nombres de paletas disponibles (UPPERCASE) dynamic_providers_["PALETTE"] = []() -> std::vector { std::vector result = {"NEXT", "PREV"}; if (Screen::get() != nullptr) { for (const auto& name : Screen::get()->getPaletteNames()) { result.push_back(toUpper(name)); } } return result; }; // SHADER PRESET: NEXT, PREV + nombres de presets del shader activo (UPPERCASE) dynamic_providers_["SHADER PRESET"] = []() -> std::vector { std::vector result = {"NEXT", "PREV"}; const bool IS_CRTPI = Options::video.shader.current_shader == Rendering::ShaderType::CRTPI; if (IS_CRTPI) { for (const auto& p : Options::crtpi_presets) { result.push_back(toUpper(p.name)); } } else { for (const auto& p : Options::postfx_presets) { result.push_back(toUpper(p.name)); } } return result; }; } void CommandRegistry::load(const std::string& yaml_path) { registerHandlers(); // Cargar y parsear el YAML auto file_data = Resource::Helper::loadFile(yaml_path); if (file_data.empty()) { std::cerr << "CommandRegistry: Unable to load " << yaml_path << '\n'; return; } std::string yaml_content(file_data.begin(), file_data.end()); fkyaml::node yaml; try { yaml = fkyaml::node::deserialize(yaml_content); } catch (const fkyaml::exception& e) { std::cerr << "CommandRegistry: YAML parse error: " << e.what() << '\n'; return; } if (!yaml.contains("categories")) { return; } for (const auto& cat_node : yaml["categories"]) { const std::string category = cat_node["name"].get_value(); const bool cat_debug_only = cat_node.contains("debug_only") && cat_node["debug_only"].get_value(); if (!cat_node.contains("commands")) { continue; } for (const auto& cmd_node : cat_node["commands"]) { CommandDef def; def.keyword = cmd_node["keyword"].get_value(); def.handler_id = cmd_node["handler"].get_value(); def.category = category; def.description = cmd_node.contains("description") ? cmd_node["description"].get_value() : ""; def.usage = cmd_node.contains("usage") ? cmd_node["usage"].get_value() : def.keyword; def.instant = cmd_node.contains("instant") && cmd_node["instant"].get_value(); def.hidden = cmd_node.contains("hidden") && cmd_node["hidden"].get_value(); def.debug_only = cat_debug_only || (cmd_node.contains("debug_only") && cmd_node["debug_only"].get_value()); def.help_hidden = cmd_node.contains("help_hidden") && cmd_node["help_hidden"].get_value(); def.dynamic_completions = cmd_node.contains("dynamic_completions") && cmd_node["dynamic_completions"].get_value(); // Completions estáticas if (cmd_node.contains("completions")) { auto completions_node = cmd_node["completions"]; for (auto it = completions_node.begin(); it != completions_node.end(); ++it) { std::string path = it.key().get_value(); std::vector opts; for (const auto& opt : *it) { opts.push_back(opt.get_value()); } def.completions[path] = std::move(opts); } } // Aplicar debug_extras en debug builds #ifdef _DEBUG if (cmd_node.contains("debug_extras")) { const auto& extras = cmd_node["debug_extras"]; if (extras.contains("description")) { def.description = extras["description"].get_value(); } if (extras.contains("usage")) { def.usage = extras["usage"].get_value(); } if (extras.contains("hidden")) { def.hidden = extras["hidden"].get_value(); } if (extras.contains("completions")) { def.completions.clear(); auto extras_completions = extras["completions"]; for (auto it = extras_completions.begin(); it != extras_completions.end(); ++it) { std::string path = it.key().get_value(); std::vector opts; for (const auto& opt : *it) { opts.push_back(opt.get_value()); } def.completions[path] = std::move(opts); } } } #endif // En Release: saltar comandos debug_only #ifndef _DEBUG if (def.debug_only) { continue; } #endif commands_.push_back(std::move(def)); } } // Registrar el handler de HELP (captura this) handlers_["cmd_help"] = [this](const std::vector&) -> std::string { std::cout << generateTerminalHelp(); return generateConsoleHelp(); }; // Aplanar completions en el mapa global for (const auto& cmd : commands_) { for (const auto& [path, opts] : cmd.completions) { completions_map_[path] = opts; } } } auto CommandRegistry::findCommand(const std::string& keyword) const -> const CommandDef* { for (const auto& cmd : commands_) { if (cmd.keyword == keyword) { return &cmd; } } return nullptr; } auto CommandRegistry::execute(const std::string& keyword, const std::vector& args) const -> std::string { const auto* def = findCommand(keyword); if (def == nullptr) { return ""; } const auto it = handlers_.find(def->handler_id); if (it == handlers_.end()) { return "Handler not found: " + def->handler_id; } return it->second(args); } auto CommandRegistry::generateTerminalHelp() const -> std::string { std::ostringstream out; out << "=== JDD CONSOLE COMMANDS ===" << '\n'; std::string current_category; for (const auto& cmd : commands_) { if (cmd.help_hidden) { continue; } if (cmd.category != current_category) { current_category = cmd.category; out << '\n' << '[' << current_category << ']' << '\n'; } // Formatear: usage alineado a la izquierda, descripción a la derecha const std::string& usage = cmd.usage; out << " " << usage; if (!cmd.description.empty()) { // Padding para alinear descripción (columna ~40) const int PAD = std::max(1, 38 - static_cast(usage.size())); out << std::string(static_cast(PAD), ' ') << cmd.description; } out << '\n'; } return out.str(); } auto CommandRegistry::generateConsoleHelp() const -> std::string { std::string release_cmds; std::string debug_cmds; for (const auto& cmd : commands_) { if (cmd.help_hidden) { continue; } // Convertir keyword a minúsculas para la lista std::string kw_lower = cmd.keyword; std::ranges::transform(kw_lower, kw_lower.begin(), ::tolower); if (cmd.debug_only) { if (!debug_cmds.empty()) { debug_cmds += ", "; } debug_cmds += kw_lower; } else { if (!release_cmds.empty()) { release_cmds += ", "; } release_cmds += kw_lower; } } std::string result = "Commands:\n" + release_cmds + "\n"; if (!debug_cmds.empty()) { result += "\nDebug commands:\n" + debug_cmds + "\n"; } result += "-- more info on the terminal"; return result; } auto CommandRegistry::getCompletions(const std::string& path) const -> std::vector { // Primero: buscar proveedor dinámico (tiene prioridad si existe) const auto dyn_it = dynamic_providers_.find(path); if (dyn_it != dynamic_providers_.end()) { return dyn_it->second(); } // Fallback: completions estáticas del YAML const auto it = completions_map_.find(path); if (it != completions_map_.end()) { return it->second; } return {}; } auto CommandRegistry::getVisibleKeywords() const -> std::vector { std::vector result; for (const auto& cmd : commands_) { if (!cmd.hidden) { result.push_back(cmd.keyword); } } return result; }