Console ara llig els comandos desde un fitxer extern
This commit is contained in:
@@ -98,6 +98,7 @@ set(APP_SOURCES
|
||||
|
||||
# Game - UI
|
||||
source/game/ui/console.cpp
|
||||
source/game/ui/console_commands.cpp
|
||||
source/game/ui/notifier.cpp
|
||||
|
||||
# Utils
|
||||
|
||||
@@ -117,6 +117,11 @@ assets:
|
||||
required: false
|
||||
absolute: true
|
||||
|
||||
# CONSOLE
|
||||
console:
|
||||
- type: DATA
|
||||
path: ${PREFIX}/data/console/commands.yaml
|
||||
|
||||
# ROOMS
|
||||
rooms:
|
||||
- type: ROOM
|
||||
|
||||
229
data/console/commands.yaml
Normal file
229
data/console/commands.yaml
Normal file
@@ -0,0 +1,229 @@
|
||||
# JailDoctor's Dilemma - Console Commands
|
||||
# Metadata for the in-game console command system.
|
||||
# Execution logic stays in C++; this file defines metadata only.
|
||||
#
|
||||
# Fields:
|
||||
# keyword - Command name (uppercase)
|
||||
# handler - C++ handler function identifier
|
||||
# description - Short description for help output
|
||||
# usage - Full usage string for terminal help
|
||||
# instant - (optional) Skip typewriter effect (default: false)
|
||||
# hidden - (optional) Hide from TAB completion (default: false)
|
||||
# debug_only - (optional) Only available in debug builds (default: false)
|
||||
# help_hidden - (optional) Don't show in help output (default: false)
|
||||
# dynamic_completions - (optional) Completions generated at runtime (default: false)
|
||||
# completions - (optional) Static TAB completion tree
|
||||
# debug_extras - (optional) Overrides applied in debug builds
|
||||
|
||||
categories:
|
||||
- name: VIDEO
|
||||
commands:
|
||||
- keyword: SS
|
||||
handler: cmd_ss
|
||||
description: Supersampling
|
||||
usage: "SS [ON|OFF|SIZE|UPSCALE [NEAREST|LINEAR]|DOWNSCALE [BILINEAR|LANCZOS2|LANCZOS3]]"
|
||||
completions:
|
||||
SS: [ON, OFF, SIZE, UPSCALE, DOWNSCALE]
|
||||
SS UPSCALE: [NEAREST, LINEAR]
|
||||
SS DOWNSCALE: [BILINEAR, LANCZOS2, LANCZOS3]
|
||||
|
||||
- keyword: SHADER
|
||||
handler: cmd_shader
|
||||
description: "Toggle/select shader (F4)"
|
||||
usage: "SHADER [ON|OFF|NEXT [PRESET]|POSTFX|CRTPI]"
|
||||
completions:
|
||||
SHADER: [ON, OFF, NEXT, POSTFX, CRTPI]
|
||||
SHADER NEXT: [PRESET]
|
||||
|
||||
- keyword: BORDER
|
||||
handler: cmd_border
|
||||
description: "Decorative border (B)"
|
||||
usage: "BORDER [ON|OFF]"
|
||||
completions:
|
||||
BORDER: [ON, OFF]
|
||||
|
||||
- keyword: FULLSCREEN
|
||||
handler: cmd_fullscreen
|
||||
description: "Fullscreen mode (F3)"
|
||||
usage: "FULLSCREEN [ON|OFF]"
|
||||
completions:
|
||||
FULLSCREEN: [ON, OFF]
|
||||
|
||||
- keyword: ZOOM
|
||||
handler: cmd_zoom
|
||||
description: "Window zoom (F1/F2)"
|
||||
usage: "ZOOM [UP|DOWN|<1-N>]"
|
||||
completions:
|
||||
ZOOM: [UP, DOWN]
|
||||
|
||||
- keyword: INTSCALE
|
||||
handler: cmd_intscale
|
||||
description: "Integer scaling (F7)"
|
||||
usage: "INTSCALE [ON|OFF]"
|
||||
completions:
|
||||
INTSCALE: [ON, OFF]
|
||||
|
||||
- keyword: VSYNC
|
||||
handler: cmd_vsync
|
||||
description: "Vertical sync"
|
||||
usage: "VSYNC [ON|OFF]"
|
||||
completions:
|
||||
VSYNC: [ON, OFF]
|
||||
|
||||
- keyword: DRIVER
|
||||
handler: cmd_driver
|
||||
description: "GPU driver (restart to apply)"
|
||||
usage: "DRIVER [LIST|AUTO|NONE|<name>]"
|
||||
completions:
|
||||
DRIVER: [LIST, AUTO, NONE]
|
||||
|
||||
- keyword: PALETTE
|
||||
handler: cmd_palette
|
||||
description: "Color palette (F5/F6)"
|
||||
usage: "PALETTE [NEXT|PREV|<name>]"
|
||||
dynamic_completions: true
|
||||
|
||||
- name: AUDIO
|
||||
commands:
|
||||
- keyword: AUDIO
|
||||
handler: cmd_audio
|
||||
description: Audio master
|
||||
usage: "AUDIO [ON|OFF|VOL <0-100>]"
|
||||
completions:
|
||||
AUDIO: [ON, OFF, VOL]
|
||||
|
||||
- keyword: MUSIC
|
||||
handler: cmd_music
|
||||
description: Music volume
|
||||
usage: "MUSIC [ON|OFF|VOL <0-100>]"
|
||||
completions:
|
||||
MUSIC: [ON, OFF, VOL]
|
||||
|
||||
- keyword: SOUND
|
||||
handler: cmd_sound
|
||||
description: Sound volume
|
||||
usage: "SOUND [ON|OFF|VOL <0-100>]"
|
||||
completions:
|
||||
SOUND: [ON, OFF, VOL]
|
||||
|
||||
- name: GAME
|
||||
commands:
|
||||
- keyword: SET
|
||||
handler: cmd_set
|
||||
description: "Change player skin"
|
||||
usage: "SET PLAYER SKIN <1|2>"
|
||||
completions:
|
||||
SET: [PLAYER]
|
||||
SET PLAYER: [SKIN]
|
||||
debug_extras:
|
||||
description: "Set player/debug options"
|
||||
usage: "SET PLAYER SKIN <1|2> | SET INITIAL [ROOM|POS|SCENE] | SET ITEMS <0-200>"
|
||||
completions:
|
||||
SET: [PLAYER, INITIAL, ITEMS]
|
||||
SET PLAYER: [SKIN]
|
||||
SET INITIAL: [ROOM, POS, SCENE]
|
||||
SET INITIAL SCENE: [LOGO, LOADING, TITLE, CREDITS, GAME, ENDING, ENDING2]
|
||||
|
||||
- keyword: RESTART
|
||||
handler: cmd_restart
|
||||
description: Restart from the beginning
|
||||
usage: RESTART
|
||||
instant: true
|
||||
|
||||
- keyword: KIOSK
|
||||
handler: cmd_kiosk
|
||||
description: Enable kiosk mode
|
||||
usage: "KIOSK [ON]"
|
||||
completions:
|
||||
KIOSK: [ON]
|
||||
|
||||
- keyword: EXIT
|
||||
handler: cmd_exit
|
||||
description: Quit application
|
||||
usage: EXIT
|
||||
instant: true
|
||||
|
||||
- keyword: QUIT
|
||||
handler: cmd_quit
|
||||
description: Quit application
|
||||
usage: QUIT
|
||||
instant: true
|
||||
help_hidden: true
|
||||
|
||||
- name: INFO
|
||||
commands:
|
||||
- keyword: SHOW
|
||||
handler: cmd_show
|
||||
description: Show info overlay
|
||||
usage: "SHOW [INFO]"
|
||||
completions:
|
||||
SHOW: [INFO]
|
||||
debug_extras:
|
||||
description: "Show overlay/test notification"
|
||||
usage: "SHOW [INFO|NOTIFICATION|CHEEVO]"
|
||||
completions:
|
||||
SHOW: [INFO, NOTIFICATION, CHEEVO]
|
||||
|
||||
- keyword: HIDE
|
||||
handler: cmd_hide
|
||||
description: Hide info overlay
|
||||
usage: "HIDE [INFO]"
|
||||
completions:
|
||||
HIDE: [INFO]
|
||||
|
||||
- keyword: SIZE
|
||||
handler: cmd_size
|
||||
description: Window size in pixels
|
||||
usage: SIZE
|
||||
|
||||
- keyword: HELP
|
||||
handler: cmd_help
|
||||
description: "Show this help"
|
||||
usage: "HELP / ?"
|
||||
|
||||
- keyword: "?"
|
||||
handler: cmd_help
|
||||
help_hidden: true
|
||||
|
||||
- name: DEBUG
|
||||
debug_only: true
|
||||
commands:
|
||||
- keyword: DEBUG
|
||||
handler: cmd_debug
|
||||
description: "Toggle debug overlay (F12)"
|
||||
usage: "DEBUG [ON|OFF]"
|
||||
completions:
|
||||
DEBUG: [ON, OFF]
|
||||
|
||||
- keyword: ROOM
|
||||
handler: cmd_room
|
||||
description: "Change to room number (GAME only)"
|
||||
usage: "ROOM <1-60>|NEXT|PREV"
|
||||
completions:
|
||||
ROOM: [NEXT, PREV]
|
||||
|
||||
- keyword: SCENE
|
||||
handler: cmd_scene
|
||||
description: Change scene
|
||||
usage: "SCENE [LOGO|LOADING|TITLE|CREDITS|GAME|ENDING|ENDING2|RESTART]"
|
||||
completions:
|
||||
SCENE: [LOGO, LOADING, TITLE, CREDITS, GAME, ENDING, ENDING2, RESTART]
|
||||
|
||||
- name: CHEATS
|
||||
commands:
|
||||
- keyword: CHEAT
|
||||
handler: cmd_cheat
|
||||
description: "Game cheats (GAME only)"
|
||||
usage: "CHEAT [INFINITE LIVES|INVINCIBILITY|OPEN THE JAIL|CLOSE THE JAIL]"
|
||||
hidden: true
|
||||
completions:
|
||||
CHEAT: [INFINITE, INVINCIBILITY, OPEN, CLOSE]
|
||||
CHEAT INFINITE: [LIVES]
|
||||
CHEAT INFINITE LIVES: [ON, OFF]
|
||||
CHEAT INVINCIBILITY: [ON, OFF]
|
||||
CHEAT OPEN: [THE]
|
||||
CHEAT OPEN THE: [JAIL]
|
||||
CHEAT CLOSE: [THE]
|
||||
CHEAT CLOSE THE: [JAIL]
|
||||
debug_extras:
|
||||
hidden: false
|
||||
@@ -2,43 +2,21 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm> // Para ranges::transform
|
||||
#include <cctype> // Para toupper
|
||||
#include <functional> // Para function
|
||||
#include <iostream> // Para std::cout
|
||||
#include <sstream> // Para std::istringstream
|
||||
#include <string> // Para string
|
||||
#include <vector> // 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/rendering/sprite/sprite.hpp" // Para Sprite
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
#include "core/rendering/text.hpp" // Para Text
|
||||
#include "core/resources/resource_cache.hpp" // Para Resource
|
||||
#include "game/game_control.hpp" // Para GameControl (refresh_player_color)
|
||||
#include "game/options.hpp" // Para Options
|
||||
#include "game/scene_manager.hpp" // Para SceneManager
|
||||
#include "game/ui/notifier.hpp" // Para Notifier
|
||||
|
||||
#ifdef _DEBUG
|
||||
#include "core/system/debug.hpp" // Para Debug
|
||||
#endif
|
||||
|
||||
// ── Sistema de comandos ────────────────────────────────────────────────────────
|
||||
|
||||
// Mapa de completions: {ruta_completa_en_mayúsculas, {opciones}}
|
||||
// Ej: {"CHEAT OPEN THE", {"JAIL"}}
|
||||
using CompletionMap = std::vector<std::pair<std::string_view, std::vector<std::string_view>>>;
|
||||
|
||||
struct ConsoleCommand {
|
||||
std::string_view keyword;
|
||||
std::function<std::string(const std::vector<std::string>& args)> execute;
|
||||
bool instant{false}; // Si true, muestra la respuesta sin efecto typewriter
|
||||
bool hidden{false}; // Si true, no aparece en el autocompletado (TAB)
|
||||
CompletionMap completions{}; // Árbol de sub-argumentos para TAB; cargado en el constructor de Console
|
||||
};
|
||||
// ── Helpers de texto ──────────────────────────────────────────────────────────
|
||||
|
||||
// Convierte la entrada a uppercase y la divide en tokens por espacios
|
||||
static auto parseTokens(const std::string& input) -> std::vector<std::string> {
|
||||
@@ -60,854 +38,6 @@ static auto parseTokens(const std::string& input) -> std::vector<std::string> {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
// Macro para comando de toggle booleano (evita repetición en ON/OFF)
|
||||
#define BOOL_TOGGLE_CMD(label, getter, toggle_fn) \
|
||||
[](const std::vector<std::string>& args) -> std::string { \
|
||||
if (args.empty()) { \
|
||||
(toggle_fn); \
|
||||
return label " " + std::string((getter) ? "ON" : "OFF"); \
|
||||
} \
|
||||
if (args[0] == "ON") { \
|
||||
if (getter) { return label " already ON"; } \
|
||||
(toggle_fn); \
|
||||
return label " ON"; \
|
||||
} \
|
||||
if (args[0] == "OFF") { \
|
||||
if (!(getter)) { return label " already OFF"; } \
|
||||
(toggle_fn); \
|
||||
return label " OFF"; \
|
||||
} \
|
||||
return "usage: " label " [on|off]"; \
|
||||
}
|
||||
|
||||
// Texto de ayuda común para HELP y ?
|
||||
static void printHelp() {
|
||||
std::cout << "=== JDD CONSOLE COMMANDS ===" << '\n';
|
||||
|
||||
std::cout << '\n';
|
||||
std::cout << "[VIDEO]" << '\n';
|
||||
std::cout << " SS [ON|OFF|SIZE] Supersampling" << '\n';
|
||||
std::cout << " SS UPSCALE [NEAREST|LINEAR] SS upscale filter" << '\n';
|
||||
std::cout << " SS DOWNSCALE [BILINEAR|LANCZOS2|LANCZOS3] SS downscale algorithm" << '\n';
|
||||
std::cout << " SHADER [ON|OFF|NEXT [PRESET]|POSTFX|CRTPI] Toggle/select shader (F4)" << '\n';
|
||||
std::cout << " BORDER [ON|OFF] Decorative border (B)" << '\n';
|
||||
std::cout << " FULLSCREEN [ON|OFF] Fullscreen mode (F3)" << '\n';
|
||||
std::cout << " ZOOM [UP|DOWN] Window zoom (F1/F2)" << '\n';
|
||||
std::cout << " INTSCALE [ON|OFF] Integer scaling (F7)" << '\n';
|
||||
std::cout << " VSYNC [ON|OFF] Vertical sync" << '\n';
|
||||
std::cout << " DRIVER [LIST|AUTO|NONE|<name>] GPU driver (restart to apply)" << '\n';
|
||||
std::cout << " PALETTE [NEXT|PREV] Color palette (F5/F6)" << '\n';
|
||||
|
||||
std::cout << '\n';
|
||||
std::cout << "[AUDIO]" << '\n';
|
||||
std::cout << " AUDIO [ON|OFF|VOL <0-100>] Audio master" << '\n';
|
||||
std::cout << " MUSIC [ON|OFF|VOL <0-100>] Music volume" << '\n';
|
||||
std::cout << " SOUND [ON|OFF|VOL <0-100>] Sound volume" << '\n';
|
||||
|
||||
std::cout << '\n';
|
||||
std::cout << "[GAME]" << '\n';
|
||||
std::cout << " SET PLAYER SKIN <1|2> Change player skin (GAME only)" << '\n';
|
||||
std::cout << " RESTART Restart from the beginning" << '\n';
|
||||
std::cout << " KIOSK [ON] Enable kiosk mode" << '\n';
|
||||
std::cout << " EXIT / QUIT Quit application" << '\n';
|
||||
|
||||
std::cout << '\n';
|
||||
std::cout << "[INFO]" << '\n';
|
||||
std::cout << " SHOW [INFO] Show info overlay" << '\n';
|
||||
std::cout << " HIDE [INFO] Hide info overlay" << '\n';
|
||||
std::cout << " SIZE Window size in pixels" << '\n';
|
||||
std::cout << " HELP / ? Show this help in terminal" << '\n';
|
||||
|
||||
#ifdef _DEBUG
|
||||
std::cout << '\n';
|
||||
std::cout << "[DEBUG]" << '\n';
|
||||
std::cout << " DEBUG Toggle debug overlay (F12)" << '\n';
|
||||
std::cout << " ROOM <1-60>|NEXT|PREV Change to room number (GAME only)" << '\n';
|
||||
std::cout << " SCENE [LOGO|LOADING|TITLE|CREDITS|GAME|ENDING|ENDING2|RESTART]" << '\n';
|
||||
std::cout << " SET INITIAL [ROOM|POS] Set initial room/position from current state (GAME only)" << '\n';
|
||||
std::cout << " SET INITIAL SCENE [<name>] Set initial debug scene (GAME|LOGO|TITLE|LOADING|CREDITS|ENDING|ENDING2)" << '\n';
|
||||
std::cout << " SET ITEMS <0-200> Set collected items count (GAME only)" << '\n';
|
||||
std::cout << " CHEAT INFINITE LIVES [ON|OFF] Infinite lives (GAME only)" << '\n';
|
||||
std::cout << " CHEAT INVINCIBILITY [ON|OFF] Invincibility (GAME only)" << '\n';
|
||||
std::cout << " CHEAT OPEN THE JAIL Open the jail (GAME only)" << '\n';
|
||||
std::cout << " CHEAT CLOSE THE JAIL Close the jail (GAME only)" << '\n';
|
||||
std::cout << " SHOW NOTIFICATION Test notification popup" << '\n';
|
||||
std::cout << " SHOW CHEEVO Test achievement notification" << '\n';
|
||||
#endif
|
||||
}
|
||||
|
||||
// En Release, los comandos de truco (CHEAT) son ocultos en el autocompletado
|
||||
#ifdef _DEBUG
|
||||
static constexpr bool CHEAT_HIDDEN = false;
|
||||
#else
|
||||
static constexpr bool CHEAT_HIDDEN = true;
|
||||
#endif
|
||||
|
||||
// Tabla de comandos disponibles
|
||||
static const std::vector<ConsoleCommand> COMMANDS = {
|
||||
// SS [ON|OFF|SIZE|UPSCALE [NEAREST|LINEAR]|DOWNSCALE [BILINEAR|LANCZOS2|LANCZOS3]] — Supersampling
|
||||
{.keyword = "SS", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
if (!Screen::get()->isHardwareAccelerated()) { return "No GPU acceleration"; }
|
||||
static const std::array<std::string_view, 3> 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<size_t>(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<size_t>(algo)]);
|
||||
}
|
||||
Screen::get()->setDownscaleAlgo(algo);
|
||||
return std::string("Downscale: ") + std::string(DOWNSCALE_NAMES[static_cast<size_t>(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]]";
|
||||
},
|
||||
.completions = {
|
||||
{"SS", {"ON", "OFF", "SIZE", "UPSCALE", "DOWNSCALE"}},
|
||||
{"SS UPSCALE", {"NEAREST", "LINEAR"}},
|
||||
{"SS DOWNSCALE", {"BILINEAR", "LANCZOS2", "LANCZOS3"}},
|
||||
}},
|
||||
|
||||
// SHADER [ON|OFF|NEXT [PRESET]|POSTFX|CRTPI] — Toggle/cicla/selecciona shader (F4 / Shift+F4)
|
||||
{.keyword = "SHADER", .execute = [](const std::vector<std::string>& 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") {
|
||||
// SHADER NEXT PRESET → cicla presets del shader activo
|
||||
if (args.size() >= 2 && args[1] == "PRESET") {
|
||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
if (Options::crtpi_presets.empty()) { return "No CrtPi presets available"; }
|
||||
Options::video.shader.current_crtpi_preset =
|
||||
(Options::video.shader.current_crtpi_preset + 1) %
|
||||
static_cast<int>(Options::crtpi_presets.size());
|
||||
Screen::get()->reloadCrtPi();
|
||||
return "CrtPi preset: " +
|
||||
Options::crtpi_presets[static_cast<size_t>(Options::video.shader.current_crtpi_preset)].name;
|
||||
}
|
||||
if (Options::postfx_presets.empty()) { return "No PostFX presets available"; }
|
||||
Options::video.shader.current_postfx_preset =
|
||||
(Options::video.shader.current_postfx_preset + 1) %
|
||||
static_cast<int>(Options::postfx_presets.size());
|
||||
Screen::get()->reloadPostFX();
|
||||
return "PostFX preset: " +
|
||||
Options::postfx_presets[static_cast<size_t>(Options::video.shader.current_postfx_preset)].name;
|
||||
}
|
||||
// SHADER NEXT → cicla entre tipos de shader (PostFX ↔ CrtPi)
|
||||
Screen::get()->nextShader();
|
||||
return std::string("Shader: ") +
|
||||
(Options::video.shader.current_shader == Rendering::ShaderType::CRTPI ? "CrtPi" : "PostFX");
|
||||
}
|
||||
return "usage: shader [on|off|next [preset]|postfx|crtpi]";
|
||||
},
|
||||
.completions = {
|
||||
{"SHADER", {"ON", "OFF", "NEXT", "POSTFX", "CRTPI"}},
|
||||
{"SHADER NEXT", {"PRESET"}},
|
||||
}},
|
||||
|
||||
// BORDER [ON|OFF] — Borde decorativo (B)
|
||||
{.keyword = "BORDER", .execute = BOOL_TOGGLE_CMD("Border", Options::video.border.enabled, Screen::get()->toggleBorder()), .completions = {{"BORDER", {"ON", "OFF"}}}},
|
||||
|
||||
// FULLSCREEN [ON|OFF [PLEASE]] — Pantalla completa (F3); OFF bloqueado en kiosk sin PLEASE
|
||||
{.keyword = "FULLSCREEN", .execute = [](const std::vector<std::string>& 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]";
|
||||
},
|
||||
.completions = {{"FULLSCREEN", {"ON", "OFF"}}}},
|
||||
|
||||
// ZOOM UP/DOWN — Zoom de ventana (F1/F2)
|
||||
{.keyword = "ZOOM", .execute = [](const std::vector<std::string>& 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);
|
||||
}
|
||||
// Zoom numérico directo
|
||||
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()) + ">]";
|
||||
},
|
||||
.completions = {{"ZOOM", {"UP", "DOWN"}}}},
|
||||
|
||||
// INTSCALE [ON|OFF] — Escalado entero (F7)
|
||||
{.keyword = "INTSCALE", .execute = [](const std::vector<std::string>& 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");
|
||||
},
|
||||
.completions = {{"INTSCALE", {"ON", "OFF"}}}},
|
||||
|
||||
// VSYNC [ON|OFF] — Sincronización vertical
|
||||
{.keyword = "VSYNC", .execute = BOOL_TOGGLE_CMD("VSync", Options::video.vertical_sync, Screen::get()->toggleVSync()), .completions = {{"VSYNC", {"ON", "OFF"}}}},
|
||||
|
||||
// DRIVER [LIST|AUTO|<nombre>] — Driver GPU (aplica en el próximo arranque)
|
||||
{.keyword = "DRIVER", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
// Sin argumentos: muestra el driver activo (permitido en kiosk)
|
||||
if (args.empty()) {
|
||||
const auto& driver = Screen::get()->getGPUDriver();
|
||||
return "GPU: " + (driver.empty() ? std::string("sdl") : driver);
|
||||
}
|
||||
// LIST: lista drivers disponibles marcando el activo con * (permitido en kiosk)
|
||||
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;
|
||||
}
|
||||
// Cambiar driver: bloqueado en kiosk salvo PLEASE
|
||||
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);
|
||||
// Validar que el nombre existe en la lista de drivers SDL
|
||||
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)";
|
||||
},
|
||||
.completions = {{"DRIVER", {"LIST", "AUTO", "NONE"}}}},
|
||||
|
||||
// PALETTE NEXT/PREV/<nombre> — Paleta de colores (F5/F6 o por nombre)
|
||||
{.keyword = "PALETTE", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
const auto palName = []() -> std::string {
|
||||
return Screen::get()->getPalettePrettyName();
|
||||
};
|
||||
if (args.empty()) { return "usage: palette [next|prev|<name>]"; }
|
||||
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();
|
||||
}},
|
||||
|
||||
#ifdef _DEBUG
|
||||
// DEBUG [ON|OFF] — Activa/desactiva el modo debug del juego (tecla 0); solo en escena GAME
|
||||
{.keyword = "DEBUG", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
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.empty() && args[0] == "ON") {
|
||||
if (ENABLED) { return "Debug mode already ON"; }
|
||||
GameControl::toggle_debug_mode();
|
||||
return "Debug mode ON";
|
||||
}
|
||||
if (!args.empty() && args[0] == "OFF") {
|
||||
if (!ENABLED) { return "Debug mode already OFF"; }
|
||||
GameControl::toggle_debug_mode();
|
||||
return "Debug mode OFF";
|
||||
}
|
||||
if (!args.empty()) { return "usage: debug [on|off]"; }
|
||||
GameControl::toggle_debug_mode();
|
||||
return std::string("Debug mode ") + (Debug::get()->isEnabled() ? "ON" : "OFF");
|
||||
},
|
||||
.completions = {{"DEBUG", {"ON", "OFF"}}}},
|
||||
|
||||
// ROOM <num>|NEXT|PREV — Cambia a la habitación indicada (1-60); solo en escena GAME
|
||||
{.keyword = "ROOM", .execute = [](const std::vector<std::string>& 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;
|
||||
},
|
||||
.completions = {{"ROOM", {"NEXT", "PREV"}}}},
|
||||
|
||||
#endif
|
||||
|
||||
// SHOW INFO — disponible en Release; SHOW NOTIFICATION / SHOW CHEEVO — solo en Debug
|
||||
{.keyword = "SHOW", .execute = [](const std::vector<std::string>& 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";
|
||||
},
|
||||
.completions = {
|
||||
#ifdef _DEBUG
|
||||
{"SHOW", {"INFO", "NOTIFICATION", "CHEEVO"}},
|
||||
#else
|
||||
{"SHOW", {"INFO"}},
|
||||
#endif
|
||||
}},
|
||||
|
||||
// HIDE INFO — disponible en Release
|
||||
{.keyword = "HIDE", .execute = [](const std::vector<std::string>& 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";
|
||||
},
|
||||
.completions = {{"HIDE", {"INFO"}}}},
|
||||
|
||||
// CHEAT <subcomando> — Trucos de juego; solo en escena GAME; no aparece en ayuda en builds Release
|
||||
{.keyword = "CHEAT", .execute = [](const std::vector<std::string>& 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<std::string> 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]";
|
||||
}
|
||||
if (GameControl::refresh_player_color) { GameControl::refresh_player_color(); }
|
||||
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]";
|
||||
}
|
||||
if (GameControl::refresh_player_color) { GameControl::refresh_player_color(); }
|
||||
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]";
|
||||
},
|
||||
.hidden = CHEAT_HIDDEN,
|
||||
.completions = {
|
||||
{"CHEAT", {"INFINITE", "INVINCIBILITY", "OPEN", "CLOSE"}},
|
||||
{"CHEAT INFINITE", {"LIVES"}},
|
||||
{"CHEAT INFINITE LIVES", {"ON", "OFF"}},
|
||||
{"CHEAT INVINCIBILITY", {"ON", "OFF"}},
|
||||
{"CHEAT OPEN", {"THE"}},
|
||||
{"CHEAT OPEN THE", {"JAIL"}},
|
||||
{"CHEAT CLOSE", {"THE"}},
|
||||
{"CHEAT CLOSE THE", {"JAIL"}},
|
||||
}},
|
||||
|
||||
// SET PLAYER SKIN <1|2> — Cambia la skin del jugador (disponible en todos los builds, GAME)
|
||||
// SET INITIAL [ROOM|POS] — Guarda habitación/posición actual como inicio (solo _DEBUG, GAME)
|
||||
// SET INITIAL SCENE [<name>] — Guarda escena como escena inicial de debug (solo _DEBUG)
|
||||
// SET ITEMS <0-200> — Fija el contador de items recogidos (solo _DEBUG, GAME)
|
||||
{.keyword = "SET", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
if (args.size() >= 3 && args[0] == "PLAYER" && args[1] == "SKIN") {
|
||||
if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; }
|
||||
int num = 0;
|
||||
try {
|
||||
num = std::stoi(args[2]);
|
||||
} catch (...) {}
|
||||
if (num < 1 || num > 2) { return "usage: set player skin <1|2>"; }
|
||||
if (!GameControl::change_player_skin) { return "Game not initialized"; }
|
||||
GameControl::change_player_skin(num);
|
||||
return "Player skin: " + std::to_string(num);
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
// SET INITIAL SCENE [<nombre>] — disponible desde cualquier escena
|
||||
if (args.size() >= 2 && args[0] == "INITIAL" && 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;
|
||||
}
|
||||
|
||||
if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; }
|
||||
|
||||
// SET ITEMS <0-200> — Fija el contador de items recogidos
|
||||
if (args[0] == "ITEMS") {
|
||||
if (args.size() < 2) { return "usage: set items <0-200>"; }
|
||||
int count = 0;
|
||||
try {
|
||||
count = std::stoi(args[1]);
|
||||
} catch (...) { return "usage: set 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);
|
||||
}
|
||||
|
||||
if (args.empty() || args[0] != "INITIAL") { return "usage: set initial [room|pos|scene] | set items <0-200> | set player skin <1|2>"; }
|
||||
|
||||
const bool DO_ROOM = args.size() == 1 || (args.size() >= 2 && args[1] == "ROOM");
|
||||
const bool DO_POS = args.size() == 1 || (args.size() >= 2 && args[1] == "POS");
|
||||
|
||||
if (!DO_ROOM && !DO_POS) { return "usage: set initial [room|pos|scene]"; }
|
||||
if (!GameControl::set_initial_room || !GameControl::set_initial_pos) { return "Game not initialized"; }
|
||||
|
||||
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;
|
||||
#else
|
||||
return "usage: set player skin <1|2>";
|
||||
#endif
|
||||
},
|
||||
.completions = {
|
||||
#ifdef _DEBUG
|
||||
{"SET", {"PLAYER", "INITIAL", "ITEMS"}},
|
||||
{"SET PLAYER", {"SKIN"}},
|
||||
{"SET INITIAL", {"ROOM", "POS", "SCENE"}},
|
||||
{"SET INITIAL SCENE", {"LOGO", "LOADING", "TITLE", "CREDITS", "GAME", "ENDING", "ENDING2"}},
|
||||
#else
|
||||
{"SET", {"PLAYER"}},
|
||||
{"SET PLAYER", {"SKIN"}},
|
||||
#endif
|
||||
}},
|
||||
|
||||
#ifdef _DEBUG
|
||||
// SCENE [LOGO|LOADING|TITLE|CREDITS|GAME|ENDING|ENDING2|RESTART] — Cambiar o reiniciar escena; solo en Debug
|
||||
{.keyword = "SCENE", .execute = [](const std::vector<std::string>& 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]"; }
|
||||
|
||||
// RESTART: reinicia la escena actual (funciona desde cualquier escena)
|
||||
if (args[0] == "RESTART") {
|
||||
SceneManager::scene_before_restart = SceneManager::current;
|
||||
SceneManager::current = SceneManager::Scene::RESTART_CURRENT;
|
||||
return "Restarting...";
|
||||
}
|
||||
|
||||
// Para el resto: si pedimos la escena que ya está activa → también reiniciar
|
||||
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];
|
||||
},
|
||||
.completions = {{"SCENE", {"LOGO", "LOADING", "TITLE", "CREDITS", "GAME", "ENDING", "ENDING2", "RESTART"}}}},
|
||||
#endif
|
||||
|
||||
// RESTART — Reiniciar desde el principio (equivale a SCENE LOGO)
|
||||
{.keyword = "RESTART", .execute = [](const std::vector<std::string>&) -> std::string {
|
||||
SceneManager::current = SceneManager::Scene::LOGO;
|
||||
Audio::get()->stopMusic();
|
||||
return "Restarting...";
|
||||
},
|
||||
.instant = true},
|
||||
|
||||
// KIOSK [ON|OFF PLEASE|PLEASE] — Modo kiosko
|
||||
{.keyword = "KIOSK", .execute = [](const std::vector<std::string>& 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]";
|
||||
},
|
||||
.completions = {{"KIOSK", {"ON"}}}},
|
||||
|
||||
// AUDIO [ON|OFF|VOL <0-100>] — Audio maestro (estado + volumen)
|
||||
{.keyword = "AUDIO", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
if (args.empty()) {
|
||||
const int VOL = static_cast<int>(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<float>(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]";
|
||||
},
|
||||
.completions = {{"AUDIO", {"ON", "OFF", "VOL"}}}},
|
||||
|
||||
// MUSIC [ON|OFF|VOL <0-100>] — Volumen e interruptor de música
|
||||
{.keyword = "MUSIC", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
if (args.empty()) {
|
||||
const int VOL = static_cast<int>(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<float>(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]";
|
||||
},
|
||||
.completions = {{"MUSIC", {"ON", "OFF", "VOL"}}}},
|
||||
|
||||
// SOUND [ON|OFF|VOL <0-100>] — Volumen e interruptor de efectos de sonido
|
||||
{.keyword = "SOUND", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
if (args.empty()) {
|
||||
const int VOL = static_cast<int>(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<float>(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]";
|
||||
},
|
||||
.completions = {{"SOUND", {"ON", "OFF", "VOL"}}}},
|
||||
|
||||
// EXIT / QUIT — Cerrar la aplicacion (bloqueado en kiosk)
|
||||
{.keyword = "EXIT", .execute = [](const std::vector<std::string>& 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...";
|
||||
},
|
||||
.instant = true},
|
||||
{.keyword = "QUIT", .execute = [](const std::vector<std::string>& 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...";
|
||||
},
|
||||
.instant = true},
|
||||
|
||||
// SIZE — Devuelve el tamaño actual de la ventana en píxeles
|
||||
{.keyword = "SIZE", .execute = [](const std::vector<std::string>&) -> 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);
|
||||
}},
|
||||
|
||||
// HELP / ? — Muestra ayuda en la terminal del sistema y lista de comandos en consola
|
||||
{.keyword = "HELP", .execute = [](const std::vector<std::string>&) -> std::string {
|
||||
printHelp();
|
||||
std::string result =
|
||||
"Commands:\n"
|
||||
"fullscreen, zoom, intscale, vsync, driver, palette, audio, music, sound, set, restart, kiosk, exit, quit, show, hide, size, help\n";
|
||||
#ifdef _DEBUG
|
||||
result +=
|
||||
"\nDebug commands:\n"
|
||||
"debug, room, scene, cheat\n";
|
||||
#endif
|
||||
result += "-- more info on the terminal";
|
||||
return result;
|
||||
}},
|
||||
{.keyword = "?", .execute = [](const std::vector<std::string>&) -> std::string {
|
||||
printHelp();
|
||||
std::string result =
|
||||
"Commands:\n"
|
||||
"fullscreen, zoom, intscale, vsync, driver, palette, audio, music, sound, set, restart, kiosk, exit, quit, show, hide, size, help\n";
|
||||
#ifdef _DEBUG
|
||||
result +=
|
||||
"\nDebug commands:\n"
|
||||
"debug, room, scene, cheat\n";
|
||||
#endif
|
||||
result += "-- more info on the terminal";
|
||||
return result;
|
||||
}},
|
||||
};
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
// Calcula la altura total de la consola para N líneas de mensaje (+ 1 línea de input)
|
||||
@@ -979,13 +109,8 @@ Console::Console(const std::string& font_name)
|
||||
target_height_ = height_;
|
||||
y_ = -height_;
|
||||
|
||||
// Construir mapa de autocompletado a partir de COMMANDS
|
||||
for (const auto& cmd : COMMANDS) {
|
||||
for (const auto& [path, opts] : cmd.completions) {
|
||||
auto& vec = tab_completions_[std::string(path)];
|
||||
for (const auto& opt : opts) { vec.emplace_back(opt); }
|
||||
}
|
||||
}
|
||||
// Cargar comandos desde YAML
|
||||
registry_.load("data/console/commands.yaml");
|
||||
|
||||
buildSurface();
|
||||
}
|
||||
@@ -1225,17 +350,16 @@ void Console::handleEvent(const SDL_Event& event) {
|
||||
|
||||
const size_t space_pos = upper.rfind(' ');
|
||||
if (space_pos == std::string::npos) {
|
||||
// Modo comando: ciclar keywords que empiecen por el prefijo
|
||||
for (const auto& cmd : COMMANDS) {
|
||||
if (cmd.hidden) { continue; }
|
||||
if (upper.empty() || cmd.keyword.starts_with(upper)) {
|
||||
tab_matches_.emplace_back(cmd.keyword);
|
||||
// Modo comando: ciclar keywords visibles que empiecen por el prefijo
|
||||
for (const auto& kw : registry_.getVisibleKeywords()) {
|
||||
if (upper.empty() || kw.starts_with(upper)) {
|
||||
tab_matches_.emplace_back(kw);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const std::string base_cmd = upper.substr(0, space_pos);
|
||||
const std::string sub_prefix = upper.substr(space_pos + 1);
|
||||
if (base_cmd == "PALETTE" && Screen::get() != nullptr) {
|
||||
if (registry_.hasDynamicCompletions(base_cmd) && base_cmd == "PALETTE" && Screen::get() != nullptr) {
|
||||
// NEXT/PREV primero, luego todos los nombres de paleta disponibles
|
||||
for (const auto* sv : {"NEXT", "PREV"}) {
|
||||
if (sub_prefix.empty() || std::string_view{sv}.starts_with(sub_prefix)) {
|
||||
@@ -1248,9 +372,9 @@ void Console::handleEvent(const SDL_Event& event) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const auto it = tab_completions_.find(base_cmd);
|
||||
if (it != tab_completions_.end()) {
|
||||
for (const auto& arg : it->second) {
|
||||
const auto* opts = registry_.getCompletions(base_cmd);
|
||||
if (opts != nullptr) {
|
||||
for (const auto& arg : *opts) {
|
||||
if (sub_prefix.empty() || std::string_view{arg}.starts_with(sub_prefix)) {
|
||||
tab_matches_.emplace_back(base_cmd + " " + arg);
|
||||
}
|
||||
@@ -1289,17 +413,13 @@ void Console::processCommand() {
|
||||
const std::string& cmd = TOKENS[0];
|
||||
const std::vector<std::string> ARGS(TOKENS.begin() + 1, TOKENS.end());
|
||||
std::string result;
|
||||
bool found = false;
|
||||
bool instant = false;
|
||||
for (const auto& command : COMMANDS) {
|
||||
if (command.keyword == cmd) {
|
||||
result = command.execute(ARGS);
|
||||
instant = command.instant;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
|
||||
const auto* def = registry_.findCommand(cmd);
|
||||
if (def != nullptr) {
|
||||
result = registry_.execute(cmd, ARGS);
|
||||
instant = def->instant;
|
||||
} else {
|
||||
std::string cmd_lower = cmd;
|
||||
std::ranges::transform(cmd_lower, cmd_lower.begin(), ::tolower);
|
||||
result = "Unknown: " + cmd_lower;
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
#include <functional> // Para function
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <unordered_map> // Para unordered_map (tab_completions_)
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "game/ui/console_commands.hpp" // Para CommandRegistry
|
||||
|
||||
class Surface;
|
||||
class Sprite;
|
||||
class Text;
|
||||
@@ -100,7 +101,9 @@ class Console {
|
||||
std::string saved_input_; // guarda input_line_ al empezar a navegar
|
||||
|
||||
// Estado de autocompletado (TAB)
|
||||
std::vector<std::string> tab_matches_; // Comandos que coinciden con el prefijo actual
|
||||
int tab_index_{-1}; // Índice actual en tab_matches_
|
||||
std::unordered_map<std::string, std::vector<std::string>> tab_completions_; // Mapa pre-calculado en constructor
|
||||
std::vector<std::string> tab_matches_; // Comandos que coinciden con el prefijo actual
|
||||
int tab_index_{-1}; // Índice actual en tab_matches_
|
||||
|
||||
// Registro de comandos (metadatos YAML + handlers C++)
|
||||
CommandRegistry registry_;
|
||||
};
|
||||
|
||||
936
source/game/ui/console_commands.cpp
Normal file
936
source/game/ui/console_commands.cpp
Normal file
@@ -0,0 +1,936 @@
|
||||
#include "game/ui/console_commands.hpp"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm> // Para ranges::transform, ranges::find
|
||||
#include <array> // Para array
|
||||
#include <iostream> // Para cout
|
||||
#include <sstream> // Para ostringstream
|
||||
#include <string> // Para string
|
||||
#include <vector> // 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
|
||||
|
||||
#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<void()>& toggle_fn,
|
||||
const std::vector<std::string>& 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<std::string>& args) -> std::string {
|
||||
if (!Screen::get()->isHardwareAccelerated()) { return "No GPU acceleration"; }
|
||||
static const std::array<std::string_view, 3> 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<size_t>(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<size_t>(algo)]);
|
||||
}
|
||||
Screen::get()->setDownscaleAlgo(algo);
|
||||
return std::string("Downscale: ") + std::string(DOWNSCALE_NAMES[static_cast<size_t>(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]]";
|
||||
}
|
||||
|
||||
// SHADER [ON|OFF|NEXT [PRESET]|POSTFX|CRTPI]
|
||||
static auto cmd_shader(const std::vector<std::string>& 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") {
|
||||
if (args.size() >= 2 && args[1] == "PRESET") {
|
||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
if (Options::crtpi_presets.empty()) { return "No CrtPi presets available"; }
|
||||
Options::video.shader.current_crtpi_preset =
|
||||
(Options::video.shader.current_crtpi_preset + 1) %
|
||||
static_cast<int>(Options::crtpi_presets.size());
|
||||
Screen::get()->reloadCrtPi();
|
||||
return "CrtPi preset: " +
|
||||
Options::crtpi_presets[static_cast<size_t>(Options::video.shader.current_crtpi_preset)].name;
|
||||
}
|
||||
if (Options::postfx_presets.empty()) { return "No PostFX presets available"; }
|
||||
Options::video.shader.current_postfx_preset =
|
||||
(Options::video.shader.current_postfx_preset + 1) %
|
||||
static_cast<int>(Options::postfx_presets.size());
|
||||
Screen::get()->reloadPostFX();
|
||||
return "PostFX preset: " +
|
||||
Options::postfx_presets[static_cast<size_t>(Options::video.shader.current_postfx_preset)].name;
|
||||
}
|
||||
Screen::get()->nextShader();
|
||||
return std::string("Shader: ") +
|
||||
(Options::video.shader.current_shader == Rendering::ShaderType::CRTPI ? "CrtPi" : "PostFX");
|
||||
}
|
||||
return "usage: shader [on|off|next [preset]|postfx|crtpi]";
|
||||
}
|
||||
|
||||
// BORDER [ON|OFF]
|
||||
static auto cmd_border(const std::vector<std::string>& 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<std::string>& 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/<num>
|
||||
static auto cmd_zoom(const std::vector<std::string>& 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<std::string>& 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<std::string>& args) -> std::string {
|
||||
return boolToggle("VSync", Options::video.vertical_sync, [] { Screen::get()->toggleVSync(); }, args);
|
||||
}
|
||||
|
||||
// DRIVER [LIST|AUTO|NONE|<name>]
|
||||
static auto cmd_driver(const std::vector<std::string>& 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/<name>
|
||||
static auto cmd_palette(const std::vector<std::string>& args) -> std::string {
|
||||
const auto palName = []() -> std::string {
|
||||
return Screen::get()->getPalettePrettyName();
|
||||
};
|
||||
if (args.empty()) { return "usage: palette [next|prev|<name>]"; }
|
||||
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<std::string>& args) -> std::string {
|
||||
if (args.empty()) {
|
||||
const int VOL = static_cast<int>(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<float>(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<std::string>& args) -> std::string {
|
||||
if (args.empty()) {
|
||||
const int VOL = static_cast<int>(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<float>(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<std::string>& args) -> std::string {
|
||||
if (args.empty()) {
|
||||
const int VOL = static_cast<int>(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<float>(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 [ON|OFF]
|
||||
static auto cmd_debug(const std::vector<std::string>& args) -> std::string {
|
||||
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.empty() && args[0] == "ON") {
|
||||
if (ENABLED) { return "Debug mode already ON"; }
|
||||
GameControl::toggle_debug_mode();
|
||||
return "Debug mode ON";
|
||||
}
|
||||
if (!args.empty() && args[0] == "OFF") {
|
||||
if (!ENABLED) { return "Debug mode already OFF"; }
|
||||
GameControl::toggle_debug_mode();
|
||||
return "Debug mode OFF";
|
||||
}
|
||||
if (!args.empty()) { return "usage: debug [on|off]"; }
|
||||
GameControl::toggle_debug_mode();
|
||||
return std::string("Debug mode ") + (Debug::get()->isEnabled() ? "ON" : "OFF");
|
||||
}
|
||||
|
||||
// ROOM <num>|NEXT|PREV
|
||||
static auto cmd_room(const std::vector<std::string>& 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;
|
||||
}
|
||||
|
||||
// SCENE [LOGO|LOADING|TITLE|CREDITS|GAME|ENDING|ENDING2|RESTART]
|
||||
static auto cmd_scene(const std::vector<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& 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<std::string> 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]";
|
||||
}
|
||||
if (GameControl::refresh_player_color) { GameControl::refresh_player_color(); }
|
||||
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]";
|
||||
}
|
||||
if (GameControl::refresh_player_color) { GameControl::refresh_player_color(); }
|
||||
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]";
|
||||
}
|
||||
|
||||
// SET PLAYER SKIN / SET INITIAL / SET ITEMS
|
||||
static auto cmd_set(const std::vector<std::string>& args) -> std::string {
|
||||
if (args.size() >= 3 && args[0] == "PLAYER" && args[1] == "SKIN") {
|
||||
if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; }
|
||||
int num = 0;
|
||||
try {
|
||||
num = std::stoi(args[2]);
|
||||
} catch (...) {}
|
||||
if (num < 1 || num > 2) { return "usage: set player skin <1|2>"; }
|
||||
if (!GameControl::change_player_skin) { return "Game not initialized"; }
|
||||
GameControl::change_player_skin(num);
|
||||
return "Player skin: " + std::to_string(num);
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
// SET INITIAL SCENE [<nombre>]
|
||||
if (args.size() >= 2 && args[0] == "INITIAL" && 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;
|
||||
}
|
||||
|
||||
if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; }
|
||||
|
||||
// SET ITEMS <0-200>
|
||||
if (!args.empty() && args[0] == "ITEMS") {
|
||||
if (args.size() < 2) { return "usage: set items <0-200>"; }
|
||||
int count = 0;
|
||||
try {
|
||||
count = std::stoi(args[1]);
|
||||
} catch (...) { return "usage: set 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);
|
||||
}
|
||||
|
||||
if (args.empty() || args[0] != "INITIAL") { return "usage: set initial [room|pos|scene] | set items <0-200> | set player skin <1|2>"; }
|
||||
|
||||
const bool DO_ROOM = args.size() == 1 || (args.size() >= 2 && args[1] == "ROOM");
|
||||
const bool DO_POS = args.size() == 1 || (args.size() >= 2 && args[1] == "POS");
|
||||
|
||||
if (!DO_ROOM && !DO_POS) { return "usage: set initial [room|pos|scene]"; }
|
||||
if (!GameControl::set_initial_room || !GameControl::set_initial_pos) { return "Game not initialized"; }
|
||||
|
||||
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;
|
||||
#else
|
||||
return "usage: set player skin <1|2>";
|
||||
#endif
|
||||
}
|
||||
|
||||
// RESTART
|
||||
static auto cmd_restart(const std::vector<std::string>&) -> std::string {
|
||||
SceneManager::current = SceneManager::Scene::LOGO;
|
||||
Audio::get()->stopMusic();
|
||||
return "Restarting...";
|
||||
}
|
||||
|
||||
// KIOSK [ON|OFF PLEASE|PLEASE]
|
||||
static auto cmd_kiosk(const std::vector<std::string>& 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<std::string>& 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>&) -> 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_set"] = cmd_set;
|
||||
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_room"] = cmd_room;
|
||||
handlers_["cmd_scene"] = cmd_scene;
|
||||
#endif
|
||||
// HELP se registra en load() como lambda que captura this
|
||||
}
|
||||
|
||||
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<std::string>();
|
||||
const bool cat_debug_only = cat_node.contains("debug_only") && cat_node["debug_only"].get_value<bool>();
|
||||
|
||||
if (!cat_node.contains("commands")) { continue; }
|
||||
|
||||
for (const auto& cmd_node : cat_node["commands"]) {
|
||||
CommandDef def;
|
||||
def.keyword = cmd_node["keyword"].get_value<std::string>();
|
||||
def.handler_id = cmd_node["handler"].get_value<std::string>();
|
||||
def.category = category;
|
||||
def.description = cmd_node.contains("description") ? cmd_node["description"].get_value<std::string>() : "";
|
||||
def.usage = cmd_node.contains("usage") ? cmd_node["usage"].get_value<std::string>() : def.keyword;
|
||||
def.instant = cmd_node.contains("instant") && cmd_node["instant"].get_value<bool>();
|
||||
def.hidden = cmd_node.contains("hidden") && cmd_node["hidden"].get_value<bool>();
|
||||
def.debug_only = cat_debug_only || (cmd_node.contains("debug_only") && cmd_node["debug_only"].get_value<bool>());
|
||||
def.help_hidden = cmd_node.contains("help_hidden") && cmd_node["help_hidden"].get_value<bool>();
|
||||
def.dynamic_completions = cmd_node.contains("dynamic_completions") && cmd_node["dynamic_completions"].get_value<bool>();
|
||||
|
||||
// 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::string>();
|
||||
std::vector<std::string> opts;
|
||||
for (const auto& opt : *it) {
|
||||
opts.push_back(opt.get_value<std::string>());
|
||||
}
|
||||
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<std::string>(); }
|
||||
if (extras.contains("usage")) { def.usage = extras["usage"].get_value<std::string>(); }
|
||||
if (extras.contains("hidden")) { def.hidden = extras["hidden"].get_value<bool>(); }
|
||||
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::string>();
|
||||
std::vector<std::string> opts;
|
||||
for (const auto& opt : *it) {
|
||||
opts.push_back(opt.get_value<std::string>());
|
||||
}
|
||||
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::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<std::string>& 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<int>(usage.size()));
|
||||
out << std::string(static_cast<size_t>(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 -> const std::vector<std::string>* {
|
||||
const auto it = completions_map_.find(path);
|
||||
if (it != completions_map_.end()) { return &it->second; }
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto CommandRegistry::getVisibleKeywords() const -> std::vector<std::string> {
|
||||
std::vector<std::string> result;
|
||||
for (const auto& cmd : commands_) {
|
||||
if (!cmd.hidden) {
|
||||
result.push_back(cmd.keyword);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
auto CommandRegistry::hasDynamicCompletions(const std::string& keyword) const -> bool {
|
||||
const auto* def = findCommand(keyword);
|
||||
return def != nullptr && def->dynamic_completions;
|
||||
}
|
||||
51
source/game/ui/console_commands.hpp
Normal file
51
source/game/ui/console_commands.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional> // Para function
|
||||
#include <string> // Para string
|
||||
#include <unordered_map> // Para unordered_map
|
||||
#include <vector> // Para vector
|
||||
|
||||
// Definición de un comando de consola (metadatos cargados desde YAML)
|
||||
struct CommandDef {
|
||||
std::string keyword;
|
||||
std::string handler_id;
|
||||
std::string category;
|
||||
std::string description;
|
||||
std::string usage;
|
||||
bool instant{false};
|
||||
bool hidden{false};
|
||||
bool debug_only{false};
|
||||
bool help_hidden{false};
|
||||
bool dynamic_completions{false};
|
||||
std::unordered_map<std::string, std::vector<std::string>> completions;
|
||||
};
|
||||
|
||||
// Tipo de función handler para comandos
|
||||
using CommandHandler = std::function<std::string(const std::vector<std::string>& args)>;
|
||||
|
||||
// Registro de comandos: une metadatos YAML con handlers C++
|
||||
class CommandRegistry {
|
||||
public:
|
||||
// Carga los metadatos de comandos desde un archivo YAML y registra los handlers
|
||||
void load(const std::string& yaml_path);
|
||||
|
||||
// Búsqueda y ejecución
|
||||
[[nodiscard]] auto findCommand(const std::string& keyword) const -> const CommandDef*;
|
||||
auto execute(const std::string& keyword, const std::vector<std::string>& args) const -> std::string;
|
||||
|
||||
// Generación de ayuda (auto-generada desde los metadatos)
|
||||
[[nodiscard]] auto generateTerminalHelp() const -> std::string;
|
||||
[[nodiscard]] auto generateConsoleHelp() const -> std::string;
|
||||
|
||||
// TAB completion
|
||||
[[nodiscard]] auto getCompletions(const std::string& path) const -> const std::vector<std::string>*;
|
||||
[[nodiscard]] auto getVisibleKeywords() const -> std::vector<std::string>;
|
||||
[[nodiscard]] auto hasDynamicCompletions(const std::string& keyword) const -> bool;
|
||||
|
||||
private:
|
||||
std::vector<CommandDef> commands_;
|
||||
std::unordered_map<std::string, CommandHandler> handlers_;
|
||||
std::unordered_map<std::string, std::vector<std::string>> completions_map_;
|
||||
|
||||
void registerHandlers();
|
||||
};
|
||||
Reference in New Issue
Block a user