From 62bf99f174e67707130ef0016d334bc548a91078 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Sun, 17 May 2026 21:56:47 +0200 Subject: [PATCH] refactor console_commands: applyCheatToggle, parsers YAML al namespace anonim, buildHelp i append_csv --- source/game/ui/console_commands.cpp | 295 ++++++++++++++-------------- source/game/ui/console_commands.hpp | 1 + 2 files changed, 145 insertions(+), 151 deletions(-) diff --git a/source/game/ui/console_commands.cpp b/source/game/ui/console_commands.cpp index fbd7ba8..a9ac85f 100644 --- a/source/game/ui/console_commands.cpp +++ b/source/game/ui/console_commands.cpp @@ -426,7 +426,7 @@ static auto cmdSound(const std::vector& args) -> std::string { #ifdef _DEBUG // DEBUG [MODE [ON|OFF]|START [HERE|ROOM|POS|SCENE ]] -static auto cmdDebug(const std::vector& args) -> std::string { // NOLINT(readability-function-cognitive-complexity) +static auto cmdDebug(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 @@ -525,7 +525,7 @@ static auto changeRoomWithEditor(const std::string& room_file) -> std::string { return std::string("Room: ") + room_file; } -static auto cmdRoom(const std::vector& args) -> std::string { // NOLINT(readability-function-cognitive-complexity) +static auto cmdRoom(const std::vector& args) -> std::string { if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; } if (args.empty()) { return "usage: room |next|prev|left|right|up|down"; } @@ -613,7 +613,7 @@ static auto cmdScene(const std::vector& args) -> std::string { } // EDIT [ON|OFF|REVERT] -static auto cmdEdit(const std::vector& args) -> std::string { // NOLINT(readability-function-cognitive-complexity) +static auto cmdEdit(const std::vector& args) -> std::string { if (args.empty()) { // Toggle: si está activo → off, si no → on if ((MapEditor::get() != nullptr) && MapEditor::get()->isActive()) { @@ -814,47 +814,38 @@ static auto cmdHide(const std::vector& args) -> std::string { } // CHEAT [subcomando] -static auto cmdCheat(const std::vector& args) -> std::string { // NOLINT(readability-function-cognitive-complexity) +// Apply ON/OFF/toggle a un cheat binari. mode="" → toggle; "ON"/"OFF" → estableix; altra cosa → cadena buida (usage error). +static auto applyCheatToggle(Options::Cheat::State& cheat, std::string_view mode, std::string_view label) -> std::string { + using State = Options::Cheat::State; + if (mode.empty()) { + cheat = (cheat == State::ENABLED) ? State::DISABLED : State::ENABLED; + } else if (mode == "ON") { + if (cheat == State::ENABLED) { return std::string(label) + " already ON"; } + cheat = State::ENABLED; + } else if (mode == "OFF") { + if (cheat == State::DISABLED) { return std::string(label) + " already OFF"; } + cheat = State::DISABLED; + } else { + return {}; // sentinel: mode invàlid + } + return std::string(label) + " " + (cheat == State::ENABLED ? "ON" : "OFF"); +} + +static auto cmdCheat(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]"; } - // 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()); - // cppcheck-suppress knownConditionTrueFalse -- cppcheck no infiere que REST puede estar vacío cuando args.size() == 2. - 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"); + const std::string_view MODE = (args.size() > 2) ? std::string_view(args[2]) : std::string_view(); + const std::string RES = applyCheatToggle(Options::cheats.infinite_lives, MODE, "Infinite lives"); + return RES.empty() ? "usage: cheat infinite lives [on|off]" : RES; } - // 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"); + const std::string_view MODE = (args.size() > 1) ? std::string_view(args[1]) : std::string_view(); + const std::string RES = applyCheatToggle(Options::cheats.invincible, MODE, "Invincibility"); + return RES.empty() ? "usage: cheat invincibility [on|off]" : RES; } return "usage: cheat [infinite lives|invincibility]"; @@ -910,7 +901,7 @@ static auto cmdSize(const std::vector& /*unused*/) -> std::string { } // CONSOLE [TRANSPARENT [ON|OFF]|BG|MSG|PROMPT|COMMAND <0-255>] -static auto cmdConsole(const std::vector& args) -> std::string { // NOLINT(readability-function-cognitive-complexity) +static auto cmdConsole(const std::vector& args) -> std::string { if (args.empty()) { return std::string("Console ") + (Options::console.transparent ? "transparent" : "solid") + " bg:" + std::to_string(Options::console.bg_color) + " msg:" + std::to_string(Options::console.msg_color) + " prompt:" + std::to_string(Options::console.prompt_color) + " cmd:" + std::to_string(Options::console.command_color); } @@ -955,7 +946,7 @@ static auto cmdConsole(const std::vector& args) -> std::string { / // ── CommandRegistry ────────────────────────────────────────────────────────── -void CommandRegistry::registerHandlers() { // NOLINT(readability-function-cognitive-complexity) +void CommandRegistry::registerHandlers() { handlers_["cmd_shader"] = cmdShader; handlers_["cmd_border"] = cmdBorder; handlers_["cmd_fullscreen"] = cmdFullscreen; @@ -1074,7 +1065,108 @@ void CommandRegistry::registerHandlers() { // NOLINT(readability-function-cogni #endif } -void CommandRegistry::load(const std::string& yaml_path) { // NOLINT(readability-function-cognitive-complexity) +namespace { + // Parseja un node "scope" (string o sequence) a un vector. Buit si node és buit. + auto parseScopeNode(const fkyaml::node& scope_node) -> std::vector { + std::vector result; + if (scope_node.is_sequence()) { + std::ranges::transform(scope_node, std::back_inserter(result), [](const auto& s) { return s.template get_value(); }); + } else { + result.push_back(scope_node.get_value()); + } + return result; + } + + // Parseja un mapping de path → [options] en l'unordered_map de destí + void parseCompletionsNode(const fkyaml::node& completions_node, std::unordered_map>& out) { + for (auto it = completions_node.begin(); it != completions_node.end(); ++it) { + auto path = it.key().get_value(); + std::vector opts; + std::ranges::transform(*it, std::back_inserter(opts), [](const auto& opt) { return opt.template get_value(); }); + out[path] = std::move(opts); + } + } + +#ifdef _DEBUG + // Aplica camps "debug_extras" sobre un CommandDef ja inicialitzat (només en _DEBUG) + void applyDebugExtras(const fkyaml::node& extras, CommandDef& def) { + 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("help_hidden")) { def.help_hidden = extras["help_hidden"].get_value(); } + if (extras.contains("completions")) { + def.completions.clear(); + parseCompletionsNode(extras["completions"], def.completions); + } + } +#endif + + // Parseja un cmd_node a CommandDef. Hereta de la categoria si el comand no defineix scope/debug_only. + auto parseCommandDef(const fkyaml::node& cmd_node, const std::string& category, bool cat_debug_only, const std::vector& cat_scopes) -> CommandDef { + 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(); + + if (cmd_node.contains("scope")) { + def.scopes = parseScopeNode(cmd_node["scope"]); + } else if (!cat_scopes.empty()) { + def.scopes = cat_scopes; + } else { + def.scopes.emplace_back("global"); + } + + if (cmd_node.contains("completions")) { + parseCompletionsNode(cmd_node["completions"], def.completions); + } + +#ifdef _DEBUG + if (cmd_node.contains("debug_extras")) { + applyDebugExtras(cmd_node["debug_extras"], def); + } +#endif + return def; + } +} // namespace + +// Implementació del handler "cmd_help" +auto CommandRegistry::buildHelp(const std::vector& args) const -> std::string { + if (!args.empty()) { + // HELP KEYS [scope]: referencia de atajos de teclado + if (args[0] == "KEYS") { + return generateKeysHelp(args.size() > 1 ? args[1] : ""); + } + // HELP : mostrar ayuda detallada de un comando + const auto* cmd = findCommand(args[0]); + if (cmd == nullptr) { return "Unknown command: " + args[0]; } + std::string kw_lower = cmd->keyword; + std::ranges::transform(kw_lower, kw_lower.begin(), ::tolower); + std::string result = kw_lower + ": " + cmd->description + "\n" + cmd->usage; + + // Listar subcomandos/opciones si hay completions + const auto OPTS = getCompletions(cmd->keyword); + if (!OPTS.empty()) { + result += "\noptions:"; + for (const auto& opt : OPTS) { + std::string opt_lower = opt; + std::ranges::transform(opt_lower, opt_lower.begin(), ::tolower); + result += " " + opt_lower; + } + } + return result; + } + std::cout << generateTerminalHelp(); + return generateConsoleHelp(); +} + +void CommandRegistry::load(const std::string& yaml_path) { registerHandlers(); // Cargar y parsear el YAML @@ -1098,121 +1190,21 @@ void CommandRegistry::load(const std::string& yaml_path) { // NOLINT(readabilit for (const auto& cat_node : yaml["categories"]) { const auto CATEGORY = cat_node["name"].get_value(); const bool CAT_DEBUG_ONLY = cat_node.contains("debug_only") && cat_node["debug_only"].get_value(); - - // Scopes por defecto de la categoría - std::vector cat_scopes; - if (cat_node.contains("scope")) { - const auto& scope_node = cat_node["scope"]; - if (scope_node.is_sequence()) { - std::ranges::transform(scope_node, std::back_inserter(cat_scopes), [](const auto& s) { return s.template get_value(); }); - } else { - cat_scopes.push_back(scope_node.get_value()); - } - } + const std::vector CAT_SCOPES = cat_node.contains("scope") ? parseScopeNode(cat_node["scope"]) : std::vector{}; 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(); - - // Scopes: del comando, o hereda de la categoría, o "global" por defecto - if (cmd_node.contains("scope")) { - const auto& scope_node = cmd_node["scope"]; - if (scope_node.is_sequence()) { - std::ranges::transform(scope_node, std::back_inserter(def.scopes), [](const auto& s) { return s.template get_value(); }); - } else { - def.scopes.push_back(scope_node.get_value()); - } - } else if (!cat_scopes.empty()) { - def.scopes = cat_scopes; - } else { - def.scopes.emplace_back("global"); - } - - // 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) { - auto path = it.key().get_value(); - std::vector opts; - const auto& options_node = *it; - std::ranges::transform(options_node, std::back_inserter(opts), [](const auto& opt) { return opt.template 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("help_hidden")) { def.help_hidden = extras["help_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) { - auto path = it.key().get_value(); - std::vector opts; - const auto& options_node = *it; - std::ranges::transform(options_node, std::back_inserter(opts), [](const auto& opt) { return opt.template get_value(); }); - def.completions[path] = std::move(opts); - } - } - } -#endif - - // En Release: saltar comandos debug_only + CommandDef def = parseCommandDef(cmd_node, CATEGORY, CAT_DEBUG_ONLY, CAT_SCOPES); #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& args) -> std::string { - if (!args.empty()) { - // HELP KEYS [scope]: referencia de atajos de teclado - if (args[0] == "KEYS") { - return generateKeysHelp(args.size() > 1 ? args[1] : ""); - } - // HELP : mostrar ayuda detallada de un comando - const auto* cmd = findCommand(args[0]); - if (cmd != nullptr) { - std::string kw_lower = cmd->keyword; - std::ranges::transform(kw_lower, kw_lower.begin(), ::tolower); - std::string result = kw_lower + ": " + cmd->description + "\n" + cmd->usage; - - // Listar subcomandos/opciones si hay completions - auto opts = getCompletions(cmd->keyword); - if (!opts.empty()) { - result += "\noptions:"; - for (const auto& opt : opts) { - std::string opt_lower = opt; - std::ranges::transform(opt_lower, opt_lower.begin(), ::tolower); - result += " " + opt_lower; - } - } - return result; - } - return "Unknown command: " + args[0]; - } - std::cout << generateTerminalHelp(); - return generateConsoleHelp(); - }; + // Registrar el handler de HELP (delega a buildHelp per mantenir baixa la complexitat de load) + handlers_["cmd_help"] = [this](const std::vector& args) -> std::string { return buildHelp(args); }; // Aplanar completions en el mapa global for (const auto& cmd : commands_) { @@ -1300,12 +1292,17 @@ auto CommandRegistry::generateTerminalHelp() const -> std::string { return out.str(); } -auto CommandRegistry::generateConsoleHelp() const -> std::string { // NOLINT(readability-function-cognitive-complexity) +auto CommandRegistry::generateConsoleHelp() const -> std::string { // Agrupar comandos visibles por scope std::string global_cmds; std::string debug_cmds; std::string editor_cmds; + auto append_csv = [](std::string& dst, const std::string& token) { + if (!dst.empty()) { dst += ", "; } + dst += token; + }; + for (const auto& cmd : commands_) { if (cmd.help_hidden) { continue; } if (!isCommandVisible(cmd)) { continue; } @@ -1315,16 +1312,12 @@ auto CommandRegistry::generateConsoleHelp() const -> std::string { // NOLINT(re // Clasificar por el PRIMER scope del comando const std::string& primary = cmd.scopes.empty() ? "global" : cmd.scopes[0]; - if (primary == "editor") { - if (!editor_cmds.empty()) { editor_cmds += ", "; } - editor_cmds += kw_lower; + append_csv(editor_cmds, kw_lower); } else if (primary == "debug") { - if (!debug_cmds.empty()) { debug_cmds += ", "; } - debug_cmds += kw_lower; + append_csv(debug_cmds, kw_lower); } else { - if (!global_cmds.empty()) { global_cmds += ", "; } - global_cmds += kw_lower; + append_csv(global_cmds, kw_lower); } } diff --git a/source/game/ui/console_commands.hpp b/source/game/ui/console_commands.hpp index b425439..ff33eda 100644 --- a/source/game/ui/console_commands.hpp +++ b/source/game/ui/console_commands.hpp @@ -59,4 +59,5 @@ class CommandRegistry { void registerHandlers(); [[nodiscard]] auto isCommandVisible(const CommandDef& cmd) const -> bool; [[nodiscard]] static auto generateKeysHelp(const std::string& scope_filter) -> std::string; + [[nodiscard]] auto buildHelp(const std::vector& args) const -> std::string; // Implementació del handler "cmd_help" };