18 Commits
v1.10 ... v1.11

Author SHA1 Message Date
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
20 changed files with 18762 additions and 1770 deletions

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

@@ -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

@@ -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,8 +9,9 @@
#include <utility> // Para std::pair #include <utility> // Para std::pair
#include <vector> // Para vector #include <vector> // Para vector
#include "core/rendering/shader_backend.hpp" // Para Rendering::ShaderType, ShaderBackend #include "core/rendering/palette_manager.hpp" // Para PaletteManager
#include "utils/utils.hpp" // Para Color #include "core/rendering/shader_backend.hpp" // Para Rendering::ShaderType, ShaderBackend
#include "utils/utils.hpp" // Para Color
class Surface; class Surface;
class Text; class Text;
@@ -35,14 +36,15 @@ class Screen {
void update(float delta_time); // Actualiza la lógica de la clase void update(float delta_time); // Actualiza la lógica de la clase
// Video y ventana // Video y ventana
void setVideoMode(bool mode); // Establece el modo de video void setVideoMode(bool mode); // Establece el modo de video
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
void show(); // Muestra la ventana auto setWindowZoom(int zoom) -> bool; // Establece zoom directo; false si fuera de [1, max_zoom]
void hide(); // Oculta la ventana void show(); // Muestra la ventana
void hide(); // Oculta la ventana
// Borde // Borde
void setBorderColor(Uint8 color); // Cambia el color del borde void setBorderColor(Uint8 color); // Cambia el color del borde
@@ -52,17 +54,18 @@ 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 auto setPaletteByName(const std::string& name) -> bool; // Cambia a paleta por nombre; false si no existe
void toggleShaders(); // Activa/desactiva todos los shaders respetando current_shader [[nodiscard]] auto getPaletteNames() const -> std::vector<std::string>; // Nombres disponibles (mayúsculas, sin .pal)
void toggleSupersampling(); // Activa/desactiva el supersampling global void toggleShaders(); // Activa/desactiva todos los shaders respetando current_shader
void reloadPostFX(); // Recarga el shader del preset actual sin toggle void toggleSupersampling(); // Activa/desactiva el supersampling global
void reloadCrtPi(); // Recarga el shader CrtPi del preset actual sin toggle void reloadPostFX(); // Recarga el shader del preset actual sin toggle
void setLinearUpscale(bool linear); // Upscale NEAREST (false) o LINEAR (true) en el paso SS void reloadCrtPi(); // Recarga el shader CrtPi del preset actual sin toggle
void setDownscaleAlgo(int algo); // 0=bilinear legacy, 1=Lanczos2, 2=Lanczos3 void setLinearUpscale(bool linear); // Upscale NEAREST (false) o LINEAR (true) en el paso SS
void setActiveShader(Rendering::ShaderType type); // Cambia el shader de post-procesado activo void setDownscaleAlgo(int algo); // 0=bilinear legacy, 1=Lanczos2, 2=Lanczos3
void nextShader(); // Cicla al siguiente shader disponible (para futura UI) 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:
@@ -116,20 +120,18 @@ class Screen {
static Screen* screen; static Screen* screen;
// Métodos privados // Métodos privados
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 void initShaders(); // Inicializa los shaders
auto findPalette(const std::string& name) -> size_t; // Localiza la paleta dentro del vector de paletas void applyCurrentPostFXPreset(); // Aplica los parámetros del preset PostFX actual al backend
void initShaders(); // Inicializa los shaders void applyCurrentCrtPiPreset(); // Aplica los parámetros del preset CrtPi actual al backend
void applyCurrentPostFXPreset(); // Aplica los parámetros del preset PostFX actual al backend void getDisplayInfo(); // Obtiene información sobre la pantalla
void applyCurrentCrtPiPreset(); // Aplica los parámetros del preset CrtPi actual al backend auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
void getDisplayInfo(); // Obtiene información sobre la pantalla void createText(); // Crea el objeto de texto
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
void createText(); // Crea el objeto de texto
// Constructor y destructor // Constructor y destructor
Screen(); Screen();
@@ -163,9 +165,8 @@ class Screen {
SDL_FRect game_surface_dstrect_; // Coordenadas donde se dibuja la textura del juego SDL_FRect game_surface_dstrect_; // Coordenadas donde se dibuja la textura del juego
// 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,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