Compare commits

34 Commits
v1.10 ... v1.12

Author SHA1 Message Date
405f2248ec actualitzat changelog 2026-04-02 08:57:24 +02:00
93b1cd80b7 corregit make release per a macos 2026-04-02 08:55:36 +02:00
b53bf87730 arreglos en makefile 2026-04-02 08:35:36 +02:00
015a9cc4e1 - restaurades les paletes amb la ordenacio original
- afegida opció de reordenar les paletes automaticament per luminositat o paregut a la paleta d'spectrum
2026-04-02 07:59:30 +02:00
3bd13b72cd afegit .vscode a git 2026-04-01 23:06:24 +02:00
c94adf39af reestructurat els comandos de consola 2026-04-01 22:46:02 +02:00
950eeffb07 el indicador de trucos ja no es el color del jugador sino que es veu al marcador 2026-04-01 22:12:52 +02:00
b37b62ef1e - pots canviar el color del jugador desde la consola (persistent)
- cokmprova que el color no siga el mateix que el del fono (canvia a default)
- eliminades animacions sobrants del jugador
- canviada la logica del marcador pero a mostrar la animació de les vides del jugador
- posibilitat d'utilitzar skins d'enemics en el jugador
- canvi en calent de la skin en el marcador (abans soles en el constructir)
2026-04-01 21:31:25 +02:00
0c8aa5fe50 fix: entrar i eixir al mode debug manté l'estat previ del jugador 2026-04-01 20:17:31 +02:00
fe520dd341 - Es pot posar shader preset directament per nom desde la consola
- shader preset i palette ja autocompleten amb la llista de noms
2026-04-01 20:08:55 +02:00
ec9a9aff81 Console ara llig els comandos desde un fitxer extern 2026-04-01 19:31:09 +02:00
f9c1c4843d reestructurat el apartat de video de config.yaml 2026-04-01 18:57:32 +02:00
a804ad1368 - posibilitat de desactivar la aceleració hardware desde el fitxer de configuració de manera mes intuitiva
- si no hi ha aceleració ja no va cap tecla ni comando relacionat amb els shaders
2026-04-01 18:24:22 +02:00
c689507982 - afegides noves paletes
- ordenades les paletes que tenien els color mal ubicats
- eliminades responsabilitats a Options sobre les paletes
- "pretty" name per a les paletes (canvia els "-" per " ")
- nova tool/ en python per a reordenar paletes
2026-03-31 20:02:18 +02:00
417643018f optimitzacions en Surface 2026-03-31 14:56:39 +02:00
2ed7316948 afegit changelog.md 2026-03-31 07:56:09 +02:00
2228153d59 corregit makefile 2026-03-31 07:29:29 +02:00
7315032ff2 fix: si entraves a GAME amb la consola oberta, el jugador no tenia els inputs deshabilitats 2026-03-31 07:28:03 +02:00
3fc6795593 decrementada la responsabilitat d'Screen i afegit PaletteManager 2026-03-31 07:14:58 +02:00
16924cf503 screen: opcio d'establir el nivell de zoom directament
console: opcio d'establir el zoom directament
2026-03-30 23:42:30 +02:00
705a9fc7cd corregit el case en algunes respostes de console 2026-03-30 23:33:58 +02:00
b164c11ba7 console crea la tabla tab_completions automaticament 2026-03-30 23:27:38 +02:00
1817d00881 screen torna llista de paletes i permet canviar a una paleta pel nom
console 2.1 por canviar de paleta pel nom
2026-03-30 23:03:21 +02:00
8dcf473f31 autocompletar amb armadura de lagarto 2026-03-30 22:50:56 +02:00
8f191f02fa afegit autocompletar a la consola. si la de raimon en te, la meua no anava a ser menos 2026-03-30 22:26:53 +02:00
1077c13fd0 amb la consola oberta el jugador te els inputs deshabilitats. si moria amb la consola oberta, recuperava els inputs i es movia mentre escrius comandos en la consola 2026-03-30 20:48:21 +02:00
d5a4caa86e al fer restart, si estava sonant la musica del attract mode, la musica no parava al anar al logo 2026-03-30 20:22:14 +02:00
f3bad9f4ed afegida guarda per a que en debug el jugador no caiga infinitament si ix de la pantalla 2026-03-30 20:19:07 +02:00
32f22c99db mil arreglos cosmetics a console 2.0 2026-03-30 19:56:31 +02:00
cd14ae22c5 separacio de linies automatica en console 2026-03-30 19:35:43 +02:00
1fdc29e9d2 afegit typewriter effect a console 2026-03-30 19:16:57 +02:00
0a740a5be2 new: treballant en Console 2.0 2026-03-30 19:11:24 +02:00
9e1b2b8960 fix: make macos_release 2026-03-30 18:09:53 +02:00
ed3724193e fix: no es llegien els fitxers de Locale desde resources.pack 2026-03-30 17:59:59 +02:00
67 changed files with 21282 additions and 3105 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,5 @@
.cache/ .cache/
.vscode/
*data/config/config.yaml *data/config/config.yaml
*stats.txt *stats.txt
*.DS_Store *.DS_Store

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json"
}

170
CHANGELOG.md Normal file
View File

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

View File

@@ -34,31 +34,32 @@ set(APP_SOURCES
# Core - Input # Core - Input
source/core/input/global_inputs.cpp source/core/input/global_inputs.cpp
source/core/input/input.cpp
source/core/input/input_types.cpp source/core/input/input_types.cpp
source/core/input/input.cpp
source/core/input/mouse.cpp source/core/input/mouse.cpp
# Core - Rendering # Core - Rendering
source/core/rendering/gif.cpp source/core/rendering/gif.cpp
source/core/rendering/palette_manager.cpp
source/core/rendering/pixel_reveal.cpp source/core/rendering/pixel_reveal.cpp
source/core/rendering/render_info.cpp source/core/rendering/render_info.cpp
source/core/rendering/screen.cpp source/core/rendering/screen.cpp
source/core/rendering/surface.cpp
source/core/rendering/sprite/animated_sprite.cpp source/core/rendering/sprite/animated_sprite.cpp
source/core/rendering/sprite/dissolve_sprite.cpp source/core/rendering/sprite/dissolve_sprite.cpp
source/core/rendering/sprite/moving_sprite.cpp source/core/rendering/sprite/moving_sprite.cpp
source/core/rendering/sprite/sprite.cpp source/core/rendering/sprite/sprite.cpp
source/core/rendering/surface.cpp
source/core/rendering/text.cpp source/core/rendering/text.cpp
# Core - Locale # Core - Locale
source/core/locale/locale.cpp source/core/locale/locale.cpp
# Core - Resources # Core - Resources
source/core/resources/resource_list.cpp
source/core/resources/resource_cache.cpp source/core/resources/resource_cache.cpp
source/core/resources/resource_pack.cpp
source/core/resources/resource_loader.cpp
source/core/resources/resource_helper.cpp source/core/resources/resource_helper.cpp
source/core/resources/resource_list.cpp
source/core/resources/resource_loader.cpp
source/core/resources/resource_pack.cpp
# Core - System # Core - System
source/core/system/director.cpp source/core/system/director.cpp
@@ -78,9 +79,9 @@ set(APP_SOURCES
source/game/gameplay/enemy_manager.cpp source/game/gameplay/enemy_manager.cpp
source/game/gameplay/item_manager.cpp source/game/gameplay/item_manager.cpp
source/game/gameplay/item_tracker.cpp source/game/gameplay/item_tracker.cpp
source/game/gameplay/room.cpp
source/game/gameplay/room_loader.cpp source/game/gameplay/room_loader.cpp
source/game/gameplay/room_tracker.cpp source/game/gameplay/room_tracker.cpp
source/game/gameplay/room.cpp
source/game/gameplay/scoreboard.cpp source/game/gameplay/scoreboard.cpp
source/game/gameplay/stats.cpp source/game/gameplay/stats.cpp
source/game/gameplay/tilemap_renderer.cpp source/game/gameplay/tilemap_renderer.cpp
@@ -89,14 +90,15 @@ 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
# Game - UI # Game - UI
source/game/ui/console.cpp source/game/ui/console.cpp
source/game/ui/console_commands.cpp
source/game/ui/notifier.cpp source/game/ui/notifier.cpp
# Utils # Utils
@@ -223,7 +225,17 @@ if(WIN32)
elseif(APPLE) elseif(APPLE)
target_compile_definitions(${PROJECT_NAME} PRIVATE MACOS_BUILD) target_compile_definitions(${PROJECT_NAME} PRIVATE MACOS_BUILD)
target_compile_options(${PROJECT_NAME} PRIVATE -Wno-deprecated) target_compile_options(${PROJECT_NAME} PRIVATE -Wno-deprecated)
if(NOT CMAKE_OSX_ARCHITECTURES)
set(CMAKE_OSX_ARCHITECTURES "arm64") set(CMAKE_OSX_ARCHITECTURES "arm64")
endif()
if(MACOS_BUNDLE)
target_compile_definitions(${PROJECT_NAME} PRIVATE MACOS_BUNDLE)
target_link_options(${PROJECT_NAME} PRIVATE
-framework SDL3
-F ${CMAKE_SOURCE_DIR}/release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64
-rpath @executable_path/../Frameworks/
)
endif()
elseif(UNIX AND NOT APPLE) elseif(UNIX AND NOT APPLE)
target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD) target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD)
endif() endif()

281
Makefile
View File

@@ -18,24 +18,13 @@ RELEASE_FILE := $(RELEASE_FOLDER)/$(TARGET_NAME)
RESOURCE_FILE := release/windows/jdd.res RESOURCE_FILE := release/windows/jdd.res
# ============================================================================== # ==============================================================================
# PACKING TOOL # TOOLS
# ==============================================================================
ifeq ($(OS),Windows_NT)
PACK_TOOL := $(DIR_TOOLS)pack_resources/pack_resources.exe
PACK_CXX := $(CXX)
else
PACK_TOOL := $(DIR_TOOLS)pack_resources/pack_resources
PACK_CXX := $(CXX)
endif
PACK_SOURCES := $(DIR_TOOLS)pack_resources/pack_resources.cpp source/core/resources/resource_pack.cpp
PACK_INCLUDES := -Isource
# ==============================================================================
# SHADERS
# ============================================================================== # ==============================================================================
DIR_PACK_TOOL := $(DIR_TOOLS)pack_resources
SHADER_SCRIPT := $(DIR_ROOT)tools/shaders/compile_spirv.sh SHADER_SCRIPT := $(DIR_ROOT)tools/shaders/compile_spirv.sh
SHADER_VERT_H := $(DIR_ROOT)source/core/rendering/sdl3gpu/postfx_vert_spv.h SHADER_CMAKE := $(DIR_ROOT)tools/shaders/compile_spirv.cmake
SHADER_FRAG_H := $(DIR_ROOT)source/core/rendering/sdl3gpu/postfx_frag_spv.h SHADERS_DIR := $(DIR_ROOT)data/shaders
HEADERS_DIR := $(DIR_ROOT)source/core/rendering/sdl3gpu
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
GLSLC := $(shell where glslc 2>NUL) GLSLC := $(shell where glslc 2>NUL)
else else
@@ -79,189 +68,87 @@ MACOS_APPLE_SILICON_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-macos-apple
LINUX_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-linux.tar.gz LINUX_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-linux.tar.gz
# ============================================================================== # ==============================================================================
# SOURCE FILES # PLATAFORMA
# ============================================================================== # ==============================================================================
APP_SOURCES := \
source/main.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/rendering/screen.cpp \
source/core/rendering/surface.cpp \
source/core/rendering/sprite/sprite.cpp \
source/core/rendering/sprite/animated_sprite.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/pixel_reveal.cpp \
source/core/rendering/render_info.cpp \
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp \
source/core/locale/locale.cpp \
source/core/resources/resource_list.cpp \
source/core/resources/resource_cache.cpp \
source/core/resources/resource_helper.cpp \
source/core/resources/resource_loader.cpp \
source/core/resources/resource_pack.cpp \
source/core/system/director.cpp \
source/core/system/debug.cpp \
source/core/system/global_events.cpp \
source/game/options.cpp \
source/game/entities/player.cpp \
source/game/entities/enemy.cpp \
source/game/entities/item.cpp \
source/game/gameplay/room.cpp \
source/game/gameplay/collision_map.cpp \
source/game/gameplay/enemy_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/room_tracker.cpp \
source/game/gameplay/stats.cpp \
source/game/scenes/logo.cpp \
source/game/scenes/loading_screen.cpp \
source/game/scenes/title.cpp \
source/game/scenes/game.cpp \
source/game/scenes/game_over.cpp \
source/game/scenes/ending.cpp \
source/game/scenes/ending2.cpp \
source/game/scenes/credits.cpp \
source/game/ui/notifier.cpp \
source/game/ui/console.cpp \
source/utils/utils.cpp \
source/utils/delta_timer.cpp
# All sources combined
ALL_SOURCES := $(APP_SOURCES)
# ==============================================================================
# INCLUDES
# ==============================================================================
INCLUDES := -Isource
# ==============================================================================
# COMPILER FLAGS (OS-specific)
# ==============================================================================
CPP_STANDARD := c++20
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
FixPath = $(subst /,\\,$1) FixPath = $(subst /,\\,$1)
CXXFLAGS := -std=$(CPP_STANDARD) -Wall -Os -ffunction-sections -fdata-sections \
-Wl,--gc-sections -static-libstdc++ -static-libgcc \
-Wl,-subsystem,windows -DWINDOWS_BUILD
CXXFLAGS_DEBUG := -std=$(CPP_STANDARD) -Wall -g -D_DEBUG -DWINDOWS_BUILD
LDFLAGS := -lmingw32 -lws2_32 -lSDL3
RM := del /Q RM := del /Q
MKDIR := mkdir MKDIR := mkdir
else else
FixPath = $1 FixPath = $1
CXXFLAGS := -std=$(CPP_STANDARD) -Wall -Os -ffunction-sections -fdata-sections
CXXFLAGS_DEBUG := -std=$(CPP_STANDARD) -Wall -g -D_DEBUG
LDFLAGS := -lSDL3
RMFILE := rm -f RMFILE := rm -f
RMDIR := rm -rdf RMDIR := rm -rdf
MKDIR := mkdir -p MKDIR := mkdir -p
UNAME_S := $(shell uname -s) UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
CXXFLAGS += -DLINUX_BUILD
endif endif
# ==============================================================================
# COMPILACIÓN CON CMAKE
# ==============================================================================
all:
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
@cmake --build build
debug:
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
@cmake --build build
# ==============================================================================
# RELEASE AUTOMÁTICO (detecta SO)
# ==============================================================================
release:
ifeq ($(OS),Windows_NT)
@"$(MAKE)" windows_release
else
ifeq ($(UNAME_S),Darwin) ifeq ($(UNAME_S),Darwin)
CXXFLAGS += -DMACOS_BUILD @$(MAKE) macos_release
CXXFLAGS_DEBUG += -DMACOS_BUILD else
# Configurar arquitectura (por defecto arm64) @$(MAKE) linux_release
CXXFLAGS += -arch arm64
CXXFLAGS_DEBUG += -arch arm64
# Usar Apple Clang explícitamente para evitar LLVM de Homebrew
MACOS_CXX := /usr/bin/clang++
endif endif
endif endif
# ============================================================================== # ==============================================================================
# REGLAS PARA COMPILACIÓN DE SHADERS # REGLAS PARA COMPILACIÓN DE SHADERS (multiplataforma via cmake)
# ============================================================================== # ==============================================================================
compile_shaders: compile_shaders:
ifdef GLSLC ifdef GLSLC
ifeq ($(OS),Windows_NT) @cmake -D GLSLC=$(GLSLC) -D SHADERS_DIR=$(SHADERS_DIR) -D HEADERS_DIR=$(HEADERS_DIR) -P $(SHADER_CMAKE)
@powershell -Command "if ((Test-Path '$(SHADER_VERT_H)') -and (Test-Path '$(SHADER_FRAG_H)')) { Write-Host 'Shaders SPIR-V precompilados OK' } else { Write-Host 'Compilando shaders SPIR-V...'; bash '$(SHADER_SCRIPT)'; Write-Host 'Shaders compilados' }"
else else
@echo "Compilando shaders SPIR-V..." @echo "glslc no encontrado - asegurate de que los headers SPIR-V precompilados existen"
@$(SHADER_SCRIPT)
@echo "✓ Shaders compilados"
endif
else
ifeq ($(OS),Windows_NT)
@powershell -Command "if ((Test-Path '$(SHADER_VERT_H)') -and (Test-Path '$(SHADER_FRAG_H)')) { Write-Host 'glslc no encontrado - usando headers SPIR-V precompilados' } else { Write-Host 'ERROR: glslc no encontrado y headers SPIR-V no existen.'; Write-Host ' Instala glslc o ejecuta: tools/shaders/compile_spirv.sh'; exit 1 }"
else
@if [ -f "$(SHADER_VERT_H)" ] && [ -f "$(SHADER_FRAG_H)" ]; then \
echo "⚠ glslc no encontrado - usando headers SPIR-V precompilados"; \
else \
echo "ERROR: glslc no encontrado y headers SPIR-V no existen."; \
echo " Instala glslc: sudo apt install glslang-tools"; \
echo " O ejecuta manualmente: tools/shaders/compile_spirv.sh"; \
exit 1; \
fi
endif
endif endif
# ============================================================================== # ==============================================================================
# REGLAS PARA HERRAMIENTA DE EMPAQUETADO Y RESOURCES.PACK # REGLAS PARA HERRAMIENTA DE EMPAQUETADO Y RESOURCES.PACK
# ============================================================================== # ==============================================================================
$(PACK_TOOL): FORCE pack_tool:
@echo "Compilando herramienta de empaquetado..." @$(MAKE) -C $(DIR_PACK_TOOL)
$(PACK_CXX) -std=$(CPP_STANDARD) -Wall -Os $(PACK_INCLUDES) $(PACK_SOURCES) -o $(PACK_TOOL)
@echo "✓ Herramienta de empaquetado lista: $(PACK_TOOL)"
pack_tool: $(PACK_TOOL) resources.pack: pack_tool
@$(MAKE) -C $(DIR_PACK_TOOL) pack
resources.pack: $(PACK_TOOL)
@echo "Generando resources.pack desde directorio data/..."
$(PACK_TOOL) data resources.pack
@echo "✓ resources.pack generado exitosamente"
# ============================================================================== # ==============================================================================
# COMPILACIÓN PARA WINDOWS # COMPILACIÓN PARA WINDOWS (RELEASE)
# ============================================================================== # ==============================================================================
windows:
@echo off
@echo Generando version.h...
@powershell -Command "$$GIT_HASH = (git rev-parse --short=7 HEAD 2>$$null); if (-not $$GIT_HASH) { $$GIT_HASH = 'unknown' }; (Get-Content source/version.h.in) -replace '@GIT_HASH@', $$GIT_HASH | Set-Content source/version.h"
@echo Compilando para Windows con nombre: "$(WIN_TARGET_FILE).exe"
windres release/windows/jdd.rc -O coff -o $(RESOURCE_FILE)
g++ $(ALL_SOURCES) $(RESOURCE_FILE) $(INCLUDES) $(CXXFLAGS) $(LDFLAGS) -o "$(WIN_TARGET_FILE).exe"
strip -s -R .comment -R .gnu.version "$(WIN_TARGET_FILE).exe" --strip-unneeded
windows_release: windows_release:
@"$(MAKE)" compile_shaders
@"$(MAKE)" resources.pack
@echo off @echo off
@echo Creando release para Windows - Version: $(VERSION) @echo Creando release para Windows - Version: $(VERSION)
# Generate version.h from version.h.in # Compila con cmake (genera shaders, resources.pack y ejecutable)
@echo "Generando version.h..." @cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
@powershell -Command "$$GIT_HASH = (git rev-parse --short=7 HEAD 2>$$null); if (-not $$GIT_HASH) { $$GIT_HASH = 'unknown' }; (Get-Content source/version.h.in) -replace '@GIT_HASH@', $$GIT_HASH | Set-Content source/version.h" @cmake --build build
# Crea carpeta de distribución y carpeta temporal 'RELEASE_FOLDER' # Crea carpeta de distribución y carpeta temporal 'RELEASE_FOLDER'
@powershell -Command "if (-not (Test-Path '$(DIST_DIR)')) {New-Item '$(DIST_DIR)' -ItemType Directory}" @powershell -Command "if (-not (Test-Path '$(DIST_DIR)')) {New-Item '$(DIST_DIR)' -ItemType Directory}"
@powershell -Command "if (Test-Path '$(RELEASE_FOLDER)') {Remove-Item '$(RELEASE_FOLDER)' -Recurse -Force}" @powershell -Command "if (Test-Path '$(RELEASE_FOLDER)') {Remove-Item '$(RELEASE_FOLDER)' -Recurse -Force}"
@powershell -Command "if (-not (Test-Path '$(RELEASE_FOLDER)')) {New-Item '$(RELEASE_FOLDER)' -ItemType Directory}" @powershell -Command "if (-not (Test-Path '$(RELEASE_FOLDER)')) {New-Item '$(RELEASE_FOLDER)' -ItemType Directory}"
# Copia el archivo 'resources.pack' # Copia ficheros
@powershell -Command "Copy-Item -Path 'resources.pack' -Destination '$(RELEASE_FOLDER)'" @powershell -Command "Copy-Item -Path 'resources.pack' -Destination '$(RELEASE_FOLDER)'"
# Copia los ficheros que están en la raíz del proyecto
@powershell -Command "Copy-Item 'LICENSE' -Destination '$(RELEASE_FOLDER)'" @powershell -Command "Copy-Item 'LICENSE' -Destination '$(RELEASE_FOLDER)'"
@powershell -Command "Copy-Item 'README.md' -Destination '$(RELEASE_FOLDER)'" @powershell -Command "Copy-Item 'README.md' -Destination '$(RELEASE_FOLDER)'"
@powershell -Command "Copy-Item 'gamecontrollerdb.txt' -Destination '$(RELEASE_FOLDER)'" @powershell -Command "Copy-Item 'gamecontrollerdb.txt' -Destination '$(RELEASE_FOLDER)'"
@powershell -Command "Copy-Item 'release\windows\dll\*.dll' -Destination '$(RELEASE_FOLDER)'" @powershell -Command "Copy-Item 'release\windows\dll\*.dll' -Destination '$(RELEASE_FOLDER)'"
@powershell -Command "Copy-Item -Path '$(TARGET_FILE)' -Destination '\"$(WIN_RELEASE_FILE).exe\"'"
# Compila (con icono)
windres release/windows/jdd.rc -O coff -o $(RESOURCE_FILE)
g++ $(ALL_SOURCES) $(RESOURCE_FILE) $(INCLUDES) -DRELEASE_BUILD $(CXXFLAGS) $(LDFLAGS) -o "$(WIN_RELEASE_FILE).exe"
strip -s -R .comment -R .gnu.version "$(WIN_RELEASE_FILE).exe" --strip-unneeded strip -s -R .comment -R .gnu.version "$(WIN_RELEASE_FILE).exe" --strip-unneeded
# Crea el fichero .zip # Crea el fichero .zip
@@ -273,31 +160,24 @@ windows_release:
@powershell -Command "if (Test-Path '$(RELEASE_FOLDER)') {Remove-Item '$(RELEASE_FOLDER)' -Recurse -Force}" @powershell -Command "if (Test-Path '$(RELEASE_FOLDER)') {Remove-Item '$(RELEASE_FOLDER)' -Recurse -Force}"
# ============================================================================== # ==============================================================================
# COMPILACIÓN PARA MACOS # COMPILACIÓN PARA MACOS (RELEASE)
# ============================================================================== # ==============================================================================
macos:
@GIT_HASH=$$(git rev-parse --short=7 HEAD 2>/dev/null || echo "unknown"); \
sed "s/@GIT_HASH@/$$GIT_HASH/g" source/version.h.in > source/version.h
@echo "Compilando para macOS: $(TARGET_NAME)"
$(MACOS_CXX) $(ALL_SOURCES) $(INCLUDES) $(CXXFLAGS) $(LDFLAGS) -o "$(TARGET_FILE)"
macos_release: macos_release:
@$(MAKE) compile_shaders
@$(MAKE) resources.pack
@echo "Creando release para macOS - Version: $(VERSION)" @echo "Creando release para macOS - Version: $(VERSION)"
# Verificar e instalar create-dmg si es necesario # Verificar e instalar create-dmg si es necesario
@which create-dmg > /dev/null || (echo "Instalando create-dmg..." && brew install create-dmg) @which create-dmg > /dev/null || (echo "Instalando create-dmg..." && brew install create-dmg)
# Generate version.h from version.h.in # Compila la versión para procesadores Intel con cmake (genera shaders y resources.pack)
@echo "Generando version.h..." @cmake -S . -B build/intel -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DMACOS_BUNDLE=ON
@GIT_HASH=$$(git rev-parse --short=7 HEAD 2>/dev/null || echo "unknown"); \ @cmake --build build/intel
sed "s/@GIT_HASH@/$$GIT_HASH/g" source/version.h.in > source/version.h
# Elimina datos de compilaciones anteriores # Elimina datos de compilaciones anteriores
$(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"
@@ -319,8 +199,8 @@ macos_release:
sed -i '' '/<key>CFBundleShortVersionString<\/key>/{n;s|<string>.*</string>|<string>'"$$RAW_VERSION"'</string>|;}' "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Info.plist"; \ sed -i '' '/<key>CFBundleShortVersionString<\/key>/{n;s|<string>.*</string>|<string>'"$$RAW_VERSION"'</string>|;}' "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Info.plist"; \
sed -i '' '/<key>CFBundleVersion<\/key>/{n;s|<string>.*</string>|<string>'"$$RAW_VERSION"'</string>|;}' "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Info.plist" sed -i '' '/<key>CFBundleVersion<\/key>/{n;s|<string>.*</string>|<string>'"$$RAW_VERSION"'</string>|;}' "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Info.plist"
# Compila la versión para procesadores Intel # Copia el ejecutable Intel al bundle
$(MACOS_CXX) $(ALL_SOURCES) $(INCLUDES) -DMACOS_BUNDLE -DRELEASE_BUILD -std=$(CPP_STANDARD) -Wall -Os -framework SDL3 -F release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64 -ffunction-sections -fdata-sections -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target x86_64-apple-macos10.15 cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)"
# Firma la aplicación # Firma la aplicación
codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app" codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app"
@@ -342,8 +222,10 @@ macos_release:
"$(RELEASE_FOLDER)" || true "$(RELEASE_FOLDER)" || true
@echo "Release Intel creado: $(MACOS_INTEL_RELEASE)" @echo "Release Intel creado: $(MACOS_INTEL_RELEASE)"
# Compila la versión para procesadores Apple Silicon # Compila la versión para procesadores Apple Silicon con cmake
$(MACOS_CXX) $(ALL_SOURCES) $(INCLUDES) -DMACOS_BUNDLE -DRELEASE_BUILD -std=$(CPP_STANDARD) -Wall -Os -framework SDL3 -F release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64 -ffunction-sections -fdata-sections -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target arm64-apple-macos11 @cmake -S . -B build/arm -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 -DMACOS_BUNDLE=ON
@cmake --build build/arm
cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)"
# Firma la aplicación # Firma la aplicación
codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app" codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app"
@@ -367,27 +249,19 @@ macos_release:
# Elimina las carpetas temporales # Elimina las carpetas temporales
$(RMDIR) "$(RELEASE_FOLDER)" $(RMDIR) "$(RELEASE_FOLDER)"
$(RMDIR) build/intel
$(RMDIR) build/arm
$(RMFILE) "$(DIST_DIR)"/rw.* $(RMFILE) "$(DIST_DIR)"/rw.*
# ============================================================================== # ==============================================================================
# COMPILACIÓN PARA LINUX # COMPILACIÓN PARA LINUX (RELEASE)
# ============================================================================== # ==============================================================================
linux:
@GIT_HASH=$$(git rev-parse --short=7 HEAD 2>/dev/null || echo "unknown"); \
sed "s/@GIT_HASH@/$$GIT_HASH/g" source/version.h.in > source/version.h
@echo "Compilando para Linux: $(TARGET_NAME)"
g++ $(ALL_SOURCES) $(INCLUDES) $(CXXFLAGS) $(LDFLAGS) -o "$(TARGET_FILE)"
strip -s -R .comment -R .gnu.version "$(TARGET_FILE)" --strip-unneeded
linux_release: linux_release:
@$(MAKE) compile_shaders
@$(MAKE) resources.pack
@echo "Creando release para Linux - Version: $(VERSION)" @echo "Creando release para Linux - Version: $(VERSION)"
# Generate version.h from version.h.in # Compila con cmake (genera shaders, resources.pack y ejecutable)
@echo "Generando version.h..." @cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
@GIT_HASH=$$(git rev-parse --short=7 HEAD 2>/dev/null || echo "unknown"); \ @cmake --build build
sed "s/@GIT_HASH@/$$GIT_HASH/g" source/version.h.in > source/version.h
# Elimina carpeta temporal previa y la recrea (crea dist/ si no existe) # Elimina carpeta temporal previa y la recrea (crea dist/ si no existe)
$(RMDIR) "$(RELEASE_FOLDER)" $(RMDIR) "$(RELEASE_FOLDER)"
@@ -398,9 +272,7 @@ linux_release:
cp LICENSE "$(RELEASE_FOLDER)" cp LICENSE "$(RELEASE_FOLDER)"
cp README.md "$(RELEASE_FOLDER)" cp README.md "$(RELEASE_FOLDER)"
cp gamecontrollerdb.txt "$(RELEASE_FOLDER)" cp gamecontrollerdb.txt "$(RELEASE_FOLDER)"
cp "$(TARGET_FILE)" "$(RELEASE_FILE)"
# Compila
g++ $(ALL_SOURCES) $(INCLUDES) -DRELEASE_BUILD $(CXXFLAGS) $(LDFLAGS) -o "$(RELEASE_FILE)"
strip -s -R .comment -R .gnu.version "$(RELEASE_FILE)" --strip-unneeded strip -s -R .comment -R .gnu.version "$(RELEASE_FILE)" --strip-unneeded
# Empaqueta ficheros # Empaqueta ficheros
@@ -422,17 +294,24 @@ show_version:
help: help:
@echo "Makefile para JailDoctor's Dilemma" @echo "Makefile para JailDoctor's Dilemma"
@echo "Comandos disponibles:" @echo "Comandos disponibles:"
@echo " windows - Compilar para Windows" @echo ""
@echo " windows_release - Crear release completo para Windows" @echo " Compilacion:"
@echo " linux - Compilar para Linux" @echo " make - Compilar con cmake (Release)"
@echo " linux_release - Crear release completo para Linux" @echo " make debug - Compilar con cmake (Debug)"
@echo " macos - Compilar para macOS" @echo ""
@echo " macos_release - Crear release completo para macOS" @echo " Release:"
@echo " pack_tool - Compilar herramienta de empaquetado" @echo " make release - Crear release (detecta SO automaticamente)"
@echo " resources.pack - Generar pack de recursos desde data/" @echo " make windows_release - Crear release para Windows"
@echo " show_version - Mostrar version actual ($(VERSION))" @echo " make linux_release - Crear release para Linux"
@echo " help - Mostrar esta ayuda" @echo " make macos_release - Crear release para macOS"
@echo ""
@echo " Herramientas:"
@echo " make compile_shaders - Compilar shaders SPIR-V"
@echo " make pack_tool - Compilar herramienta de empaquetado"
@echo " make resources.pack - Generar pack de recursos desde data/"
@echo ""
@echo " Otros:"
@echo " make show_version - Mostrar version actual ($(VERSION))"
@echo " make help - Mostrar esta ayuda"
FORCE: .PHONY: all debug release windows_release macos_release linux_release compile_shaders pack_tool resources.pack show_version help
.PHONY: windows windows_release macos macos_release linux linux_release compile_shaders pack_tool resources.pack show_version help

View File

@@ -32,7 +32,7 @@ assets:
- type: PALETTE - type: PALETTE
path: ${PREFIX}/data/palette/zx-spectrum-adjusted.pal path: ${PREFIX}/data/palette/zx-spectrum-adjusted.pal
- type: PALETTE - type: PALETTE
path: ${PREFIX}/data/palette/zxarne-5-2.pal path: ${PREFIX}/data/palette/zxarne-5.2.pal
- type: PALETTE - type: PALETTE
path: ${PREFIX}/data/palette/black-and-white.pal path: ${PREFIX}/data/palette/black-and-white.pal
- type: PALETTE - type: PALETTE
@@ -46,15 +46,33 @@ assets:
- type: PALETTE - type: PALETTE
path: ${PREFIX}/data/palette/pico-8.pal path: ${PREFIX}/data/palette/pico-8.pal
- type: PALETTE - type: PALETTE
path: ${PREFIX}/data/palette/sweetie-16.pal path: ${PREFIX}/data/palette/sweetie.pal
- type: PALETTE - type: PALETTE
path: ${PREFIX}/data/palette/island-joy-16.pal path: ${PREFIX}/data/palette/island-joy.pal
- type: PALETTE - type: PALETTE
path: ${PREFIX}/data/palette/lost-century.pal path: ${PREFIX}/data/palette/lost-century.pal
- type: PALETTE - type: PALETTE
path: ${PREFIX}/data/palette/na16.pal path: ${PREFIX}/data/palette/na.pal
- type: PALETTE - type: PALETTE
path: ${PREFIX}/data/palette/steam-lords.pal path: ${PREFIX}/data/palette/steam-lords.pal
- type: PALETTE
path: ${PREFIX}/data/palette/winds-seed-pc98.pal
- type: PALETTE
path: ${PREFIX}/data/palette/psychic-fibre.pal
- type: PALETTE
path: ${PREFIX}/data/palette/shido-cyberneon.pal
- type: PALETTE
path: ${PREFIX}/data/palette/darkseed.pal
- type: PALETTE
path: ${PREFIX}/data/palette/antiquity.pal
- type: PALETTE
path: ${PREFIX}/data/palette/bubblegum.pal
- type: PALETTE
path: ${PREFIX}/data/palette/vanilla-milkshake.pal
- type: PALETTE
path: ${PREFIX}/data/palette/aged-terracotta.pal
- type: PALETTE
path: ${PREFIX}/data/palette/h16da.pal
# LOCALE # LOCALE
locale: locale:
@@ -99,6 +117,11 @@ assets:
required: false required: false
absolute: true absolute: true
# CONSOLE
console:
- type: DATA
path: ${PREFIX}/data/console/commands.yaml
# ROOMS # ROOMS
rooms: rooms:
- type: ROOM - type: ROOM

232
data/console/commands.yaml Normal file
View File

@@ -0,0 +1,232 @@
# JailDoctor's Dilemma - Console Commands
# Metadata for the in-game console command system.
# Execution logic stays in C++; this file defines metadata only.
#
# Fields:
# keyword - Command name (uppercase)
# handler - C++ handler function identifier
# description - Short description for help output
# usage - Full usage string for terminal help
# instant - (optional) Skip typewriter effect (default: false)
# hidden - (optional) Hide from TAB completion (default: false)
# debug_only - (optional) Only available in debug builds (default: false)
# help_hidden - (optional) Don't show in help output (default: false)
# dynamic_completions - (optional) Completions generated at runtime (default: false)
# completions - (optional) Static TAB completion tree
# debug_extras - (optional) Overrides applied in debug builds
categories:
- name: VIDEO
commands:
- keyword: SS
handler: cmd_ss
description: Supersampling
usage: "SS [ON|OFF|SIZE|UPSCALE [NEAREST|LINEAR]|DOWNSCALE [BILINEAR|LANCZOS2|LANCZOS3]]"
completions:
SS: [ON, OFF, SIZE, UPSCALE, DOWNSCALE]
SS UPSCALE: [NEAREST, LINEAR]
SS DOWNSCALE: [BILINEAR, LANCZOS2, LANCZOS3]
- keyword: SHADER
handler: cmd_shader
description: "Toggle/select shader (F4)"
usage: "SHADER [ON|OFF|NEXT|POSTFX|CRTPI|PRESET [NEXT|PREV|<name>]]"
completions:
SHADER: [ON, OFF, NEXT, POSTFX, CRTPI, PRESET]
dynamic_completions: true
- keyword: BORDER
handler: cmd_border
description: "Decorative border (B)"
usage: "BORDER [ON|OFF]"
completions:
BORDER: [ON, OFF]
- keyword: FULLSCREEN
handler: cmd_fullscreen
description: "Fullscreen mode (F3)"
usage: "FULLSCREEN [ON|OFF]"
completions:
FULLSCREEN: [ON, OFF]
- keyword: ZOOM
handler: cmd_zoom
description: "Window zoom (F1/F2)"
usage: "ZOOM [UP|DOWN|<1-N>]"
completions:
ZOOM: [UP, DOWN]
- keyword: INTSCALE
handler: cmd_intscale
description: "Integer scaling (F7)"
usage: "INTSCALE [ON|OFF]"
completions:
INTSCALE: [ON, OFF]
- keyword: VSYNC
handler: cmd_vsync
description: "Vertical sync"
usage: "VSYNC [ON|OFF]"
completions:
VSYNC: [ON, OFF]
- keyword: DRIVER
handler: cmd_driver
description: "GPU driver (restart to apply)"
usage: "DRIVER [LIST|AUTO|NONE|<name>]"
completions:
DRIVER: [LIST, AUTO, NONE]
- keyword: PALETTE
handler: cmd_palette
description: "Color palette (F5/F6)"
usage: "PALETTE [NEXT|PREV|SORT [ORIGINAL|LUMINANCE|SPECTRUM]|DEFAULT|<name>]"
completions:
PALETTE SORT: [ORIGINAL, LUMINANCE, SPECTRUM]
dynamic_completions: true
- name: AUDIO
commands:
- keyword: AUDIO
handler: cmd_audio
description: Audio master
usage: "AUDIO [ON|OFF|VOL <0-100>]"
completions:
AUDIO: [ON, OFF, VOL]
- keyword: MUSIC
handler: cmd_music
description: Music volume
usage: "MUSIC [ON|OFF|VOL <0-100>]"
completions:
MUSIC: [ON, OFF, VOL]
- keyword: SOUND
handler: cmd_sound
description: Sound volume
usage: "SOUND [ON|OFF|VOL <0-100>]"
completions:
SOUND: [ON, OFF, VOL]
- name: GAME
commands:
- keyword: PLAYER
handler: cmd_player
description: "Player skin and color"
usage: "PLAYER SKIN <name> | PLAYER COLOR <0-15>|DEFAULT"
completions:
PLAYER: [SKIN, COLOR]
PLAYER SKIN: [DEFAULT, ABAD, BATMAN, CHIP, CONGO, JEANNINE, MUMMY, UPV_STUDENT]
PLAYER COLOR: [DEFAULT]
- keyword: RESTART
handler: cmd_restart
description: Restart from the beginning
usage: RESTART
instant: true
- keyword: KIOSK
handler: cmd_kiosk
description: Enable kiosk mode
usage: "KIOSK [ON]"
completions:
KIOSK: [ON]
- keyword: EXIT
handler: cmd_exit
description: Quit application
usage: EXIT
instant: true
- keyword: QUIT
handler: cmd_quit
description: Quit application
usage: QUIT
instant: true
help_hidden: true
- name: INFO
commands:
- keyword: SHOW
handler: cmd_show
description: Show info overlay
usage: "SHOW [INFO]"
completions:
SHOW: [INFO]
debug_extras:
description: "Show overlay/test notification"
usage: "SHOW [INFO|NOTIFICATION|CHEEVO]"
completions:
SHOW: [INFO, NOTIFICATION, CHEEVO]
- keyword: HIDE
handler: cmd_hide
description: Hide info overlay
usage: "HIDE [INFO]"
completions:
HIDE: [INFO]
- keyword: SIZE
handler: cmd_size
description: Window size in pixels
usage: SIZE
- keyword: HELP
handler: cmd_help
description: "Show this help"
usage: "HELP / ?"
- keyword: "?"
handler: cmd_help
help_hidden: true
- name: DEBUG
debug_only: true
commands:
- keyword: DEBUG
handler: cmd_debug
description: "Debug mode and start options (F12)"
usage: "DEBUG [MODE [ON|OFF]|START [HERE|ROOM|POS|SCENE <name>]]"
completions:
DEBUG: [MODE, START]
DEBUG MODE: [ON, OFF]
DEBUG START: [HERE, ROOM, POS, SCENE]
DEBUG START SCENE: [LOGO, LOADING, TITLE, CREDITS, GAME, ENDING, ENDING2]
- keyword: ITEMS
handler: cmd_items
description: "Set item count (GAME only)"
usage: "ITEMS <0-200>"
- keyword: ROOM
handler: cmd_room
description: "Change to room number (GAME only)"
usage: "ROOM <1-60>|NEXT|PREV"
completions:
ROOM: [NEXT, PREV]
- keyword: SCENE
handler: cmd_scene
description: Change scene
usage: "SCENE [LOGO|LOADING|TITLE|CREDITS|GAME|ENDING|ENDING2|RESTART]"
completions:
SCENE: [LOGO, LOADING, TITLE, CREDITS, GAME, ENDING, ENDING2, RESTART]
- name: CHEATS
commands:
- keyword: CHEAT
handler: cmd_cheat
description: "Game cheats (GAME only)"
usage: "CHEAT [INFINITE LIVES|INVINCIBILITY|OPEN THE JAIL|CLOSE THE JAIL]"
hidden: true
completions:
CHEAT: [INFINITE, INVINCIBILITY, OPEN, CLOSE]
CHEAT INFINITE: [LIVES]
CHEAT INFINITE LIVES: [ON, OFF]
CHEAT INVINCIBILITY: [ON, OFF]
CHEAT OPEN: [THE]
CHEAT OPEN THE: [JAIL]
CHEAT CLOSE: [THE]
CHEAT CLOSE THE: [JAIL]
debug_extras:
hidden: false

View File

@@ -116,6 +116,7 @@ ui:
supersampling_enabled: "SUPERMOSTREIG ACTIVAT" supersampling_enabled: "SUPERMOSTREIG ACTIVAT"
supersampling_disabled: "SUPERMOSTREIG DESACTIVAT" supersampling_disabled: "SUPERMOSTREIG DESACTIVAT"
palette: "PALETA" palette: "PALETA"
palette_sort: "ORDENACIÓ PALETA"
integer_scale_enabled: "ESCALAT SENCER ACTIVAT" integer_scale_enabled: "ESCALAT SENCER ACTIVAT"
integer_scale_disabled: "ESCALAT SENCER DESACTIVAT" integer_scale_disabled: "ESCALAT SENCER DESACTIVAT"
vsync_enabled: "V-SYNC ACTIVAT" vsync_enabled: "V-SYNC ACTIVAT"
@@ -125,6 +126,8 @@ scoreboard:
items: "TRESORS PILLATS " items: "TRESORS PILLATS "
time: " HORA " time: " HORA "
rooms: "SALES" rooms: "SALES"
cheat_infinite_lives: "vides inf"
cheat_invincibility: "inv"
game: game:
music_enabled: "MÚSICA ACTIVADA" music_enabled: "MÚSICA ACTIVADA"

View File

@@ -116,6 +116,7 @@ ui:
supersampling_enabled: "SUPERSAMPLING ON" supersampling_enabled: "SUPERSAMPLING ON"
supersampling_disabled: "SUPERSAMPLING OFF" supersampling_disabled: "SUPERSAMPLING OFF"
palette: "PALETTE" palette: "PALETTE"
palette_sort: "PALETTE SORT"
integer_scale_enabled: "INTEGER SCALE ENABLED" integer_scale_enabled: "INTEGER SCALE ENABLED"
integer_scale_disabled: "INTEGER SCALE DISABLED" integer_scale_disabled: "INTEGER SCALE DISABLED"
vsync_enabled: "V-SYNC ENABLED" vsync_enabled: "V-SYNC ENABLED"
@@ -125,6 +126,8 @@ scoreboard:
items: "ITEMS COLLECTED " items: "ITEMS COLLECTED "
time: " TIME " time: " TIME "
rooms: "ROOMS" rooms: "ROOMS"
cheat_infinite_lives: "inf lives"
cheat_invincibility: "inv"
game: game:
music_enabled: "MUSIC ENABLED" music_enabled: "MUSIC ENABLED"

View File

@@ -0,0 +1,19 @@
JASC-PAL
0100
16
52 49 40
80 73 57
92 104 82
108 116 76
125 130 73
163 158 85
202 181 103
119 63 53
132 86 64
160 119 84
188 153 120
214 193 157
234 220 193
247 240 221
255 251 237
255 255 255

View File

@@ -0,0 +1,19 @@
JASC-PAL
0100
16
32 32 32
45 33 30
69 41 35
109 61 41
177 107 74
232 159 110
232 190 130
93 117 87
142 146 87
112 123 136
138 167 172
229 93 77
241 134 108
210 103 48
222 154 40
232 216 165

View File

@@ -0,0 +1,19 @@
JASC-PAL
0100
16
22 23 26
127 6 34
214 36 17
255 132 38
255 209 0
250 253 255
255 128 164
255 38 116
148 33 106
67 0 103
35 73 117
104 174 212
191 255 60
16 210 117
0 120 153
0 40 89

19
data/palette/darkseed.pal Normal file
View File

@@ -0,0 +1,19 @@
JASC-PAL
0100
16
0 0 0
0 20 24
0 32 36
0 44 56
20 52 68
68 52 68
88 60 72
108 76 68
128 96 88
108 112 108
136 128 120
164 148 132
196 172 156
216 176 168
236 212 208
252 252 252

19
data/palette/h16da.pal Normal file
View File

@@ -0,0 +1,19 @@
JASC-PAL
0100
16
226 217 228
108 154 154
82 103 93
55 64 59
243 200 147
229 152 125
203 94 92
114 51 76
192 165 169
191 125 133
128 77 83
64 48 56
124 143 178
76 82 116
46 51 77
31 32 37

View File

@@ -0,0 +1,19 @@
JASC-PAL
0100
16
10 8 25
49 50 67
69 59 70
87 84 117
130 105 128
164 111 114
185 115 113
205 95 105
229 76 81
201 55 73
144 161 168
140 147 137
195 150 145
236 151 134
235 171 145
219 182 167

View File

@@ -0,0 +1,19 @@
JASC-PAL
0100
16
0 3 60
0 82 96
0 157 74
10 255 82
0 56 132
0 138 197
0 247 255
255 92 255
172 41 206
96 0 136
177 5 133
255 0 78
42 46 121
78 110 168
173 212 250
255 255 255

View File

@@ -1,19 +0,0 @@
JASC-PAL
0100
16
26 28 44
93 39 93
177 62 83
239 125 87
255 205 117
167 240 112
56 183 100
37 113 121
41 54 111
59 93 201
65 166 246
115 239 247
244 244 244
148 176 194
86 108 134
51 60 87

View File

@@ -2,18 +2,18 @@ JASC-PAL
0100 0100
16 16
26 28 44 26 28 44
41 54 111
51 60 87
86 108 134
59 93 201
37 113 121
93 39 93 93 39 93
177 62 83 177 62 83
56 183 100
167 240 112
65 166 246
115 239 247
239 125 87 239 125 87
255 205 117 255 205 117
148 176 194 167 240 112
56 183 100
37 113 121
41 54 111
59 93 201
65 166 246
115 239 247
244 244 244 244 244 244
148 176 194
86 108 134
51 60 87

View File

@@ -0,0 +1,19 @@
JASC-PAL
0100
16
40 40 46
108 86 113
217 200 191
249 130 132
176 169 228
172 204 228
179 227 218
254 170 228
135 168 137
176 235 147
233 245 157
255 230 198
222 163 139
255 195 132
255 247 160
255 247 228

View File

@@ -0,0 +1,19 @@
JASC-PAL
0100
16
0 0 0
50 1 50
84 1 103
118 50 118
50 50 171
35 103 239
152 186 220
253 253 253
1 186 152
1 137 84
254 239 69
119 70 2
186 118 84
254 1 69
239 152 152
254 205 205

View File

@@ -4,17 +4,7 @@ frameWidth: 8
frameHeight: 16 frameHeight: 16
animations: animations:
- name: stand - name: default
speed: 0.1333
loop: 0
frames: [0]
- name: walk
speed: 0.1333 speed: 0.1333
loop: 0 loop: 0
frames: [0, 1, 2, 3] frames: [0, 1, 2, 3]
- name: walk_menu
speed: 0.0
loop: 0
frames: [0, 1, 2, 3]

View File

@@ -4,17 +4,7 @@ frameWidth: 8
frameHeight: 16 frameHeight: 16
animations: animations:
- name: stand - name: default
speed: 0.1333
loop: 0
frames: [0]
- name: walk
speed: 0.1333 speed: 0.1333
loop: 0 loop: 0
frames: [0, 1, 2, 3, 4, 5, 6, 7] frames: [0, 1, 2, 3, 4, 5, 6, 7]
- name: walk_menu
speed: 0.0
loop: 0
frames: [0, 1, 2, 3, 4, 5, 6, 7]

View File

@@ -92,21 +92,21 @@ namespace GlobalInputs {
void handleToggleShaders() { void handleToggleShaders() {
Screen::get()->toggleShaders(); Screen::get()->toggleShaders();
Notifier::get()->show({Locale::get()->get(Options::video.postfx ? "ui.shaders_enabled" : "ui.shaders_disabled")}); // NOLINT(readability-static-accessed-through-instance) Notifier::get()->show({Locale::get()->get(Options::video.shader.enabled ? "ui.shaders_enabled" : "ui.shaders_disabled")}); // NOLINT(readability-static-accessed-through-instance)
} }
void handleNextShaderPreset() { void handleNextShaderPreset() {
if (Options::current_shader == Rendering::ShaderType::CRTPI) { if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
if (!Options::crtpi_presets.empty()) { if (!Options::crtpi_presets.empty()) {
Options::current_crtpi_preset = (Options::current_crtpi_preset + 1) % static_cast<int>(Options::crtpi_presets.size()); Options::video.shader.current_crtpi_preset = (Options::video.shader.current_crtpi_preset + 1) % static_cast<int>(Options::crtpi_presets.size());
Screen::get()->reloadCrtPi(); Screen::get()->reloadCrtPi();
Notifier::get()->show({Locale::get()->get("ui.crtpi") + " " + Options::crtpi_presets[static_cast<size_t>(Options::current_crtpi_preset)].name}); // NOLINT(readability-static-accessed-through-instance) Notifier::get()->show({Locale::get()->get("ui.crtpi") + " " + prettyName(Options::crtpi_presets[static_cast<size_t>(Options::video.shader.current_crtpi_preset)].name)}); // NOLINT(readability-static-accessed-through-instance)
} }
} else { } else {
if (!Options::postfx_presets.empty()) { if (!Options::postfx_presets.empty()) {
Options::current_postfx_preset = (Options::current_postfx_preset + 1) % static_cast<int>(Options::postfx_presets.size()); Options::video.shader.current_postfx_preset = (Options::video.shader.current_postfx_preset + 1) % static_cast<int>(Options::postfx_presets.size());
Screen::get()->reloadPostFX(); Screen::get()->reloadPostFX();
Notifier::get()->show({Locale::get()->get("ui.postfx") + " " + Options::postfx_presets[static_cast<size_t>(Options::current_postfx_preset)].name}); // NOLINT(readability-static-accessed-through-instance) Notifier::get()->show({Locale::get()->get("ui.postfx") + " " + prettyName(Options::postfx_presets[static_cast<size_t>(Options::video.shader.current_postfx_preset)].name)}); // NOLINT(readability-static-accessed-through-instance)
} }
} }
} }
@@ -114,17 +114,22 @@ namespace GlobalInputs {
void handleNextShader() { void handleNextShader() {
Screen::get()->nextShader(); Screen::get()->nextShader();
Notifier::get()->show({Locale::get()->get("ui.shader") + " " + // NOLINT(readability-static-accessed-through-instance) Notifier::get()->show({Locale::get()->get("ui.shader") + " " + // NOLINT(readability-static-accessed-through-instance)
(Options::current_shader == Rendering::ShaderType::CRTPI ? "CRTPI" : "POSTFX")}); (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI ? "CRTPI" : "POSTFX")});
} }
void handleNextPalette() { void handleNextPalette() {
Screen::get()->nextPalette(); Screen::get()->nextPalette();
Notifier::get()->show({Locale::get()->get("ui.palette") + " " + Options::video.palette}); // NOLINT(readability-static-accessed-through-instance) Notifier::get()->show({Locale::get()->get("ui.palette") + " " + toUpper(Screen::get()->getPalettePrettyName())}); // NOLINT(readability-static-accessed-through-instance)
} }
void handlePreviousPalette() { void handlePreviousPalette() {
Screen::get()->previousPalette(); Screen::get()->previousPalette();
Notifier::get()->show({Locale::get()->get("ui.palette") + " " + Options::video.palette}); // NOLINT(readability-static-accessed-through-instance) Notifier::get()->show({Locale::get()->get("ui.palette") + " " + toUpper(Screen::get()->getPalettePrettyName())}); // NOLINT(readability-static-accessed-through-instance)
}
void handleNextPaletteSortMode() {
Screen::get()->nextPaletteSortMode();
Notifier::get()->show({Locale::get()->get("ui.palette_sort") + " " + toUpper(Screen::get()->getPaletteSortModeName())}); // NOLINT(readability-static-accessed-through-instance)
} }
void handleToggleIntegerScale() { void handleToggleIntegerScale() {
@@ -160,20 +165,25 @@ namespace GlobalInputs {
return InputAction::WINDOW_INC_ZOOM; return InputAction::WINDOW_INC_ZOOM;
} }
} }
if (Screen::get()->isHardwareAccelerated()) {
if (Input::get()->checkAction(InputAction::TOGGLE_SHADER, Input::DO_NOT_ALLOW_REPEAT)) { if (Input::get()->checkAction(InputAction::TOGGLE_SHADER, Input::DO_NOT_ALLOW_REPEAT)) {
if ((SDL_GetModState() & SDL_KMOD_CTRL) != 0U) { if ((SDL_GetModState() & SDL_KMOD_CTRL) != 0U) {
return InputAction::TOGGLE_SUPERSAMPLING; // Ctrl+F4 return InputAction::TOGGLE_SUPERSAMPLING; // Ctrl+F4
} }
if (Options::video.postfx && ((SDL_GetModState() & SDL_KMOD_SHIFT) != 0U)) { if (Options::video.shader.enabled && ((SDL_GetModState() & SDL_KMOD_SHIFT) != 0U)) {
return InputAction::NEXT_SHADER_PRESET; // Shift+F4 return InputAction::NEXT_SHADER_PRESET; // Shift+F4
} }
return InputAction::TOGGLE_SHADER; // F4 return InputAction::TOGGLE_SHADER; // F4
} }
if (Input::get()->checkAction(InputAction::NEXT_PALETTE, Input::DO_NOT_ALLOW_REPEAT)) {
return InputAction::NEXT_PALETTE;
} }
if (Input::get()->checkAction(InputAction::PREVIOUS_PALETTE, Input::DO_NOT_ALLOW_REPEAT)) { if (Input::get()->checkAction(InputAction::NEXT_PALETTE, Input::DO_NOT_ALLOW_REPEAT)) {
return InputAction::PREVIOUS_PALETTE; if ((SDL_GetModState() & SDL_KMOD_CTRL) != 0U) {
return InputAction::PREVIOUS_PALETTE; // Ctrl+F5
}
return InputAction::NEXT_PALETTE; // F5
}
if (Input::get()->checkAction(InputAction::NEXT_PALETTE_SORT, Input::DO_NOT_ALLOW_REPEAT)) {
return InputAction::NEXT_PALETTE_SORT; // F6
} }
if (Input::get()->checkAction(InputAction::TOGGLE_INTEGER_SCALE, Input::DO_NOT_ALLOW_REPEAT)) { if (Input::get()->checkAction(InputAction::TOGGLE_INTEGER_SCALE, Input::DO_NOT_ALLOW_REPEAT)) {
return InputAction::TOGGLE_INTEGER_SCALE; return InputAction::TOGGLE_INTEGER_SCALE;
@@ -271,6 +281,10 @@ namespace GlobalInputs {
handlePreviousPalette(); handlePreviousPalette();
break; break;
case InputAction::NEXT_PALETTE_SORT:
handleNextPaletteSortMode();
break;
case InputAction::TOGGLE_INTEGER_SCALE: case InputAction::TOGGLE_INTEGER_SCALE:
handleToggleIntegerScale(); handleToggleIntegerScale();
break; break;

View File

@@ -45,14 +45,14 @@ Input::Input(std::string game_controller_db_path)
{Action::TOGGLE_FULLSCREEN, KeyState{.scancode = SDL_SCANCODE_F3}}, {Action::TOGGLE_FULLSCREEN, KeyState{.scancode = SDL_SCANCODE_F3}},
{Action::TOGGLE_SHADER, KeyState{.scancode = SDL_SCANCODE_F4}}, {Action::TOGGLE_SHADER, KeyState{.scancode = SDL_SCANCODE_F4}},
{Action::NEXT_PALETTE, KeyState{.scancode = SDL_SCANCODE_F5}}, {Action::NEXT_PALETTE, KeyState{.scancode = SDL_SCANCODE_F5}},
{Action::PREVIOUS_PALETTE, KeyState{.scancode = SDL_SCANCODE_F6}}, {Action::NEXT_PALETTE_SORT, KeyState{.scancode = SDL_SCANCODE_F6}},
{Action::TOGGLE_INTEGER_SCALE, KeyState{.scancode = SDL_SCANCODE_F7}}, {Action::TOGGLE_INTEGER_SCALE, KeyState{.scancode = SDL_SCANCODE_F7}},
{Action::TOGGLE_IN_GAME_MUSIC, KeyState{.scancode = SDL_SCANCODE_F8}}, {Action::TOGGLE_IN_GAME_MUSIC, KeyState{.scancode = SDL_SCANCODE_F8}},
{Action::TOGGLE_BORDER, KeyState{.scancode = SDL_SCANCODE_F9}}, {Action::TOGGLE_BORDER, KeyState{.scancode = SDL_SCANCODE_F9}},
{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

@@ -20,6 +20,7 @@ const std::unordered_map<InputAction, std::string> ACTION_TO_STRING = {
{InputAction::TOGGLE_IN_GAME_MUSIC, "TOGGLE_MUSIC"}, {InputAction::TOGGLE_IN_GAME_MUSIC, "TOGGLE_MUSIC"},
{InputAction::NEXT_PALETTE, "NEXT_PALETTE"}, {InputAction::NEXT_PALETTE, "NEXT_PALETTE"},
{InputAction::PREVIOUS_PALETTE, "PREVIOUS_PALETTE"}, {InputAction::PREVIOUS_PALETTE, "PREVIOUS_PALETTE"},
{InputAction::NEXT_PALETTE_SORT, "NEXT_PALETTE_SORT"},
{InputAction::TOGGLE_SHADER, "TOGGLE_POSTFX"}, {InputAction::TOGGLE_SHADER, "TOGGLE_POSTFX"},
{InputAction::NEXT_SHADER_PRESET, "NEXT_POSTFX_PRESET"}, {InputAction::NEXT_SHADER_PRESET, "NEXT_POSTFX_PRESET"},
{InputAction::TOGGLE_INFO, "TOGGLE_DEBUG"}, {InputAction::TOGGLE_INFO, "TOGGLE_DEBUG"},
@@ -42,6 +43,7 @@ const std::unordered_map<std::string, InputAction> STRING_TO_ACTION = {
{"TOGGLE_MUSIC", InputAction::TOGGLE_IN_GAME_MUSIC}, {"TOGGLE_MUSIC", InputAction::TOGGLE_IN_GAME_MUSIC},
{"NEXT_PALETTE", InputAction::NEXT_PALETTE}, {"NEXT_PALETTE", InputAction::NEXT_PALETTE},
{"PREVIOUS_PALETTE", InputAction::PREVIOUS_PALETTE}, {"PREVIOUS_PALETTE", InputAction::PREVIOUS_PALETTE},
{"NEXT_PALETTE_SORT", InputAction::NEXT_PALETTE_SORT},
{"TOGGLE_POSTFX", InputAction::TOGGLE_SHADER}, {"TOGGLE_POSTFX", InputAction::TOGGLE_SHADER},
{"NEXT_POSTFX_PRESET", InputAction::NEXT_SHADER_PRESET}, {"NEXT_POSTFX_PRESET", InputAction::NEXT_SHADER_PRESET},
{"TOGGLE_DEBUG", InputAction::TOGGLE_INFO}, {"TOGGLE_DEBUG", InputAction::TOGGLE_INFO},

View File

@@ -31,6 +31,7 @@ enum class InputAction : int { // Acciones de entrada posibles en el juego
TOGGLE_IN_GAME_MUSIC, TOGGLE_IN_GAME_MUSIC,
NEXT_PALETTE, NEXT_PALETTE,
PREVIOUS_PALETTE, PREVIOUS_PALETTE,
NEXT_PALETTE_SORT,
TOGGLE_INFO, TOGGLE_INFO,
TOGGLE_CONSOLE, TOGGLE_CONSOLE,

View File

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

View File

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

View File

@@ -0,0 +1,276 @@
#include "core/rendering/palette_manager.hpp"
#include <algorithm>
#include <cctype>
#include <cmath>
#include <string>
#include <vector>
#include "core/rendering/surface.hpp"
#include "core/resources/resource_cache.hpp"
#include "game/defaults.hpp"
#include "game/options.hpp"
#include "utils/utils.hpp"
// ── Conversión string ↔ PaletteSortMode ──────────────────────────────────────
auto sortModeFromString(const std::string& str) -> PaletteSortMode {
const std::string lower = toLower(str);
if (lower == "luminance") { return PaletteSortMode::LUMINANCE; }
if (lower == "spectrum") { return PaletteSortMode::SPECTRUM; }
return PaletteSortMode::ORIGINAL;
}
auto sortModeToString(PaletteSortMode mode) -> std::string {
switch (mode) {
case PaletteSortMode::LUMINANCE:
return "luminance";
case PaletteSortMode::SPECTRUM:
return "spectrum";
default:
return "original";
}
}
// ── Paleta de referencia ZX Spectrum (16 colores ARGB) ───────────────────────
namespace {
// Helpers para extraer componentes RGB de un color ARGB (0xAARRGGBB)
constexpr auto redOf(Uint32 c) -> int { return static_cast<int>((c >> 16) & 0xFF); }
constexpr auto greenOf(Uint32 c) -> int { return static_cast<int>((c >> 8) & 0xFF); }
constexpr auto blueOf(Uint32 c) -> int { return static_cast<int>(c & 0xFF); }
constexpr auto makeARGB(int r, int g, int b) -> Uint32 {
return (0xFFU << 24) | (static_cast<Uint32>(r) << 16) | (static_cast<Uint32>(g) << 8) | static_cast<Uint32>(b);
}
// Paleta ZX Spectrum de referencia (misma que en tools/sort_palette/sort_palette.py)
constexpr std::array<Uint32, 16> SPECTRUM_REFERENCE = {
makeARGB(0, 0, 0),
makeARGB(0, 0, 0),
makeARGB(0, 0, 216),
makeARGB(0, 0, 255),
makeARGB(216, 0, 0),
makeARGB(255, 0, 0),
makeARGB(216, 0, 216),
makeARGB(255, 0, 255),
makeARGB(0, 216, 0),
makeARGB(0, 255, 0),
makeARGB(0, 216, 216),
makeARGB(0, 255, 255),
makeARGB(216, 216, 0),
makeARGB(255, 255, 0),
makeARGB(216, 216, 216),
makeARGB(255, 255, 255),
};
// Luminancia percibida (ITU-R BT.709)
auto luminance(Uint32 color) -> double {
return 0.2126 * redOf(color) + 0.7152 * greenOf(color) + 0.0722 * blueOf(color);
}
// Distancia euclídea al cuadrado en espacio RGB (no necesita sqrt para comparar)
auto rgbDistanceSq(Uint32 a, Uint32 b) -> int {
const int dr = redOf(a) - redOf(b);
const int dg = greenOf(a) - greenOf(b);
const int db = blueOf(a) - blueOf(b);
return dr * dr + dg * dg + db * db;
}
// Cuenta los colores activos en la paleta (los que tienen alpha != 0)
auto countActiveColors(const Palette& palette) -> size_t {
size_t count = 0;
for (const auto& c : palette) {
if (c == 0) { break; }
++count;
}
return count;
}
// Ordenar por luminancia
auto sortByLuminance(const Palette& palette) -> Palette {
const size_t n = countActiveColors(palette);
std::vector<Uint32> colors(palette.begin(), palette.begin() + static_cast<ptrdiff_t>(n));
std::sort(colors.begin(), colors.end(), [](Uint32 a, Uint32 b) {
return luminance(a) < luminance(b);
});
Palette result{};
result.fill(0);
std::copy(colors.begin(), colors.end(), result.begin());
return result;
}
// Ordenar por similitud con la paleta ZX Spectrum (greedy matching)
auto sortBySpectrum(const Palette& palette) -> Palette {
const size_t n = countActiveColors(palette);
std::vector<Uint32> available(palette.begin(), palette.begin() + static_cast<ptrdiff_t>(n));
std::vector<Uint32> result;
result.reserve(n);
// Para cada color de referencia del Spectrum, buscar el más cercano disponible
const size_t refs = std::min(n, SPECTRUM_REFERENCE.size());
for (size_t i = 0; i < refs && !available.empty(); ++i) {
const Uint32 ref = SPECTRUM_REFERENCE[i];
auto best = std::min_element(available.begin(), available.end(), [ref](Uint32 a, Uint32 b) {
return rgbDistanceSq(a, ref) < rgbDistanceSq(b, ref);
});
result.push_back(*best);
available.erase(best);
}
// Si quedan colores sin asignar, añadirlos al final
for (const auto& c : available) {
result.push_back(c);
}
Palette out{};
out.fill(0);
std::copy(result.begin(), result.end(), out.begin());
return out;
}
} // namespace
// ── PaletteManager ───────────────────────────────────────────────────────────
PaletteManager::PaletteManager(
std::vector<std::string> raw_paths,
const std::string& initial_name,
PaletteSortMode initial_sort_mode,
std::shared_ptr<Surface> game_surface,
std::shared_ptr<Surface> border_surface,
OnChangeCallback on_change)
: palettes_(std::move(raw_paths)),
sort_mode_(initial_sort_mode),
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 = sortPalette(readPalFile(palettes_.at(current_)), sort_mode_);
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 lower_name = toLower(name + ".pal");
for (size_t i = 0; i < palettes_.size(); ++i) {
if (toLower(palettes_[i]) == lower_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(), ::tolower);
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(), ::tolower);
return name;
}
auto PaletteManager::getPrettyName() const -> std::string {
std::string name = getCurrentName();
std::ranges::replace(name, '-', ' ');
return name;
}
void PaletteManager::nextSortMode() {
sort_mode_ = static_cast<PaletteSortMode>((static_cast<int>(sort_mode_) + 1) % static_cast<int>(PaletteSortMode::COUNT));
Options::video.palette_sort = sortModeToString(sort_mode_);
apply();
}
void PaletteManager::setSortMode(PaletteSortMode mode) {
sort_mode_ = mode;
Options::video.palette_sort = sortModeToString(sort_mode_);
apply();
}
auto PaletteManager::getSortMode() const -> PaletteSortMode {
return sort_mode_;
}
auto PaletteManager::getSortModeName() const -> std::string {
return sortModeToString(sort_mode_);
}
void PaletteManager::apply() {
Palette raw = Resource::Cache::get()->getPalette(palettes_.at(current_));
Palette sorted = sortPalette(raw, sort_mode_);
game_surface_->loadPalette(sorted);
border_surface_->loadPalette(sorted);
Options::video.palette = getCurrentName();
if (on_change_) {
on_change_();
}
}
auto PaletteManager::findIndex(const std::string& name) const -> size_t {
const std::string lower_name = toLower(name + ".pal");
for (size_t i = 0; i < palettes_.size(); ++i) {
if (toLower(getFileName(palettes_[i])) == lower_name) {
return i;
}
}
// Fallback: buscar la paleta por defecto
const std::string default_name = toLower(std::string(Defaults::Video::PALETTE_NAME) + ".pal");
for (size_t i = 0; i < palettes_.size(); ++i) {
if (toLower(getFileName(palettes_[i])) == default_name) {
return i;
}
}
return 0;
}
void PaletteManager::processPathList() {
for (auto& palette : palettes_) {
palette = getFileName(palette);
}
}
auto PaletteManager::sortPalette(const Palette& palette, PaletteSortMode mode) -> Palette {
switch (mode) {
case PaletteSortMode::LUMINANCE:
return sortByLuminance(palette);
case PaletteSortMode::SPECTRUM:
return sortBySpectrum(palette);
default:
return palette;
}
}

View File

@@ -0,0 +1,64 @@
#pragma once
#include <SDL3/SDL.h>
#include <array>
#include <functional>
#include <memory>
#include <string>
#include <vector>
// Alias de paleta (igual que en surface.hpp; evita incluir todo el header)
using Palette = std::array<Uint32, 256>;
class Surface;
// Modo de ordenación de paletas
enum class PaletteSortMode : int {
ORIGINAL = 0, // Paleta tal cual viene del fichero
LUMINANCE = 1, // Ordenada por luminancia percibida
SPECTRUM = 2, // Reordenada para imitar la paleta ZX Spectrum
COUNT = 3
};
// Conversión string ↔ PaletteSortMode
auto sortModeFromString(const std::string& str) -> PaletteSortMode;
auto sortModeToString(PaletteSortMode mode) -> std::string;
class PaletteManager {
public:
using OnChangeCallback = std::function<void()>;
PaletteManager(
std::vector<std::string> raw_paths,
const std::string& initial_name,
PaletteSortMode initial_sort_mode,
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 (minúsculas, sin .pal)
[[nodiscard]] auto getCurrentName() const -> std::string; // Nombre de la paleta actual (minúsculas, sin .pal)
[[nodiscard]] auto getPrettyName() const -> std::string; // Nombre actual con guiones sustituidos por espacios
void nextSortMode(); // Cicla al siguiente modo de ordenación
void setSortMode(PaletteSortMode mode); // Establece un modo de ordenación concreto
[[nodiscard]] auto getSortMode() const -> PaletteSortMode; // Devuelve el modo de ordenación actual
[[nodiscard]] auto getSortModeName() const -> std::string; // Nombre del modo actual ("ORIGINAL", etc.)
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
static auto sortPalette(const Palette& palette, PaletteSortMode mode) -> Palette; // Reordena una paleta según el modo
std::vector<std::string> palettes_;
size_t current_{0};
PaletteSortMode sort_mode_{PaletteSortMode::ORIGINAL};
std::shared_ptr<Surface> game_surface_;
std::shared_ptr<Surface> border_surface_;
OnChangeCallback on_change_;
};

View File

@@ -14,6 +14,7 @@
#include "game/options.hpp" // Para Options #include "game/options.hpp" // Para Options
#include "game/ui/console.hpp" // Para Console #include "game/ui/console.hpp" // Para Console
#include "game/ui/notifier.hpp" // Para Notifier #include "game/ui/notifier.hpp" // Para Notifier
#include "utils/utils.hpp" // Para prettyName
// [SINGLETON] // [SINGLETON]
RenderInfo* RenderInfo::render_info = nullptr; RenderInfo* RenderInfo::render_info = nullptr;
@@ -89,20 +90,20 @@ void RenderInfo::render() const {
line += " | " + zoom_str + "x"; line += " | " + zoom_str + "x";
// PostFX: muestra shader + preset y supersampling, o nada si está desactivado // PostFX: muestra shader + preset y supersampling, o nada si está desactivado
if (Options::video.postfx) { if (Options::video.shader.enabled) {
const bool IS_CRTPI = (Options::current_shader == Rendering::ShaderType::CRTPI); const bool IS_CRTPI = (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI);
const std::string SHADER_NAME = IS_CRTPI ? "crtpi" : "postfx"; const std::string SHADER_NAME = IS_CRTPI ? "crtpi" : "postfx";
std::string preset_name = "-"; std::string preset_name = "-";
if (IS_CRTPI) { if (IS_CRTPI) {
if (!Options::crtpi_presets.empty()) { if (!Options::crtpi_presets.empty()) {
preset_name = Options::crtpi_presets[static_cast<size_t>(Options::current_crtpi_preset)].name; preset_name = prettyName(Options::crtpi_presets[static_cast<size_t>(Options::video.shader.current_crtpi_preset)].name);
} }
} else { } else {
if (!Options::postfx_presets.empty()) { if (!Options::postfx_presets.empty()) {
preset_name = Options::postfx_presets[static_cast<size_t>(Options::current_postfx_preset)].name; preset_name = prettyName(Options::postfx_presets[static_cast<size_t>(Options::video.shader.current_postfx_preset)].name);
} }
} }
const bool SHOW_SS = Options::video.supersampling && !IS_CRTPI; const bool SHOW_SS = Options::video.supersampling.enabled && !IS_CRTPI;
line += " | " + SHADER_NAME + " " + preset_name + (SHOW_SS ? " (ss)" : ""); line += " | " + SHADER_NAME + " " + preset_name + (SHOW_SS ? " (ss)" : "");
} }

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,28 @@ 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,
sortModeFromString(Options::video.palette_sort),
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 +106,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 +198,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;
@@ -221,11 +240,11 @@ void Screen::renderNotifications() const {
// Activa/desactiva todos los shaders respetando el shader actualmente seleccionado // Activa/desactiva todos los shaders respetando el shader actualmente seleccionado
void Screen::toggleShaders() { void Screen::toggleShaders() {
Options::video.postfx = !Options::video.postfx; Options::video.shader.enabled = !Options::video.shader.enabled;
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) { if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
if (Options::video.postfx) { if (Options::video.shader.enabled) {
// Activar: usar el shader actualmente seleccionado // Activar: usar el shader actualmente seleccionado
if (Options::current_shader == Rendering::ShaderType::CRTPI) { if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
shader_backend_->setActiveShader(Rendering::ShaderType::CRTPI); shader_backend_->setActiveShader(Rendering::ShaderType::CRTPI);
applyCurrentCrtPiPreset(); applyCurrentCrtPiPreset();
} else { } else {
@@ -244,10 +263,10 @@ void Screen::toggleShaders() {
// Recarga el shader del preset actual sin toggle // Recarga el shader del preset actual sin toggle
void Screen::reloadPostFX() { void Screen::reloadPostFX() {
if (Options::video.postfx && shader_backend_ && shader_backend_->isHardwareAccelerated()) { if (Options::video.shader.enabled && shader_backend_ && shader_backend_->isHardwareAccelerated()) {
// El backend ya está activo: solo actualizar uniforms, sin recrear el pipeline // El backend ya está activo: solo actualizar uniforms, sin recrear el pipeline
applyCurrentPostFXPreset(); applyCurrentPostFXPreset();
} else if (Options::video.postfx) { } else if (Options::video.shader.enabled) {
initShaders(); initShaders();
} }
} }
@@ -342,55 +361,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 +444,15 @@ 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 (minú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; auto Screen::getPalettePrettyName() const -> std::string { return palette_manager_->getPrettyName(); }
} void Screen::nextPaletteSortMode() { palette_manager_->nextSortMode(); }
} void Screen::setPaletteSortMode(PaletteSortMode mode) { palette_manager_->setSortMode(mode); }
return static_cast<size_t>(0); auto Screen::getPaletteSortModeName() const -> std::string { return palette_manager_->getSortModeName(); }
}
// 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); }
@@ -537,14 +509,14 @@ auto loadData(const std::string& filepath) -> std::vector<uint8_t> {
} }
void Screen::setLinearUpscale(bool linear) { void Screen::setLinearUpscale(bool linear) {
Options::video.linear_upscale = linear; Options::video.supersampling.linear_upscale = linear;
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) { if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
shader_backend_->setLinearUpscale(linear); shader_backend_->setLinearUpscale(linear);
} }
} }
void Screen::setDownscaleAlgo(int algo) { void Screen::setDownscaleAlgo(int algo) {
Options::video.downscale_algo = algo; Options::video.supersampling.downscale_algo = algo;
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) { if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
shader_backend_->setDownscaleAlgo(algo); shader_backend_->setDownscaleAlgo(algo);
} }
@@ -557,8 +529,8 @@ auto Screen::getSsTextureSize() const -> std::pair<int, int> {
// Activa/desactiva el supersampling global (Ctrl+F4) // Activa/desactiva el supersampling global (Ctrl+F4)
void Screen::toggleSupersampling() { void Screen::toggleSupersampling() {
Options::video.supersampling = !Options::video.supersampling; Options::video.supersampling.enabled = !Options::video.supersampling.enabled;
if (Options::video.postfx && shader_backend_ && shader_backend_->isHardwareAccelerated()) { if (Options::video.shader.enabled && shader_backend_ && shader_backend_->isHardwareAccelerated()) {
applyCurrentPostFXPreset(); applyCurrentPostFXPreset();
} }
} }
@@ -566,11 +538,11 @@ void Screen::toggleSupersampling() {
// Aplica los parámetros del preset actual al backend de shaders // Aplica los parámetros del preset actual al backend de shaders
void Screen::applyCurrentPostFXPreset() { // NOLINT(readability-convert-member-functions-to-static) void Screen::applyCurrentPostFXPreset() { // NOLINT(readability-convert-member-functions-to-static)
if (shader_backend_ && !Options::postfx_presets.empty()) { if (shader_backend_ && !Options::postfx_presets.empty()) {
const auto& p = Options::postfx_presets[static_cast<size_t>(Options::current_postfx_preset)]; const auto& p = Options::postfx_presets[static_cast<size_t>(Options::video.shader.current_postfx_preset)];
// Supersampling es un toggle global (Options::video.supersampling), no por preset. // Supersampling es un toggle global (Options::video.supersampling.enabled), no por preset.
// setOversample primero: puede recrear texturas antes de que setPostFXParams // setOversample primero: puede recrear texturas antes de que setPostFXParams
// decida si hornear scanlines en CPU o aplicarlas en GPU. // decida si hornear scanlines en CPU o aplicarlas en GPU.
shader_backend_->setOversample(Options::video.supersampling ? 3 : 1); shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
Rendering::PostFXParams params{.vignette = p.vignette, .scanlines = p.scanlines, .chroma = p.chroma, .mask = p.mask, .gamma = p.gamma, .curvature = p.curvature, .bleeding = p.bleeding, .flicker = p.flicker}; Rendering::PostFXParams params{.vignette = p.vignette, .scanlines = p.scanlines, .chroma = p.chroma, .mask = p.mask, .gamma = p.gamma, .curvature = p.curvature, .bleeding = p.bleeding, .flicker = p.flicker};
shader_backend_->setPostFXParams(params); shader_backend_->setPostFXParams(params);
} }
@@ -579,7 +551,7 @@ void Screen::applyCurrentPostFXPreset() { // NOLINT(readability-convert-member-
// Aplica los parámetros del preset CrtPi actual al backend de shaders // Aplica los parámetros del preset CrtPi actual al backend de shaders
void Screen::applyCurrentCrtPiPreset() { // NOLINT(readability-convert-member-functions-to-static) void Screen::applyCurrentCrtPiPreset() { // NOLINT(readability-convert-member-functions-to-static)
if (shader_backend_ && !Options::crtpi_presets.empty()) { if (shader_backend_ && !Options::crtpi_presets.empty()) {
const auto& p = Options::crtpi_presets[static_cast<size_t>(Options::current_crtpi_preset)]; const auto& p = Options::crtpi_presets[static_cast<size_t>(Options::video.shader.current_crtpi_preset)];
Rendering::CrtPiParams params{ Rendering::CrtPiParams params{
.scanline_weight = p.scanline_weight, .scanline_weight = p.scanline_weight,
.scanline_gap_brightness = p.scanline_gap_brightness, .scanline_gap_brightness = p.scanline_gap_brightness,
@@ -602,9 +574,9 @@ void Screen::applyCurrentCrtPiPreset() { // NOLINT(readability-convert-member-f
// Cambia el shader de post-procesado activo y aplica el preset correspondiente // Cambia el shader de post-procesado activo y aplica el preset correspondiente
void Screen::setActiveShader(Rendering::ShaderType type) { void Screen::setActiveShader(Rendering::ShaderType type) {
Options::current_shader = type; Options::video.shader.current_shader = type;
if (!shader_backend_) { return; } if (!shader_backend_) { return; }
if (!Options::video.postfx) { if (!Options::video.shader.enabled) {
// Shaders desactivados: guardar preferencia pero mantener pass-through // Shaders desactivados: guardar preferencia pero mantener pass-through
shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX); shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
shader_backend_->setPostFXParams(Rendering::PostFXParams{}); shader_backend_->setPostFXParams(Rendering::PostFXParams{});
@@ -620,7 +592,7 @@ void Screen::setActiveShader(Rendering::ShaderType type) {
// Cicla al siguiente shader disponible (preparado para futura UI) // Cicla al siguiente shader disponible (preparado para futura UI)
void Screen::nextShader() { void Screen::nextShader() {
const Rendering::ShaderType NEXT = (Options::current_shader == Rendering::ShaderType::POSTFX) const Rendering::ShaderType NEXT = (Options::video.shader.current_shader == Rendering::ShaderType::POSTFX)
? Rendering::ShaderType::CRTPI ? Rendering::ShaderType::CRTPI
: Rendering::ShaderType::POSTFX; : Rendering::ShaderType::POSTFX;
setActiveShader(NEXT); setActiveShader(NEXT);
@@ -634,7 +606,8 @@ void Screen::initShaders() {
if (!shader_backend_) { if (!shader_backend_) {
shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>(); shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
shader_backend_->setPreferredDriver(Options::video.gpu_preferred_driver); const std::string fallback_driver = "none";
shader_backend_->setPreferredDriver(Options::video.gpu.acceleration ? Options::video.gpu.preferred_driver : fallback_driver);
} }
shader_backend_->init(window_, tex, "", ""); shader_backend_->init(window_, tex, "", "");
gpu_driver_ = shader_backend_->getDriverName(); gpu_driver_ = shader_backend_->getDriverName();
@@ -642,10 +615,10 @@ void Screen::initShaders() {
// Propagar flags de vsync, integer scale, upscale y downscale al backend GPU // Propagar flags de vsync, integer scale, upscale y downscale al backend GPU
shader_backend_->setVSync(Options::video.vertical_sync); shader_backend_->setVSync(Options::video.vertical_sync);
shader_backend_->setScaleMode(Options::video.integer_scale); shader_backend_->setScaleMode(Options::video.integer_scale);
shader_backend_->setLinearUpscale(Options::video.linear_upscale); shader_backend_->setLinearUpscale(Options::video.supersampling.linear_upscale);
shader_backend_->setDownscaleAlgo(Options::video.downscale_algo); shader_backend_->setDownscaleAlgo(Options::video.supersampling.downscale_algo);
if (Options::video.postfx) { if (Options::video.shader.enabled) {
applyCurrentPostFXPreset(); applyCurrentPostFXPreset();
} else { } else {
// Pass-through: todos los efectos a 0, el shader solo copia la textura // Pass-through: todos los efectos a 0, el shader solo copia la textura
@@ -653,8 +626,8 @@ void Screen::initShaders() {
} }
// Restaurar el shader activo guardado en config (y sus parámetros CrtPi si aplica) // Restaurar el shader activo guardado en config (y sus parámetros CrtPi si aplica)
shader_backend_->setActiveShader(Options::current_shader); shader_backend_->setActiveShader(Options::video.shader.current_shader);
if (Options::current_shader == Rendering::ShaderType::CRTPI) { if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
applyCurrentCrtPiPreset(); applyCurrentCrtPiPreset();
} }
} }

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -403,7 +403,7 @@ namespace Rendering {
// ---------------------------------------------------------------- // ----------------------------------------------------------------
if (preferred_driver_ == "none") { if (preferred_driver_ == "none") {
SDL_Log("SDL3GPUShader: GPU disabled by config, using SDL_Renderer fallback"); SDL_Log("SDL3GPUShader: GPU disabled by config, using SDL_Renderer fallback");
driver_name_ = "none"; driver_name_ = ""; // vacío → RenderInfo mostrará "sdl"
return false; return false;
} }
if (device_ == nullptr) { if (device_ == nullptr) {

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

@@ -175,12 +175,12 @@ void Surface::fillRect(const SDL_FRect* rect, Uint8 color) { // NOLINT(readabil
float x_end = std::min(rect->x + rect->w, surface_data_->width); float x_end = std::min(rect->x + rect->w, surface_data_->width);
float y_end = std::min(rect->y + rect->h, surface_data_->height); float y_end = std::min(rect->y + rect->h, surface_data_->height);
// Recorrer cada píxel dentro del rectángulo directamente // Rellenar fila a fila con memset (memoria contigua por fila)
for (int y = y_start; y < y_end; ++y) { Uint8* data_ptr = surface_data_->data.get();
for (int x = x_start; x < x_end; ++x) { const int surf_width = static_cast<int>(surface_data_->width);
const int INDEX = x + (y * surface_data_->width); const int row_width = static_cast<int>(x_end) - static_cast<int>(x_start);
surface_data_->data.get()[INDEX] = color; for (int y = static_cast<int>(y_start); y < static_cast<int>(y_end); ++y) {
} std::memset(data_ptr + (y * surf_width) + static_cast<int>(x_start), color, row_width);
} }
} }
@@ -192,16 +192,12 @@ void Surface::drawRectBorder(const SDL_FRect* rect, Uint8 color) { // NOLINT(re
float x_end = std::min(rect->x + rect->w, surface_data_->width); float x_end = std::min(rect->x + rect->w, surface_data_->width);
float y_end = std::min(rect->y + rect->h, surface_data_->height); float y_end = std::min(rect->y + rect->h, surface_data_->height);
// Dibujar bordes horizontales // Dibujar bordes horizontales con memset (líneas contiguas en memoria)
for (int x = x_start; x < x_end; ++x) { Uint8* data_ptr = surface_data_->data.get();
// Borde superior const int surf_width = static_cast<int>(surface_data_->width);
const int TOP_INDEX = x + (y_start * surface_data_->width); const int row_width = static_cast<int>(x_end) - static_cast<int>(x_start);
surface_data_->data.get()[TOP_INDEX] = color; std::memset(data_ptr + (static_cast<int>(y_start) * surf_width) + static_cast<int>(x_start), color, row_width);
std::memset(data_ptr + ((static_cast<int>(y_end) - 1) * surf_width) + static_cast<int>(x_start), color, row_width);
// Borde inferior
const int BOTTOM_INDEX = x + ((y_end - 1) * surface_data_->width);
surface_data_->data.get()[BOTTOM_INDEX] = color;
}
// Dibujar bordes verticales // Dibujar bordes verticales
for (int y = y_start; y < y_end; ++y) { for (int y = y_start; y < y_end; ++y) {
@@ -261,6 +257,8 @@ void Surface::render(float dx, float dy, float sx, float sy, float w, float h) {
w = std::min(w, surface_data->width - dx); w = std::min(w, surface_data->width - dx);
h = std::min(h, surface_data->height - dy); h = std::min(h, surface_data->height - dy);
const Uint8* src_ptr = surface_data_->data.get();
Uint8* dst_ptr = surface_data->data.get();
for (int iy = 0; iy < h; ++iy) { for (int iy = 0; iy < h; ++iy) {
for (int ix = 0; ix < w; ++ix) { for (int ix = 0; ix < w; ++ix) {
// Verificar que las coordenadas de destino están dentro de los límites // Verificar que las coordenadas de destino están dentro de los límites
@@ -269,9 +267,9 @@ void Surface::render(float dx, float dy, float sx, float sy, float w, float h) {
int src_x = sx + ix; int src_x = sx + ix;
int src_y = sy + iy; int src_y = sy + iy;
Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + (src_y * surface_data_->width))]; Uint8 color = src_ptr[static_cast<size_t>(src_x + (src_y * surface_data_->width))];
if (color != static_cast<Uint8>(transparent_color_)) { if (color != static_cast<Uint8>(transparent_color_)) {
surface_data->data.get()[static_cast<size_t>(dest_x + (dest_y * surface_data->width))] = sub_palette_[color]; dst_ptr[static_cast<size_t>(dest_x + (dest_y * surface_data->width))] = sub_palette_[color];
} }
} }
} }
@@ -299,6 +297,8 @@ void Surface::render(int x, int y, SDL_FRect* src_rect, SDL_FlipMode flip) { //
h = std::min(h, surface_data_dest->height - y); h = std::min(h, surface_data_dest->height - y);
// Renderiza píxel por píxel aplicando el flip si es necesario // Renderiza píxel por píxel aplicando el flip si es necesario
const Uint8* src_ptr = surface_data_->data.get();
Uint8* dst_ptr = surface_data_dest->data.get();
for (int iy = 0; iy < h; ++iy) { for (int iy = 0; iy < h; ++iy) {
for (int ix = 0; ix < w; ++ix) { for (int ix = 0; ix < w; ++ix) {
// Coordenadas de origen // Coordenadas de origen
@@ -312,9 +312,9 @@ void Surface::render(int x, int y, SDL_FRect* src_rect, SDL_FlipMode flip) { //
// Verificar que las coordenadas de destino están dentro de los límites // Verificar que las coordenadas de destino están dentro de los límites
if (dest_x >= 0 && dest_x < surface_data_dest->width && dest_y >= 0 && dest_y < surface_data_dest->height) { if (dest_x >= 0 && dest_x < surface_data_dest->width && dest_y >= 0 && dest_y < surface_data_dest->height) {
// Copia el píxel si no es transparente // Copia el píxel si no es transparente
Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + (src_y * surface_data_->width))]; Uint8 color = src_ptr[static_cast<size_t>(src_x + (src_y * surface_data_->width))];
if (color != static_cast<Uint8>(transparent_color_)) { if (color != static_cast<Uint8>(transparent_color_)) {
surface_data_dest->data[dest_x + (dest_y * surface_data_dest->width)] = sub_palette_[color]; dst_ptr[static_cast<size_t>(dest_x + (dest_y * surface_data_dest->width))] = sub_palette_[color];
} }
} }
} }
@@ -567,13 +567,16 @@ void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture) { //
// Convertir `pitch` de bytes a Uint32 (asegurando alineación correcta en hardware) // Convertir `pitch` de bytes a Uint32 (asegurando alineación correcta en hardware)
int row_stride = pitch / sizeof(Uint32); int row_stride = pitch / sizeof(Uint32);
for (int y = 0; y < surface_data_->height; ++y) { // Cachear punteros fuera del bucle para permitir autovectorización SIMD
for (int x = 0; x < surface_data_->width; ++x) { const Uint8* src = surface_data_->data.get();
// Calcular la posición correcta en la textura teniendo en cuenta el stride const Uint32* pal = palette_.data();
int texture_index = (y * row_stride) + x; const int width = surface_data_->width;
int surface_index = (y * surface_data_->width) + x; const int height = surface_data_->height;
for (int y = 0; y < height; ++y) {
pixels[texture_index] = palette_[surface_data_->data.get()[surface_index]]; const Uint8* src_row = src + (y * width);
Uint32* dst_row = pixels + (y * row_stride);
for (int x = 0; x < width; ++x) {
dst_row[x] = pal[src_row[x]];
} }
} }
@@ -613,12 +616,16 @@ void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FR
int row_stride = pitch / sizeof(Uint32); int row_stride = pitch / sizeof(Uint32);
for (int y = 0; y < surface_data_->height; ++y) { // Cachear punteros fuera del bucle para permitir autovectorización SIMD
for (int x = 0; x < surface_data_->width; ++x) { const Uint8* src = surface_data_->data.get();
int texture_index = (y * row_stride) + x; const Uint32* pal = palette_.data();
int surface_index = (y * surface_data_->width) + x; const int width = surface_data_->width;
const int height = surface_data_->height;
pixels[texture_index] = palette_[surface_data_->data.get()[surface_index]]; for (int y = 0; y < height; ++y) {
const Uint8* src_row = src + (y * width);
Uint32* dst_row = pixels + (y * row_stride);
for (int x = 0; x < width; ++x) {
dst_row[x] = pal[src_row[x]];
} }
} }

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

@@ -25,13 +25,15 @@ namespace Defaults::Video {
constexpr bool FULLSCREEN = false; // Modo de pantalla completa por defecto (false = ventana) constexpr bool FULLSCREEN = false; // Modo de pantalla completa por defecto (false = ventana)
constexpr Screen::Filter FILTER = Screen::Filter::NEAREST; // Filtro por defecto constexpr Screen::Filter FILTER = Screen::Filter::NEAREST; // Filtro por defecto
constexpr bool VERTICAL_SYNC = true; // Vsync activado por defecto constexpr bool VERTICAL_SYNC = true; // Vsync activado por defecto
constexpr bool POSTFX = false; // PostFX desactivado por defecto constexpr bool SHADER_ENABLED = false; // Shaders de post-procesado desactivados por defecto
constexpr bool SUPERSAMPLING = false; // Supersampling desactivado por defecto constexpr bool SUPERSAMPLING = false; // Supersampling desactivado por defecto
constexpr bool INTEGER_SCALE = true; // Escalado entero activado por defecto constexpr bool INTEGER_SCALE = true; // Escalado entero activado por defecto
constexpr bool KEEP_ASPECT = true; // Mantener aspecto activado por defecto constexpr bool KEEP_ASPECT = true; // Mantener aspecto activado por defecto
constexpr const char* PALETTE_NAME = "zx-spectrum"; // Paleta por defecto constexpr const char* PALETTE_NAME = "zx-spectrum"; // Paleta por defecto
constexpr const char* PALETTE_SORT = "original"; // Modo de ordenación de paleta por defecto
constexpr bool LINEAR_UPSCALE = false; // Upscale NEAREST por defecto constexpr bool LINEAR_UPSCALE = false; // Upscale NEAREST por defecto
constexpr int DOWNSCALE_ALGO = 1; // Downscale Lanczos2 por defecto constexpr int DOWNSCALE_ALGO = 1; // Downscale Lanczos2 por defecto
constexpr bool GPU_ACCELERATION = true; // Aceleración GPU activada por defecto
} // namespace Defaults::Video } // namespace Defaults::Video
namespace Defaults::Border { namespace Defaults::Border {
@@ -100,5 +102,6 @@ namespace Defaults::Game::Player {
constexpr int SPAWN_X = 25 * Tile::SIZE; // Posición X inicial constexpr int SPAWN_X = 25 * Tile::SIZE; // Posición X inicial
constexpr int SPAWN_Y = 13 * Tile::SIZE; // Posición Y inicial constexpr int SPAWN_Y = 13 * Tile::SIZE; // Posición Y inicial
constexpr SDL_FlipMode SPAWN_FLIP = Flip::LEFT; // Orientación inicial constexpr SDL_FlipMode SPAWN_FLIP = Flip::LEFT; // Orientación inicial
constexpr int SKIN = 1; // Skin del jugador por defecto (1=normal, 2=alternativa) constexpr const char* SKIN = "default"; // Skin del jugador por defecto
constexpr int COLOR = -1; // Color del jugador (-1 = automático según cheats)
} // namespace Defaults::Game::Player } // namespace Defaults::Game::Player

View File

@@ -620,20 +620,26 @@ auto Player::handleKillingTiles() -> bool {
return false; // No se encontró ninguna colisión return false; // No se encontró ninguna colisión
} }
// Establece el color del jugador (0 = automático según cheats) // Establece el color del jugador (0 = automático según options)
void Player::setColor(Uint8 color) { void Player::setColor(Uint8 color) {
if (color != 0) { if (color != 0) {
color_ = color; color_ = color;
return; return;
} }
if (Options::cheats.invincible == Options::Cheat::State::ENABLED) { // Color personalizado desde opciones
color_ = static_cast<Uint8>(PaletteColor::CYAN); if (Options::game.player_color >= 0) {
} else if (Options::cheats.infinite_lives == Options::Cheat::State::ENABLED) { color_ = static_cast<Uint8>(Options::game.player_color);
color_ = static_cast<Uint8>(PaletteColor::YELLOW);
} else { } else {
color_ = static_cast<Uint8>(PaletteColor::WHITE); color_ = static_cast<Uint8>(PaletteColor::WHITE);
} }
// Si el color coincide con el fondo de la habitación, usar fallback
if (room_ != nullptr && color_ == room_->getBGColor()) {
color_ = (room_->getBGColor() != static_cast<Uint8>(PaletteColor::WHITE))
? static_cast<Uint8>(PaletteColor::WHITE)
: static_cast<Uint8>(PaletteColor::BRIGHT_BLACK);
}
} }
// Actualiza los puntos de colisión // Actualiza los puntos de colisión
@@ -765,11 +771,18 @@ void Player::applySpawnValues(const SpawnData& spawn) {
sprite_->setFlip(spawn.flip); sprite_->setFlip(spawn.flip);
} }
// Resuelve nombre de skin a fichero de animación
auto Player::skinToAnimationPath(const std::string& skin_name) -> std::string {
if (skin_name == "default") {
return "player.yaml";
}
return skin_name + ".yaml";
}
// Cambia la skin del jugador en caliente preservando la orientación actual // Cambia la skin del jugador en caliente preservando la orientación actual
void Player::setSkin(int skin_num) { void Player::setSkin(const std::string& skin_name) {
const auto FLIP = sprite_->getFlip(); const auto FLIP = sprite_->getFlip();
const std::string PATH = (skin_num == 2) ? "player2.yaml" : "player.yaml"; initSprite(skinToAnimationPath(skin_name));
initSprite(PATH);
sprite_->setFlip(FLIP); sprite_->setFlip(FLIP);
} }
@@ -779,7 +792,7 @@ void Player::initSprite(const std::string& animations_path) { // NOLINT(readabi
sprite_ = std::make_unique<AnimatedSprite>(animation_data); sprite_ = std::make_unique<AnimatedSprite>(animation_data);
sprite_->setWidth(WIDTH); sprite_->setWidth(WIDTH);
sprite_->setHeight(HEIGHT); sprite_->setHeight(HEIGHT);
sprite_->setCurrentAnimation("walk"); sprite_->setCurrentAnimation("default");
} }
// Actualiza la posición del sprite y las colisiones // Actualiza la posición del sprite y las colisiones
@@ -880,6 +893,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

@@ -100,12 +100,14 @@ class Player {
auto getCollider() -> SDL_FRect& { return collider_box_; } // Obtiene el rectangulo de colision del jugador auto getCollider() -> SDL_FRect& { return collider_box_; } // Obtiene el rectangulo de colision del jugador
auto getSpawnParams() -> SpawnData { return {.x = x_, .y = y_, .vx = vx_, .vy = vy_, .last_grounded_position = last_grounded_position_, .state = state_, .flip = sprite_->getFlip()}; } // Obtiene el estado de reaparición del jugador auto getSpawnParams() -> SpawnData { return {.x = x_, .y = y_, .vx = vx_, .vy = vy_, .last_grounded_position = last_grounded_position_, .state = state_, .flip = sprite_->getFlip()}; } // Obtiene el estado de reaparición del jugador
void setColor(Uint8 color = 0); // Establece el color del jugador (0 = automático según cheats) void setColor(Uint8 color = 0); // Establece el color del jugador (0 = automático según cheats)
void setSkin(int skin_num); // Cambia la skin del jugador en caliente (1=normal, 2=alternativa) void setSkin(const std::string& skin_name); // Cambia la skin del jugador en caliente ("default" o nombre de enemigo)
static auto skinToAnimationPath(const std::string& skin_name) -> std::string; // Resuelve nombre de skin a fichero de animación
void setRoom(std::shared_ptr<Room> room) { room_ = std::move(room); } // Establece la habitación en la que se encuentra el jugador void setRoom(std::shared_ptr<Room> room) { room_ = std::move(room); } // Establece la habitación en la que se encuentra el jugador
//[[nodiscard]] auto isAlive() const -> bool { return is_alive_ || (Options::cheats.invincible == Options::Cheat::State::ENABLED); } // Comprueba si el jugador esta vivo //[[nodiscard]] auto isAlive() const -> bool { return is_alive_ || (Options::cheats.invincible == Options::Cheat::State::ENABLED); } // Comprueba si el jugador esta vivo
[[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

@@ -3,10 +3,10 @@
#include <string> #include <string>
namespace GameControl { namespace GameControl {
// Disponible en todos los builds — refresca el color del jugador según cheats // Disponible en todos los builds — cambia la skin del jugador ("default" o nombre de enemigo)
inline std::function<void()> refresh_player_color; inline std::function<void(const std::string&)> change_player_skin;
// Disponible en todos los builds — cambia la skin del jugador (1=normal, 2=alternativa) // Disponible en todos los builds — cambia el color del jugador (-1 = automático, 0-15 = color fijo)
inline std::function<void(int)> change_player_skin; inline std::function<void(int)> change_player_color;
} // namespace GameControl } // namespace GameControl
#ifdef _DEBUG #ifdef _DEBUG

View File

@@ -10,6 +10,7 @@
#include "core/rendering/surface.hpp" // Para Surface #include "core/rendering/surface.hpp" // Para Surface
#include "core/rendering/text.hpp" // Para Text #include "core/rendering/text.hpp" // Para Text
#include "core/resources/resource_cache.hpp" // Para Resource #include "core/resources/resource_cache.hpp" // Para Resource
#include "game/entities/player.hpp" // Para Player::skinToAnimationPath
#include "game/options.hpp" // Para Options, options, Cheat, OptionsGame #include "game/options.hpp" // Para Options, options, Cheat, OptionsGame
#include "utils/defines.hpp" // Para BLOCK #include "utils/defines.hpp" // Para BLOCK
#include "utils/utils.hpp" // Para stringToColor #include "utils/utils.hpp" // Para stringToColor
@@ -22,9 +23,10 @@ Scoreboard::Scoreboard(std::shared_ptr<Data> data)
constexpr float SURFACE_HEIGHT = 6.0F * Tile::SIZE; constexpr float SURFACE_HEIGHT = 6.0F * Tile::SIZE;
// Reserva memoria para los objetos // Reserva memoria para los objetos
const auto& player_animation_data = Resource::Cache::get()->getAnimationData((Options::game.player_skin == 2) ? "player2.yaml" : "player.yaml"); const std::string player_anim_path = Player::skinToAnimationPath(Options::game.player_skin);
const auto& player_animation_data = Resource::Cache::get()->getAnimationData(player_anim_path);
player_sprite_ = std::make_shared<AnimatedSprite>(player_animation_data); player_sprite_ = std::make_shared<AnimatedSprite>(player_animation_data);
player_sprite_->setCurrentAnimation("walk_menu"); player_sprite_->setCurrentAnimation("default");
surface_ = std::make_shared<Surface>(SURFACE_WIDTH, SURFACE_HEIGHT); surface_ = std::make_shared<Surface>(SURFACE_WIDTH, SURFACE_HEIGHT);
surface_dest_ = {.x = 0, .y = Options::game.height - SURFACE_HEIGHT, .w = SURFACE_WIDTH, .h = SURFACE_HEIGHT}; surface_dest_ = {.x = 0, .y = Options::game.height - SURFACE_HEIGHT, .w = SURFACE_WIDTH, .h = SURFACE_HEIGHT};
@@ -49,9 +51,6 @@ void Scoreboard::update(float delta_time) {
// Acumular tiempo para animaciones // Acumular tiempo para animaciones
time_accumulator_ += delta_time; time_accumulator_ += delta_time;
// Actualizar sprite con delta time
player_sprite_->update(delta_time);
// Actualiza el color de la cantidad de items recogidos // Actualiza el color de la cantidad de items recogidos
updateItemsColor(delta_time); updateItemsColor(delta_time);
@@ -77,6 +76,14 @@ auto Scoreboard::getTime() -> Scoreboard::ClockData { // NOLINT(readability-con
return time; return time;
} }
// Actualiza el sprite del jugador con la skin actual
void Scoreboard::refreshPlayerSkin() {
const std::string player_anim_path = Player::skinToAnimationPath(Options::game.player_skin);
const auto& player_animation_data = Resource::Cache::get()->getAnimationData(player_anim_path);
player_sprite_ = std::make_shared<AnimatedSprite>(player_animation_data);
player_sprite_->setCurrentAnimation("default");
}
// Pone el marcador en modo pausa // Pone el marcador en modo pausa
void Scoreboard::setPaused(bool value) { void Scoreboard::setPaused(bool value) {
if (is_paused_ == value) { if (is_paused_ == value) {
@@ -130,18 +137,14 @@ void Scoreboard::fillTexture() {
// Limpia la textura // Limpia la textura
surface_->clear(stringToColor("black")); surface_->clear(stringToColor("black"));
// Anclas
constexpr int LINE1 = Tile::SIZE;
constexpr int LINE2 = 3 * Tile::SIZE;
// Dibuja las vidas // Dibuja las vidas
// Calcular desplazamiento basado en tiempo const int WALK_FRAMES = player_sprite_->getCurrentAnimationSize();
const int DESP = static_cast<int>(time_accumulator_ / SPRITE_WALK_CYCLE_DURATION) % 8; const int DESP = static_cast<int>(time_accumulator_ / SPRITE_WALK_CYCLE_DURATION) % (WALK_FRAMES * 2);
const int FRAME = DESP % SPRITE_WALK_FRAMES; const int FRAME = DESP % WALK_FRAMES;
player_sprite_->setCurrentAnimationFrame(FRAME); player_sprite_->setCurrentAnimationFrame(FRAME);
player_sprite_->setPosY(LINE2); player_sprite_->setPosY(LINE2_Y);
for (int i = 0; i < data_->lives; ++i) { for (int i = 0; i < data_->lives; ++i) {
player_sprite_->setPosX(8 + (16 * i) + DESP); player_sprite_->setPosX(LIVES_START_X + (LIVES_SPACING * i) + DESP);
const int INDEX = i % color_.size(); const int INDEX = i % color_.size();
player_sprite_->render(1, color_.at(INDEX)); player_sprite_->render(1, color_.at(INDEX));
} }
@@ -150,21 +153,30 @@ void Scoreboard::fillTexture() {
if (data_->music) { if (data_->music) {
const Uint8 C = data_->color; const Uint8 C = data_->color;
SDL_FRect clip = {.x = 0, .y = 8, .w = 8, .h = 8}; SDL_FRect clip = {.x = 0, .y = 8, .w = 8, .h = 8};
item_surface_->renderWithColorReplace(20 * Tile::SIZE, LINE2, 1, C, &clip); item_surface_->renderWithColorReplace(MUSIC_ICON_X, LINE2_Y, 1, C, &clip);
} }
// Escribe los textos // Escribe los textos
auto text = Resource::Cache::get()->getText("smb2"); auto text = Resource::Cache::get()->getText("smb2");
const std::string TIME_TEXT = std::to_string((clock_.minutes % 100) / 10) + std::to_string(clock_.minutes % 10) + clock_.separator + std::to_string((clock_.seconds % 60) / 10) + std::to_string(clock_.seconds % 10); const std::string TIME_TEXT = std::to_string((clock_.minutes % 100) / 10) + std::to_string(clock_.minutes % 10) + clock_.separator + std::to_string((clock_.seconds % 60) / 10) + std::to_string(clock_.seconds % 10);
const std::string ITEMS_TEXT = std::to_string(data_->items / 100) + std::to_string((data_->items % 100) / 10) + std::to_string(data_->items % 10); const std::string ITEMS_TEXT = std::to_string(data_->items / 100) + std::to_string((data_->items % 100) / 10) + std::to_string(data_->items % 10);
text->writeColored(Tile::SIZE, LINE1, Locale::get()->get("scoreboard.items"), data_->color); // NOLINT(readability-static-accessed-through-instance) text->writeColored(ITEMS_LABEL_X, LINE1_Y, Locale::get()->get("scoreboard.items"), data_->color); // NOLINT(readability-static-accessed-through-instance)
text->writeColored(17 * Tile::SIZE, LINE1, ITEMS_TEXT, items_color_); text->writeColored(ITEMS_VALUE_X, LINE1_Y, ITEMS_TEXT, items_color_);
text->writeColored(20 * Tile::SIZE, LINE1, Locale::get()->get("scoreboard.time"), data_->color); // NOLINT(readability-static-accessed-through-instance) text->writeColored(TIME_LABEL_X, LINE1_Y, Locale::get()->get("scoreboard.time"), data_->color); // NOLINT(readability-static-accessed-through-instance)
text->writeColored(26 * Tile::SIZE, LINE1, TIME_TEXT, stringToColor("white")); text->writeColored(TIME_VALUE_X, LINE1_Y, TIME_TEXT, stringToColor("white"));
const std::string ROOMS_TEXT = std::to_string(data_->rooms / 100) + std::to_string((data_->rooms % 100) / 10) + std::to_string(data_->rooms % 10); const std::string ROOMS_TEXT = std::to_string(data_->rooms / 100) + std::to_string((data_->rooms % 100) / 10) + std::to_string(data_->rooms % 10);
text->writeColored(22 * Tile::SIZE, LINE2, Locale::get()->get("scoreboard.rooms"), stringToColor("white")); // NOLINT(readability-static-accessed-through-instance) text->writeColored(ROOMS_LABEL_X, LINE2_Y, Locale::get()->get("scoreboard.rooms"), stringToColor("white")); // NOLINT(readability-static-accessed-through-instance)
text->writeColored(28 * Tile::SIZE, LINE2, ROOMS_TEXT, stringToColor("white")); text->writeColored(ROOMS_VALUE_X, LINE2_Y, ROOMS_TEXT, stringToColor("white"));
// Indicadores de trucos activos (fuente 8bithud)
auto cheat_text = Resource::Cache::get()->getText("8bithud");
if (Options::cheats.infinite_lives == Options::Cheat::State::ENABLED) {
cheat_text->writeColored(CHEAT_INF_LIVES_X, CHEAT_INF_LIVES_Y, Locale::get()->get("scoreboard.cheat_infinite_lives"), data_->color); // NOLINT(readability-static-accessed-through-instance)
}
if (Options::cheats.invincible == Options::Cheat::State::ENABLED) {
cheat_text->writeColored(CHEAT_INVINCIBLE_X, CHEAT_INVINCIBLE_Y, Locale::get()->get("scoreboard.cheat_invincibility"), data_->color); // NOLINT(readability-static-accessed-through-instance)
}
// Deja el renderizador como estaba // Deja el renderizador como estaba
Screen::get()->setRendererSurface(previuos_renderer); Screen::get()->setRendererSurface(previuos_renderer);

View File

@@ -28,6 +28,7 @@ class Scoreboard {
void render(); // Pinta el objeto en pantalla void render(); // Pinta el objeto en pantalla
void update(float delta_time); // Actualiza las variables del objeto void update(float delta_time); // Actualiza las variables del objeto
void setPaused(bool value); // Pone el marcador en modo pausa void setPaused(bool value); // Pone el marcador en modo pausa
void refreshPlayerSkin(); // Actualiza el sprite del jugador con la skin actual
auto getMinutes() -> int; // Devuelve la cantidad de minutos de juego transcurridos auto getMinutes() -> int; // Devuelve la cantidad de minutos de juego transcurridos
private: private:
@@ -42,7 +43,23 @@ class Scoreboard {
// Constantes de tiempo // Constantes de tiempo
static constexpr float ITEMS_COLOR_BLINK_DURATION = 0.333F; // Duración de cada estado del parpadeo (era 10 frames @ 60fps) static constexpr float ITEMS_COLOR_BLINK_DURATION = 0.333F; // Duración de cada estado del parpadeo (era 10 frames @ 60fps)
static constexpr float SPRITE_WALK_CYCLE_DURATION = 0.667F; // Duración del ciclo de caminar (era 40 frames @ 60fps) static constexpr float SPRITE_WALK_CYCLE_DURATION = 0.667F; // Duración del ciclo de caminar (era 40 frames @ 60fps)
static constexpr int SPRITE_WALK_FRAMES = 4; // Número de frames de animación
// Posición de los elementos del marcador (en pixels, Tile::SIZE = 8)
static constexpr int LINE1_Y = 8; // Fila superior (1 * Tile::SIZE)
static constexpr int LINE2_Y = 24; // Fila inferior (3 * Tile::SIZE)
static constexpr int ITEMS_LABEL_X = 8; // "TRESORS PILLATS" / "ITEMS COLLECTED"
static constexpr int ITEMS_VALUE_X = 136; // Valor numérico de items
static constexpr int TIME_LABEL_X = 160; // "HORA" / "TIME"
static constexpr int TIME_VALUE_X = 208; // Valor numérico del tiempo
static constexpr int LIVES_START_X = 8; // Primera vida (sprite)
static constexpr int LIVES_SPACING = 16; // Separación entre vidas
static constexpr int MUSIC_ICON_X = 160; // Icono de música
static constexpr int ROOMS_LABEL_X = 176; // "SALES" / "ROOMS"
static constexpr int ROOMS_VALUE_X = 224; // Valor numérico de salas
static constexpr int CHEAT_INF_LIVES_X = 176; // Indicador "vides inf" / "inf lives"
static constexpr int CHEAT_INF_LIVES_Y = 34; // Posición Y del indicador de vidas infinitas
static constexpr int CHEAT_INVINCIBLE_X = 231; // Indicador "inv"
static constexpr int CHEAT_INVINCIBLE_Y = 34; // Posición Y del indicador de invencibilidad
// Métodos privados // Métodos privados
auto getTime() -> ClockData; // Obtiene el tiempo transcurrido de partida auto getTime() -> ClockData; // Obtiene el tiempo transcurrido de partida

View File

@@ -11,6 +11,7 @@
#include "core/input/input_types.hpp" // Para BUTTON_TO_STRING, STRING_TO_BUTTON #include "core/input/input_types.hpp" // Para BUTTON_TO_STRING, STRING_TO_BUTTON
#include "external/fkyaml_node.hpp" // Para fkyaml::node #include "external/fkyaml_node.hpp" // Para fkyaml::node
#include "game/defaults.hpp" // Para GameDefaults::VERSION #include "game/defaults.hpp" // Para GameDefaults::VERSION
#include "utils/utils.hpp" // Para toLower
namespace Options { namespace Options {
@@ -177,24 +178,6 @@ namespace Options {
{"LEFT_STICK_LEFT", 200}, {"LEFT_STICK_LEFT", 200},
{"LEFT_STICK_RIGHT", 201}}; {"LEFT_STICK_RIGHT", 201}};
// Lista de paletas válidas
const std::vector<std::string> VALID_PALETTES = {
"black-and-white",
"green-phosphor",
"island-joy-16",
"lost-century",
"na16",
"orange-screen",
"pico-8",
"ruzx-spectrum",
"ruzx-spectrum-revision-2",
"steam-lords",
"sweetie-16",
"sweetie-16_bona",
"zx-spectrum",
"zx-spectrum-adjusted",
"zxarne-5-2"};
// Funciones helper de conversión // Funciones helper de conversión
auto filterToString(Screen::Filter filter) -> std::string { auto filterToString(Screen::Filter filter) -> std::string {
auto it = FILTER_TO_STRING.find(filter); auto it = FILTER_TO_STRING.find(filter);
@@ -257,12 +240,6 @@ namespace Options {
} }
} }
auto isValidPalette(const std::string& palette) -> bool {
std::string lower_palette = palette;
std::ranges::transform(lower_palette, lower_palette.begin(), ::tolower);
return std::ranges::any_of(VALID_PALETTES, [&lower_palette](const auto& valid) { return valid == lower_palette; });
}
// --- Funciones helper para loadFromFile() --- // --- Funciones helper para loadFromFile() ---
// Carga configuración de ventana desde YAML // Carga configuración de ventana desde YAML
@@ -309,20 +286,99 @@ namespace Options {
} }
} }
// Helper: carga el campo palette con validación extra // Helper: carga el campo palette; PaletteManager hará el fallback si el nombre no existe
void loadPaletteFromYaml(const fkyaml::node& vid) { void loadPaletteFromYaml(const fkyaml::node& vid) {
if (!vid.contains("palette")) { return; } if (vid.contains("palette")) {
try { try {
auto palette_str = vid["palette"].get_value<std::string>(); auto palette_str = vid["palette"].get_value<std::string>();
video.palette = isValidPalette(palette_str) ? palette_str : Defaults::Video::PALETTE_NAME; video.palette = toLower(palette_str);
} catch (...) { } catch (...) {
video.palette = Defaults::Video::PALETTE_NAME; video.palette = Defaults::Video::PALETTE_NAME;
} }
} }
if (vid.contains("palette_sort")) {
try {
video.palette_sort = toLower(vid["palette_sort"].get_value<std::string>());
} catch (...) {
video.palette_sort = Defaults::Video::PALETTE_SORT;
}
}
}
// Carga los campos básicos de configuración de video // Helper: carga la sección gpu desde YAML
void loadGPUConfigFromYaml(const fkyaml::node& gpu_node) {
if (gpu_node.contains("acceleration")) {
try {
video.gpu.acceleration = gpu_node["acceleration"].get_value<bool>();
} catch (...) {
video.gpu.acceleration = Defaults::Video::GPU_ACCELERATION;
}
}
if (gpu_node.contains("preferred_driver")) {
try {
video.gpu.preferred_driver = gpu_node["preferred_driver"].get_value<std::string>();
} catch (...) {
video.gpu.preferred_driver = "";
}
}
}
// Helper: carga la sección supersampling desde YAML
void loadSupersamplingConfigFromYaml(const fkyaml::node& ss_node) {
if (ss_node.contains("enabled")) {
try {
video.supersampling.enabled = ss_node["enabled"].get_value<bool>();
} catch (...) {
video.supersampling.enabled = Defaults::Video::SUPERSAMPLING;
}
}
if (ss_node.contains("linear_upscale")) {
try {
video.supersampling.linear_upscale = ss_node["linear_upscale"].get_value<bool>();
} catch (...) {
video.supersampling.linear_upscale = Defaults::Video::LINEAR_UPSCALE;
}
}
if (ss_node.contains("downscale_algo")) {
try {
video.supersampling.downscale_algo = ss_node["downscale_algo"].get_value<int>();
} catch (...) {
video.supersampling.downscale_algo = Defaults::Video::DOWNSCALE_ALGO;
}
}
}
// Helper: carga la sección shader desde YAML
void loadShaderConfigFromYaml(const fkyaml::node& sh_node) {
if (sh_node.contains("enabled")) {
try {
video.shader.enabled = sh_node["enabled"].get_value<bool>();
} catch (...) {
video.shader.enabled = Defaults::Video::SHADER_ENABLED;
}
}
if (sh_node.contains("current_shader")) {
try {
const std::string s = sh_node["current_shader"].get_value<std::string>();
video.shader.current_shader = (s == "crtpi") ? Rendering::ShaderType::CRTPI : Rendering::ShaderType::POSTFX;
} catch (...) {
video.shader.current_shader = Rendering::ShaderType::POSTFX;
}
}
if (sh_node.contains("current_postfx_preset")) {
try {
video.shader.current_postfx_preset_name = sh_node["current_postfx_preset"].get_value<std::string>();
} catch (...) {}
}
if (sh_node.contains("current_crtpi_preset")) {
try {
video.shader.current_crtpi_preset_name = sh_node["current_crtpi_preset"].get_value<std::string>();
} catch (...) {}
}
}
// Carga los campos básicos de configuración de video (nivel video:)
void loadBasicVideoFieldsFromYaml(const fkyaml::node& vid) { void loadBasicVideoFieldsFromYaml(const fkyaml::node& vid) {
// fullscreen (antes era "mode")
if (vid.contains("fullscreen")) { if (vid.contains("fullscreen")) {
try { try {
video.fullscreen = vid["fullscreen"].get_value<bool>(); video.fullscreen = vid["fullscreen"].get_value<bool>();
@@ -331,7 +387,6 @@ namespace Options {
} }
} }
// filter (ahora es string)
if (vid.contains("filter")) { if (vid.contains("filter")) {
try { try {
auto filter_str = vid["filter"].get_value<std::string>(); auto filter_str = vid["filter"].get_value<std::string>();
@@ -341,47 +396,6 @@ namespace Options {
} }
} }
if (vid.contains("postfx")) {
try {
video.postfx = vid["postfx"].get_value<bool>();
} catch (...) {
video.postfx = Defaults::Video::POSTFX;
}
}
if (vid.contains("supersampling")) {
try {
video.supersampling = vid["supersampling"].get_value<bool>();
} catch (...) {
video.supersampling = Defaults::Video::SUPERSAMPLING;
}
}
if (vid.contains("current_postfx_preset")) {
try {
current_postfx_preset = vid["current_postfx_preset"].get_value<int>();
} catch (...) {
current_postfx_preset = 0;
}
}
if (vid.contains("current_crtpi_preset")) {
try {
current_crtpi_preset = vid["current_crtpi_preset"].get_value<int>();
} catch (...) {
current_crtpi_preset = 0;
}
}
if (vid.contains("current_shader")) {
try {
const std::string s = vid["current_shader"].get_value<std::string>();
current_shader = (s == "crtpi") ? Rendering::ShaderType::CRTPI : Rendering::ShaderType::POSTFX;
} catch (...) {
current_shader = Rendering::ShaderType::POSTFX;
}
}
if (vid.contains("vertical_sync")) { if (vid.contains("vertical_sync")) {
try { try {
video.vertical_sync = vid["vertical_sync"].get_value<bool>(); video.vertical_sync = vid["vertical_sync"].get_value<bool>();
@@ -406,30 +420,6 @@ namespace Options {
} }
} }
if (vid.contains("linear_upscale")) {
try {
video.linear_upscale = vid["linear_upscale"].get_value<bool>();
} catch (...) {
video.linear_upscale = Defaults::Video::LINEAR_UPSCALE;
}
}
if (vid.contains("downscale_algo")) {
try {
video.downscale_algo = vid["downscale_algo"].get_value<int>();
} catch (...) {
video.downscale_algo = Defaults::Video::DOWNSCALE_ALGO;
}
}
if (vid.contains("gpu_preferred_driver")) {
try {
video.gpu_preferred_driver = vid["gpu_preferred_driver"].get_value<std::string>();
} catch (...) {
video.gpu_preferred_driver = "";
}
}
loadPaletteFromYaml(vid); loadPaletteFromYaml(vid);
} }
@@ -439,10 +429,18 @@ namespace Options {
const auto& vid = yaml["video"]; const auto& vid = yaml["video"];
loadBasicVideoFieldsFromYaml(vid); loadBasicVideoFieldsFromYaml(vid);
// Lee border
if (vid.contains("border")) { if (vid.contains("border")) {
loadBorderConfigFromYaml(vid["border"]); loadBorderConfigFromYaml(vid["border"]);
} }
if (vid.contains("gpu")) {
loadGPUConfigFromYaml(vid["gpu"]);
}
if (vid.contains("supersampling")) {
loadSupersamplingConfigFromYaml(vid["supersampling"]);
}
if (vid.contains("shader")) {
loadShaderConfigFromYaml(vid["shader"]);
}
} }
} }
@@ -520,12 +518,19 @@ namespace Options {
const auto& player_node = yaml["player"]; const auto& player_node = yaml["player"];
if (player_node.contains("skin")) { if (player_node.contains("skin")) {
try { try {
int skin = player_node["skin"].get_value<int>(); game.player_skin = player_node["skin"].get_value<std::string>();
game.player_skin = (skin == 2) ? 2 : Defaults::Game::Player::SKIN;
} catch (...) { } catch (...) {
game.player_skin = Defaults::Game::Player::SKIN; game.player_skin = Defaults::Game::Player::SKIN;
} }
} }
if (player_node.contains("color")) {
try {
int color = player_node["color"].get_value<int>();
game.player_color = (color >= 0 && color <= 15) ? color : Defaults::Game::Player::COLOR;
} catch (...) {
game.player_color = Defaults::Game::Player::COLOR;
}
}
} }
} }
@@ -681,6 +686,25 @@ namespace Options {
} }
} }
// Helper: retorna el nombre del preset PostFX actual (para guardar en config)
auto currentPostFXPresetName() -> std::string {
const auto idx = static_cast<size_t>(video.shader.current_postfx_preset);
if (idx < postfx_presets.size()) {
return postfx_presets[idx].name;
}
// Presets no cargados aún: devolver el nombre almacenado del config
return video.shader.current_postfx_preset_name;
}
// Helper: retorna el nombre del preset CrtPi actual (para guardar en config)
auto currentCrtPiPresetName() -> std::string {
const auto idx = static_cast<size_t>(video.shader.current_crtpi_preset);
if (idx < crtpi_presets.size()) {
return crtpi_presets[idx].name;
}
return video.shader.current_crtpi_preset_name;
}
// Guarda las opciones al fichero configurado // Guarda las opciones al fichero configurado
auto saveToFile() -> bool { auto saveToFile() -> bool {
// Abre el fichero para escritura // Abre el fichero para escritura
@@ -727,23 +751,28 @@ namespace Options {
file << "# VIDEO \n"; file << "# VIDEO \n";
file << "video:\n"; file << "video:\n";
file << " fullscreen: " << (video.fullscreen ? "true" : "false") << "\n"; file << " fullscreen: " << (video.fullscreen ? "true" : "false") << "\n";
file << " filter: " << filterToString(video.filter) << " # filter: nearest (pixel perfect) | linear (smooth)\n";
file << " postfx: " << (video.postfx ? "true" : "false") << "\n";
file << " supersampling: " << (video.supersampling ? "true" : "false") << "\n";
file << " current_postfx_preset: " << current_postfx_preset << "\n";
file << " current_crtpi_preset: " << current_crtpi_preset << "\n";
file << " current_shader: " << (current_shader == Rendering::ShaderType::CRTPI ? "crtpi" : "postfx") << "\n";
file << " vertical_sync: " << (video.vertical_sync ? "true" : "false") << "\n"; file << " vertical_sync: " << (video.vertical_sync ? "true" : "false") << "\n";
file << " integer_scale: " << (video.integer_scale ? "true" : "false") << "\n"; file << " integer_scale: " << (video.integer_scale ? "true" : "false") << "\n";
file << " keep_aspect: " << (video.keep_aspect ? "true" : "false") << "\n"; file << " keep_aspect: " << (video.keep_aspect ? "true" : "false") << "\n";
file << " linear_upscale: " << (video.linear_upscale ? "true" : "false") << "\n"; file << " filter: " << filterToString(video.filter) << " # filter: nearest (pixel perfect) | linear (smooth)\n";
file << " downscale_algo: " << video.downscale_algo << " # 0=bilinear, 1=Lanczos2, 2=Lanczos3\n";
file << " gpu_preferred_driver: \"" << video.gpu_preferred_driver << "\" # GPU driver (empty = auto)\n";
file << " palette: " << video.palette << "\n"; file << " palette: " << video.palette << "\n";
file << " palette_sort: " << video.palette_sort << "\n";
file << " border:\n"; file << " border:\n";
file << " enabled: " << (video.border.enabled ? "true" : "false") << "\n"; file << " enabled: " << (video.border.enabled ? "true" : "false") << "\n";
file << " width: " << video.border.width << "\n"; file << " width: " << video.border.width << "\n";
file << " height: " << video.border.height << "\n"; file << " height: " << video.border.height << "\n";
file << " gpu:\n";
file << " acceleration: " << (video.gpu.acceleration ? "true" : "false") << " # Usar aceleración hardware GPU (false = SDL fallback)\n";
file << " preferred_driver: \"" << video.gpu.preferred_driver << "\" # Driver GPU específico (empty = auto, aplica solo si gpu_acceleration: true)\n";
file << " supersampling:\n";
file << " enabled: " << (video.supersampling.enabled ? "true" : "false") << "\n";
file << " linear_upscale: " << (video.supersampling.linear_upscale ? "true" : "false") << "\n";
file << " downscale_algo: " << video.supersampling.downscale_algo << " # 0=bilinear, 1=Lanczos2, 2=Lanczos3\n";
file << " shader:\n";
file << " enabled: " << (video.shader.enabled ? "true" : "false") << "\n";
file << " current_shader: " << (video.shader.current_shader == Rendering::ShaderType::CRTPI ? "crtpi" : "postfx") << "\n";
file << " current_postfx_preset: " << currentPostFXPresetName() << "\n";
file << " current_crtpi_preset: " << currentCrtPiPresetName() << "\n";
file << "\n"; file << "\n";
// KEYBOARD CONTROLS // KEYBOARD CONTROLS
@@ -765,7 +794,8 @@ namespace Options {
file << "\n"; file << "\n";
file << "# PLAYER\n"; file << "# PLAYER\n";
file << "player:\n"; file << "player:\n";
file << " skin: " << game.player_skin << "\n"; file << " skin: \"" << game.player_skin << "\"\n";
file << " color: " << game.player_color << "\n";
file << "\n"; file << "\n";
file << "# KIOSK MODE\n"; file << "# KIOSK MODE\n";
@@ -787,6 +817,40 @@ namespace Options {
return true; return true;
} }
// Resuelve el nombre del preset PostFX a índice dentro del vector de presets
void resolvePostFXPresetName() {
const auto& name = video.shader.current_postfx_preset_name;
if (name.empty()) {
video.shader.current_postfx_preset = 0;
return;
}
for (int i = 0; i < static_cast<int>(postfx_presets.size()); ++i) {
if (postfx_presets[static_cast<size_t>(i)].name == name) {
video.shader.current_postfx_preset = i;
return;
}
}
std::cout << "PostFX preset '" << name << "' not found, defaulting to first preset\n";
video.shader.current_postfx_preset = 0;
}
// Resuelve el nombre del preset CrtPi a índice dentro del vector de presets
void resolveCrtPiPresetName() {
const auto& name = video.shader.current_crtpi_preset_name;
if (name.empty()) {
video.shader.current_crtpi_preset = 0;
return;
}
for (int i = 0; i < static_cast<int>(crtpi_presets.size()); ++i) {
if (crtpi_presets[static_cast<size_t>(i)].name == name) {
video.shader.current_crtpi_preset = i;
return;
}
}
std::cout << "CrtPi preset '" << name << "' not found, defaulting to first preset\n";
video.shader.current_crtpi_preset = 0;
}
// Establece la ruta del fichero de PostFX // Establece la ruta del fichero de PostFX
void setPostFXFile(const std::string& path) { void setPostFXFile(const std::string& path) {
postfx_file_path = path; postfx_file_path = path;
@@ -838,14 +902,11 @@ namespace Options {
} }
} }
// Preservar el índice cargado desde config.yaml; clampar al rango válido. // Resolver el nombre del preset a índice
if (!postfx_presets.empty()) { if (!postfx_presets.empty()) {
current_postfx_preset = std::clamp( resolvePostFXPresetName();
current_postfx_preset,
0,
static_cast<int>(postfx_presets.size()) - 1);
} else { } else {
current_postfx_preset = 0; video.shader.current_postfx_preset = 0;
} }
std::cout << "PostFX file loaded: " << postfx_presets.size() << " preset(s)\n"; std::cout << "PostFX file loaded: " << postfx_presets.size() << " preset(s)\n";
@@ -950,7 +1011,7 @@ namespace Options {
postfx_presets.push_back({"SCANLINES", 0.0F, 0.8F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F}); postfx_presets.push_back({"SCANLINES", 0.0F, 0.8F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F});
postfx_presets.push_back({"SUBTLE", 0.3F, 0.4F, 0.05F, 0.0F, 0.3F, 0.0F, 0.0F, 0.0F}); postfx_presets.push_back({"SUBTLE", 0.3F, 0.4F, 0.05F, 0.0F, 0.3F, 0.0F, 0.0F, 0.0F});
postfx_presets.push_back({"CRT LIVE", 0.5F, 0.6F, 0.3F, 0.3F, 0.4F, 0.3F, 0.4F, 0.8F}); postfx_presets.push_back({"CRT LIVE", 0.5F, 0.6F, 0.3F, 0.3F, 0.4F, 0.3F, 0.4F, 0.8F});
current_postfx_preset = 0; video.shader.current_postfx_preset = 0;
return true; return true;
} }
@@ -981,7 +1042,7 @@ namespace Options {
crtpi_presets.push_back({"CURVED", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, true, false}); crtpi_presets.push_back({"CURVED", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, true, false});
crtpi_presets.push_back({"SHARP", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, false, true, false, true}); crtpi_presets.push_back({"SHARP", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, false, true, false, true});
crtpi_presets.push_back({"MINIMAL", 8.0F, 0.05F, 2.0F, 2.4F, 2.2F, 1.00F, 0.0F, 0.0F, 0, true, false, false, false, false}); crtpi_presets.push_back({"MINIMAL", 8.0F, 0.05F, 2.0F, 2.4F, 2.2F, 1.00F, 0.0F, 0.0F, 0, true, false, false, false, false});
current_crtpi_preset = 0; video.shader.current_crtpi_preset = 0;
return true; return true;
} }
out << "# JailDoctor's Dilemma - CrtPi Shader Presets\n"; out << "# JailDoctor's Dilemma - CrtPi Shader Presets\n";
@@ -1062,7 +1123,7 @@ namespace Options {
crtpi_presets.push_back({"CURVED", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, true, false}); crtpi_presets.push_back({"CURVED", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, true, false});
crtpi_presets.push_back({"SHARP", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, false, true, false, true}); crtpi_presets.push_back({"SHARP", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, false, true, false, true});
crtpi_presets.push_back({"MINIMAL", 8.0F, 0.05F, 2.0F, 2.4F, 2.2F, 1.00F, 0.0F, 0.0F, 0, true, false, false, false, false}); crtpi_presets.push_back({"MINIMAL", 8.0F, 0.05F, 2.0F, 2.4F, 2.2F, 1.00F, 0.0F, 0.0F, 0, true, false, false, false, false});
current_crtpi_preset = 0; video.shader.current_crtpi_preset = 0;
return true; return true;
} }
@@ -1121,13 +1182,11 @@ namespace Options {
} }
} }
// Resolver el nombre del preset a índice
if (!crtpi_presets.empty()) { if (!crtpi_presets.empty()) {
current_crtpi_preset = std::clamp( resolveCrtPiPresetName();
current_crtpi_preset,
0,
static_cast<int>(crtpi_presets.size()) - 1);
} else { } else {
current_crtpi_preset = 0; video.shader.current_crtpi_preset = 0;
} }
std::cout << "CrtPi file loaded: " << crtpi_presets.size() << " preset(s)\n"; std::cout << "CrtPi file loaded: " << crtpi_presets.size() << " preset(s)\n";
@@ -1138,7 +1197,7 @@ namespace Options {
// Cargar defaults en memoria en caso de error // Cargar defaults en memoria en caso de error
crtpi_presets.clear(); crtpi_presets.clear();
crtpi_presets.push_back({"DEFAULT", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, false, false}); crtpi_presets.push_back({"DEFAULT", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, false, false});
current_crtpi_preset = 0; video.shader.current_crtpi_preset = 0;
return false; return false;
} }
} }

View File

@@ -75,21 +75,43 @@ namespace Options {
float height{Defaults::Border::HEIGHT}; // Alto del borde float height{Defaults::Border::HEIGHT}; // Alto del borde
}; };
// Estructura para las opciones de GPU
struct GPU {
bool acceleration{Defaults::Video::GPU_ACCELERATION}; // Usar aceleración GPU; false = path SDL fallback directo
std::string preferred_driver; // Driver GPU preferido; vacío = auto. Aplica en el próximo arranque.
};
// Estructura para las opciones de supersampling
struct Supersampling {
bool enabled{Defaults::Video::SUPERSAMPLING}; // Indica si el supersampling 3× está activo
bool linear_upscale{Defaults::Video::LINEAR_UPSCALE}; // Upscale LINEAR (true) o NEAREST (false)
int downscale_algo{Defaults::Video::DOWNSCALE_ALGO}; // 0=bilinear, 1=Lanczos2, 2=Lanczos3
};
// Estructura para las opciones de shader (dentro de Video)
struct ShaderConfig {
bool enabled{Defaults::Video::SHADER_ENABLED}; // Indica si se usan shaders de post-procesado
Rendering::ShaderType current_shader{Rendering::ShaderType::POSTFX}; // Shader de post-procesado activo
std::string current_postfx_preset_name; // Nombre del preset PostFX leído del config
std::string current_crtpi_preset_name; // Nombre del preset CrtPi leído del config
int current_postfx_preset{0}; // Índice resuelto del preset PostFX
int current_crtpi_preset{0}; // Índice resuelto del preset CrtPi
};
// Estructura para las opciones de video // Estructura para las opciones de video
struct Video { struct Video {
bool fullscreen{Defaults::Video::FULLSCREEN}; // Contiene el valor del modo de pantalla completa bool fullscreen{Defaults::Video::FULLSCREEN}; // Contiene el valor del modo de pantalla completa
Screen::Filter filter{Defaults::Video::FILTER}; // Filtro usado para el escalado de la imagen Screen::Filter filter{Defaults::Video::FILTER}; // Filtro usado para el escalado de la imagen
bool vertical_sync{Defaults::Video::VERTICAL_SYNC}; // Indica si se quiere usar vsync o no bool vertical_sync{Defaults::Video::VERTICAL_SYNC}; // Indica si se quiere usar vsync o no
bool postfx{Defaults::Video::POSTFX}; // Indica si se van a usar efectos PostFX o no
bool supersampling{Defaults::Video::SUPERSAMPLING}; // Indica si el supersampling 3× está activo
bool integer_scale{Defaults::Video::INTEGER_SCALE}; // Indica si el escalado de la imagen ha de ser entero en el modo a pantalla completa bool integer_scale{Defaults::Video::INTEGER_SCALE}; // Indica si el escalado de la imagen ha de ser entero en el modo a pantalla completa
bool keep_aspect{Defaults::Video::KEEP_ASPECT}; // Indica si se ha de mantener la relación de aspecto al poner el modo a pantalla completa bool keep_aspect{Defaults::Video::KEEP_ASPECT}; // Indica si se ha de mantener la relación de aspecto al poner el modo a pantalla completa
bool linear_upscale{Defaults::Video::LINEAR_UPSCALE}; // Upscale LINEAR (true) o NEAREST (false)
int downscale_algo{Defaults::Video::DOWNSCALE_ALGO}; // 0=bilinear, 1=Lanczos2, 2=Lanczos3
Border border{}; // Borde de la pantalla
std::string palette{Defaults::Video::PALETTE_NAME}; // Paleta de colores a usar en el juego std::string palette{Defaults::Video::PALETTE_NAME}; // Paleta de colores a usar en el juego
std::string palette_sort{Defaults::Video::PALETTE_SORT}; // Modo de ordenación de la paleta (original/luminance/spectrum)
std::string info; // Información sobre el modo de vídeo std::string info; // Información sobre el modo de vídeo
std::string gpu_preferred_driver; // Driver GPU preferido; vacío = auto. Aplica en el próximo arranque. Border border{}; // Borde de la pantalla
GPU gpu{}; // Opciones de aceleración GPU
Supersampling supersampling{}; // Opciones de supersampling
ShaderConfig shader{}; // Opciones de shader post-procesado
}; };
// Estructura para las opciones de musica // Estructura para las opciones de musica
@@ -116,7 +138,8 @@ namespace Options {
struct Game { struct Game {
float width{Defaults::Canvas::WIDTH}; // Ancho de la resolucion del juego float width{Defaults::Canvas::WIDTH}; // Ancho de la resolucion del juego
float height{Defaults::Canvas::HEIGHT}; // Alto de la resolucion del juego float height{Defaults::Canvas::HEIGHT}; // Alto de la resolucion del juego
int player_skin{Defaults::Game::Player::SKIN}; // Skin del jugador (1=normal, 2=alternativa) std::string player_skin{Defaults::Game::Player::SKIN}; // Skin del jugador ("default" o nombre de enemigo)
int player_color{Defaults::Game::Player::COLOR}; // Color del jugador (-1 = automático, 0-15 = color fijo)
}; };
// Estructura para un preset de PostFX // Estructura para un preset de PostFX
@@ -171,17 +194,12 @@ namespace Options {
// --- Variables PostFX --- // --- Variables PostFX ---
inline std::vector<PostFXPreset> postfx_presets{}; // Lista de presets de PostFX inline std::vector<PostFXPreset> postfx_presets{}; // Lista de presets de PostFX
inline int current_postfx_preset{0}; // Índice del preset de PostFX actual
inline std::string postfx_file_path{}; // Ruta del fichero postfx.yaml inline std::string postfx_file_path{}; // Ruta del fichero postfx.yaml
// --- Variables CrtPi --- // --- Variables CrtPi ---
inline std::vector<CrtPiPreset> crtpi_presets{}; // Lista de presets del shader CRT-Pi inline std::vector<CrtPiPreset> crtpi_presets{}; // Lista de presets del shader CRT-Pi
inline int current_crtpi_preset{0}; // Índice del preset CRT-Pi actual
inline std::string crtpi_file_path{}; // Ruta del fichero crtpi.yaml inline std::string crtpi_file_path{}; // Ruta del fichero crtpi.yaml
// --- Shader activo ---
inline Rendering::ShaderType current_shader{Rendering::ShaderType::POSTFX}; // Shader de post-procesado activo
// --- Funciones públicas --- // --- Funciones públicas ---
void setConfigFile(const std::string& path); // Establece la ruta del fichero de configuración void setConfigFile(const std::string& path); // Establece la ruta del fichero de configuración
auto loadFromFile() -> bool; // Carga las opciones desde el fichero configurado auto loadFromFile() -> bool; // Carga las opciones desde el fichero configurado
@@ -192,4 +210,7 @@ namespace Options {
void setCrtPiFile(const std::string& path); // Establece la ruta del fichero de CrtPi void setCrtPiFile(const std::string& path); // Establece la ruta del fichero de CrtPi
auto loadCrtPiFromFile() -> bool; // Carga los presets de CrtPi desde el fichero (crea defaults si no existe) auto loadCrtPiFromFile() -> bool; // Carga los presets de CrtPi desde el fichero (crea defaults si no existe)
void resolvePostFXPresetName(); // Resuelve el nombre del preset PostFX a índice
void resolveCrtPiPresetName(); // Resuelve el nombre del preset CrtPi a índice
} // namespace Options } // namespace Options

View File

@@ -391,7 +391,7 @@ void Ending2::placeSprites() const {
const float X = (Options::game.width - sprites_.back()->getWidth()) / 2; const float X = (Options::game.width - sprites_.back()->getWidth()) / 2;
const float Y = sprites_.back()->getPosY() + (sprite_max_height_ * 2); const float Y = sprites_.back()->getPosY() + (sprite_max_height_ * 2);
sprites_.back()->setPos(X, Y); sprites_.back()->setPos(X, Y);
sprites_.back()->setCurrentAnimation("walk"); sprites_.back()->setCurrentAnimation("default");
} }
// Crea los sprites con las texturas con los textos // Crea los sprites con las texturas con los textos

View File

@@ -64,11 +64,16 @@ Game::Game(Mode mode)
Cheevos::get()->enable(!Options::cheats.enabled()); // Deshabilita los logros si hay trucos activados Cheevos::get()->enable(!Options::cheats.enabled()); // Deshabilita los logros si hay trucos activados
Cheevos::get()->clearUnobtainableState(); Cheevos::get()->clearUnobtainableState();
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); };
GameControl::change_player_skin = [this](int skin_num) -> void { if (Console::get()->isActive()) { player_->setIgnoreInput(true); }
Options::game.player_skin = skin_num; GameControl::change_player_skin = [this](const std::string& skin_name) -> void {
player_->setSkin(skin_num); Options::game.player_skin = skin_name;
player_->setSkin(skin_name);
scoreboard_->refreshPlayerSkin();
};
GameControl::change_player_color = [this](int color) -> void {
Options::game.player_color = color;
player_->setColor();
}; };
#ifdef _DEBUG #ifdef _DEBUG
@@ -79,9 +84,17 @@ Game::Game(Mode mode)
Options::stats.items = count; Options::stats.items = count;
}; };
GameControl::toggle_debug_mode = [this]() -> void { GameControl::toggle_debug_mode = [this]() -> void {
const bool ENTERING_DEBUG = !Debug::get()->isEnabled();
if (ENTERING_DEBUG) {
invincible_before_debug_ = (Options::cheats.invincible == Options::Cheat::State::ENABLED);
}
Debug::get()->toggleEnabled(); Debug::get()->toggleEnabled();
room_->redrawMap(); room_->redrawMap();
Options::cheats.invincible = static_cast<Options::Cheat::State>(Debug::get()->isEnabled()); if (Debug::get()->isEnabled()) {
Options::cheats.invincible = Options::Cheat::State::ENABLED;
} else {
Options::cheats.invincible = invincible_before_debug_ ? Options::Cheat::State::ENABLED : Options::Cheat::State::DISABLED;
}
player_->setColor(); player_->setColor();
scoreboard_data_->music = !Debug::get()->isEnabled(); scoreboard_data_->music = !Debug::get()->isEnabled();
scoreboard_data_->music ? Audio::get()->resumeMusic() : Audio::get()->pauseMusic(); scoreboard_data_->music ? Audio::get()->resumeMusic() : Audio::get()->pauseMusic();
@@ -115,8 +128,8 @@ Game::Game(Mode mode)
Game::~Game() { Game::~Game() {
ItemTracker::destroy(); ItemTracker::destroy();
GameControl::refresh_player_color = nullptr;
GameControl::change_player_skin = nullptr; GameControl::change_player_skin = nullptr;
GameControl::change_player_color = nullptr;
Console::get()->on_toggle = nullptr; Console::get()->on_toggle = nullptr;
#ifdef _DEBUG #ifdef _DEBUG
@@ -511,15 +524,24 @@ void Game::handleDebugEvents(const SDL_Event& event) { // NOLINT(readability-co
Notifier::get()->show({Locale::get()->get("achievements.header"), Locale::get()->get("achievements.c11")}, Notifier::Style::CHEEVO, -1, false, "F7"); // NOLINT(readability-static-accessed-through-instance) Notifier::get()->show({Locale::get()->get("achievements.header"), Locale::get()->get("achievements.c11")}, Notifier::Style::CHEEVO, -1, false, "F7"); // NOLINT(readability-static-accessed-through-instance)
break; break;
case SDLK_0: case SDLK_0: {
const bool ENTERING_DEBUG = !Debug::get()->isEnabled();
if (ENTERING_DEBUG) {
invincible_before_debug_ = (Options::cheats.invincible == Options::Cheat::State::ENABLED);
}
Debug::get()->toggleEnabled(); Debug::get()->toggleEnabled();
Notifier::get()->show({Debug::get()->isEnabled() ? Locale::get()->get("game.debug_enabled") : Locale::get()->get("game.debug_disabled")}); // NOLINT(readability-static-accessed-through-instance) Notifier::get()->show({Debug::get()->isEnabled() ? Locale::get()->get("game.debug_enabled") : Locale::get()->get("game.debug_disabled")}); // NOLINT(readability-static-accessed-through-instance)
room_->redrawMap(); room_->redrawMap();
Options::cheats.invincible = static_cast<Options::Cheat::State>(Debug::get()->isEnabled()); if (Debug::get()->isEnabled()) {
Options::cheats.invincible = Options::Cheat::State::ENABLED;
} else {
Options::cheats.invincible = invincible_before_debug_ ? Options::Cheat::State::ENABLED : Options::Cheat::State::DISABLED;
}
player_->setColor(); player_->setColor();
scoreboard_data_->music = !Debug::get()->isEnabled(); scoreboard_data_->music = !Debug::get()->isEnabled();
scoreboard_data_->music ? Audio::get()->resumeMusic() : Audio::get()->pauseMusic(); scoreboard_data_->music ? Audio::get()->resumeMusic() : Audio::get()->pauseMusic();
break; break;
}
default: default:
break; break;
@@ -637,6 +659,9 @@ auto Game::changeRoom(const std::string& room_path) -> bool {
// Pasa la nueva habitación al jugador // Pasa la nueva habitación al jugador
player_->setRoom(room_); player_->setRoom(room_);
// Recalcula el color del jugador (evita coincidir con el fondo)
player_->setColor();
// Cambia la habitación actual // Cambia la habitación actual
current_room_ = room_path; current_room_ = room_path;
@@ -882,9 +907,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)
std::string player_animations = (Options::game.player_skin == 2) ? "player2.yaml" : "player.yaml"; const bool IGNORE_INPUT = player_ != nullptr && player_->getIgnoreInput();
std::string player_animations = Player::skinToAnimationPath(Options::game.player_skin);
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

@@ -130,5 +130,7 @@ class Game {
// Variables de debug para arrastre con ratón // Variables de debug para arrastre con ratón
bool debug_dragging_player_{false}; // Indica si estamos arrastrando al jugador con el ratón bool debug_dragging_player_{false}; // Indica si estamos arrastrando al jugador con el ratón
float debug_drag_speed_{0.0F}; // Velocidad actual del arrastre (ease-in) float debug_drag_speed_{0.0F}; // Velocidad actual del arrastre (ease-in)
// Estado previo de invencibilidad antes de entrar en modo debug
bool invincible_before_debug_{false};
#endif #endif
}; };

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,9 @@
#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 <vector> // Para vector
#include "game/ui/console_commands.hpp" // Para CommandRegistry
class Surface; class Surface;
class Sprite; class Sprite;
@@ -44,15 +47,16 @@ class Console {
static constexpr Uint8 BG_COLOR = 0; // PaletteColor::BLACK static constexpr Uint8 BG_COLOR = 0; // PaletteColor::BLACK
static constexpr Uint8 BORDER_COLOR = 9; // PaletteColor::BRIGHT_GREEN static constexpr Uint8 BORDER_COLOR = 9; // PaletteColor::BRIGHT_GREEN
static constexpr Uint8 MSG_COLOR = 8; // PaletteColor::GREEN static constexpr Uint8 MSG_COLOR = 8; // PaletteColor::GREEN
static constexpr float SLIDE_SPEED = 120.0F; static constexpr float SLIDE_SPEED = 180.0F;
// Constantes de consola // Constantes de consola
static constexpr std::string_view CONSOLE_NAME = "JDD Console"; static constexpr std::string_view CONSOLE_NAME = "JDD Console";
static constexpr std::string_view CONSOLE_VERSION = "v1.0"; static constexpr std::string_view CONSOLE_VERSION = "v2.1";
static constexpr int MAX_LINE_CHARS = 32; static constexpr int MAX_LINE_CHARS = 32;
static constexpr int MAX_HISTORY_SIZE = 20; static constexpr int MAX_HISTORY_SIZE = 20;
static constexpr float CURSOR_ON_TIME = 0.5F; static constexpr float CURSOR_ON_TIME = 0.5F;
static constexpr float CURSOR_OFF_TIME = 0.3F; static constexpr float CURSOR_OFF_TIME = 0.3F;
static constexpr float TYPEWRITER_CHAR_DELAY = 0.01F; // segundos entre letra y letra
// [SINGLETON] // [SINGLETON]
static Console* console; static Console* console;
@@ -65,6 +69,7 @@ class Console {
void buildSurface(); // Crea la Surface con el aspecto visual void buildSurface(); // Crea la Surface con el aspecto visual
void redrawText(); // Redibuja el texto dinámico (msg + input + cursor) void redrawText(); // Redibuja el texto dinámico (msg + input + cursor)
void processCommand(); // Procesa el comando introducido por el usuario void processCommand(); // Procesa el comando introducido por el usuario
[[nodiscard]] auto wrapText(const std::string& text) const -> std::vector<std::string>; // Word-wrap por ancho en píxeles
// Objetos de renderizado // Objetos de renderizado
std::shared_ptr<Text> text_; std::shared_ptr<Text> text_;
@@ -77,13 +82,28 @@ 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_
// Registro de comandos (metadatos YAML + handlers C++)
CommandRegistry registry_;
}; };

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,56 @@
#pragma once
#include <functional> // Para function
#include <string> // Para string
#include <unordered_map> // Para unordered_map
#include <vector> // Para vector
// Definición de un comando de consola (metadatos cargados desde YAML)
struct CommandDef {
std::string keyword;
std::string handler_id;
std::string category;
std::string description;
std::string usage;
bool instant{false};
bool hidden{false};
bool debug_only{false};
bool help_hidden{false};
bool dynamic_completions{false};
std::unordered_map<std::string, std::vector<std::string>> completions;
};
// Tipo de función handler para comandos
using CommandHandler = std::function<std::string(const std::vector<std::string>& args)>;
// Proveedor de completions dinámicas: devuelve las opciones para TAB en UPPERCASE
using DynamicCompletionProvider = std::function<std::vector<std::string>()>;
// Registro de comandos: une metadatos YAML con handlers C++
class CommandRegistry {
public:
// Carga los metadatos de comandos desde un archivo YAML y registra los handlers
void load(const std::string& yaml_path);
// Búsqueda y ejecución
[[nodiscard]] auto findCommand(const std::string& keyword) const -> const CommandDef*;
auto execute(const std::string& keyword, const std::vector<std::string>& args) const -> std::string;
// Generación de ayuda (auto-generada desde los metadatos)
[[nodiscard]] auto generateTerminalHelp() const -> std::string;
[[nodiscard]] auto generateConsoleHelp() const -> std::string;
// TAB completion
// Devuelve las opciones de completado para un path dado (ej: "SHADER", "SHADER PRESET")
// Combina completions estáticas del YAML con dinámicas registradas en C++
[[nodiscard]] auto getCompletions(const std::string& path) const -> std::vector<std::string>;
[[nodiscard]] auto getVisibleKeywords() const -> std::vector<std::string>;
private:
std::vector<CommandDef> commands_;
std::unordered_map<std::string, CommandHandler> handlers_;
std::unordered_map<std::string, std::vector<std::string>> completions_map_;
std::unordered_map<std::string, DynamicCompletionProvider> dynamic_providers_;
void registerHandlers();
};

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.12"; // Versión por defecto
} // namespace Texts } // namespace Texts
// Tamaño de bloque // Tamaño de bloque

View File

@@ -414,6 +414,13 @@ auto toUpper(const std::string& str) -> std::string {
return upper_str; return upper_str;
} }
// Convierte guiones a espacios ("crt-live" → "crt live")
auto prettyName(const std::string& str) -> std::string {
std::string result = str;
std::ranges::replace(result, '-', ' ');
return result;
}
// Obtiene el nombre de un fichero a partir de una ruta completa // Obtiene el nombre de un fichero a partir de una ruta completa
auto getFileName(const std::string& path) -> std::string { auto getFileName(const std::string& path) -> std::string {
return std::filesystem::path(path).filename().string(); return std::filesystem::path(path).filename().string();

View File

@@ -98,6 +98,7 @@ auto stringToBool(const std::string& str) -> bool; // Strin
auto boolToString(bool value) -> std::string; // Bool a string (1/0) auto boolToString(bool value) -> std::string; // Bool a string (1/0)
auto toLower(const std::string& str) -> std::string; // String a minúsculas auto toLower(const std::string& str) -> std::string; // String a minúsculas
auto toUpper(const std::string& str) -> std::string; // String a mayúsculas auto toUpper(const std::string& str) -> std::string; // String a mayúsculas
auto prettyName(const std::string& str) -> std::string; // Guiones a espacios ("crt-live" → "crt live")
// OPERACIONES CON STRINGS // OPERACIONES CON STRINGS
auto stringInVector(const std::vector<std::string>& vec, const std::string& str) -> bool; // Busca string en vector auto stringInVector(const std::vector<std::string>& vec, const std::string& str) -> bool; // Busca string en vector

View File

@@ -0,0 +1,125 @@
import sys
import math
import tempfile
import os
# ------------------------------
# Utilidades de color
# ------------------------------
def luminosidad(rgb):
r, g, b = rgb
return 0.2126*r + 0.7152*g + 0.0722*b
def distancia_rgb(c1, c2):
return math.sqrt(
(c1[0] - c2[0])**2 +
(c1[1] - c2[1])**2 +
(c1[2] - c2[2])**2
)
# Paleta ZX Spectrum
PALETA_SPECTRUM = [
(0, 0, 0),
(0, 0, 0),
(0, 0, 216),
(0, 0, 255),
(216, 0, 0),
(255, 0, 0),
(216, 0, 216),
(255, 0, 255),
(0, 216, 0),
(0, 255, 0),
(0, 216, 216),
(0, 255, 255),
(216, 216, 0),
(255, 255, 0),
(216, 216, 216),
(255, 255, 255),
]
# ------------------------------
# Lectura / escritura JASC-PAL
# ------------------------------
def leer_paleta_jasc(ruta):
with open(ruta, "r") as f:
lineas = [l.strip() for l in f.readlines()]
if lineas[0] != "JASC-PAL":
raise ValueError("El fichero no es un JASC-PAL válido")
num_colores = int(lineas[2])
colores = []
for i in range(num_colores):
r, g, b = map(int, lineas[3 + i].split())
colores.append((r, g, b))
return colores
def guardar_paleta_jasc(ruta, colores):
with open(ruta, "w") as f:
f.write("JASC-PAL\n")
f.write("0100\n")
f.write(f"{len(colores)}\n")
for r, g, b in colores:
f.write(f"{r} {g} {b}\n")
# ------------------------------
# Métodos de ordenación
# ------------------------------
def ordenar_por_luminosidad(colores):
return sorted(colores, key=luminosidad)
def ordenar_por_similitud_spectrum(colores):
colores_disponibles = colores.copy()
resultado = []
for ref in PALETA_SPECTRUM:
mejor = min(colores_disponibles, key=lambda c: distancia_rgb(c, ref))
resultado.append(mejor)
colores_disponibles.remove(mejor)
return resultado
# ------------------------------
# Main
# ------------------------------
def main():
if len(sys.argv) < 4:
print("Uso: python ordenar_paleta.py entrada.pal salida.pal [luminosidad|spectrum]")
return
entrada = sys.argv[1]
salida = sys.argv[2]
modo = sys.argv[3].lower()
colores = leer_paleta_jasc(entrada)
if modo == "luminosidad":
colores_ordenados = ordenar_por_luminosidad(colores)
elif modo == "spectrum":
colores_ordenados = ordenar_por_similitud_spectrum(colores)
else:
print(f"Modo desconocido: {modo}")
print("Modos disponibles: luminosidad, spectrum")
return
# Si salida == entrada, sobrescribimos de forma segura
if entrada == salida:
# Guardamos primero en un temporal para evitar corrupción si algo falla
with tempfile.NamedTemporaryFile(delete=False) as tmp:
temp_path = tmp.name
guardar_paleta_jasc(temp_path, colores_ordenados)
os.replace(temp_path, entrada)
print(f"Paleta sobrescrita ({modo}) en {entrada}")
else:
guardar_paleta_jasc(salida, colores_ordenados)
print(f"Paleta ordenada ({modo}) guardada en {salida}")
if __name__ == "__main__":
main()