19 Commits
v1.10 ... main

Author SHA1 Message Date
2ed7316948 afegit changelog.md 2026-03-31 07:56:09 +02:00
2228153d59 corregit makefile 2026-03-31 07:29:29 +02:00
7315032ff2 fix: si entraves a GAME amb la consola oberta, el jugador no tenia els inputs deshabilitats 2026-03-31 07:28:03 +02:00
3fc6795593 decrementada la responsabilitat d'Screen i afegit PaletteManager 2026-03-31 07:14:58 +02:00
16924cf503 screen: opcio d'establir el nivell de zoom directament
console: opcio d'establir el zoom directament
2026-03-30 23:42:30 +02:00
705a9fc7cd corregit el case en algunes respostes de console 2026-03-30 23:33:58 +02:00
b164c11ba7 console crea la tabla tab_completions automaticament 2026-03-30 23:27:38 +02:00
1817d00881 screen torna llista de paletes i permet canviar a una paleta pel nom
console 2.1 por canviar de paleta pel nom
2026-03-30 23:03:21 +02:00
8dcf473f31 autocompletar amb armadura de lagarto 2026-03-30 22:50:56 +02:00
8f191f02fa afegit autocompletar a la consola. si la de raimon en te, la meua no anava a ser menos 2026-03-30 22:26:53 +02:00
1077c13fd0 amb la consola oberta el jugador te els inputs deshabilitats. si moria amb la consola oberta, recuperava els inputs i es movia mentre escrius comandos en la consola 2026-03-30 20:48:21 +02:00
d5a4caa86e al fer restart, si estava sonant la musica del attract mode, la musica no parava al anar al logo 2026-03-30 20:22:14 +02:00
f3bad9f4ed afegida guarda per a que en debug el jugador no caiga infinitament si ix de la pantalla 2026-03-30 20:19:07 +02:00
32f22c99db mil arreglos cosmetics a console 2.0 2026-03-30 19:56:31 +02:00
cd14ae22c5 separacio de linies automatica en console 2026-03-30 19:35:43 +02:00
1fdc29e9d2 afegit typewriter effect a console 2026-03-30 19:16:57 +02:00
0a740a5be2 new: treballant en Console 2.0 2026-03-30 19:11:24 +02:00
9e1b2b8960 fix: make macos_release 2026-03-30 18:09:53 +02:00
ed3724193e fix: no es llegien els fitxers de Locale desde resources.pack 2026-03-30 17:59:59 +02:00
21 changed files with 18905 additions and 1770 deletions

143
CHANGELOG.md Normal file
View File

@@ -0,0 +1,143 @@
# Changelog
All notable changes to JailDoctor's Dilemma are documented here.
---
## [v1.11] - 2026-03-31
### Novedades
- **PaletteManager:** refactorización de `Screen`, responsabilidades de gestión de paletas extraídas a clase propia
- **Consola 2.1:** la consola puede cambiar de paleta por nombre (`Screen` devuelve lista de paletas)
- **Zoom configurable:** `Screen` permite establecer el nivel de zoom directamente desde consola
- **Autocompletar en consola:** autocompletado de comandos con Tab (incluyendo soporte para armadura de lagarto)
- Generación automática de tabla de tab-completions en la consola
### Correcciones
- Fix: al entrar a GAME con la consola abierta, el jugador no tenía los inputs deshabilitados
- Fix: al hacer restart con la música del attract mode sonando, la música no paraba al ir al logo
- Fix: en modo debug, protección para que el jugador no caiga infinitamente si sale de pantalla
- Corregido el case en algunas respuestas de la consola
- Corregido Makefile
---
## [v1.10] - 2026-03-30
### Novedades
- **Consola 2.0:** rediseño completo de la consola de desarrollador
- Efecto typewriter al mostrar texto
- Separación de líneas automática
- Cambio de skin
- Historial y navegación mejorada
- Comandos para cheats, control de escena, debug, audio y shaders
- Teclas de función operativas con la consola abierta
- Límite de caracteres ampliado
- La consola ya no pausa al jugador
- Reorganización del sistema de comandos y aliases (`show info`, `hide info`, etc.)
- **RenderInfo:** nueva clase con animación para mostrar info de renderizado en pantalla
- **Soporte multi-shader:** comandos y teclas para manejar el nuevo diseño de shaders (SPIRV/SPIR-V)
- **Modo kiosko:** defaults y restricciones de comandos para modo kiosk
- **Supersampling Lanczos:** implementación de escalado Lanczos en el supersampling
- **Driver GPU configurable:** permite elegir driver de GPU o ninguno desde consola
- Cheats accesibles desde la consola
- Cambio y reinicio de escena desde la consola
- Posición e habitación inicial de debug configurables desde consola y fichero
- `Debug` carga posición e habitación inicial desde fichero
- Comandos de audio configurables desde consola
- Renderizado del dispositivo GPU en info_debug
- `Screen` optimizado (`textureToRenderer()`)
- Eliminado soporte para argumentos de línea de comandos
- Eliminado `Options::console`
- Help de consola organizado
### Correcciones
- Fix: vsync off no funcionaba en Wayland
- Fix: en TITLE, la consola no bloqueaba la pulsación del 1 al 4 y entraba a opciones
- Fix: dos logs de consola con formato incorrecto
- Fix: lógica para abrir y entrar a la jail (ahora usa número de habitación, no nombre)
- Corregido `compile_spirv.cmake` y la `system_folder` para shaders
- Corregido carácter de caret que se había perdido
- Eliminados acentos en títulos de habitaciones que causaban problemas con fuentes
- Revisadas y corregidas traducciones
- Corregidos ficheros `.fnt`
- Corrección en `Screen` para `std::setprecision()` (faltaba `#include <iomanip>`)
---
## [v1.09] - 2025-03-01
### Novedades
- **Refactorización a singletons:** `Screen`, `Input`, `Audio`, `Resource::Cache`, `Resource::List`, `Director`, `Cheevos`, `Debug` convertidos a singletons thread-safe
- **Smart pointers:** uso de `std::shared_ptr` y `std::unique_ptr` para gestión de recursos y sprites
- **Surfaces 8-bit indexadas:** nuevo sistema de renderizado con color indexado y paletas intercambiables
- **Sistema de notificaciones rediseñado:** nuevo engine de notificaciones con control de offset
- **Modos de vídeo mejorados:** la ventana mantiene posición al cambiar tamaño o activar borde; puede crecer según el escritorio
- **ItemTracker:** nuevo singleton para rastrear ítems recogidos
- **globalEvents:** nuevo sistema de eventos globales SDL
- **Barra de progreso en carga de recursos** (actualización cada 5 ítems para mayor rendimiento con vsync)
- **Métodos show/hide ventana:** métodos para mostrar u ocultar la ventana
- Afinada la clase `Options`
- Actualizada a la última versión de `jail_audio`
- Implementados shaders
- Nueva tipografía añadida
- Parametros de ficheros `.ani` migrados a snake_case
- Música de Title y attract mode restaurada
- Eliminado sistema online completo
### Correcciones
- Fix: notificaciones ya no ensucian la pantalla de carga
- Fix: no pintaba el efecto de carga del borde en `LoadingScreen`
- Fix: bug con el puntero a `ScoreboardData`
- Fix: carga de opciones y recursos corregida
- Eliminados acentos problemáticos
---
## [v1.08] - 2024-02-22
### Novedades
- Posibilidad de saltar la pantalla de carga ya completada desde el menú de título
- El `gamestate_title` puede empezar en diferentes estados
- Pantalla de carga con fade de paleta
- GIF loader: dibujado correcto de GIFs en pantalla
- Añadida `paleta.cpp`/`.h` y `gif.c`
### Correcciones
- Corregido bug en el fade de paleta (el canal azul no se propagaba)
- Arreglada la separación entre el título y el fade
- Online deshabilitado por defecto al crear el fichero de configuración
- Tiempo de la pantalla de carga aumentado
---
## [v1.07] - 2022-12-02
### Novedades
- El nombre de la habitación se pinta a partir de una textura
- Añadido Batman a FEEL THE HEAT
- Cielo de la Jail actualizado
- Retocada la pantalla de título
- Sprite de PACO modificado
- Nombre del enemigo diskette cambiado a floppy
- Cambios cosméticos en algunas habitaciones (BE CAREFUL WITH THE FUSE renombrado)
- El color de fondo de la habitación se pinta en la textura del mapa
- Optimizaciones en intro y title
- Preparación para compatibilidad con consolas
- Actualizado `jail_audio` a la última versión
- Eliminados la mayor parte de accesos a `vector::at()`
### Correcciones
- Corregido bug: en la jail se rellenaban las vidas mientras estaba activa la pausa
- Corregido memory leak en `texture.cpp`
- Corregido bug en apertura de la Jail
---
## [v1.0] - 2022-11-13
Versión de lanzamiento inicial.
---
*El formato de este changelog sigue [Keep a Changelog](https://keepachangelog.com/).*

View File

@@ -34,31 +34,32 @@ set(APP_SOURCES
# Core - Input # Core - Input
source/core/input/global_inputs.cpp source/core/input/global_inputs.cpp
source/core/input/input.cpp
source/core/input/input_types.cpp source/core/input/input_types.cpp
source/core/input/input.cpp
source/core/input/mouse.cpp source/core/input/mouse.cpp
# Core - Rendering # Core - Rendering
source/core/rendering/gif.cpp source/core/rendering/gif.cpp
source/core/rendering/palette_manager.cpp
source/core/rendering/pixel_reveal.cpp source/core/rendering/pixel_reveal.cpp
source/core/rendering/render_info.cpp source/core/rendering/render_info.cpp
source/core/rendering/screen.cpp source/core/rendering/screen.cpp
source/core/rendering/surface.cpp
source/core/rendering/sprite/animated_sprite.cpp source/core/rendering/sprite/animated_sprite.cpp
source/core/rendering/sprite/dissolve_sprite.cpp source/core/rendering/sprite/dissolve_sprite.cpp
source/core/rendering/sprite/moving_sprite.cpp source/core/rendering/sprite/moving_sprite.cpp
source/core/rendering/sprite/sprite.cpp source/core/rendering/sprite/sprite.cpp
source/core/rendering/surface.cpp
source/core/rendering/text.cpp source/core/rendering/text.cpp
# Core - Locale # Core - Locale
source/core/locale/locale.cpp source/core/locale/locale.cpp
# Core - Resources # Core - Resources
source/core/resources/resource_list.cpp
source/core/resources/resource_cache.cpp source/core/resources/resource_cache.cpp
source/core/resources/resource_pack.cpp
source/core/resources/resource_loader.cpp
source/core/resources/resource_helper.cpp source/core/resources/resource_helper.cpp
source/core/resources/resource_list.cpp
source/core/resources/resource_loader.cpp
source/core/resources/resource_pack.cpp
# Core - System # Core - System
source/core/system/director.cpp source/core/system/director.cpp
@@ -78,9 +79,9 @@ set(APP_SOURCES
source/game/gameplay/enemy_manager.cpp source/game/gameplay/enemy_manager.cpp
source/game/gameplay/item_manager.cpp source/game/gameplay/item_manager.cpp
source/game/gameplay/item_tracker.cpp source/game/gameplay/item_tracker.cpp
source/game/gameplay/room.cpp
source/game/gameplay/room_loader.cpp source/game/gameplay/room_loader.cpp
source/game/gameplay/room_tracker.cpp source/game/gameplay/room_tracker.cpp
source/game/gameplay/room.cpp
source/game/gameplay/scoreboard.cpp source/game/gameplay/scoreboard.cpp
source/game/gameplay/stats.cpp source/game/gameplay/stats.cpp
source/game/gameplay/tilemap_renderer.cpp source/game/gameplay/tilemap_renderer.cpp
@@ -89,8 +90,8 @@ set(APP_SOURCES
source/game/scenes/credits.cpp source/game/scenes/credits.cpp
source/game/scenes/ending.cpp source/game/scenes/ending.cpp
source/game/scenes/ending2.cpp source/game/scenes/ending2.cpp
source/game/scenes/game.cpp
source/game/scenes/game_over.cpp source/game/scenes/game_over.cpp
source/game/scenes/game.cpp
source/game/scenes/loading_screen.cpp source/game/scenes/loading_screen.cpp
source/game/scenes/logo.cpp source/game/scenes/logo.cpp
source/game/scenes/title.cpp source/game/scenes/title.cpp

View File

@@ -84,57 +84,58 @@ LINUX_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-linux.tar.g
APP_SOURCES := \ APP_SOURCES := \
source/main.cpp \ source/main.cpp \
source/core/audio/audio.cpp \ source/core/audio/audio.cpp \
source/core/input/input.cpp \
source/core/input/input_types.cpp \
source/core/input/mouse.cpp \
source/core/input/global_inputs.cpp \ source/core/input/global_inputs.cpp \
source/core/rendering/screen.cpp \ source/core/input/input_types.cpp \
source/core/rendering/surface.cpp \ source/core/input/input.cpp \
source/core/rendering/sprite/sprite.cpp \ source/core/input/mouse.cpp \
source/core/rendering/sprite/animated_sprite.cpp \ source/core/locale/locale.cpp \
source/core/rendering/sprite/moving_sprite.cpp \
source/core/rendering/sprite/dissolve_sprite.cpp \
source/core/rendering/text.cpp \
source/core/rendering/gif.cpp \ source/core/rendering/gif.cpp \
source/core/rendering/palette_manager.cpp \
source/core/rendering/pixel_reveal.cpp \ source/core/rendering/pixel_reveal.cpp \
source/core/rendering/render_info.cpp \ source/core/rendering/render_info.cpp \
source/core/rendering/screen.cpp \
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp \ source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp \
source/core/locale/locale.cpp \ source/core/rendering/sprite/animated_sprite.cpp \
source/core/resources/resource_list.cpp \ source/core/rendering/sprite/dissolve_sprite.cpp \
source/core/rendering/sprite/moving_sprite.cpp \
source/core/rendering/sprite/sprite.cpp \
source/core/rendering/surface.cpp \
source/core/rendering/text.cpp \
source/core/resources/resource_cache.cpp \ source/core/resources/resource_cache.cpp \
source/core/resources/resource_helper.cpp \ source/core/resources/resource_helper.cpp \
source/core/resources/resource_list.cpp \
source/core/resources/resource_loader.cpp \ source/core/resources/resource_loader.cpp \
source/core/resources/resource_pack.cpp \ source/core/resources/resource_pack.cpp \
source/core/system/director.cpp \
source/core/system/debug.cpp \ source/core/system/debug.cpp \
source/core/system/director.cpp \
source/core/system/global_events.cpp \ source/core/system/global_events.cpp \
source/game/options.cpp \
source/game/entities/player.cpp \
source/game/entities/enemy.cpp \ source/game/entities/enemy.cpp \
source/game/entities/item.cpp \ source/game/entities/item.cpp \
source/game/gameplay/room.cpp \ source/game/entities/player.cpp \
source/game/gameplay/cheevos.cpp \
source/game/gameplay/collision_map.cpp \ source/game/gameplay/collision_map.cpp \
source/game/gameplay/enemy_manager.cpp \ source/game/gameplay/enemy_manager.cpp \
source/game/gameplay/item_manager.cpp \ source/game/gameplay/item_manager.cpp \
source/game/gameplay/room_loader.cpp \
source/game/gameplay/tilemap_renderer.cpp \
source/game/gameplay/scoreboard.cpp \
source/game/gameplay/cheevos.cpp \
source/game/gameplay/item_tracker.cpp \ source/game/gameplay/item_tracker.cpp \
source/game/gameplay/room_loader.cpp \
source/game/gameplay/room_tracker.cpp \ source/game/gameplay/room_tracker.cpp \
source/game/gameplay/room.cpp \
source/game/gameplay/scoreboard.cpp \
source/game/gameplay/stats.cpp \ source/game/gameplay/stats.cpp \
source/game/scenes/logo.cpp \ source/game/gameplay/tilemap_renderer.cpp \
source/game/scenes/loading_screen.cpp \ source/game/options.cpp \
source/game/scenes/title.cpp \ source/game/scenes/credits.cpp \
source/game/scenes/game.cpp \
source/game/scenes/game_over.cpp \
source/game/scenes/ending.cpp \ source/game/scenes/ending.cpp \
source/game/scenes/ending2.cpp \ source/game/scenes/ending2.cpp \
source/game/scenes/credits.cpp \ source/game/scenes/game_over.cpp \
source/game/ui/notifier.cpp \ source/game/scenes/game.cpp \
source/game/scenes/loading_screen.cpp \
source/game/scenes/logo.cpp \
source/game/scenes/title.cpp \
source/game/ui/console.cpp \ source/game/ui/console.cpp \
source/utils/utils.cpp \ source/game/ui/notifier.cpp \
source/utils/delta_timer.cpp source/utils/delta_timer.cpp \
source/utils/utils.cpp
# All sources combined # All sources combined
ALL_SOURCES := $(APP_SOURCES) ALL_SOURCES := $(APP_SOURCES)
@@ -298,6 +299,8 @@ macos_release:
$(RMDIR) "$(RELEASE_FOLDER)" $(RMDIR) "$(RELEASE_FOLDER)"
$(RMFILE) tmp.dmg $(RMFILE) tmp.dmg
$(RMFILE) "$(DIST_DIR)"/rw.* $(RMFILE) "$(DIST_DIR)"/rw.*
$(RMFILE) "$(MACOS_INTEL_RELEASE)"
$(RMFILE) "$(MACOS_APPLE_SILICON_RELEASE)"
# Crea la carpeta temporal para hacer el trabajo y las carpetas obligatorias para crear una app de macOS # Crea la carpeta temporal para hacer el trabajo y las carpetas obligatorias para crear una app de macOS
$(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks" $(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks"

View File

@@ -52,7 +52,7 @@ Input::Input(std::string game_controller_db_path)
{Action::TOGGLE_VSYNC, KeyState{.scancode = SDL_SCANCODE_F10}}, {Action::TOGGLE_VSYNC, KeyState{.scancode = SDL_SCANCODE_F10}},
{Action::PAUSE, KeyState{.scancode = SDL_SCANCODE_F11}}, {Action::PAUSE, KeyState{.scancode = SDL_SCANCODE_F11}},
{Action::TOGGLE_INFO, KeyState{.scancode = SDL_SCANCODE_F12}}, {Action::TOGGLE_INFO, KeyState{.scancode = SDL_SCANCODE_F12}},
{Action::TOGGLE_CONSOLE, KeyState{.scancode = SDL_SCANCODE_TAB}}}; {Action::TOGGLE_CONSOLE, KeyState{.scancode = SDL_SCANCODE_GRAVE}}};
initSDLGamePad(); // Inicializa el subsistema SDL_INIT_GAMEPAD initSDLGamePad(); // Inicializa el subsistema SDL_INIT_GAMEPAD
} }

View File

@@ -2,6 +2,7 @@
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <sstream>
#include <string> #include <string>
#include "external/fkyaml_node.hpp" // Para fkyaml::node #include "external/fkyaml_node.hpp" // Para fkyaml::node
@@ -15,6 +16,12 @@ void Locale::init(const std::string& file_path) { // NOLINT(readability-convert
Locale::instance->loadFromFile(file_path); Locale::instance->loadFromFile(file_path);
} }
// [SINGLETON] Crea el objeto desde contenido en memoria (para release con pack)
void Locale::initFromContent(const std::string& content) { // NOLINT(readability-convert-member-functions-to-static)
Locale::instance = new Locale();
Locale::instance->loadFromContent(content);
}
// [SINGLETON] Destruye el objeto con esta función estática // [SINGLETON] Destruye el objeto con esta función estática
void Locale::destroy() { void Locale::destroy() {
delete Locale::instance; delete Locale::instance;
@@ -55,6 +62,24 @@ void Locale::flatten(const void* node_ptr, const std::string& prefix) { // NOLI
} }
} }
// Carga las traducciones desde contenido YAML en memoria
void Locale::loadFromContent(const std::string& content) { // NOLINT(readability-convert-member-functions-to-static)
if (content.empty()) {
std::cerr << "Locale: contenido vacío, sin traducciones cargadas\n";
return;
}
try {
std::istringstream stream(content);
auto yaml = fkyaml::node::deserialize(stream);
flatten(&yaml, "");
std::cout << "Locale: " << strings_.size() << " traducciones cargadas desde pack\n";
} catch (const fkyaml::exception& e) {
std::cerr << "Locale: error al parsear YAML: " << e.what() << '\n';
}
}
// Carga las traducciones desde el fichero YAML indicado // Carga las traducciones desde el fichero YAML indicado
void Locale::loadFromFile(const std::string& file_path) { // NOLINT(readability-convert-member-functions-to-static) void Locale::loadFromFile(const std::string& file_path) { // NOLINT(readability-convert-member-functions-to-static)
if (file_path.empty()) { if (file_path.empty()) {

View File

@@ -9,6 +9,7 @@
class Locale { class Locale {
public: public:
static void init(const std::string& file_path); // Crea e inicializa el singleton static void init(const std::string& file_path); // Crea e inicializa el singleton
static void initFromContent(const std::string& content); // Crea e inicializa desde contenido en memoria (pack)
static void destroy(); // Destruye el singleton static void destroy(); // Destruye el singleton
static auto get() -> Locale*; // Devuelve el singleton static auto get() -> Locale*; // Devuelve el singleton
@@ -19,6 +20,7 @@ class Locale {
private: private:
Locale() = default; Locale() = default;
void loadFromFile(const std::string& file_path); void loadFromFile(const std::string& file_path);
void loadFromContent(const std::string& content);
void flatten(const void* node_ptr, const std::string& prefix); // Aplana nodos YAML anidados void flatten(const void* node_ptr, const std::string& prefix); // Aplana nodos YAML anidados
static Locale* instance; static Locale* instance;

View File

@@ -0,0 +1,104 @@
#include "core/rendering/palette_manager.hpp"
#include <algorithm>
#include <cctype>
#include <string>
#include "core/rendering/surface.hpp"
#include "core/resources/resource_cache.hpp"
#include "game/options.hpp"
#include "utils/utils.hpp"
PaletteManager::PaletteManager(
std::vector<std::string> raw_paths,
const std::string& initial_name,
std::shared_ptr<Surface> game_surface,
std::shared_ptr<Surface> border_surface,
OnChangeCallback on_change)
: palettes_(std::move(raw_paths)),
game_surface_(std::move(game_surface)),
border_surface_(std::move(border_surface)),
on_change_(std::move(on_change)) {
current_ = findIndex(initial_name);
// Leer y aplicar paleta inicial directamente desde el archivo
// (Resource::Cache aún no está disponible en este punto del ciclo de vida)
const auto initial_palette = readPalFile(palettes_.at(current_));
game_surface_->setPalette(initial_palette);
border_surface_->setPalette(initial_palette);
// Procesar la lista: conservar solo los nombres de archivo (sin ruta)
processPathList();
}
void PaletteManager::next() {
if (++current_ == palettes_.size()) {
current_ = 0;
}
apply();
}
void PaletteManager::previous() {
current_ = (current_ > 0) ? current_ - 1 : palettes_.size() - 1;
apply();
}
auto PaletteManager::setByName(const std::string& name) -> bool {
const std::string upper_name = toUpper(name + ".pal");
for (size_t i = 0; i < palettes_.size(); ++i) {
if (toUpper(palettes_[i]) == upper_name) {
current_ = i;
apply();
return true;
}
}
return false;
}
auto PaletteManager::getNames() const -> std::vector<std::string> {
std::vector<std::string> names;
names.reserve(palettes_.size());
for (const auto& p : palettes_) {
std::string name = p;
const size_t pos = name.find(".pal");
if (pos != std::string::npos) { name.erase(pos, 4); }
std::ranges::transform(name, name.begin(), ::toupper);
names.push_back(std::move(name));
}
return names;
}
auto PaletteManager::getCurrentName() const -> std::string {
std::string name = palettes_.at(current_);
const size_t pos = name.find(".pal");
if (pos != std::string::npos) { name.erase(pos, 4); }
std::ranges::transform(name, name.begin(), ::toupper);
return name;
}
void PaletteManager::apply() {
game_surface_->loadPalette(Resource::Cache::get()->getPalette(palettes_.at(current_)));
border_surface_->loadPalette(Resource::Cache::get()->getPalette(palettes_.at(current_)));
Options::video.palette = getCurrentName();
if (on_change_) {
on_change_();
}
}
auto PaletteManager::findIndex(const std::string& name) const -> size_t {
const std::string upper_name = toUpper(name + ".pal");
for (size_t i = 0; i < palettes_.size(); ++i) {
if (toUpper(getFileName(palettes_[i])) == upper_name) {
return i;
}
}
return 0;
}
void PaletteManager::processPathList() {
for (auto& palette : palettes_) {
palette = getFileName(palette);
}
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include <functional>
#include <memory>
#include <string>
#include <vector>
class Surface;
class PaletteManager {
public:
using OnChangeCallback = std::function<void()>;
PaletteManager(
std::vector<std::string> raw_paths,
const std::string& initial_name,
std::shared_ptr<Surface> game_surface,
std::shared_ptr<Surface> border_surface,
OnChangeCallback on_change = nullptr);
void next(); // Avanza a la siguiente paleta
void previous(); // Retrocede a la paleta anterior
auto setByName(const std::string& name) -> bool; // Cambia a paleta por nombre; false si no existe
[[nodiscard]] auto getNames() const -> std::vector<std::string>; // Nombres disponibles (mayúsculas, sin .pal)
[[nodiscard]] auto getCurrentName() const -> std::string; // Nombre de la paleta actual (mayúsculas, sin .pal)
private:
void apply(); // Aplica la paleta actual a ambas surfaces
[[nodiscard]] auto findIndex(const std::string& name) const -> size_t; // Localiza paleta por nombre en el vector
void processPathList(); // Extrae nombres de archivo de las rutas completas
std::vector<std::string> palettes_;
size_t current_{0};
std::shared_ptr<Surface> game_surface_;
std::shared_ptr<Surface> border_surface_;
OnChangeCallback on_change_;
};

View File

@@ -42,8 +42,7 @@ auto Screen::get() -> Screen* {
} }
// Constructor // Constructor
Screen::Screen() Screen::Screen() {
: palettes_(Resource::List::get()->getListByType(Resource::List::Type::PALETTE)) {
// Arranca SDL VIDEO, crea la ventana y el renderizador // Arranca SDL VIDEO, crea la ventana y el renderizador
initSDLVideo(); initSDLVideo();
if (Options::video.fullscreen) { SDL_HideCursor(); } if (Options::video.fullscreen) { SDL_HideCursor(); }
@@ -55,7 +54,6 @@ Screen::Screen()
// Ajusta los tamaños // Ajusta los tamaños
game_surface_dstrect_ = {.x = Options::video.border.width, .y = Options::video.border.height, .w = Options::game.width, .h = Options::game.height}; game_surface_dstrect_ = {.x = Options::video.border.width, .y = Options::video.border.height, .w = Options::game.width, .h = Options::game.height};
current_palette_ = findPalette(Options::video.palette);
// Define el color del borde para el modo de pantalla completa // Define el color del borde para el modo de pantalla completa
border_color_ = static_cast<Uint8>(PaletteColor::BLACK); border_color_ = static_cast<Uint8>(PaletteColor::BLACK);
@@ -76,19 +74,27 @@ Screen::Screen()
} }
SDL_SetTextureScaleMode(border_texture_, SDL_SCALEMODE_NEAREST); SDL_SetTextureScaleMode(border_texture_, SDL_SCALEMODE_NEAREST);
// Cargar la paleta una sola vez // Crea las surfaces (PaletteManager aplicará la paleta inicial en su constructor)
auto initial_palette = readPalFile(palettes_.at(current_palette_));
// Crea la surface donde se dibujan los graficos del juego
game_surface_ = std::make_shared<Surface>(Options::game.width, Options::game.height); game_surface_ = std::make_shared<Surface>(Options::game.width, Options::game.height);
game_surface_->setPalette(initial_palette);
game_surface_->clear(static_cast<Uint8>(PaletteColor::BLACK)); game_surface_->clear(static_cast<Uint8>(PaletteColor::BLACK));
// Crea la surface para el borde de colores
border_surface_ = std::make_shared<Surface>(Options::game.width + (Options::video.border.width * 2), Options::game.height + (Options::video.border.height * 2)); border_surface_ = std::make_shared<Surface>(Options::game.width + (Options::video.border.width * 2), Options::game.height + (Options::video.border.height * 2));
border_surface_->setPalette(initial_palette);
border_surface_->clear(border_color_); border_surface_->clear(border_color_);
// Crea el gestor de paletas; aplica la paleta inicial a ambas surfaces
palette_manager_ = std::make_unique<PaletteManager>(
Resource::List::get()->getListByType(Resource::List::Type::PALETTE),
Options::video.palette,
game_surface_,
border_surface_,
[this]() {
// Actualizar caché ARGB del borde cuando cambia la paleta
if (border_is_solid_) {
border_surface_->toARGBBuffer(border_pixel_buffer_.data());
border_argb_color_ = border_pixel_buffer_[0];
}
});
// Cachear el color ARGB inicial del borde (borde sólido por defecto) // Cachear el color ARGB inicial del borde (borde sólido por defecto)
border_surface_->toARGBBuffer(border_pixel_buffer_.data()); border_surface_->toARGBBuffer(border_pixel_buffer_.data());
border_argb_color_ = border_pixel_buffer_[0]; border_argb_color_ = border_pixel_buffer_[0];
@@ -99,9 +105,6 @@ Screen::Screen()
// Crea el objeto de texto para la pantalla de carga // Crea el objeto de texto para la pantalla de carga
createText(); createText();
// Extrae el nombre de las paletas desde su ruta
processPaletteList();
// Renderizar una vez la textura vacía para que tenga contenido válido // Renderizar una vez la textura vacía para que tenga contenido válido
// antes de inicializar los shaders (evita pantalla negra) // antes de inicializar los shaders (evita pantalla negra)
SDL_RenderTexture(renderer_, game_texture_, nullptr, nullptr); SDL_RenderTexture(renderer_, game_texture_, nullptr, nullptr);
@@ -194,6 +197,21 @@ auto Screen::incWindowZoom() -> bool {
return false; return false;
} }
// Establece el zoom directamente; false si fuera del rango [1, max_zoom] o en pantalla completa
auto Screen::setWindowZoom(int zoom) -> bool {
if (Options::video.fullscreen) { return false; }
if (zoom < 1 || zoom > Options::window.max_zoom) { return false; }
if (zoom == Options::window.zoom) { return false; }
Options::window.zoom = zoom;
setVideoMode(Options::video.fullscreen);
return true;
}
// Devuelve el zoom máximo permitido según la pantalla actual
auto Screen::getMaxZoom() const -> int {
return Options::window.max_zoom;
}
// Cambia el color del borde // Cambia el color del borde
void Screen::setBorderColor(Uint8 color) { void Screen::setBorderColor(Uint8 color) {
border_color_ = color; border_color_ = color;
@@ -342,55 +360,10 @@ void Screen::setRendererSurface(const std::shared_ptr<Surface>& surface) {
} }
// Cambia la paleta // Cambia la paleta
void Screen::nextPalette() { void Screen::nextPalette() { palette_manager_->next(); }
++current_palette_;
if (current_palette_ == static_cast<int>(palettes_.size())) {
current_palette_ = 0;
}
setPalete();
}
// Cambia la paleta // Cambia la paleta
void Screen::previousPalette() { void Screen::previousPalette() { palette_manager_->previous(); }
if (current_palette_ > 0) {
--current_palette_;
} else {
current_palette_ = static_cast<Uint8>(palettes_.size() - 1);
}
setPalete();
}
// Establece la paleta
void Screen::setPalete() { // NOLINT(readability-convert-member-functions-to-static)
game_surface_->loadPalette(Resource::Cache::get()->getPalette(palettes_.at(current_palette_)));
border_surface_->loadPalette(Resource::Cache::get()->getPalette(palettes_.at(current_palette_)));
Options::video.palette = palettes_.at(current_palette_);
// Eliminar ".gif"
size_t pos = Options::video.palette.find(".pal");
if (pos != std::string::npos) {
Options::video.palette.erase(pos, 4);
}
// Convertir a mayúsculas
std::ranges::transform(Options::video.palette, Options::video.palette.begin(), ::toupper);
// Actualizar caché si el borde es sólido (la paleta cambia el valor ARGB del color)
if (border_is_solid_) {
border_surface_->toARGBBuffer(border_pixel_buffer_.data());
border_argb_color_ = border_pixel_buffer_[0];
}
}
// Extrae los nombres de las paletas
void Screen::processPaletteList() {
for (auto& palette : palettes_) {
palette = getFileName(palette);
}
}
// Copia la surface a la textura // Copia la surface a la textura
void Screen::surfaceToTexture() { // NOLINT(readability-convert-member-functions-to-static) void Screen::surfaceToTexture() { // NOLINT(readability-convert-member-functions-to-static)
@@ -470,17 +443,11 @@ void Screen::renderOverlays() {
if (Console::get() != nullptr) { Console::get()->render(); } // Console (encima) if (Console::get() != nullptr) { Console::get()->render(); } // Console (encima)
} }
// Localiza la paleta dentro del vector de paletas // Cambia a una paleta por nombre (case-insensitive); devuelve false si no existe
auto Screen::findPalette(const std::string& name) -> size_t { // NOLINT(readability-convert-member-functions-to-static) auto Screen::setPaletteByName(const std::string& name) -> bool { return palette_manager_->setByName(name); }
std::string upper_name = toUpper(name + ".pal");
for (size_t i = 0; i < palettes_.size(); ++i) { // Devuelve los nombres de paletas disponibles (mayúsculas, sin extensión .pal)
if (toUpper(getFileName(palettes_[i])) == upper_name) { auto Screen::getPaletteNames() const -> std::vector<std::string> { return palette_manager_->getNames(); }
return i;
}
}
return static_cast<size_t>(0);
}
// Limpia la game_surface_ // Limpia la game_surface_
void Screen::clearSurface(Uint8 index) { game_surface_->clear(index); } void Screen::clearSurface(Uint8 index) { game_surface_->clear(index); }

View File

@@ -9,6 +9,7 @@
#include <utility> // Para std::pair #include <utility> // Para std::pair
#include <vector> // Para vector #include <vector> // Para vector
#include "core/rendering/palette_manager.hpp" // Para PaletteManager
#include "core/rendering/shader_backend.hpp" // Para Rendering::ShaderType, ShaderBackend #include "core/rendering/shader_backend.hpp" // Para Rendering::ShaderType, ShaderBackend
#include "utils/utils.hpp" // Para Color #include "utils/utils.hpp" // Para Color
class Surface; class Surface;
@@ -41,6 +42,7 @@ class Screen {
void toggleVSync(); // Alterna entre activar y desactivar el V-Sync void toggleVSync(); // Alterna entre activar y desactivar el V-Sync
auto decWindowZoom() -> bool; // Reduce el tamaño de la ventana auto decWindowZoom() -> bool; // Reduce el tamaño de la ventana
auto incWindowZoom() -> bool; // Aumenta el tamaño de la ventana auto incWindowZoom() -> bool; // Aumenta el tamaño de la ventana
auto setWindowZoom(int zoom) -> bool; // Establece zoom directo; false si fuera de [1, max_zoom]
void show(); // Muestra la ventana void show(); // Muestra la ventana
void hide(); // Oculta la ventana void hide(); // Oculta la ventana
@@ -54,7 +56,8 @@ class Screen {
// Paletas y PostFX // Paletas y PostFX
void nextPalette(); // Cambia a la siguiente paleta void nextPalette(); // Cambia a la siguiente paleta
void previousPalette(); // Cambia a la paleta anterior void previousPalette(); // Cambia a la paleta anterior
void setPalete(); // Establece la paleta actual auto setPaletteByName(const std::string& name) -> bool; // Cambia a paleta por nombre; false si no existe
[[nodiscard]] auto getPaletteNames() const -> std::vector<std::string>; // Nombres disponibles (mayúsculas, sin .pal)
void toggleShaders(); // Activa/desactiva todos los shaders respetando current_shader void toggleShaders(); // Activa/desactiva todos los shaders respetando current_shader
void toggleSupersampling(); // Activa/desactiva el supersampling global void toggleSupersampling(); // Activa/desactiva el supersampling global
void reloadPostFX(); // Recarga el shader del preset actual sin toggle void reloadPostFX(); // Recarga el shader del preset actual sin toggle
@@ -79,6 +82,7 @@ class Screen {
[[nodiscard]] auto getGPUDriver() const -> const std::string& { return gpu_driver_; } [[nodiscard]] auto getGPUDriver() const -> const std::string& { return gpu_driver_; }
[[nodiscard]] auto getLastFPS() const -> int { return fps_.last_value; } [[nodiscard]] auto getLastFPS() const -> int { return fps_.last_value; }
[[nodiscard]] auto getZoomFactor() const -> float { return zoom_factor_; } [[nodiscard]] auto getZoomFactor() const -> float { return zoom_factor_; }
[[nodiscard]] auto getMaxZoom() const -> int;
[[nodiscard]] auto getSsTextureSize() const -> std::pair<int, int>; [[nodiscard]] auto getSsTextureSize() const -> std::pair<int, int>;
private: private:
@@ -119,11 +123,9 @@ class Screen {
void renderNotifications() const; // Dibuja las notificaciones void renderNotifications() const; // Dibuja las notificaciones
void adjustWindowSize(); // Calcula el tamaño de la ventana void adjustWindowSize(); // Calcula el tamaño de la ventana
void adjustRenderLogicalSize(); // Ajusta el tamaño lógico del renderizador void adjustRenderLogicalSize(); // Ajusta el tamaño lógico del renderizador
void processPaletteList(); // Extrae los nombres de las paletas
void surfaceToTexture(); // Copia la surface a la textura void surfaceToTexture(); // Copia la surface a la textura
void textureToRenderer(); // Copia la textura al renderizador void textureToRenderer(); // Copia la textura al renderizador
void renderOverlays(); // Renderiza todos los overlays void renderOverlays(); // Renderiza todos los overlays
auto findPalette(const std::string& name) -> size_t; // Localiza la paleta dentro del vector de paletas
void initShaders(); // Inicializa los shaders void initShaders(); // Inicializa los shaders
void applyCurrentPostFXPreset(); // Aplica los parámetros del preset PostFX actual al backend void applyCurrentPostFXPreset(); // Aplica los parámetros del preset PostFX actual al backend
void applyCurrentCrtPiPreset(); // Aplica los parámetros del preset CrtPi actual al backend void applyCurrentCrtPiPreset(); // Aplica los parámetros del preset CrtPi actual al backend
@@ -164,8 +166,7 @@ class Screen {
// Paletas y colores // Paletas y colores
Uint8 border_color_{0}; // Color del borde Uint8 border_color_{0}; // Color del borde
std::vector<std::string> palettes_; // Listado de ficheros de paleta disponibles std::unique_ptr<PaletteManager> palette_manager_; // Gestor de paletas de color
Uint8 current_palette_{0}; // Índice para el vector de paletas
// Estado y configuración // Estado y configuración
bool notifications_enabled_{false}; // Indica si se muestran las notificaciones bool notifications_enabled_{false}; // Indica si se muestran las notificaciones

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,59 +1,633 @@
#pragma once #pragma once
#include <cstdint>
#include <cstddef> #include <cstddef>
#include <cstdint>
static const uint8_t kupscale_frag_spv[] = { static const uint8_t kupscale_frag_spv[] = {
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x00, 0x0d, 0x00, 0x03,
0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x02,
0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x23,
0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30, 0x07,
0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x01,
0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x0b,
0x02, 0x00, 0x00, 0x00, 0xc2, 0x01, 0x00, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00,
0x47, 0x4c, 0x5f, 0x47, 0x4f, 0x4f, 0x47, 0x4c, 0x45, 0x5f, 0x63, 0x70, 0x0d,
0x70, 0x5f, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x00,
0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00, 0x00, 0x14,
0x04, 0x00, 0x08, 0x00, 0x47, 0x4c, 0x5f, 0x47, 0x4f, 0x4f, 0x47, 0x4c, 0x00,
0x45, 0x5f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x64, 0x69, 0x00,
0x72, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00, 0x05, 0x00, 0x04, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x05, 0x00, 0x09, 0x00, 0x00, 0x00, 0x6f, 0x75, 0x74, 0x5f, 0x00,
0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x00,
0x0d, 0x00, 0x00, 0x00, 0x73, 0x63, 0x65, 0x6e, 0x65, 0x00, 0x00, 0x00, 0x00,
0x05, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, 0x76, 0x5f, 0x75, 0x76, 0x11,
0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00,
0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x02,
0x0d, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x47, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x01,
0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00,
0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0b,
0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x06,
0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00,
0x3b, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01,
0x03, 0x00, 0x00, 0x00, 0x19, 0x00, 0x09, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x03, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x47,
0x0a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x4c,
0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x53,
0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c,
0x17, 0x00, 0x04, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2e,
0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00, 0x73,
0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x74,
0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64,
0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x2e,
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x34,
0x05, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x35,
0x0e, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x30,
0x0f, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00,
0x57, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00,
0x0e, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x00,
0x09, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00, 0x00,
0x38, 0x00, 0x01, 0x00 0x0e,
}; 0x00,
0x03,
0x00,
0x00,
0x00,
0x00,
0x00,
0x01,
0x00,
0x00,
0x00,
0x0f,
0x00,
0x07,
0x00,
0x04,
0x00,
0x00,
0x00,
0x04,
0x00,
0x00,
0x00,
0x6d,
0x61,
0x69,
0x6e,
0x00,
0x00,
0x00,
0x00,
0x09,
0x00,
0x00,
0x00,
0x11,
0x00,
0x00,
0x00,
0x10,
0x00,
0x03,
0x00,
0x04,
0x00,
0x00,
0x00,
0x07,
0x00,
0x00,
0x00,
0x03,
0x00,
0x03,
0x00,
0x02,
0x00,
0x00,
0x00,
0xc2,
0x01,
0x00,
0x00,
0x04,
0x00,
0x0a,
0x00,
0x47,
0x4c,
0x5f,
0x47,
0x4f,
0x4f,
0x47,
0x4c,
0x45,
0x5f,
0x63,
0x70,
0x70,
0x5f,
0x73,
0x74,
0x79,
0x6c,
0x65,
0x5f,
0x6c,
0x69,
0x6e,
0x65,
0x5f,
0x64,
0x69,
0x72,
0x65,
0x63,
0x74,
0x69,
0x76,
0x65,
0x00,
0x00,
0x04,
0x00,
0x08,
0x00,
0x47,
0x4c,
0x5f,
0x47,
0x4f,
0x4f,
0x47,
0x4c,
0x45,
0x5f,
0x69,
0x6e,
0x63,
0x6c,
0x75,
0x64,
0x65,
0x5f,
0x64,
0x69,
0x72,
0x65,
0x63,
0x74,
0x69,
0x76,
0x65,
0x00,
0x05,
0x00,
0x04,
0x00,
0x04,
0x00,
0x00,
0x00,
0x6d,
0x61,
0x69,
0x6e,
0x00,
0x00,
0x00,
0x00,
0x05,
0x00,
0x05,
0x00,
0x09,
0x00,
0x00,
0x00,
0x6f,
0x75,
0x74,
0x5f,
0x63,
0x6f,
0x6c,
0x6f,
0x72,
0x00,
0x00,
0x00,
0x05,
0x00,
0x04,
0x00,
0x0d,
0x00,
0x00,
0x00,
0x73,
0x63,
0x65,
0x6e,
0x65,
0x00,
0x00,
0x00,
0x05,
0x00,
0x04,
0x00,
0x11,
0x00,
0x00,
0x00,
0x76,
0x5f,
0x75,
0x76,
0x00,
0x00,
0x00,
0x00,
0x47,
0x00,
0x04,
0x00,
0x09,
0x00,
0x00,
0x00,
0x1e,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x47,
0x00,
0x04,
0x00,
0x0d,
0x00,
0x00,
0x00,
0x21,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x47,
0x00,
0x04,
0x00,
0x0d,
0x00,
0x00,
0x00,
0x22,
0x00,
0x00,
0x00,
0x02,
0x00,
0x00,
0x00,
0x47,
0x00,
0x04,
0x00,
0x11,
0x00,
0x00,
0x00,
0x1e,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x13,
0x00,
0x02,
0x00,
0x02,
0x00,
0x00,
0x00,
0x21,
0x00,
0x03,
0x00,
0x03,
0x00,
0x00,
0x00,
0x02,
0x00,
0x00,
0x00,
0x16,
0x00,
0x03,
0x00,
0x06,
0x00,
0x00,
0x00,
0x20,
0x00,
0x00,
0x00,
0x17,
0x00,
0x04,
0x00,
0x07,
0x00,
0x00,
0x00,
0x06,
0x00,
0x00,
0x00,
0x04,
0x00,
0x00,
0x00,
0x20,
0x00,
0x04,
0x00,
0x08,
0x00,
0x00,
0x00,
0x03,
0x00,
0x00,
0x00,
0x07,
0x00,
0x00,
0x00,
0x3b,
0x00,
0x04,
0x00,
0x08,
0x00,
0x00,
0x00,
0x09,
0x00,
0x00,
0x00,
0x03,
0x00,
0x00,
0x00,
0x19,
0x00,
0x09,
0x00,
0x0a,
0x00,
0x00,
0x00,
0x06,
0x00,
0x00,
0x00,
0x01,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x01,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x1b,
0x00,
0x03,
0x00,
0x0b,
0x00,
0x00,
0x00,
0x0a,
0x00,
0x00,
0x00,
0x20,
0x00,
0x04,
0x00,
0x0c,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x0b,
0x00,
0x00,
0x00,
0x3b,
0x00,
0x04,
0x00,
0x0c,
0x00,
0x00,
0x00,
0x0d,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x17,
0x00,
0x04,
0x00,
0x0f,
0x00,
0x00,
0x00,
0x06,
0x00,
0x00,
0x00,
0x02,
0x00,
0x00,
0x00,
0x20,
0x00,
0x04,
0x00,
0x10,
0x00,
0x00,
0x00,
0x01,
0x00,
0x00,
0x00,
0x0f,
0x00,
0x00,
0x00,
0x3b,
0x00,
0x04,
0x00,
0x10,
0x00,
0x00,
0x00,
0x11,
0x00,
0x00,
0x00,
0x01,
0x00,
0x00,
0x00,
0x36,
0x00,
0x05,
0x00,
0x02,
0x00,
0x00,
0x00,
0x04,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x03,
0x00,
0x00,
0x00,
0xf8,
0x00,
0x02,
0x00,
0x05,
0x00,
0x00,
0x00,
0x3d,
0x00,
0x04,
0x00,
0x0b,
0x00,
0x00,
0x00,
0x0e,
0x00,
0x00,
0x00,
0x0d,
0x00,
0x00,
0x00,
0x3d,
0x00,
0x04,
0x00,
0x0f,
0x00,
0x00,
0x00,
0x12,
0x00,
0x00,
0x00,
0x11,
0x00,
0x00,
0x00,
0x57,
0x00,
0x05,
0x00,
0x07,
0x00,
0x00,
0x00,
0x13,
0x00,
0x00,
0x00,
0x0e,
0x00,
0x00,
0x00,
0x12,
0x00,
0x00,
0x00,
0x3e,
0x00,
0x03,
0x00,
0x09,
0x00,
0x00,
0x00,
0x13,
0x00,
0x00,
0x00,
0xfd,
0x00,
0x01,
0x00,
0x38,
0x00,
0x01,
0x00};
static const size_t kupscale_frag_spv_size = 628; static const size_t kupscale_frag_spv_size = 628;

View File

@@ -189,8 +189,13 @@ Director::Director() {
// Inicializa el sistema de localización (antes de Cheevos que usa textos traducidos) // Inicializa el sistema de localización (antes de Cheevos que usa textos traducidos)
#ifdef RELEASE_BUILD #ifdef RELEASE_BUILD
std::string locale_path = executable_path_ + PREFIX + "/data/locale/" + Options::language + ".yaml"; {
Locale::init(locale_path); // En release el locale está en el pack, no en el filesystem
std::string locale_key = Resource::List::get()->get(Options::language + ".yaml"); // NOLINT(readability-static-accessed-through-instance)
auto locale_bytes = Resource::Helper::loadFile(locale_key);
std::string locale_content(locale_bytes.begin(), locale_bytes.end());
Locale::initFromContent(locale_content);
}
#else #else
Locale::init(Resource::List::get()->get(Options::language + ".yaml")); // NOLINT(readability-static-accessed-through-instance) Locale::init(Resource::List::get()->get(Options::language + ".yaml")); // NOLINT(readability-static-accessed-through-instance)
#endif #endif

View File

@@ -880,6 +880,10 @@ auto Player::handleLandingFromAir(float displacement, const SDL_FRect& projectio
// No hay colisión // No hay colisión
y_ += displacement; y_ += displacement;
#ifdef _DEBUG
// Guarda por si en debug el jugador se sale de la pantalla, para que no esté cayendo infinitamente
if (y_ > PlayArea::BOTTOM + HEIGHT) { y_ = PlayArea::TOP + 2; }
#endif
return false; return false;
} }

View File

@@ -106,6 +106,7 @@ class Player {
[[nodiscard]] auto isAlive() const -> bool { return is_alive_; } // Comprueba si el jugador esta vivo [[nodiscard]] auto isAlive() const -> bool { return is_alive_; } // Comprueba si el jugador esta vivo
void setPaused(bool value) { is_paused_ = value; } // Pone el jugador en modo pausa void setPaused(bool value) { is_paused_ = value; } // Pone el jugador en modo pausa
void setIgnoreInput(bool value) { ignore_input_ = value; } // Ignora inputs del jugador (física sigue activa) void setIgnoreInput(bool value) { ignore_input_ = value; } // Ignora inputs del jugador (física sigue activa)
[[nodiscard]] auto getIgnoreInput() const -> bool { return ignore_input_; }
#ifdef _DEBUG #ifdef _DEBUG
// --- Funciones de debug --- // --- Funciones de debug ---

View File

@@ -66,6 +66,7 @@ Game::Game(Mode mode)
GameControl::refresh_player_color = [this]() -> void { player_->setColor(); }; GameControl::refresh_player_color = [this]() -> void { player_->setColor(); };
Console::get()->on_toggle = [this](bool open) { player_->setIgnoreInput(open); }; Console::get()->on_toggle = [this](bool open) { player_->setIgnoreInput(open); };
if (Console::get()->isActive()) { player_->setIgnoreInput(true); }
GameControl::change_player_skin = [this](int skin_num) -> void { GameControl::change_player_skin = [this](int skin_num) -> void {
Options::game.player_skin = skin_num; Options::game.player_skin = skin_num;
player_->setSkin(skin_num); player_->setSkin(skin_num);
@@ -882,9 +883,11 @@ void Game::checkEndGameCheevos() { // NOLINT(readability-convert-member-functio
// Inicializa al jugador // Inicializa al jugador
void Game::initPlayer(const Player::SpawnData& spawn_point, std::shared_ptr<Room> room) { // NOLINT(readability-convert-member-functions-to-static) void Game::initPlayer(const Player::SpawnData& spawn_point, std::shared_ptr<Room> room) { // NOLINT(readability-convert-member-functions-to-static)
const bool IGNORE_INPUT = player_ != nullptr && player_->getIgnoreInput();
std::string player_animations = (Options::game.player_skin == 2) ? "player2.yaml" : "player.yaml"; std::string player_animations = (Options::game.player_skin == 2) ? "player2.yaml" : "player.yaml";
const Player::Data PLAYER{.spawn_data = spawn_point, .animations_path = player_animations, .room = std::move(room)}; const Player::Data PLAYER{.spawn_data = spawn_point, .animations_path = player_animations, .room = std::move(room)};
player_ = std::make_shared<Player>(PLAYER); player_ = std::make_shared<Player>(PLAYER);
if (IGNORE_INPUT) { player_->setIgnoreInput(true); }
} }
// Crea la textura para poner el nombre de la habitación // Crea la textura para poner el nombre de la habitación

View File

@@ -5,6 +5,7 @@
#include <cctype> // Para toupper #include <cctype> // Para toupper
#include <functional> // Para function #include <functional> // Para function
#include <iostream> // Para std::cout #include <iostream> // Para std::cout
#include <sstream> // Para std::istringstream
#include <string> // Para string #include <string> // Para string
#include <vector> // Para vector #include <vector> // Para vector
@@ -27,9 +28,16 @@
// ── Sistema de comandos ──────────────────────────────────────────────────────── // ── 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 { struct ConsoleCommand {
std::string_view keyword; std::string_view keyword;
std::function<std::string(const std::vector<std::string>& args)> execute; 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
}; };
// Convierte la entrada a uppercase y la divide en tokens por espacios // Convierte la entrada a uppercase y la divide en tokens por espacios
@@ -69,7 +77,7 @@ static auto parseTokens(const std::string& input) -> std::vector<std::string> {
(toggle_fn); \ (toggle_fn); \
return label " OFF"; \ return label " OFF"; \
} \ } \
return "Usage: " label " [ON|OFF]"; \ return "usage: " label " [on|off]"; \
} }
// Texto de ayuda común para HELP y ? // Texto de ayuda común para HELP y ?
@@ -128,6 +136,13 @@ static void printHelp() {
#endif #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 // Tabla de comandos disponibles
static const std::vector<ConsoleCommand> COMMANDS = { static const std::vector<ConsoleCommand> COMMANDS = {
// SS [ON|OFF|SIZE|UPSCALE [NEAREST|LINEAR]|DOWNSCALE [BILINEAR|LANCZOS2|LANCZOS3]] — Supersampling // SS [ON|OFF|SIZE|UPSCALE [NEAREST|LINEAR]|DOWNSCALE [BILINEAR|LANCZOS2|LANCZOS3]] — Supersampling
@@ -154,7 +169,7 @@ static const std::vector<ConsoleCommand> COMMANDS = {
Screen::get()->setLinearUpscale(true); Screen::get()->setLinearUpscale(true);
return "Upscale: Linear"; return "Upscale: Linear";
} }
return "Usage: SS UPSCALE [NEAREST|LINEAR]"; return "usage: ss upscale [nearest|linear]";
} }
if (!args.empty() && args[0] == "DOWNSCALE") { if (!args.empty() && args[0] == "DOWNSCALE") {
if (args.size() == 1) { if (args.size() == 1) {
@@ -164,7 +179,7 @@ static const std::vector<ConsoleCommand> COMMANDS = {
if (args[1] == "BILINEAR") { algo = 0; } if (args[1] == "BILINEAR") { algo = 0; }
if (args[1] == "LANCZOS2") { algo = 1; } if (args[1] == "LANCZOS2") { algo = 1; }
if (args[1] == "LANCZOS3") { algo = 2; } if (args[1] == "LANCZOS3") { algo = 2; }
if (algo == -1) { return "Usage: SS DOWNSCALE [BILINEAR|LANCZOS2|LANCZOS3]"; } if (algo == -1) { return "usage: ss downscale [bilinear|lanczos2|lanczos3]"; }
if (Options::video.downscale_algo == algo) { if (Options::video.downscale_algo == algo) {
return std::string("Downscale already ") + std::string(DOWNSCALE_NAMES[static_cast<size_t>(algo)]); return std::string("Downscale already ") + std::string(DOWNSCALE_NAMES[static_cast<size_t>(algo)]);
} }
@@ -185,14 +200,19 @@ static const std::vector<ConsoleCommand> COMMANDS = {
Screen::get()->toggleSupersampling(); Screen::get()->toggleSupersampling();
return "PostFX Supersampling OFF"; return "PostFX Supersampling OFF";
} }
return "Usage: SS [ON|OFF|SIZE|UPSCALE [NEAREST|LINEAR]|DOWNSCALE [BILINEAR|LANCZOS2|LANCZOS3]]"; 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) // 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 { {.keyword = "SHADER", .execute = [](const std::vector<std::string>& args) -> std::string {
if (args.empty()) { if (args.empty()) {
Screen::get()->toggleShaders(); Screen::get()->toggleShaders();
return std::string("Shaders ") + (Options::video.postfx ? "ON" : "OFF"); return std::string("Shader ") + (Options::video.postfx ? "ON" : "OFF");
} }
if (args[0] == "ON") { if (args[0] == "ON") {
if (Options::video.postfx) { return "Shader already ON"; } if (Options::video.postfx) { return "Shader already ON"; }
@@ -206,11 +226,11 @@ static const std::vector<ConsoleCommand> COMMANDS = {
} }
if (args[0] == "POSTFX") { if (args[0] == "POSTFX") {
Screen::get()->setActiveShader(Rendering::ShaderType::POSTFX); Screen::get()->setActiveShader(Rendering::ShaderType::POSTFX);
return "Shader: POSTFX"; return "Shader: PostFX";
} }
if (args[0] == "CRTPI") { if (args[0] == "CRTPI") {
Screen::get()->setActiveShader(Rendering::ShaderType::CRTPI); Screen::get()->setActiveShader(Rendering::ShaderType::CRTPI);
return "Shader: CRTPI"; return "Shader: CrtPi";
} }
if (args[0] == "NEXT") { if (args[0] == "NEXT") {
// SHADER NEXT PRESET → cicla presets del shader activo // SHADER NEXT PRESET → cicla presets del shader activo
@@ -232,16 +252,20 @@ static const std::vector<ConsoleCommand> COMMANDS = {
return "PostFX preset: " + return "PostFX preset: " +
Options::postfx_presets[static_cast<size_t>(Options::current_postfx_preset)].name; Options::postfx_presets[static_cast<size_t>(Options::current_postfx_preset)].name;
} }
// SHADER NEXT → cicla entre tipos de shader (POSTFX ↔ CRTPI) // SHADER NEXT → cicla entre tipos de shader (PostFX ↔ CrtPi)
Screen::get()->nextShader(); Screen::get()->nextShader();
return std::string("Shader: ") + return std::string("Shader: ") +
(Options::current_shader == Rendering::ShaderType::CRTPI ? "CRTPI" : "POSTFX"); (Options::current_shader == Rendering::ShaderType::CRTPI ? "CrtPi" : "PostFX");
} }
return "Usage: SHADER [ON|OFF|NEXT [PRESET]|POSTFX|CRTPI]"; 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) // BORDER [ON|OFF] — Borde decorativo (B)
{.keyword = "BORDER", .execute = BOOL_TOGGLE_CMD("Border", Options::video.border.enabled, Screen::get()->toggleBorder())}, {.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 // FULLSCREEN [ON|OFF [PLEASE]] — Pantalla completa (F3); OFF bloqueado en kiosk sin PLEASE
{.keyword = "FULLSCREEN", .execute = [](const std::vector<std::string>& args) -> std::string { {.keyword = "FULLSCREEN", .execute = [](const std::vector<std::string>& args) -> std::string {
@@ -268,12 +292,13 @@ static const std::vector<ConsoleCommand> COMMANDS = {
Screen::get()->toggleVideoMode(); Screen::get()->toggleVideoMode();
return std::string("Fullscreen ") + (Options::video.fullscreen ? "ON" : "OFF"); return std::string("Fullscreen ") + (Options::video.fullscreen ? "ON" : "OFF");
} }
return "Usage: FULLSCREEN [ON|OFF]"; return "usage: fullscreen [on|off]";
}}, },
.completions = {{"FULLSCREEN", {"ON", "OFF"}}}},
// ZOOM UP/DOWN — Zoom de ventana (F1/F2) // ZOOM UP/DOWN — Zoom de ventana (F1/F2)
{.keyword = "ZOOM", .execute = [](const std::vector<std::string>& args) -> std::string { {.keyword = "ZOOM", .execute = [](const std::vector<std::string>& args) -> std::string {
if (args.empty()) { return "Usage: ZOOM [UP|DOWN]"; } if (args.empty()) { return "usage: zoom [up|down|<1-" + std::to_string(Screen::get()->getMaxZoom()) + ">]"; }
if (args[0] == "UP") { if (args[0] == "UP") {
if (!Screen::get()->incWindowZoom()) { return "Max zoom reached"; } if (!Screen::get()->incWindowZoom()) { return "Max zoom reached"; }
return "Zoom " + std::to_string(Options::window.zoom); return "Zoom " + std::to_string(Options::window.zoom);
@@ -282,15 +307,27 @@ static const std::vector<ConsoleCommand> COMMANDS = {
if (!Screen::get()->decWindowZoom()) { return "Min zoom reached"; } if (!Screen::get()->decWindowZoom()) { return "Min zoom reached"; }
return "Zoom " + std::to_string(Options::window.zoom); return "Zoom " + std::to_string(Options::window.zoom);
} }
return "Usage: ZOOM [UP|DOWN]"; // 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) // INTSCALE [ON|OFF] — Escalado entero (F7)
{.keyword = "INTSCALE", .execute = [](const std::vector<std::string>& args) -> std::string { {.keyword = "INTSCALE", .execute = [](const std::vector<std::string>& args) -> std::string {
const bool ON = args.empty() ? !Options::video.integer_scale const bool ON = args.empty() ? !Options::video.integer_scale
: (args[0] == "ON"); : (args[0] == "ON");
if (!args.empty() && args[0] != "ON" && args[0] != "OFF") { if (!args.empty() && args[0] != "ON" && args[0] != "OFF") {
return "Usage: INTSCALE [ON|OFF]"; return "usage: intscale [on|off]";
} }
if (ON == Options::video.integer_scale) { if (ON == Options::video.integer_scale) {
return std::string("IntScale already ") + (ON ? "ON" : "OFF"); return std::string("IntScale already ") + (ON ? "ON" : "OFF");
@@ -298,10 +335,11 @@ static const std::vector<ConsoleCommand> COMMANDS = {
Screen::get()->toggleIntegerScale(); Screen::get()->toggleIntegerScale();
Screen::get()->setVideoMode(Options::video.fullscreen); Screen::get()->setVideoMode(Options::video.fullscreen);
return std::string("IntScale ") + (Options::video.integer_scale ? "ON" : "OFF"); return std::string("IntScale ") + (Options::video.integer_scale ? "ON" : "OFF");
}}, },
.completions = {{"INTSCALE", {"ON", "OFF"}}}},
// VSYNC [ON|OFF] — Sincronización vertical // VSYNC [ON|OFF] — Sincronización vertical
{.keyword = "VSYNC", .execute = BOOL_TOGGLE_CMD("VSync", Options::video.vertical_sync, Screen::get()->toggleVSync())}, {.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) // DRIVER [LIST|AUTO|<nombre>] — Driver GPU (aplica en el próximo arranque)
{.keyword = "DRIVER", .execute = [](const std::vector<std::string>& args) -> std::string { {.keyword = "DRIVER", .execute = [](const std::vector<std::string>& args) -> std::string {
@@ -357,20 +395,31 @@ static const std::vector<ConsoleCommand> COMMANDS = {
Options::video.gpu_preferred_driver = driver_lower; Options::video.gpu_preferred_driver = driver_lower;
Options::saveToFile(); Options::saveToFile();
return "Driver: " + driver_lower + " (restart)"; return "Driver: " + driver_lower + " (restart)";
}}, },
.completions = {{"DRIVER", {"LIST", "AUTO", "NONE"}}}},
// PALETTE NEXT/PREV — Paleta de colores (F5/F6) // PALETTE NEXT/PREV/<nombre> — Paleta de colores (F5/F6 o por nombre)
{.keyword = "PALETTE", .execute = [](const std::vector<std::string>& args) -> std::string { {.keyword = "PALETTE", .execute = [](const std::vector<std::string>& args) -> std::string {
if (args.empty()) { return "Usage: PALETTE [NEXT|PREV]"; } const auto palName = []() -> std::string {
std::string name = Options::video.palette;
std::ranges::transform(name, name.begin(), ::tolower);
return name;
};
if (args.empty()) { return "usage: palette [next|prev|<name>]"; }
if (args[0] == "NEXT") { if (args[0] == "NEXT") {
Screen::get()->nextPalette(); Screen::get()->nextPalette();
return "Palette: " + Options::video.palette; return "Palette: " + palName();
} }
if (args[0] == "PREV") { if (args[0] == "PREV") {
Screen::get()->previousPalette(); Screen::get()->previousPalette();
return "Palette: " + Options::video.palette; return "Palette: " + palName();
} }
return "Usage: PALETTE [NEXT|PREV]"; 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 #ifdef _DEBUG
@@ -389,15 +438,16 @@ static const std::vector<ConsoleCommand> COMMANDS = {
GameControl::toggle_debug_mode(); GameControl::toggle_debug_mode();
return "Debug mode OFF"; return "Debug mode OFF";
} }
if (!args.empty()) { return "Usage: DEBUG [ON|OFF]"; } if (!args.empty()) { return "usage: debug [on|off]"; }
GameControl::toggle_debug_mode(); GameControl::toggle_debug_mode();
return std::string("Debug mode ") + (Debug::get()->isEnabled() ? "ON" : "OFF"); 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 // 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 { {.keyword = "ROOM", .execute = [](const std::vector<std::string>& args) -> std::string {
if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; } if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; }
if (args.empty()) { return "Usage: ROOM <1-60>|NEXT|PREV"; } if (args.empty()) { return "usage: room <1-60>|next|prev"; }
int num = 0; int num = 0;
if (args[0] == "NEXT" || args[0] == "PREV") { if (args[0] == "NEXT" || args[0] == "PREV") {
if (!GameControl::get_current_room) { return "Game not initialized"; } if (!GameControl::get_current_room) { return "Game not initialized"; }
@@ -409,7 +459,7 @@ static const std::vector<ConsoleCommand> COMMANDS = {
} else { } else {
try { try {
num = std::stoi(args[0]); num = std::stoi(args[0]);
} catch (...) { return "Usage: ROOM <1-60>|NEXT|PREV"; } } catch (...) { return "usage: room <1-60>|next|prev"; }
} }
if (num < 1 || num > 60) { return "Room must be between 1 and 60"; } if (num < 1 || num > 60) { return "Room must be between 1 and 60"; }
char buf[16]; char buf[16];
@@ -418,7 +468,8 @@ static const std::vector<ConsoleCommand> COMMANDS = {
return std::string("Room: ") + buf; return std::string("Room: ") + buf;
} }
return std::string("Room not found: ") + buf; return std::string("Room not found: ") + buf;
}}, },
.completions = {{"ROOM", {"NEXT", "PREV"}}}},
#endif #endif
@@ -433,31 +484,39 @@ static const std::vector<ConsoleCommand> COMMANDS = {
Notifier::get()->show({Locale::get()->get("achievements.header"), Locale::get()->get("achievements.c1")}, Notifier::Style::CHEEVO, -1, false); // NOLINT(readability-static-accessed-through-instance) 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"; return "Cheevo notification shown";
} }
if (args.empty() || args[0] != "INFO") { return "Usage: SHOW [INFO|NOTIFICATION|CHEEVO]"; } if (args.empty() || args[0] != "INFO") { return "usage: show [info|notification|cheevo]"; }
#else #else
if (args.empty() || args[0] != "INFO") { return "Usage: SHOW [INFO]"; } if (args.empty() || args[0] != "INFO") { return "usage: show [info]"; }
#endif #endif
if (RenderInfo::get()->isActive()) { return "Info overlay already ON"; } if (RenderInfo::get()->isActive()) { return "Info overlay already ON"; }
RenderInfo::get()->toggle(); RenderInfo::get()->toggle();
return "Info overlay ON"; return "Info overlay ON";
},
.completions = {
#ifdef _DEBUG
{"SHOW", {"INFO", "NOTIFICATION", "CHEEVO"}},
#else
{"SHOW", {"INFO"}},
#endif
}}, }},
// HIDE INFO — disponible en Release // HIDE INFO — disponible en Release
{.keyword = "HIDE", .execute = [](const std::vector<std::string>& args) -> std::string { {.keyword = "HIDE", .execute = [](const std::vector<std::string>& args) -> std::string {
if (args.empty() || args[0] != "INFO") { return "Usage: HIDE [INFO]"; } if (args.empty() || args[0] != "INFO") { return "usage: hide [info]"; }
if (!RenderInfo::get()->isActive()) { return "Info overlay already OFF"; } if (!RenderInfo::get()->isActive()) { return "Info overlay already OFF"; }
RenderInfo::get()->toggle(); RenderInfo::get()->toggle();
return "Info overlay OFF"; return "Info overlay OFF";
}}, },
.completions = {{"HIDE", {"INFO"}}}},
// CHEAT <subcomando> — Trucos de juego; solo en escena GAME; no aparece en ayuda en builds Release // 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 { {.keyword = "CHEAT", .execute = [](const std::vector<std::string>& args) -> std::string {
if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; } 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]"; } if (args.empty()) { return "usage: cheat [infinite lives|invincibility|open the jail|close the jail]"; }
// CHEAT INFINITE LIVES [ON|OFF] // CHEAT INFINITE LIVES [ON|OFF]
if (args[0] == "INFINITE") { if (args[0] == "INFINITE") {
if (args.size() < 2 || args[1] != "LIVES") { return "Usage: CHEAT INFINITE LIVES [ON|OFF]"; } if (args.size() < 2 || args[1] != "LIVES") { return "usage: cheat infinite lives [on|off]"; }
auto& cheat = Options::cheats.infinite_lives; auto& cheat = Options::cheats.infinite_lives;
using State = Options::Cheat::State; using State = Options::Cheat::State;
const std::vector<std::string> REST(args.begin() + 2, args.end()); const std::vector<std::string> REST(args.begin() + 2, args.end());
@@ -470,7 +529,7 @@ static const std::vector<ConsoleCommand> COMMANDS = {
if (cheat == State::DISABLED) { return "Infinite lives already OFF"; } if (cheat == State::DISABLED) { return "Infinite lives already OFF"; }
cheat = State::DISABLED; cheat = State::DISABLED;
} else { } else {
return "Usage: CHEAT INFINITE LIVES [ON|OFF]"; return "usage: cheat infinite lives [on|off]";
} }
if (GameControl::refresh_player_color) { GameControl::refresh_player_color(); } if (GameControl::refresh_player_color) { GameControl::refresh_player_color(); }
return std::string("Infinite lives ") + (cheat == State::ENABLED ? "ON" : "OFF"); return std::string("Infinite lives ") + (cheat == State::ENABLED ? "ON" : "OFF");
@@ -489,7 +548,7 @@ static const std::vector<ConsoleCommand> COMMANDS = {
if (cheat == State::DISABLED) { return "Invincibility already OFF"; } if (cheat == State::DISABLED) { return "Invincibility already OFF"; }
cheat = State::DISABLED; cheat = State::DISABLED;
} else { } else {
return "Usage: CHEAT INVINCIBILITY [ON|OFF]"; return "usage: cheat invincibility [on|off]";
} }
if (GameControl::refresh_player_color) { GameControl::refresh_player_color(); } if (GameControl::refresh_player_color) { GameControl::refresh_player_color(); }
return std::string("Invincibility ") + (cheat == State::ENABLED ? "ON" : "OFF"); return std::string("Invincibility ") + (cheat == State::ENABLED ? "ON" : "OFF");
@@ -497,7 +556,7 @@ static const std::vector<ConsoleCommand> COMMANDS = {
// CHEAT OPEN THE JAIL // CHEAT OPEN THE JAIL
if (args[0] == "OPEN") { if (args[0] == "OPEN") {
if (args.size() < 3 || args[1] != "THE" || args[2] != "JAIL") { return "Usage: CHEAT OPEN THE JAIL"; } 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"; } if (Options::cheats.jail_is_open == Options::Cheat::State::ENABLED) { return "Jail already open"; }
Options::cheats.jail_is_open = Options::Cheat::State::ENABLED; Options::cheats.jail_is_open = Options::Cheat::State::ENABLED;
return "Jail opened"; return "Jail opened";
@@ -505,13 +564,24 @@ static const std::vector<ConsoleCommand> COMMANDS = {
// CHEAT CLOSE THE JAIL // CHEAT CLOSE THE JAIL
if (args[0] == "CLOSE") { if (args[0] == "CLOSE") {
if (args.size() < 3 || args[1] != "THE" || args[2] != "JAIL") { return "Usage: CHEAT CLOSE THE JAIL"; } 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"; } if (Options::cheats.jail_is_open == Options::Cheat::State::DISABLED) { return "Jail already closed"; }
Options::cheats.jail_is_open = Options::Cheat::State::DISABLED; Options::cheats.jail_is_open = Options::Cheat::State::DISABLED;
return "Jail closed"; return "Jail closed";
} }
return "Usage: CHEAT [INFINITE LIVES|INVINCIBILITY|OPEN THE JAIL|CLOSE THE JAIL]"; 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 PLAYER SKIN <1|2> — Cambia la skin del jugador (disponible en todos los builds, GAME)
@@ -525,7 +595,7 @@ static const std::vector<ConsoleCommand> COMMANDS = {
try { try {
num = std::stoi(args[2]); num = std::stoi(args[2]);
} catch (...) {} } catch (...) {}
if (num < 1 || num > 2) { return "Usage: SET PLAYER SKIN <1|2>"; } if (num < 1 || num > 2) { return "usage: set player skin <1|2>"; }
if (!GameControl::change_player_skin) { return "Game not initialized"; } if (!GameControl::change_player_skin) { return "Game not initialized"; }
GameControl::change_player_skin(num); GameControl::change_player_skin(num);
return "Player skin: " + std::to_string(num); return "Player skin: " + std::to_string(num);
@@ -538,27 +608,29 @@ static const std::vector<ConsoleCommand> COMMANDS = {
if (args.size() >= 3) { if (args.size() >= 3) {
if (args[2] == "GAME") { if (args[2] == "GAME") {
target = SceneManager::Scene::GAME; target = SceneManager::Scene::GAME;
name = "GAME"; name = "game";
} else if (args[2] == "LOGO") { } else if (args[2] == "LOGO") {
target = SceneManager::Scene::LOGO; target = SceneManager::Scene::LOGO;
name = "LOGO"; name = "logo";
} else if (args[2] == "LOADING") { } else if (args[2] == "LOADING") {
target = SceneManager::Scene::LOADING_SCREEN; target = SceneManager::Scene::LOADING_SCREEN;
name = "LOADING"; name = "loading";
} else if (args[2] == "TITLE") { } else if (args[2] == "TITLE") {
target = SceneManager::Scene::TITLE; target = SceneManager::Scene::TITLE;
name = "TITLE"; name = "title";
} else if (args[2] == "CREDITS") { } else if (args[2] == "CREDITS") {
target = SceneManager::Scene::CREDITS; target = SceneManager::Scene::CREDITS;
name = "CREDITS"; name = "credits";
} else if (args[2] == "ENDING") { } else if (args[2] == "ENDING") {
target = SceneManager::Scene::ENDING; target = SceneManager::Scene::ENDING;
name = "ENDING"; name = "ending";
} else if (args[2] == "ENDING2") { } else if (args[2] == "ENDING2") {
target = SceneManager::Scene::ENDING2; target = SceneManager::Scene::ENDING2;
name = "ENDING2"; name = "ending2";
} else { } else {
return "Unknown scene: " + args[2]; 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()->setInitialScene(target);
@@ -570,23 +642,23 @@ static const std::vector<ConsoleCommand> COMMANDS = {
// SET ITEMS <0-200> — Fija el contador de items recogidos // SET ITEMS <0-200> — Fija el contador de items recogidos
if (args[0] == "ITEMS") { if (args[0] == "ITEMS") {
if (args.size() < 2) { return "Usage: SET ITEMS <0-200>"; } if (args.size() < 2) { return "usage: set items <0-200>"; }
int count = 0; int count = 0;
try { try {
count = std::stoi(args[1]); count = std::stoi(args[1]);
} catch (...) { return "Usage: SET ITEMS <0-200>"; } } catch (...) { return "usage: set items <0-200>"; }
if (count < 0 || count > 200) { return "Items must be between 0 and 200"; } if (count < 0 || count > 200) { return "Items must be between 0 and 200"; }
if (!GameControl::set_items) { return "Game not initialized"; } if (!GameControl::set_items) { return "Game not initialized"; }
GameControl::set_items(count); GameControl::set_items(count);
return "Items: " + std::to_string(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>"; } 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_ROOM = args.size() == 1 || (args.size() >= 2 && args[1] == "ROOM");
const bool DO_POS = args.size() == 1 || (args.size() >= 2 && args[1] == "POS"); 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 (!DO_ROOM && !DO_POS) { return "usage: set initial [room|pos|scene]"; }
if (!GameControl::set_initial_room || !GameControl::set_initial_pos) { return "Game not initialized"; } if (!GameControl::set_initial_room || !GameControl::set_initial_pos) { return "Game not initialized"; }
std::string result; std::string result;
@@ -597,7 +669,18 @@ static const std::vector<ConsoleCommand> COMMANDS = {
} }
return result; return result;
#else #else
return "Usage: SET PLAYER SKIN <1|2>"; 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 #endif
}}, }},
@@ -605,7 +688,7 @@ static const std::vector<ConsoleCommand> COMMANDS = {
// SCENE [LOGO|LOADING|TITLE|CREDITS|GAME|ENDING|ENDING2|RESTART] — Cambiar o reiniciar escena; solo en 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 { {.keyword = "SCENE", .execute = [](const std::vector<std::string>& args) -> std::string {
if (Options::kiosk.enabled) { return "Not allowed in kiosk mode"; } 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.empty()) { return "usage: scene [logo|loading|title|credits|game|ending|ending2|restart]"; }
// RESTART: reinicia la escena actual (funciona desde cualquier escena) // RESTART: reinicia la escena actual (funciona desde cualquier escena)
if (args[0] == "RESTART") { if (args[0] == "RESTART") {
@@ -633,14 +716,17 @@ static const std::vector<ConsoleCommand> COMMANDS = {
if (args[0] == "ENDING") { return GO_TO(SceneManager::Scene::ENDING, "Ending"); } if (args[0] == "ENDING") { return GO_TO(SceneManager::Scene::ENDING, "Ending"); }
if (args[0] == "ENDING2") { return GO_TO(SceneManager::Scene::ENDING2, "Ending 2"); } if (args[0] == "ENDING2") { return GO_TO(SceneManager::Scene::ENDING2, "Ending 2"); }
return "Unknown scene: " + args[0]; return "Unknown scene: " + args[0];
}}, },
.completions = {{"SCENE", {"LOGO", "LOADING", "TITLE", "CREDITS", "GAME", "ENDING", "ENDING2", "RESTART"}}}},
#endif #endif
// RESTART — Reiniciar desde el principio (equivale a SCENE LOGO) // RESTART — Reiniciar desde el principio (equivale a SCENE LOGO)
{.keyword = "RESTART", .execute = [](const std::vector<std::string>&) -> std::string { {.keyword = "RESTART", .execute = [](const std::vector<std::string>&) -> std::string {
SceneManager::current = SceneManager::Scene::LOGO; SceneManager::current = SceneManager::Scene::LOGO;
Audio::get()->stopMusic();
return "Restarting..."; return "Restarting...";
}}, },
.instant = true},
// KIOSK [ON|OFF PLEASE|PLEASE] — Modo kiosko // KIOSK [ON|OFF PLEASE|PLEASE] — Modo kiosko
{.keyword = "KIOSK", .execute = [](const std::vector<std::string>& args) -> std::string { {.keyword = "KIOSK", .execute = [](const std::vector<std::string>& args) -> std::string {
@@ -659,8 +745,9 @@ static const std::vector<ConsoleCommand> COMMANDS = {
if (!Options::video.fullscreen) { Screen::get()->toggleVideoMode(); } if (!Options::video.fullscreen) { Screen::get()->toggleVideoMode(); }
return "Kiosk mode ON"; return "Kiosk mode ON";
} }
return "Usage: KIOSK [ON]"; return "usage: kiosk [on]";
}}, },
.completions = {{"KIOSK", {"ON"}}}},
// AUDIO [ON|OFF|VOL <0-100>] — Audio maestro (estado + volumen) // AUDIO [ON|OFF|VOL <0-100>] — Audio maestro (estado + volumen)
{.keyword = "AUDIO", .execute = [](const std::vector<std::string>& args) -> std::string { {.keyword = "AUDIO", .execute = [](const std::vector<std::string>& args) -> std::string {
@@ -688,10 +775,11 @@ static const std::vector<ConsoleCommand> COMMANDS = {
Options::audio.volume = static_cast<float>(VAL) / 100.0F; Options::audio.volume = static_cast<float>(VAL) / 100.0F;
Audio::get()->enable(Options::audio.enabled); Audio::get()->enable(Options::audio.enabled);
return "Audio vol:" + std::to_string(VAL); return "Audio vol:" + std::to_string(VAL);
} catch (...) { return "Usage: AUDIO VOL <0-100>"; } } catch (...) { return "usage: audio vol <0-100>"; }
} }
return "Usage: AUDIO [ON|OFF|VOL N]"; return "usage: audio [on|off|vol n]";
}}, },
.completions = {{"AUDIO", {"ON", "OFF", "VOL"}}}},
// MUSIC [ON|OFF|VOL <0-100>] — Volumen e interruptor de música // MUSIC [ON|OFF|VOL <0-100>] — Volumen e interruptor de música
{.keyword = "MUSIC", .execute = [](const std::vector<std::string>& args) -> std::string { {.keyword = "MUSIC", .execute = [](const std::vector<std::string>& args) -> std::string {
@@ -723,10 +811,11 @@ static const std::vector<ConsoleCommand> COMMANDS = {
Audio::get()->setMusicVolume(Options::audio.music.volume); Audio::get()->setMusicVolume(Options::audio.music.volume);
} }
return "Music vol:" + std::to_string(VAL); return "Music vol:" + std::to_string(VAL);
} catch (...) { return "Usage: MUSIC VOL <0-100>"; } } catch (...) { return "usage: music vol <0-100>"; }
} }
return "Usage: MUSIC [ON|OFF|VOL N]"; 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 // SOUND [ON|OFF|VOL <0-100>] — Volumen e interruptor de efectos de sonido
{.keyword = "SOUND", .execute = [](const std::vector<std::string>& args) -> std::string { {.keyword = "SOUND", .execute = [](const std::vector<std::string>& args) -> std::string {
@@ -758,10 +847,11 @@ static const std::vector<ConsoleCommand> COMMANDS = {
Audio::get()->setSoundVolume(Options::audio.sound.volume); Audio::get()->setSoundVolume(Options::audio.sound.volume);
} }
return "Sound vol:" + std::to_string(VAL); return "Sound vol:" + std::to_string(VAL);
} catch (...) { return "Usage: SOUND VOL <0-100>"; } } catch (...) { return "usage: sound vol <0-100>"; }
} }
return "Usage: SOUND [ON|OFF|VOL N]"; return "usage: sound [on|off|vol n]";
}}, },
.completions = {{"SOUND", {"ON", "OFF", "VOL"}}}},
// EXIT / QUIT — Cerrar la aplicacion (bloqueado en kiosk) // EXIT / QUIT — Cerrar la aplicacion (bloqueado en kiosk)
{.keyword = "EXIT", .execute = [](const std::vector<std::string>& args) -> std::string { {.keyword = "EXIT", .execute = [](const std::vector<std::string>& args) -> std::string {
@@ -770,14 +860,16 @@ static const std::vector<ConsoleCommand> COMMANDS = {
} }
SceneManager::current = SceneManager::Scene::QUIT; SceneManager::current = SceneManager::Scene::QUIT;
return "Quitting..."; return "Quitting...";
}}, },
.instant = true},
{.keyword = "QUIT", .execute = [](const std::vector<std::string>& args) -> std::string { {.keyword = "QUIT", .execute = [](const std::vector<std::string>& args) -> std::string {
if (Options::kiosk.enabled && (args.empty() || args[0] != "PLEASE")) { if (Options::kiosk.enabled && (args.empty() || args[0] != "PLEASE")) {
return "Not allowed in kiosk mode"; return "Not allowed in kiosk mode";
} }
SceneManager::current = SceneManager::Scene::QUIT; SceneManager::current = SceneManager::Scene::QUIT;
return "Quitting..."; return "Quitting...";
}}, },
.instant = true},
// SIZE — Devuelve el tamaño actual de la ventana en píxeles // SIZE — Devuelve el tamaño actual de la ventana en píxeles
{.keyword = "SIZE", .execute = [](const std::vector<std::string>&) -> std::string { {.keyword = "SIZE", .execute = [](const std::vector<std::string>&) -> std::string {
@@ -787,17 +879,77 @@ static const std::vector<ConsoleCommand> COMMANDS = {
return std::to_string(w) + "x" + std::to_string(h); return std::to_string(w) + "x" + std::to_string(h);
}}, }},
// HELP / ? — Muestra ayuda en la terminal del sistema // HELP / ? — Muestra ayuda en la terminal del sistema y lista de comandos en consola
{.keyword = "HELP", .execute = [](const std::vector<std::string>&) -> std::string { {.keyword = "HELP", .execute = [](const std::vector<std::string>&) -> std::string {
printHelp(); printHelp();
return "Help printed to terminal"; 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 { {.keyword = "?", .execute = [](const std::vector<std::string>&) -> std::string {
printHelp(); printHelp();
return "Help printed to terminal"; 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)
static auto calcTargetHeight(int num_msg_lines) -> float {
constexpr int TEXT_SIZE = 6;
constexpr int PADDING_IN_V = TEXT_SIZE / 2;
return static_cast<float>((TEXT_SIZE * (num_msg_lines + 1)) + (PADDING_IN_V * 2));
}
// Divide text en líneas respetando los \n existentes y haciendo word-wrap por ancho en píxeles
auto Console::wrapText(const std::string& text) const -> std::vector<std::string> {
constexpr int PADDING_IN_H = 6; // TEXT_SIZE; simétrico a ambos lados
const int MAX_PX = static_cast<int>(Options::game.width) - (2 * PADDING_IN_H);
std::vector<std::string> result;
std::istringstream segment_stream(text);
std::string segment;
while (std::getline(segment_stream, segment)) {
if (segment.empty()) {
result.emplace_back();
continue;
}
std::string current_line;
std::istringstream word_stream(segment);
std::string word;
while (word_stream >> word) {
const std::string TEST = current_line.empty() ? word : (current_line + ' ' + word);
if (text_->length(TEST) <= MAX_PX) {
current_line = TEST;
} else {
if (!current_line.empty()) { result.push_back(current_line); }
current_line = word;
}
}
if (!current_line.empty()) { result.push_back(current_line); }
}
if (result.empty()) { result.emplace_back(); }
return result;
}
// ── Singleton ───────────────────────────────────────────────────────────────── // ── Singleton ─────────────────────────────────────────────────────────────────
// [SINGLETON] // [SINGLETON]
@@ -822,12 +974,18 @@ auto Console::get() -> Console* {
// Constructor // Constructor
Console::Console(const std::string& font_name) Console::Console(const std::string& font_name)
: text_(Resource::Cache::get()->getText(font_name)) { : text_(Resource::Cache::get()->getText(font_name)) {
const int TEXT_SIZE = 6; msg_lines_ = {std::string(CONSOLE_NAME) + " " + std::string(CONSOLE_VERSION)};
const int PADDING_IN_V = TEXT_SIZE / 2; height_ = calcTargetHeight(static_cast<int>(msg_lines_.size()));
height_ = static_cast<float>((TEXT_SIZE * 2) + (PADDING_IN_V * 2)); target_height_ = height_;
y_ = -height_; y_ = -height_;
msg_line_ = std::string(CONSOLE_NAME) + " " + std::string(CONSOLE_VERSION); // 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); }
}
}
buildSurface(); buildSurface();
} }
@@ -849,9 +1007,9 @@ void Console::buildSurface() {
// Redibuja el texto dinámico sobre la surface (fondo + borde + líneas) // Redibuja el texto dinámico sobre la surface (fondo + borde + líneas)
void Console::redrawText() { void Console::redrawText() {
const float WIDTH = Options::game.width; const float WIDTH = Options::game.width;
const int TEXT_SIZE = 6; constexpr int TEXT_SIZE = 6;
const int PADDING_IN_H = TEXT_SIZE; constexpr int PADDING_IN_H = TEXT_SIZE;
const int PADDING_IN_V = TEXT_SIZE / 2; constexpr int PADDING_IN_V = TEXT_SIZE / 2;
auto previous_renderer = Screen::get()->getRendererSurface(); auto previous_renderer = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(surface_); Screen::get()->setRendererSurface(surface_);
@@ -861,13 +1019,21 @@ void Console::redrawText() {
SDL_FRect rect = {.x = 0, .y = 0, .w = WIDTH, .h = height_}; SDL_FRect rect = {.x = 0, .y = 0, .w = WIDTH, .h = height_};
surface_->drawRectBorder(&rect, BORDER_COLOR); surface_->drawRectBorder(&rect, BORDER_COLOR);
// Línea 1: mensajes // Líneas de mensaje con efecto typewriter (solo muestra los primeros typewriter_chars_)
text_->writeColored(PADDING_IN_H, PADDING_IN_V, msg_line_, MSG_COLOR); int y_pos = PADDING_IN_V;
int remaining = typewriter_chars_;
for (const auto& line : msg_lines_) {
if (remaining <= 0) { break; }
const int VISIBLE = std::min(remaining, static_cast<int>(line.size()));
text_->writeColored(PADDING_IN_H, y_pos, line.substr(0, VISIBLE), MSG_COLOR);
remaining -= VISIBLE;
y_pos += TEXT_SIZE;
}
// Línea 2: prompt + input + cursor // Línea de input (siempre la última)
const bool SHOW_CURSOR = cursor_visible_ && (static_cast<int>(input_line_.size()) < MAX_LINE_CHARS); const bool SHOW_CURSOR = cursor_visible_ && (static_cast<int>(input_line_.size()) < MAX_LINE_CHARS);
const std::string INPUT_STR = "> " + input_line_ + (SHOW_CURSOR ? "_" : ""); const std::string INPUT_STR = "> " + input_line_ + (SHOW_CURSOR ? "_" : "");
text_->writeColored(PADDING_IN_H, PADDING_IN_V + TEXT_SIZE, INPUT_STR, BORDER_COLOR); text_->writeColored(PADDING_IN_H, y_pos, INPUT_STR, BORDER_COLOR);
Screen::get()->setRendererSurface(previous_renderer); Screen::get()->setRendererSurface(previous_renderer);
} }
@@ -888,6 +1054,44 @@ void Console::update(float delta_time) {
} }
} }
// Efecto typewriter: revelar letras una a una (solo cuando ACTIVE)
if (status_ == Status::ACTIVE) {
int total_chars = 0;
for (const auto& line : msg_lines_) { total_chars += static_cast<int>(line.size()); }
if (typewriter_chars_ < total_chars) {
typewriter_timer_ += delta_time;
while (typewriter_timer_ >= TYPEWRITER_CHAR_DELAY && typewriter_chars_ < total_chars) {
typewriter_timer_ -= TYPEWRITER_CHAR_DELAY;
++typewriter_chars_;
}
}
}
// Animación de altura (resize cuando msg_lines_ cambia); solo en ACTIVE
if (status_ == Status::ACTIVE && height_ != target_height_) {
const float PREV_HEIGHT = height_;
if (height_ < target_height_) {
height_ = std::min(height_ + SLIDE_SPEED * delta_time, target_height_);
} else {
height_ = std::max(height_ - SLIDE_SPEED * delta_time, target_height_);
}
// Actualizar el Notifier incrementalmente con el delta de altura
if (Notifier::get() != nullptr) {
const int DELTA_PX = static_cast<int>(height_) - static_cast<int>(PREV_HEIGHT);
if (DELTA_PX > 0) {
Notifier::get()->addYOffset(DELTA_PX);
notifier_offset_applied_ += DELTA_PX;
} else if (DELTA_PX < 0) {
Notifier::get()->removeYOffset(-DELTA_PX);
notifier_offset_applied_ += DELTA_PX;
}
}
// Reconstruir la Surface al nuevo tamaño (pequeña: 256×~18-72px)
const float WIDTH = Options::game.width;
surface_ = std::make_shared<Surface>(WIDTH, height_);
sprite_->setSurface(surface_);
}
// Redibujar texto cada frame // Redibujar texto cada frame
redrawText(); redrawText();
@@ -905,6 +1109,9 @@ void Console::update(float delta_time) {
if (y_ <= -height_) { if (y_ <= -height_) {
y_ = -height_; y_ = -height_;
status_ = Status::HIDDEN; status_ = Status::HIDDEN;
// Resetear el mensaje una vez completamente oculta
msg_lines_ = {std::string(CONSOLE_NAME) + " " + std::string(CONSOLE_VERSION)};
target_height_ = calcTargetHeight(static_cast<int>(msg_lines_.size()));
} }
break; break;
} }
@@ -914,6 +1121,7 @@ void Console::update(float delta_time) {
SDL_FRect rect = {.x = 0, .y = y_, .w = Options::game.width, .h = height_}; SDL_FRect rect = {.x = 0, .y = y_, .w = Options::game.width, .h = height_};
sprite_->setPosition(rect); sprite_->setPosition(rect);
sprite_->setClip({.x = 0.0F, .y = 0.0F, .w = Options::game.width, .h = height_});
} }
// Renderiza la consola // Renderiza la consola
@@ -928,21 +1136,36 @@ void Console::render() {
void Console::toggle() { void Console::toggle() {
switch (status_) { switch (status_) {
case Status::HIDDEN: case Status::HIDDEN:
// Al abrir: la consola siempre empieza con 1 línea de mensaje (altura base)
target_height_ = calcTargetHeight(static_cast<int>(msg_lines_.size()));
height_ = target_height_;
y_ = -height_;
status_ = Status::RISING; status_ = Status::RISING;
input_line_.clear(); input_line_.clear();
cursor_timer_ = 0.0F; cursor_timer_ = 0.0F;
cursor_visible_ = true; cursor_visible_ = true;
// El mensaje inicial ("JDD Console v1.0") aparece completo, sin typewriter
typewriter_chars_ = static_cast<int>(msg_lines_[0].size());
typewriter_timer_ = 0.0F;
SDL_StartTextInput(SDL_GetKeyboardFocus()); SDL_StartTextInput(SDL_GetKeyboardFocus());
if (Notifier::get() != nullptr) { Notifier::get()->addYOffset(static_cast<int>(height_)); } if (Notifier::get() != nullptr) {
const int OFFSET = static_cast<int>(height_);
Notifier::get()->addYOffset(OFFSET);
notifier_offset_applied_ = OFFSET;
}
if (on_toggle) { on_toggle(true); } if (on_toggle) { on_toggle(true); }
break; break;
case Status::ACTIVE: case Status::ACTIVE:
// Al cerrar: mantener el texto visible hasta que esté completamente oculta
status_ = Status::VANISHING; status_ = Status::VANISHING;
msg_line_ = std::string(CONSOLE_NAME) + " " + std::string(CONSOLE_VERSION); target_height_ = height_; // No animar durante VANISHING
history_index_ = -1; history_index_ = -1;
saved_input_.clear(); saved_input_.clear();
SDL_StopTextInput(SDL_GetKeyboardFocus()); SDL_StopTextInput(SDL_GetKeyboardFocus());
if (Notifier::get() != nullptr) { Notifier::get()->removeYOffset(static_cast<int>(height_)); } if (Notifier::get() != nullptr) {
Notifier::get()->removeYOffset(notifier_offset_applied_);
notifier_offset_applied_ = 0;
}
if (on_toggle) { on_toggle(false); } if (on_toggle) { on_toggle(false); }
break; break;
default: default:
@@ -956,15 +1179,19 @@ void Console::handleEvent(const SDL_Event& event) {
if (status_ != Status::ACTIVE) { return; } if (status_ != Status::ACTIVE) { return; }
if (event.type == SDL_EVENT_TEXT_INPUT) { if (event.type == SDL_EVENT_TEXT_INPUT) {
// Filtrar caracteres de control (tab, newline, etc.)
if (static_cast<unsigned char>(event.text.text[0]) < 32) { return; }
if (static_cast<int>(input_line_.size()) < MAX_LINE_CHARS) { if (static_cast<int>(input_line_.size()) < MAX_LINE_CHARS) {
input_line_ += event.text.text; input_line_ += event.text.text;
} }
tab_matches_.clear();
return; return;
} }
if (event.type == SDL_EVENT_KEY_DOWN) { if (event.type == SDL_EVENT_KEY_DOWN) {
switch (event.key.scancode) { switch (event.key.scancode) {
case SDL_SCANCODE_BACKSPACE: case SDL_SCANCODE_BACKSPACE:
tab_matches_.clear();
if (!input_line_.empty()) { input_line_.pop_back(); } if (!input_line_.empty()) { input_line_.pop_back(); }
break; break;
case SDL_SCANCODE_RETURN: case SDL_SCANCODE_RETURN:
@@ -973,6 +1200,7 @@ void Console::handleEvent(const SDL_Event& event) {
break; break;
case SDL_SCANCODE_UP: case SDL_SCANCODE_UP:
// Navegar hacia atrás en el historial // Navegar hacia atrás en el historial
tab_matches_.clear();
if (history_index_ < static_cast<int>(history_.size()) - 1) { if (history_index_ < static_cast<int>(history_.size()) - 1) {
if (history_index_ == -1) { saved_input_ = input_line_; } if (history_index_ == -1) { saved_input_ = input_line_; }
++history_index_; ++history_index_;
@@ -981,6 +1209,7 @@ void Console::handleEvent(const SDL_Event& event) {
break; break;
case SDL_SCANCODE_DOWN: case SDL_SCANCODE_DOWN:
// Navegar hacia el presente en el historial // Navegar hacia el presente en el historial
tab_matches_.clear();
if (history_index_ >= 0) { if (history_index_ >= 0) {
--history_index_; --history_index_;
input_line_ = (history_index_ == -1) input_line_ = (history_index_ == -1)
@@ -988,6 +1217,56 @@ void Console::handleEvent(const SDL_Event& event) {
: history_[static_cast<size_t>(history_index_)]; : history_[static_cast<size_t>(history_index_)];
} }
break; break;
case SDL_SCANCODE_TAB: {
if (tab_matches_.empty()) {
// Calcular el input actual en mayúsculas
std::string upper;
for (unsigned char c : input_line_) { upper += static_cast<char>(std::toupper(c)); }
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);
}
}
} 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) {
// 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)) {
tab_matches_.emplace_back("PALETTE " + std::string(sv));
}
}
for (const auto& name : Screen::get()->getPaletteNames()) {
if (sub_prefix.empty() || std::string_view{name}.starts_with(sub_prefix)) {
tab_matches_.emplace_back("PALETTE " + name);
}
}
} else {
const auto it = tab_completions_.find(base_cmd);
if (it != tab_completions_.end()) {
for (const auto& arg : it->second) {
if (sub_prefix.empty() || std::string_view{arg}.starts_with(sub_prefix)) {
tab_matches_.emplace_back(base_cmd + " " + arg);
}
}
}
}
}
tab_index_ = -1;
}
if (tab_matches_.empty()) { break; }
tab_index_ = (tab_index_ + 1) % static_cast<int>(tab_matches_.size());
std::string result = tab_matches_[static_cast<size_t>(tab_index_)];
for (char& c : result) { c = static_cast<char>(std::tolower(static_cast<unsigned char>(c))); }
input_line_ = result;
break;
}
default: default:
break; break;
} }
@@ -1009,25 +1288,44 @@ void Console::processCommand() {
if (!TOKENS.empty()) { if (!TOKENS.empty()) {
const std::string& cmd = TOKENS[0]; const std::string& cmd = TOKENS[0];
const std::vector<std::string> ARGS(TOKENS.begin() + 1, TOKENS.end()); const std::vector<std::string> ARGS(TOKENS.begin() + 1, TOKENS.end());
std::string result;
bool found = false; bool found = false;
bool instant = false;
for (const auto& command : COMMANDS) { for (const auto& command : COMMANDS) {
if (command.keyword == cmd) { if (command.keyword == cmd) {
msg_line_ = command.execute(ARGS); result = command.execute(ARGS);
instant = command.instant;
found = true; found = true;
break; break;
} }
} }
if (!found) { if (!found) {
msg_line_ = "Unknown: " + cmd; std::string cmd_lower = cmd;
std::ranges::transform(cmd_lower, cmd_lower.begin(), ::tolower);
result = "Unknown: " + cmd_lower;
} }
if (static_cast<int>(msg_line_.size()) > MAX_LINE_CHARS) {
msg_line_.resize(MAX_LINE_CHARS); // Word-wrap automático según el ancho disponible en píxeles
msg_lines_ = wrapText(result);
// Actualizar la altura objetivo para animar el resize
target_height_ = calcTargetHeight(static_cast<int>(msg_lines_.size()));
// Typewriter: instantáneo si el comando lo requiere, letra a letra si no
if (instant) {
int total = 0;
for (const auto& l : msg_lines_) { total += static_cast<int>(l.size()); }
typewriter_chars_ = total;
} else {
typewriter_chars_ = 0;
} }
typewriter_timer_ = 0.0F;
} }
} }
input_line_.clear(); input_line_.clear();
history_index_ = -1; history_index_ = -1;
saved_input_.clear(); saved_input_.clear();
tab_matches_.clear();
cursor_timer_ = 0.0F; cursor_timer_ = 0.0F;
cursor_visible_ = true; cursor_visible_ = true;
} }

View File

@@ -6,6 +6,8 @@
#include <functional> // Para function #include <functional> // Para function
#include <memory> // Para shared_ptr #include <memory> // Para shared_ptr
#include <string> // Para string #include <string> // Para string
#include <unordered_map> // Para unordered_map (tab_completions_)
#include <vector> // Para vector
class Surface; class Surface;
class Sprite; class Sprite;
@@ -44,15 +46,16 @@ class Console {
static constexpr Uint8 BG_COLOR = 0; // PaletteColor::BLACK static constexpr Uint8 BG_COLOR = 0; // PaletteColor::BLACK
static constexpr Uint8 BORDER_COLOR = 9; // PaletteColor::BRIGHT_GREEN static constexpr Uint8 BORDER_COLOR = 9; // PaletteColor::BRIGHT_GREEN
static constexpr Uint8 MSG_COLOR = 8; // PaletteColor::GREEN static constexpr Uint8 MSG_COLOR = 8; // PaletteColor::GREEN
static constexpr float SLIDE_SPEED = 120.0F; static constexpr float SLIDE_SPEED = 180.0F;
// Constantes de consola // Constantes de consola
static constexpr std::string_view CONSOLE_NAME = "JDD Console"; static constexpr std::string_view CONSOLE_NAME = "JDD Console";
static constexpr std::string_view CONSOLE_VERSION = "v1.0"; static constexpr std::string_view CONSOLE_VERSION = "v2.1";
static constexpr int MAX_LINE_CHARS = 32; static constexpr int MAX_LINE_CHARS = 32;
static constexpr int MAX_HISTORY_SIZE = 20; static constexpr int MAX_HISTORY_SIZE = 20;
static constexpr float CURSOR_ON_TIME = 0.5F; static constexpr float CURSOR_ON_TIME = 0.5F;
static constexpr float CURSOR_OFF_TIME = 0.3F; static constexpr float CURSOR_OFF_TIME = 0.3F;
static constexpr float TYPEWRITER_CHAR_DELAY = 0.01F; // segundos entre letra y letra
// [SINGLETON] // [SINGLETON]
static Console* console; static Console* console;
@@ -65,6 +68,7 @@ class Console {
void buildSurface(); // Crea la Surface con el aspecto visual void buildSurface(); // Crea la Surface con el aspecto visual
void redrawText(); // Redibuja el texto dinámico (msg + input + cursor) void redrawText(); // Redibuja el texto dinámico (msg + input + cursor)
void processCommand(); // Procesa el comando introducido por el usuario void processCommand(); // Procesa el comando introducido por el usuario
[[nodiscard]] auto wrapText(const std::string& text) const -> std::vector<std::string>; // Word-wrap por ancho en píxeles
// Objetos de renderizado // Objetos de renderizado
std::shared_ptr<Text> text_; std::shared_ptr<Text> text_;
@@ -77,13 +81,26 @@ class Console {
float height_{0.0F}; // Altura del panel float height_{0.0F}; // Altura del panel
// Estado de la entrada de texto // Estado de la entrada de texto
std::string msg_line_; // inicializado en constructor con CONSOLE_NAME + CONSOLE_VERSION std::vector<std::string> msg_lines_; // Líneas de mensaje (1 o más)
std::string input_line_; std::string input_line_;
float cursor_timer_{0.0F}; float cursor_timer_{0.0F};
bool cursor_visible_{true}; bool cursor_visible_{true};
// Efecto typewriter
int typewriter_chars_{0}; // Caracteres de msg_lines_ actualmente visibles
float typewriter_timer_{0.0F};
// Animación de altura dinámica
float target_height_{0.0F}; // Altura objetivo (según número de líneas de mensaje)
int notifier_offset_applied_{0}; // Acumulador del offset enviado al Notifier
// Historial de comandos (navegable con flechas arriba/abajo) // Historial de comandos (navegable con flechas arriba/abajo)
std::deque<std::string> history_; std::deque<std::string> history_;
int history_index_{-1}; // -1 = en la entrada actual (presente) int history_index_{-1}; // -1 = en la entrada actual (presente)
std::string saved_input_; // guarda input_line_ al empezar a navegar 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
}; };

View File

@@ -6,7 +6,7 @@
namespace Texts { namespace Texts {
constexpr const char* WINDOW_CAPTION = "© 2022 JailDoctor's Dilemma — JailDesigner"; constexpr const char* WINDOW_CAPTION = "© 2022 JailDoctor's Dilemma — JailDesigner";
constexpr const char* COPYRIGHT = "@2022 JailDesigner"; constexpr const char* COPYRIGHT = "@2022 JailDesigner";
constexpr const char* VERSION = "1.10"; // Versión por defecto constexpr const char* VERSION = "1.11"; // Versión por defecto
} // namespace Texts } // namespace Texts
// Tamaño de bloque // Tamaño de bloque