15 Commits
v1.10 ... main

Author SHA1 Message Date
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
17 changed files with 18565 additions and 1639 deletions

View File

@@ -298,6 +298,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

@@ -8,9 +8,10 @@
// No se permite cambio de idioma en caliente. // No se permite cambio de idioma en caliente.
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 destroy(); // Destruye el singleton static void initFromContent(const std::string& content); // Crea e inicializa desde contenido en memoria (pack)
static auto get() -> Locale*; // Devuelve el singleton static void destroy(); // Destruye el singleton
static auto get() -> Locale*; // Devuelve el singleton
// Devuelve la traducción de la clave dada. // Devuelve la traducción de la clave dada.
// Si la clave no existe, devuelve la propia clave como fallback. // Si la clave no existe, devuelve la propia clave como fallback.
@@ -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

@@ -194,6 +194,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;
@@ -482,6 +497,33 @@ auto Screen::findPalette(const std::string& name) -> size_t { // NOLINT(readabi
return static_cast<size_t>(0); return static_cast<size_t>(0);
} }
// Cambia a una paleta por nombre (case-insensitive); devuelve false si no existe
bool Screen::setPaletteByName(const std::string& name) {
const std::string upper_name = toUpper(name + ".pal");
for (size_t i = 0; i < palettes_.size(); ++i) {
if (toUpper(getFileName(palettes_[i])) == upper_name) {
current_palette_ = static_cast<Uint8>(i);
setPalete();
return true;
}
}
return false;
}
// Devuelve los nombres de paletas disponibles (mayúsculas, sin extensión .pal)
auto Screen::getPaletteNames() 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;
}
// 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

@@ -39,8 +39,9 @@ class Screen {
void toggleVideoMode(); // Cambia entre pantalla completa y ventana void toggleVideoMode(); // Cambia entre pantalla completa y ventana
void toggleIntegerScale(); // Alterna entre activar y desactivar el escalado entero void toggleIntegerScale(); // Alterna entre activar y desactivar el escalado entero
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
@@ -52,17 +53,19 @@ class Screen {
void toggleBorder(); // Cambia entre borde visible y no visible void toggleBorder(); // Cambia entre borde visible y no visible
// 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 void setPalete(); // Establece la paleta actual
void toggleShaders(); // Activa/desactiva todos los shaders respetando current_shader bool setPaletteByName(const std::string& name); // Cambia a paleta por nombre; false si no existe
void toggleSupersampling(); // Activa/desactiva el supersampling global [[nodiscard]] auto getPaletteNames() const -> std::vector<std::string>; // Nombres disponibles (mayúsculas, sin .pal)
void reloadPostFX(); // Recarga el shader del preset actual sin toggle void toggleShaders(); // Activa/desactiva todos los shaders respetando current_shader
void reloadCrtPi(); // Recarga el shader CrtPi del preset actual sin toggle void toggleSupersampling(); // Activa/desactiva el supersampling global
void setLinearUpscale(bool linear); // Upscale NEAREST (false) o LINEAR (true) en el paso SS void reloadPostFX(); // Recarga el shader del preset actual sin toggle
void setDownscaleAlgo(int algo); // 0=bilinear legacy, 1=Lanczos2, 2=Lanczos3 void reloadCrtPi(); // Recarga el shader CrtPi del preset actual sin toggle
void setActiveShader(Rendering::ShaderType type); // Cambia el shader de post-procesado activo void setLinearUpscale(bool linear); // Upscale NEAREST (false) o LINEAR (true) en el paso SS
void nextShader(); // Cicla al siguiente shader disponible (para futura UI) void setDownscaleAlgo(int algo); // 0=bilinear legacy, 1=Lanczos2, 2=Lanczos3
void setActiveShader(Rendering::ShaderType type); // Cambia el shader de post-procesado activo
void nextShader(); // Cicla al siguiente shader disponible (para futura UI)
// Surfaces y notificaciones // Surfaces y notificaciones
void setRendererSurface(const std::shared_ptr<Surface>& surface = nullptr); // Establece el renderizador para las surfaces void setRendererSurface(const std::shared_ptr<Surface>& surface = nullptr); // Establece el renderizador para las surfaces
@@ -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:

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

@@ -882,9 +882,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,14 +564,25 @@ 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)
// SET INITIAL [ROOM|POS] — Guarda habitación/posición actual como inicio (solo _DEBUG, GAME) // SET INITIAL [ROOM|POS] — Guarda habitación/posición actual como inicio (solo _DEBUG, 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,15 +669,26 @@ 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 #endif
}}, },
.completions = {
#ifdef _DEBUG
{"SET", {"PLAYER", "INITIAL", "ITEMS"}},
{"SET PLAYER", {"SKIN"}},
{"SET INITIAL", {"ROOM", "POS", "SCENE"}},
{"SET INITIAL SCENE", {"LOGO", "LOADING", "TITLE", "CREDITS", "GAME", "ENDING", "ENDING2"}},
#else
{"SET", {"PLAYER"}},
{"SET PLAYER", {"SKIN"}},
#endif
}},
#ifdef _DEBUG #ifdef _DEBUG
// 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

@@ -2,10 +2,12 @@
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <deque> // Para deque (historial) #include <deque> // Para deque (historial)
#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;
@@ -62,9 +65,10 @@ class Console {
~Console() = default; ~Console() = default;
// Métodos privados // Métodos privados
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