key_config.cpp i keys.yaml per a centralitzar i no hardcodejar tecles
This commit is contained in:
@@ -36,6 +36,7 @@ set(APP_SOURCES
|
||||
source/core/input/global_inputs.cpp
|
||||
source/core/input/input_types.cpp
|
||||
source/core/input/input.cpp
|
||||
source/core/input/key_config.cpp
|
||||
source/core/input/mouse.cpp
|
||||
|
||||
# Core - Rendering
|
||||
|
||||
@@ -35,6 +35,7 @@ assets:
|
||||
input:
|
||||
DATA:
|
||||
- ${PREFIX}/gamecontrollerdb.txt
|
||||
- ${PREFIX}/data/input/keys.yaml
|
||||
|
||||
# SYSTEM
|
||||
system:
|
||||
|
||||
@@ -182,7 +182,7 @@ categories:
|
||||
- keyword: HELP
|
||||
handler: cmd_help
|
||||
description: "Show this help"
|
||||
usage: "HELP [<command>]"
|
||||
usage: "HELP [KEYS [scope]|<command>]"
|
||||
dynamic_completions: true
|
||||
|
||||
- keyword: "?"
|
||||
|
||||
203
data/input/keys.yaml
Normal file
203
data/input/keys.yaml
Normal file
@@ -0,0 +1,203 @@
|
||||
# Projecte 2026 - Keybinding Configuration
|
||||
# Single source of truth for all key assignments.
|
||||
# Code reads this at startup; HELP KEYS displays it in the console.
|
||||
#
|
||||
# Fields:
|
||||
# id - Identifier used in C++ code (KeyConfig::key("SCOPE", "id"))
|
||||
# key - Display text for HELP KEYS (human-readable)
|
||||
# code - SDL key name for SDL_GetKeyFromName() — omit for mouse/composite entries
|
||||
# desc - Short description (console is 256px wide)
|
||||
# action - (Optional, GLOBAL only) InputAction name to bind via Input::bindKey()
|
||||
|
||||
scopes:
|
||||
- name: GLOBAL
|
||||
keys:
|
||||
- id: zoom_down
|
||||
key: "F1"
|
||||
code: "F1"
|
||||
desc: "zoom down"
|
||||
action: WINDOW_DEC_ZOOM
|
||||
- id: zoom_up
|
||||
key: "F2"
|
||||
code: "F2"
|
||||
desc: "zoom up"
|
||||
action: WINDOW_INC_ZOOM
|
||||
- id: fullscreen
|
||||
key: "F3"
|
||||
code: "F3"
|
||||
desc: "fullscreen"
|
||||
action: TOGGLE_FULLSCREEN
|
||||
- id: shader
|
||||
key: "F4"
|
||||
code: "F4"
|
||||
desc: "shader on/off"
|
||||
action: TOGGLE_POSTFX
|
||||
- id: shader_preset
|
||||
key: "Shift+F4"
|
||||
desc: "next shader preset"
|
||||
- id: shader_type
|
||||
key: "Ctrl+F4"
|
||||
desc: "next shader type"
|
||||
- id: next_palette
|
||||
key: "F5"
|
||||
code: "F5"
|
||||
desc: "next palette"
|
||||
action: NEXT_PALETTE
|
||||
- id: prev_palette
|
||||
key: "Ctrl+F5"
|
||||
desc: "prev palette"
|
||||
- id: palette_sort
|
||||
key: "F6"
|
||||
code: "F6"
|
||||
desc: "palette sort mode"
|
||||
action: NEXT_PALETTE_SORT
|
||||
- id: integer_scale
|
||||
key: "F7"
|
||||
code: "F7"
|
||||
desc: "integer scale"
|
||||
action: TOGGLE_INTEGER_SCALE
|
||||
- id: music
|
||||
key: "F8"
|
||||
code: "F8"
|
||||
desc: "music on/off"
|
||||
action: TOGGLE_MUSIC
|
||||
- id: border
|
||||
key: "F9"
|
||||
code: "F9"
|
||||
desc: "border"
|
||||
action: TOGGLE_BORDER
|
||||
- id: vsync
|
||||
key: "F10"
|
||||
code: "F10"
|
||||
desc: "vsync"
|
||||
action: TOGGLE_VSYNC
|
||||
- id: pause
|
||||
key: "F11"
|
||||
code: "F11"
|
||||
desc: "pause"
|
||||
action: PAUSE
|
||||
- id: info
|
||||
key: "F12"
|
||||
code: "F12"
|
||||
desc: "show info"
|
||||
action: TOGGLE_DEBUG
|
||||
- id: screenshot
|
||||
key: "Ctrl+S"
|
||||
code: "S"
|
||||
desc: "screenshot"
|
||||
action: SCREENSHOT
|
||||
- id: console
|
||||
key: "`"
|
||||
code: "`"
|
||||
desc: "console"
|
||||
action: TOGGLE_CONSOLE
|
||||
- id: quit
|
||||
key: "Esc"
|
||||
code: "Escape"
|
||||
desc: "quit/back"
|
||||
action: EXIT
|
||||
|
||||
- name: EDITOR
|
||||
keys:
|
||||
- id: toggle
|
||||
key: "9"
|
||||
code: "9"
|
||||
desc: "toggle editor"
|
||||
- id: grid
|
||||
key: "G"
|
||||
code: "G"
|
||||
desc: "toggle grid"
|
||||
- id: collision
|
||||
key: "8"
|
||||
code: "8"
|
||||
desc: "draw/collision mode"
|
||||
- id: tile_picker
|
||||
key: "T"
|
||||
code: "T"
|
||||
desc: "tile picker"
|
||||
- id: eraser
|
||||
key: "E"
|
||||
code: "E"
|
||||
desc: "eraser"
|
||||
- id: minimap
|
||||
key: "M"
|
||||
code: "M"
|
||||
desc: "minimap"
|
||||
- id: nav_up
|
||||
key: "Up"
|
||||
code: "Up"
|
||||
desc: "room up"
|
||||
- id: nav_down
|
||||
key: "Down"
|
||||
code: "Down"
|
||||
desc: "room down"
|
||||
- id: nav_left
|
||||
key: "Left"
|
||||
code: "Left"
|
||||
desc: "room left"
|
||||
- id: nav_right
|
||||
key: "Right"
|
||||
code: "Right"
|
||||
desc: "room right"
|
||||
- id: cancel
|
||||
key: "Esc"
|
||||
code: "Escape"
|
||||
desc: "cancel/clear brush"
|
||||
|
||||
- name: MINIMAP
|
||||
keys:
|
||||
- id: numbers
|
||||
key: "N"
|
||||
code: "N"
|
||||
desc: "room numbers"
|
||||
- id: capture
|
||||
key: "S"
|
||||
code: "S"
|
||||
desc: "capture minimap"
|
||||
- id: close_m
|
||||
key: "M"
|
||||
code: "M"
|
||||
desc: "close"
|
||||
- id: close_esc
|
||||
key: "Esc"
|
||||
code: "Escape"
|
||||
desc: "close"
|
||||
|
||||
- name: DEBUG
|
||||
keys:
|
||||
- id: debug_mode
|
||||
key: "0"
|
||||
code: "0"
|
||||
desc: "debug mode"
|
||||
- id: infinite_lives
|
||||
key: "1"
|
||||
code: "1"
|
||||
desc: "infinite lives"
|
||||
- id: invincibility
|
||||
key: "2"
|
||||
code: "2"
|
||||
desc: "invincibility"
|
||||
- id: nav_up
|
||||
key: "W"
|
||||
code: "W"
|
||||
desc: "room up"
|
||||
- id: nav_left
|
||||
key: "A"
|
||||
code: "A"
|
||||
desc: "room left"
|
||||
- id: nav_down
|
||||
key: "S"
|
||||
code: "S"
|
||||
desc: "room down"
|
||||
- id: nav_right
|
||||
key: "D"
|
||||
code: "D"
|
||||
desc: "room right"
|
||||
- id: reload
|
||||
key: "R"
|
||||
code: "R"
|
||||
desc: "reload resources"
|
||||
- id: test_cheevo
|
||||
key: "3"
|
||||
code: "3"
|
||||
desc: "test achievement"
|
||||
@@ -7,9 +7,9 @@
|
||||
|
||||
#include "core/input/input.hpp" // Para Input, InputAction, Input::DO_NOT_ALLOW_REPEAT
|
||||
#include "core/locale/locale.hpp" // Para Locale
|
||||
#include "core/rendering/render_info.hpp" // Para RenderInfo
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/rendering/screenshot.hpp" // Para Screenshot
|
||||
#include "core/rendering/render_info.hpp" // Para RenderInfo
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/rendering/screenshot.hpp" // Para Screenshot
|
||||
#ifdef _DEBUG
|
||||
#include "core/system/debug.hpp" // Para Debug (persistencia de render_info en debug.yaml)
|
||||
#endif
|
||||
|
||||
@@ -27,34 +27,17 @@ auto Input::get() -> Input* { return Input::instance; }
|
||||
// Constructor
|
||||
Input::Input(std::string game_controller_db_path)
|
||||
: gamepad_mappings_file_(std::move(game_controller_db_path)) {
|
||||
// Inicializar bindings del teclado
|
||||
// Bindings de gameplay (rebindeables por el jugador vía Options)
|
||||
// Las teclas de sistema (F1-F12, B, S, `, Esc...) se cargan desde keys.yaml
|
||||
// vía KeyConfig::applyGlobalBindings() — NO duplicar aquí.
|
||||
keyboard_.bindings = {
|
||||
// Movimiento del jugador
|
||||
{Action::LEFT, KeyState{.scancode = SDL_SCANCODE_LEFT}},
|
||||
{Action::RIGHT, KeyState{.scancode = SDL_SCANCODE_RIGHT}},
|
||||
{Action::JUMP, KeyState{.scancode = SDL_SCANCODE_UP}},
|
||||
{Action::DOWN, KeyState{.scancode = SDL_SCANCODE_DOWN}},
|
||||
|
||||
// Inputs de control
|
||||
{Action::ACCEPT, KeyState{.scancode = SDL_SCANCODE_RETURN}},
|
||||
{Action::CANCEL, KeyState{.scancode = SDL_SCANCODE_ESCAPE}},
|
||||
{Action::EXIT, KeyState{.scancode = SDL_SCANCODE_ESCAPE}},
|
||||
|
||||
// Inputs de sistema
|
||||
{Action::WINDOW_DEC_ZOOM, KeyState{.scancode = SDL_SCANCODE_F1}},
|
||||
{Action::WINDOW_INC_ZOOM, KeyState{.scancode = SDL_SCANCODE_F2}},
|
||||
{Action::TOGGLE_FULLSCREEN, KeyState{.scancode = SDL_SCANCODE_F3}},
|
||||
{Action::TOGGLE_SHADER, KeyState{.scancode = SDL_SCANCODE_F4}},
|
||||
{Action::NEXT_PALETTE, KeyState{.scancode = SDL_SCANCODE_F5}},
|
||||
{Action::NEXT_PALETTE_SORT, KeyState{.scancode = SDL_SCANCODE_F6}},
|
||||
{Action::TOGGLE_INTEGER_SCALE, KeyState{.scancode = SDL_SCANCODE_F7}},
|
||||
{Action::TOGGLE_IN_GAME_MUSIC, KeyState{.scancode = SDL_SCANCODE_F8}},
|
||||
{Action::TOGGLE_BORDER, KeyState{.scancode = SDL_SCANCODE_F9}},
|
||||
{Action::TOGGLE_VSYNC, KeyState{.scancode = SDL_SCANCODE_F10}},
|
||||
{Action::PAUSE, KeyState{.scancode = SDL_SCANCODE_F11}},
|
||||
{Action::TOGGLE_INFO, KeyState{.scancode = SDL_SCANCODE_F12}},
|
||||
{Action::TOGGLE_CONSOLE, KeyState{.scancode = SDL_SCANCODE_GRAVE}},
|
||||
{Action::SCREENSHOT, KeyState{.scancode = SDL_SCANCODE_S}}};
|
||||
};
|
||||
|
||||
initSDLGamePad(); // Inicializa el subsistema SDL_INIT_GAMEPAD
|
||||
}
|
||||
|
||||
@@ -24,7 +24,9 @@ const std::unordered_map<InputAction, std::string> ACTION_TO_STRING = {
|
||||
{InputAction::NEXT_PALETTE_SORT, "NEXT_PALETTE_SORT"},
|
||||
{InputAction::TOGGLE_SHADER, "TOGGLE_POSTFX"},
|
||||
{InputAction::NEXT_SHADER_PRESET, "NEXT_POSTFX_PRESET"},
|
||||
{InputAction::TOGGLE_SUPERSAMPLING, "TOGGLE_SUPERSAMPLING"},
|
||||
{InputAction::TOGGLE_INFO, "TOGGLE_DEBUG"},
|
||||
{InputAction::TOGGLE_CONSOLE, "TOGGLE_CONSOLE"},
|
||||
{InputAction::SCREENSHOT, "SCREENSHOT"},
|
||||
{InputAction::NONE, "NONE"}};
|
||||
|
||||
@@ -49,7 +51,9 @@ const std::unordered_map<std::string, InputAction> STRING_TO_ACTION = {
|
||||
{"NEXT_PALETTE_SORT", InputAction::NEXT_PALETTE_SORT},
|
||||
{"TOGGLE_POSTFX", InputAction::TOGGLE_SHADER},
|
||||
{"NEXT_POSTFX_PRESET", InputAction::NEXT_SHADER_PRESET},
|
||||
{"TOGGLE_SUPERSAMPLING", InputAction::TOGGLE_SUPERSAMPLING},
|
||||
{"TOGGLE_DEBUG", InputAction::TOGGLE_INFO},
|
||||
{"TOGGLE_CONSOLE", InputAction::TOGGLE_CONSOLE},
|
||||
{"SCREENSHOT", InputAction::SCREENSHOT},
|
||||
{"NONE", InputAction::NONE}};
|
||||
|
||||
|
||||
126
source/core/input/key_config.cpp
Normal file
126
source/core/input/key_config.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
#include "core/input/key_config.hpp"
|
||||
|
||||
#include <iostream> // Para cerr
|
||||
#include <string> // Para string
|
||||
#include <utility> // Para move
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "core/input/input.hpp" // Para Input
|
||||
#include "core/input/input_types.hpp" // Para STRING_TO_ACTION
|
||||
#include "core/resources/resource_helper.hpp" // Para Resource::Helper
|
||||
#include "external/fkyaml_node.hpp" // Para fkyaml::node
|
||||
|
||||
// ── Singleton ────────────────────────────────────────────────────────────────
|
||||
|
||||
KeyConfig* KeyConfig::instance_ = nullptr;
|
||||
|
||||
void KeyConfig::init(const std::string& yaml_path) {
|
||||
instance_ = new KeyConfig();
|
||||
instance_->load(yaml_path);
|
||||
}
|
||||
|
||||
void KeyConfig::destroy() {
|
||||
delete instance_;
|
||||
instance_ = nullptr;
|
||||
}
|
||||
|
||||
auto KeyConfig::get() -> KeyConfig* { return instance_; }
|
||||
|
||||
// ── Carga del YAML ──────────────────────────────────────────────────────────
|
||||
|
||||
void KeyConfig::load(const std::string& yaml_path) {
|
||||
auto file_data = Resource::Helper::loadFile(yaml_path);
|
||||
if (file_data.empty()) {
|
||||
std::cerr << "KeyConfig: 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 << "KeyConfig: YAML parse error: " << e.what() << '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!yaml.contains("scopes")) { return; }
|
||||
|
||||
for (const auto& scope_node : yaml["scopes"]) {
|
||||
KeyConfigScope scope;
|
||||
scope.name = scope_node["name"].get_value<std::string>();
|
||||
|
||||
if (scope_node.contains("keys")) {
|
||||
for (const auto& key_node : scope_node["keys"]) {
|
||||
KeyEntry entry;
|
||||
entry.id = key_node["id"].get_value<std::string>();
|
||||
entry.display_key = key_node["key"].get_value<std::string>();
|
||||
entry.desc = key_node["desc"].get_value<std::string>();
|
||||
|
||||
// Convertir el nombre SDL a keycode
|
||||
if (key_node.contains("code")) {
|
||||
auto code = key_node["code"].get_value<std::string>();
|
||||
entry.keycode = SDL_GetKeyFromName(code.c_str());
|
||||
if (entry.keycode == SDLK_UNKNOWN) {
|
||||
std::cerr << "KeyConfig: Unknown key name '" << code
|
||||
<< "' for " << scope.name << "." << entry.id << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
// InputAction opcional (para scope GLOBAL)
|
||||
if (key_node.contains("action")) {
|
||||
entry.action = key_node["action"].get_value<std::string>();
|
||||
}
|
||||
|
||||
scope.index[entry.id] = scope.entries.size();
|
||||
scope.entries.push_back(std::move(entry));
|
||||
}
|
||||
}
|
||||
|
||||
scope_index_[scope.name] = scopes_.size();
|
||||
scopes_.push_back(std::move(scope));
|
||||
}
|
||||
|
||||
std::cout << "KeyConfig: Loaded " << scopes_.size() << " scopes from " << yaml_path << '\n';
|
||||
}
|
||||
|
||||
// ── Consultas ───────────────────────────────────────────────────────────────
|
||||
|
||||
auto KeyConfig::key(const std::string& scope, const std::string& id) const -> SDL_Keycode {
|
||||
const auto* s = getScope(scope);
|
||||
if (s == nullptr) { return SDLK_UNKNOWN; }
|
||||
auto it = s->index.find(id);
|
||||
if (it == s->index.end()) { return SDLK_UNKNOWN; }
|
||||
return s->entries[it->second].keycode;
|
||||
}
|
||||
|
||||
void KeyConfig::applyGlobalBindings() const {
|
||||
const auto* global = getScope("GLOBAL");
|
||||
if (global == nullptr) { return; }
|
||||
|
||||
for (const auto& entry : global->entries) {
|
||||
if (entry.action.empty() || entry.keycode == SDLK_UNKNOWN) { continue; }
|
||||
|
||||
auto it = STRING_TO_ACTION.find(entry.action);
|
||||
if (it == STRING_TO_ACTION.end()) {
|
||||
std::cerr << "KeyConfig: Unknown action '" << entry.action << "' for " << entry.id << '\n';
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convertir keycode a scancode para el sistema de Input
|
||||
SDL_Scancode scancode = SDL_GetScancodeFromKey(entry.keycode, nullptr);
|
||||
if (scancode != SDL_SCANCODE_UNKNOWN) {
|
||||
Input::get()->bindKey(it->second, scancode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto KeyConfig::getScopes() const -> const std::vector<KeyConfigScope>& {
|
||||
return scopes_;
|
||||
}
|
||||
|
||||
auto KeyConfig::getScope(const std::string& name) const -> const KeyConfigScope* {
|
||||
auto it = scope_index_.find(name);
|
||||
if (it == scope_index_.end()) { return nullptr; }
|
||||
return &scopes_[it->second];
|
||||
}
|
||||
53
source/core/input/key_config.hpp
Normal file
53
source/core/input/key_config.hpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <string> // Para string
|
||||
#include <unordered_map> // Para unordered_map
|
||||
#include <vector> // Para vector
|
||||
|
||||
// Entrada de tecla cargada desde keys.yaml
|
||||
struct KeyEntry {
|
||||
std::string id; // Identificador usado en código (ej. "grid", "tile_picker")
|
||||
std::string display_key; // Texto para mostrar en HELP KEYS (ej. "G", "T")
|
||||
std::string desc; // Descripción corta
|
||||
SDL_Keycode keycode{SDLK_UNKNOWN}; // Tecla SDL asignada
|
||||
std::string action; // (Opcional) Nombre de InputAction para scope GLOBAL
|
||||
};
|
||||
|
||||
// Grupo de teclas por ámbito
|
||||
struct KeyConfigScope {
|
||||
std::string name; // "GLOBAL", "EDITOR", "MINIMAP", "DEBUG"
|
||||
std::vector<KeyEntry> entries;
|
||||
std::unordered_map<std::string, size_t> index; // id → posición en entries
|
||||
};
|
||||
|
||||
// Registro centralizado de teclas — fuente única de verdad (keys.yaml)
|
||||
class KeyConfig {
|
||||
public:
|
||||
// Singleton
|
||||
static void init(const std::string& yaml_path);
|
||||
static void destroy();
|
||||
static auto get() -> KeyConfig*;
|
||||
|
||||
// Consulta la SDL_Keycode asignada a un id dentro de un scope
|
||||
[[nodiscard]] auto key(const std::string& scope, const std::string& id) const -> SDL_Keycode;
|
||||
|
||||
// Aplica las teclas del scope GLOBAL al sistema de Input (Input::bindKey)
|
||||
void applyGlobalBindings() const;
|
||||
|
||||
// Acceso a scopes para HELP KEYS y para aplicar bindings globales
|
||||
[[nodiscard]] auto getScopes() const -> const std::vector<KeyConfigScope>&;
|
||||
[[nodiscard]] auto getScope(const std::string& name) const -> const KeyConfigScope*;
|
||||
|
||||
private:
|
||||
static KeyConfig* instance_;
|
||||
|
||||
KeyConfig() = default;
|
||||
~KeyConfig() = default;
|
||||
|
||||
void load(const std::string& yaml_path);
|
||||
|
||||
std::vector<KeyConfigScope> scopes_;
|
||||
std::unordered_map<std::string, size_t> scope_index_; // nombre → posición en scopes_
|
||||
};
|
||||
@@ -9,9 +9,8 @@
|
||||
#include <vector> // Para vector
|
||||
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "external/stb_image_write.h" // Para stbi_write_png
|
||||
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
#include "external/stb_image_write.h" // Para stbi_write_png
|
||||
|
||||
namespace Screenshot {
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "core/audio/audio.hpp" // Para Audio
|
||||
#include "core/input/input.hpp" // Para Input, InputAction
|
||||
#include "core/input/key_config.hpp" // Para KeyConfig
|
||||
#include "core/locale/locale.hpp" // Para Locale
|
||||
#include "core/rendering/render_info.hpp" // Para RenderInfo
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
@@ -186,6 +187,9 @@ Director::Director() {
|
||||
Console::init("8bithud");
|
||||
Screen::get()->setNotificationsEnabled(true);
|
||||
|
||||
// Cargar configuración de teclas desde YAML (fuente única de verdad)
|
||||
KeyConfig::init("data/input/keys.yaml");
|
||||
|
||||
// Special handling for gamecontrollerdb.txt - SDL needs filesystem path
|
||||
#ifdef RELEASE_BUILD
|
||||
// In release, construct the path manually (not from Asset which has empty executable_path)
|
||||
@@ -196,6 +200,9 @@ Director::Director() {
|
||||
Input::init(Resource::List::get()->get("gamecontrollerdb.txt")); // Carga configuración de controles
|
||||
#endif
|
||||
|
||||
// Aplicar teclas globales desde KeyConfig al sistema de Input
|
||||
KeyConfig::get()->applyGlobalBindings();
|
||||
|
||||
// Aplica las teclas y botones del gamepad configurados desde Options
|
||||
Input::get()->applyKeyboardBindingsFromOptions();
|
||||
Input::get()->applyGamepadBindingsFromOptions();
|
||||
@@ -246,6 +253,7 @@ Director::~Director() {
|
||||
Debug::destroy();
|
||||
#endif
|
||||
Input::destroy();
|
||||
KeyConfig::destroy();
|
||||
Console::destroy();
|
||||
RenderInfo::destroy();
|
||||
Notifier::destroy();
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <set> // Para set
|
||||
#include <system_error> // Para std::error_code
|
||||
|
||||
#include "core/input/key_config.hpp" // Para KeyConfig
|
||||
#include "core/input/mouse.hpp" // Para Mouse
|
||||
#include "core/rendering/render_info.hpp" // Para RenderInfo
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
@@ -434,8 +435,10 @@ void MapEditor::render() {
|
||||
void MapEditor::handleEvent(const SDL_Event& event) { // NOLINT(readability-function-cognitive-complexity)
|
||||
// Si el tile picker está abierto, los eventos van a él.
|
||||
// Excepción: la T lo cierra como toggle (sin tocar el brush).
|
||||
const auto* kc = KeyConfig::get();
|
||||
|
||||
if (tile_picker_.isOpen()) {
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_T && static_cast<int>(event.key.repeat) == 0) {
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == kc->key("EDITOR", "tile_picker") && static_cast<int>(event.key.repeat) == 0) {
|
||||
tile_picker_.close();
|
||||
return;
|
||||
}
|
||||
@@ -446,7 +449,7 @@ void MapEditor::handleEvent(const SDL_Event& event) { // NOLINT(readability-fun
|
||||
// Si el mini mapa está visible, delegar eventos (ESC o M para cerrar)
|
||||
if (mini_map_visible_ && mini_map_) {
|
||||
if (event.type == SDL_EVENT_KEY_DOWN &&
|
||||
(event.key.key == SDLK_ESCAPE || event.key.key == SDLK_M) &&
|
||||
(event.key.key == kc->key("EDITOR", "cancel") || event.key.key == kc->key("EDITOR", "minimap")) &&
|
||||
static_cast<int>(event.key.repeat) == 0) {
|
||||
mini_map_visible_ = false;
|
||||
return;
|
||||
@@ -456,19 +459,19 @@ void MapEditor::handleEvent(const SDL_Event& event) { // NOLINT(readability-fun
|
||||
}
|
||||
|
||||
// ESC: cancelar eyedropper en progreso (sin tocar el brush previo)
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE && eyedropper_.active) {
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == kc->key("EDITOR", "cancel") && eyedropper_.active) {
|
||||
eyedropper_.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// ESC: desactivar brush
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE && !brush_.isEmpty()) {
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == kc->key("EDITOR", "cancel") && !brush_.isEmpty()) {
|
||||
brush_.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// E: toggle borrador (alterna entre brush vacío y brush 1x1 ERASE)
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_E && static_cast<int>(event.key.repeat) == 0) {
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == kc->key("EDITOR", "eraser") && static_cast<int>(event.key.repeat) == 0) {
|
||||
if (brush_.width == 1 && brush_.height == 1 && !brush_.tiles.empty() && brush_.tiles[0] == BrushPattern::ERASE) {
|
||||
brush_.clear();
|
||||
} else {
|
||||
@@ -478,13 +481,13 @@ void MapEditor::handleEvent(const SDL_Event& event) { // NOLINT(readability-fun
|
||||
}
|
||||
|
||||
// M: toggle mini mapa
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_M && static_cast<int>(event.key.repeat) == 0) {
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == kc->key("EDITOR", "minimap") && static_cast<int>(event.key.repeat) == 0) {
|
||||
toggleMiniMap();
|
||||
return;
|
||||
}
|
||||
|
||||
// 8: alternar entre draw y collision
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_8 && static_cast<int>(event.key.repeat) == 0) {
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == kc->key("EDITOR", "collision") && static_cast<int>(event.key.repeat) == 0) {
|
||||
setEditingCollision(!editing_collision_);
|
||||
return;
|
||||
}
|
||||
@@ -492,21 +495,15 @@ void MapEditor::handleEvent(const SDL_Event& event) { // NOLINT(readability-fun
|
||||
// Cursores: navegar a habitación adyacente
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && static_cast<int>(event.key.repeat) == 0) {
|
||||
std::string direction;
|
||||
switch (event.key.key) {
|
||||
case SDLK_UP:
|
||||
direction = "UP";
|
||||
break;
|
||||
case SDLK_DOWN:
|
||||
direction = "DOWN";
|
||||
break;
|
||||
case SDLK_LEFT:
|
||||
direction = "LEFT";
|
||||
break;
|
||||
case SDLK_RIGHT:
|
||||
direction = "RIGHT";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
const auto NAV_KEY = event.key.key;
|
||||
if (NAV_KEY == kc->key("EDITOR", "nav_up")) {
|
||||
direction = "UP";
|
||||
} else if (NAV_KEY == kc->key("EDITOR", "nav_down")) {
|
||||
direction = "DOWN";
|
||||
} else if (NAV_KEY == kc->key("EDITOR", "nav_left")) {
|
||||
direction = "LEFT";
|
||||
} else if (NAV_KEY == kc->key("EDITOR", "nav_right")) {
|
||||
direction = "RIGHT";
|
||||
}
|
||||
if (!direction.empty() && GameControl::get_adjacent_room) {
|
||||
std::string adjacent = GameControl::get_adjacent_room(direction);
|
||||
@@ -523,7 +520,7 @@ void MapEditor::handleEvent(const SDL_Event& event) { // NOLINT(readability-fun
|
||||
}
|
||||
|
||||
// T: abrir TilePicker (el cierre con T también se gestiona arriba, antes de delegar al picker)
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_T && static_cast<int>(event.key.repeat) == 0) {
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == kc->key("EDITOR", "tile_picker") && static_cast<int>(event.key.repeat) == 0) {
|
||||
// Deseleccionar entidades
|
||||
selection_.clear();
|
||||
|
||||
|
||||
@@ -10,9 +10,10 @@
|
||||
#include <queue> // Para queue (BFS)
|
||||
#include <set> // Para set
|
||||
|
||||
#include "core/input/key_config.hpp" // Para KeyConfig
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
#include "core/rendering/screenshot.hpp" // Para Screenshot::save
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
#include "core/rendering/text.hpp" // Para Text (números de room)
|
||||
#include "core/resources/resource_cache.hpp" // Para Resource::Cache
|
||||
#include "game/gameplay/room.hpp" // Para Room::Data
|
||||
@@ -365,13 +366,15 @@ void MiniMap::render(const std::string& current_room) {
|
||||
// Maneja eventos del minimapa (drag para explorar, click para navegar)
|
||||
void MiniMap::handleEvent(const SDL_Event& event, const std::string& current_room) {
|
||||
// Toggle de números de room
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_N && static_cast<int>(event.key.repeat) == 0) {
|
||||
const auto* kc = KeyConfig::get();
|
||||
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == kc->key("MINIMAP", "numbers") && static_cast<int>(event.key.repeat) == 0) {
|
||||
show_numbers_ = !show_numbers_;
|
||||
return;
|
||||
}
|
||||
|
||||
// Captura del minimapa
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_S && static_cast<int>(event.key.repeat) == 0) {
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == kc->key("MINIMAP", "capture") && static_cast<int>(event.key.repeat) == 0) {
|
||||
if (map_surface_) {
|
||||
// Renderizar números sobre map_surface_ si están activos
|
||||
if (show_numbers_) {
|
||||
|
||||
@@ -103,10 +103,10 @@ class MiniMap {
|
||||
static constexpr int PADDING = 4; // Padding alrededor del minimapa
|
||||
|
||||
// Colores del minimapa (índices de paleta)
|
||||
Uint8 bg_color_{2}; // Fondo general (configurable)
|
||||
Uint8 conn_color_{14}; // Líneas de conexión (configurable)
|
||||
static constexpr Uint8 COLOR_ROOM_BORDER = 0; // Borde de cada miniroom
|
||||
static constexpr Uint8 COLOR_SHADOW = 1; // Sombra de cada miniroom
|
||||
Uint8 bg_color_{2}; // Fondo general (configurable)
|
||||
Uint8 conn_color_{14}; // Líneas de conexión (configurable)
|
||||
static constexpr Uint8 COLOR_ROOM_BORDER = 0; // Borde de cada miniroom
|
||||
static constexpr Uint8 COLOR_SHADOW = 1; // Sombra de cada miniroom
|
||||
static constexpr Uint8 COLOR_NUMBER_TEXT = 15; // Texto de números (blanco)
|
||||
static constexpr Uint8 COLOR_NUMBER_SHADOW = 1; // Sombra de números (oscuro)
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "core/audio/audio.hpp" // Para Audio
|
||||
#include "core/input/global_inputs.hpp" // Para check
|
||||
#include "core/input/input.hpp" // Para Input, InputAction, Input::DO_NOT_ALLOW_REPEAT
|
||||
#include "core/input/key_config.hpp" // Para KeyConfig
|
||||
#include "core/locale/locale.hpp" // Para Locale
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
@@ -207,7 +208,7 @@ void Game::handleEvents() {
|
||||
|
||||
if (!Console::get()->isActive()) {
|
||||
// Tecla 9: toggle editor (funciona tanto dentro como fuera del editor)
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_9 && static_cast<int>(event.key.repeat) == 0) {
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == KeyConfig::get()->key("EDITOR", "toggle") && static_cast<int>(event.key.repeat) == 0) {
|
||||
if (MapEditor::get()->isActive()) {
|
||||
GameControl::exit_editor();
|
||||
Notifier::get()->show({Locale::get()->get("game.editor_disabled")});
|
||||
@@ -215,7 +216,7 @@ void Game::handleEvents() {
|
||||
GameControl::enter_editor();
|
||||
Notifier::get()->show({Locale::get()->get("game.editor_enabled")});
|
||||
}
|
||||
} else if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_G && static_cast<int>(event.key.repeat) == 0 && MapEditor::get()->isActive()) {
|
||||
} else if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == KeyConfig::get()->key("EDITOR", "grid") && static_cast<int>(event.key.repeat) == 0 && MapEditor::get()->isActive()) {
|
||||
MapEditor::get()->showGrid(!MapEditor::get()->isGridEnabled());
|
||||
} else if (MapEditor::get()->isActive()) {
|
||||
MapEditor::get()->handleEvent(event);
|
||||
@@ -640,59 +641,40 @@ void Game::renderDebugInfo() {
|
||||
// Comprueba los eventos
|
||||
void Game::handleDebugEvents(const SDL_Event& event) {
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && static_cast<int>(event.key.repeat) == 0) {
|
||||
switch (event.key.key) {
|
||||
case SDLK_R:
|
||||
Resource::Cache::get()->reload();
|
||||
break;
|
||||
const auto KEY = event.key.key;
|
||||
const auto* kc = KeyConfig::get();
|
||||
|
||||
case SDLK_W:
|
||||
changeRoom(room_->getRoom(Room::Border::TOP));
|
||||
break;
|
||||
|
||||
case SDLK_A:
|
||||
changeRoom(room_->getRoom(Room::Border::LEFT));
|
||||
break;
|
||||
|
||||
case SDLK_S:
|
||||
changeRoom(room_->getRoom(Room::Border::BOTTOM));
|
||||
break;
|
||||
|
||||
case SDLK_D:
|
||||
changeRoom(room_->getRoom(Room::Border::RIGHT));
|
||||
break;
|
||||
|
||||
case SDLK_1:
|
||||
toggleCheat(Options::cheats.infinite_lives, Locale::get()->get("game.cheat_infinite_lives"));
|
||||
break;
|
||||
|
||||
case SDLK_2:
|
||||
toggleCheat(Options::cheats.invincible, Locale::get()->get("game.cheat_invincible"));
|
||||
break;
|
||||
|
||||
case SDLK_7:
|
||||
Notifier::get()->show({Locale::get()->get("achievements.header"), Locale::get()->get("achievements.c11")}, Notifier::Style::CHEEVO, -1, false, "F7");
|
||||
break;
|
||||
|
||||
case SDLK_0: {
|
||||
const bool ENTERING_DEBUG = !Debug::get()->isEnabled();
|
||||
if (ENTERING_DEBUG) {
|
||||
invincible_before_debug_ = (Options::cheats.invincible == Options::Cheat::State::ENABLED);
|
||||
}
|
||||
Debug::get()->toggleEnabled();
|
||||
Notifier::get()->show({Debug::get()->isEnabled() ? Locale::get()->get("game.debug_enabled") : Locale::get()->get("game.debug_disabled")});
|
||||
room_->redrawMap();
|
||||
if (Debug::get()->isEnabled()) {
|
||||
Options::cheats.invincible = Options::Cheat::State::ENABLED;
|
||||
} else {
|
||||
Options::cheats.invincible = invincible_before_debug_ ? Options::Cheat::State::ENABLED : Options::Cheat::State::DISABLED;
|
||||
}
|
||||
scoreboard_data_->music = !Debug::get()->isEnabled();
|
||||
scoreboard_data_->music ? Audio::get()->resumeMusic() : Audio::get()->pauseMusic();
|
||||
break;
|
||||
if (KEY == kc->key("DEBUG", "reload")) {
|
||||
Resource::Cache::get()->reload();
|
||||
} else if (KEY == kc->key("DEBUG", "nav_up")) {
|
||||
changeRoom(room_->getRoom(Room::Border::TOP));
|
||||
} else if (KEY == kc->key("DEBUG", "nav_left")) {
|
||||
changeRoom(room_->getRoom(Room::Border::LEFT));
|
||||
} else if (KEY == kc->key("DEBUG", "nav_down")) {
|
||||
changeRoom(room_->getRoom(Room::Border::BOTTOM));
|
||||
} else if (KEY == kc->key("DEBUG", "nav_right")) {
|
||||
changeRoom(room_->getRoom(Room::Border::RIGHT));
|
||||
} else if (KEY == kc->key("DEBUG", "infinite_lives")) {
|
||||
toggleCheat(Options::cheats.infinite_lives, Locale::get()->get("game.cheat_infinite_lives"));
|
||||
} else if (KEY == kc->key("DEBUG", "invincibility")) {
|
||||
toggleCheat(Options::cheats.invincible, Locale::get()->get("game.cheat_invincible"));
|
||||
} else if (KEY == kc->key("DEBUG", "test_cheevo")) {
|
||||
Notifier::get()->show({Locale::get()->get("achievements.header"), Locale::get()->get("achievements.c11")}, Notifier::Style::CHEEVO, -1, false, "F7");
|
||||
} else if (KEY == kc->key("DEBUG", "debug_mode")) {
|
||||
const bool ENTERING_DEBUG = !Debug::get()->isEnabled();
|
||||
if (ENTERING_DEBUG) {
|
||||
invincible_before_debug_ = (Options::cheats.invincible == Options::Cheat::State::ENABLED);
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
Debug::get()->toggleEnabled();
|
||||
Notifier::get()->show({Debug::get()->isEnabled() ? Locale::get()->get("game.debug_enabled") : Locale::get()->get("game.debug_disabled")});
|
||||
room_->redrawMap();
|
||||
if (Debug::get()->isEnabled()) {
|
||||
Options::cheats.invincible = Options::Cheat::State::ENABLED;
|
||||
} else {
|
||||
Options::cheats.invincible = invincible_before_debug_ ? Options::Cheat::State::ENABLED : Options::Cheat::State::DISABLED;
|
||||
}
|
||||
scoreboard_data_->music = !Debug::get()->isEnabled();
|
||||
scoreboard_data_->music ? Audio::get()->resumeMusic() : Audio::get()->pauseMusic();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ static auto parseTokens(const std::string& input) -> std::vector<std::string> {
|
||||
// Calcula la altura total de la consola para N líneas de mensaje (+ 1 línea de input)
|
||||
static auto calcTargetHeight(int num_msg_lines) -> float {
|
||||
constexpr int PADDING_IN_V = Console::TEXT_SIZE / 2;
|
||||
return static_cast<float>((Console::TEXT_SIZE * (num_msg_lines + 1)) + (PADDING_IN_V * 2));
|
||||
return static_cast<float>((Console::LINE_HEIGHT * (num_msg_lines + 1)) + (PADDING_IN_V * 2));
|
||||
}
|
||||
|
||||
// Divide text en líneas respetando los \n existentes y haciendo word-wrap por ancho en píxeles
|
||||
@@ -173,7 +173,7 @@ void Console::redrawText() {
|
||||
const int VISIBLE = std::min(remaining, static_cast<int>(line.size()));
|
||||
text_->writeColored(PADDING_IN_H, y_pos, line.substr(0, VISIBLE), MSG_COLOR);
|
||||
remaining -= VISIBLE;
|
||||
y_pos += TEXT_SIZE;
|
||||
y_pos += LINE_HEIGHT;
|
||||
}
|
||||
|
||||
// Línea de input (siempre la última): prompt en PROMPT_COLOR, comando + cursor en COMMAND_COLOR
|
||||
|
||||
@@ -29,7 +29,8 @@ class Console {
|
||||
void handleEvent(const SDL_Event& event);
|
||||
|
||||
// Constantes públicas
|
||||
static constexpr int TEXT_SIZE = 6; // Tamaño de carácter del font de la consola
|
||||
static constexpr int TEXT_SIZE = 6; // Tamaño de carácter del font de la consola
|
||||
static constexpr int LINE_HEIGHT = 7; // TEXT_SIZE + 1px de interlineado
|
||||
|
||||
// Consultas
|
||||
auto isActive() -> bool; // true si RISING, ACTIVE o VANISHING
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "core/audio/audio.hpp" // Para Audio
|
||||
#include "core/input/key_config.hpp" // Para KeyConfig
|
||||
#include "core/locale/locale.hpp" // Para Locale
|
||||
#include "core/rendering/render_info.hpp" // Para RenderInfo
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
@@ -1092,7 +1093,9 @@ void CommandRegistry::registerHandlers() { // NOLINT(readability-function-cogni
|
||||
|
||||
// HELP: lista de comandos visibles en el scope activo
|
||||
dynamic_providers_["HELP"] = [this]() -> std::vector<std::string> {
|
||||
return getVisibleKeywords();
|
||||
auto kws = getVisibleKeywords();
|
||||
kws.insert(kws.begin(), "KEYS");
|
||||
return kws;
|
||||
};
|
||||
dynamic_providers_["SET ITEMCOLOR1"] = color_provider;
|
||||
dynamic_providers_["SET ITEMCOLOR2"] = color_provider;
|
||||
@@ -1237,6 +1240,10 @@ void CommandRegistry::load(const std::string& yaml_path) { // NOLINT(readabilit
|
||||
// Registrar el handler de HELP (captura this)
|
||||
handlers_["cmd_help"] = [this](const std::vector<std::string>& 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 <command>: mostrar ayuda detallada de un comando
|
||||
const auto* cmd = findCommand(args[0]);
|
||||
if (cmd != nullptr) {
|
||||
@@ -1268,6 +1275,44 @@ void CommandRegistry::load(const std::string& yaml_path) { // NOLINT(readabilit
|
||||
completions_map_[path] = opts;
|
||||
}
|
||||
}
|
||||
|
||||
// Proveedor dinámico para HELP KEYS <scope> (usa KeyConfig)
|
||||
dynamic_providers_["HELP KEYS"] = []() -> std::vector<std::string> {
|
||||
std::vector<std::string> names;
|
||||
if (KeyConfig::get() != nullptr) {
|
||||
for (const auto& scope : KeyConfig::get()->getScopes()) {
|
||||
names.push_back(scope.name);
|
||||
}
|
||||
}
|
||||
return names;
|
||||
};
|
||||
}
|
||||
|
||||
auto CommandRegistry::generateKeysHelp(const std::string& scope_filter) -> std::string {
|
||||
if (KeyConfig::get() == nullptr) { return "KeyConfig not loaded"; }
|
||||
|
||||
// Sin argumento: mostrar solo GLOBAL
|
||||
const std::string FILTER = scope_filter.empty() ? "GLOBAL" : scope_filter;
|
||||
const auto* scope = KeyConfig::get()->getScope(FILTER);
|
||||
|
||||
if (scope == nullptr) {
|
||||
std::string filter_lower = FILTER;
|
||||
std::ranges::transform(filter_lower, filter_lower.begin(), ::tolower);
|
||||
return "Unknown scope: " + filter_lower;
|
||||
}
|
||||
|
||||
// Cabecera del scope
|
||||
std::string name_lower = scope->name;
|
||||
std::ranges::transform(name_lower, name_lower.begin(), ::tolower);
|
||||
std::string result = '[' + name_lower + "]\n";
|
||||
|
||||
// Una tecla por línea: key = desc
|
||||
for (size_t i = 0; i < scope->entries.size(); ++i) {
|
||||
const auto& entry = scope->entries[i];
|
||||
result += entry.display_key + " = " + entry.desc;
|
||||
if (i + 1 < scope->entries.size()) { result += '\n'; }
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
auto CommandRegistry::findCommand(const std::string& keyword) const -> const CommandDef* {
|
||||
@@ -1346,7 +1391,6 @@ auto CommandRegistry::generateConsoleHelp() const -> std::string { // NOLINT(re
|
||||
|
||||
if (active_scope_ == "editor" && !editor_cmds.empty()) {
|
||||
result += "Editor:\n" + editor_cmds + "\n";
|
||||
result += "keys: 9=editor g=grid 8=collision e=eraser m=map\n";
|
||||
}
|
||||
|
||||
if (!debug_cmds.empty()) {
|
||||
|
||||
@@ -58,4 +58,5 @@ class CommandRegistry {
|
||||
|
||||
void registerHandlers();
|
||||
[[nodiscard]] auto isCommandVisible(const CommandDef& cmd) const -> bool;
|
||||
[[nodiscard]] static auto generateKeysHelp(const std::string& scope_filter) -> std::string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user