30 Commits
v1.10 ... main

Author SHA1 Message Date
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
65 changed files with 20864 additions and 2841 deletions

2
.gitignore vendored
View File

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

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

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

143
CHANGELOG.md Normal file
View File

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

View File

@@ -34,31 +34,32 @@ set(APP_SOURCES
# Core - Input
source/core/input/global_inputs.cpp
source/core/input/input.cpp
source/core/input/input_types.cpp
source/core/input/input.cpp
source/core/input/mouse.cpp
# Core - Rendering
source/core/rendering/gif.cpp
source/core/rendering/palette_manager.cpp
source/core/rendering/pixel_reveal.cpp
source/core/rendering/render_info.cpp
source/core/rendering/screen.cpp
source/core/rendering/surface.cpp
source/core/rendering/sprite/animated_sprite.cpp
source/core/rendering/sprite/dissolve_sprite.cpp
source/core/rendering/sprite/moving_sprite.cpp
source/core/rendering/sprite/sprite.cpp
source/core/rendering/surface.cpp
source/core/rendering/text.cpp
# Core - Locale
source/core/locale/locale.cpp
# Core - Resources
source/core/resources/resource_list.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_list.cpp
source/core/resources/resource_loader.cpp
source/core/resources/resource_pack.cpp
# Core - System
source/core/system/director.cpp
@@ -78,9 +79,9 @@ set(APP_SOURCES
source/game/gameplay/enemy_manager.cpp
source/game/gameplay/item_manager.cpp
source/game/gameplay/item_tracker.cpp
source/game/gameplay/room.cpp
source/game/gameplay/room_loader.cpp
source/game/gameplay/room_tracker.cpp
source/game/gameplay/room.cpp
source/game/gameplay/scoreboard.cpp
source/game/gameplay/stats.cpp
source/game/gameplay/tilemap_renderer.cpp
@@ -89,14 +90,15 @@ set(APP_SOURCES
source/game/scenes/credits.cpp
source/game/scenes/ending.cpp
source/game/scenes/ending2.cpp
source/game/scenes/game.cpp
source/game/scenes/game_over.cpp
source/game/scenes/game.cpp
source/game/scenes/loading_screen.cpp
source/game/scenes/logo.cpp
source/game/scenes/title.cpp
# Game - UI
source/game/ui/console.cpp
source/game/ui/console_commands.cpp
source/game/ui/notifier.cpp
# Utils

View File

@@ -84,57 +84,58 @@ LINUX_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-linux.tar.g
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/input/input_types.cpp \
source/core/input/input.cpp \
source/core/input/mouse.cpp \
source/core/locale/locale.cpp \
source/core/rendering/gif.cpp \
source/core/rendering/palette_manager.cpp \
source/core/rendering/pixel_reveal.cpp \
source/core/rendering/render_info.cpp \
source/core/rendering/screen.cpp \
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp \
source/core/locale/locale.cpp \
source/core/resources/resource_list.cpp \
source/core/rendering/sprite/animated_sprite.cpp \
source/core/rendering/sprite/dissolve_sprite.cpp \
source/core/rendering/sprite/moving_sprite.cpp \
source/core/rendering/sprite/sprite.cpp \
source/core/rendering/surface.cpp \
source/core/rendering/text.cpp \
source/core/resources/resource_cache.cpp \
source/core/resources/resource_helper.cpp \
source/core/resources/resource_list.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/director.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/entities/player.cpp \
source/game/gameplay/cheevos.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_loader.cpp \
source/game/gameplay/room_tracker.cpp \
source/game/gameplay/room.cpp \
source/game/gameplay/scoreboard.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/gameplay/tilemap_renderer.cpp \
source/game/options.cpp \
source/game/scenes/credits.cpp \
source/game/scenes/ending.cpp \
source/game/scenes/ending2.cpp \
source/game/scenes/credits.cpp \
source/game/ui/notifier.cpp \
source/game/scenes/game_over.cpp \
source/game/scenes/game.cpp \
source/game/scenes/loading_screen.cpp \
source/game/scenes/logo.cpp \
source/game/scenes/title.cpp \
source/game/ui/console.cpp \
source/utils/utils.cpp \
source/utils/delta_timer.cpp
source/game/ui/notifier.cpp \
source/utils/delta_timer.cpp \
source/utils/utils.cpp
# All sources combined
ALL_SOURCES := $(APP_SOURCES)
@@ -298,6 +299,8 @@ macos_release:
$(RMDIR) "$(RELEASE_FOLDER)"
$(RMFILE) tmp.dmg
$(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
$(MKDIR) "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Frameworks"

View File

@@ -32,7 +32,7 @@ assets:
- type: PALETTE
path: ${PREFIX}/data/palette/zx-spectrum-adjusted.pal
- type: PALETTE
path: ${PREFIX}/data/palette/zxarne-5-2.pal
path: ${PREFIX}/data/palette/zxarne-5.2.pal
- type: PALETTE
path: ${PREFIX}/data/palette/black-and-white.pal
- type: PALETTE
@@ -46,15 +46,33 @@ assets:
- type: PALETTE
path: ${PREFIX}/data/palette/pico-8.pal
- type: PALETTE
path: ${PREFIX}/data/palette/sweetie-16.pal
path: ${PREFIX}/data/palette/sweetie.pal
- type: PALETTE
path: ${PREFIX}/data/palette/island-joy-16.pal
path: ${PREFIX}/data/palette/island-joy.pal
- type: PALETTE
path: ${PREFIX}/data/palette/lost-century.pal
- type: PALETTE
path: ${PREFIX}/data/palette/na16.pal
path: ${PREFIX}/data/palette/na.pal
- type: PALETTE
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:
@@ -99,6 +117,11 @@ assets:
required: false
absolute: true
# CONSOLE
console:
- type: DATA
path: ${PREFIX}/data/console/commands.yaml
# ROOMS
rooms:
- type: ROOM

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

@@ -0,0 +1,230 @@
# 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|<name>]"
dynamic_completions: true
- name: AUDIO
commands:
- keyword: AUDIO
handler: cmd_audio
description: Audio master
usage: "AUDIO [ON|OFF|VOL <0-100>]"
completions:
AUDIO: [ON, OFF, VOL]
- keyword: MUSIC
handler: cmd_music
description: Music volume
usage: "MUSIC [ON|OFF|VOL <0-100>]"
completions:
MUSIC: [ON, OFF, VOL]
- keyword: SOUND
handler: cmd_sound
description: Sound volume
usage: "SOUND [ON|OFF|VOL <0-100>]"
completions:
SOUND: [ON, OFF, VOL]
- name: GAME
commands:
- keyword: 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

@@ -125,6 +125,8 @@ scoreboard:
items: "TRESORS PILLATS "
time: " HORA "
rooms: "SALES"
cheat_infinite_lives: "vides inf"
cheat_invincibility: "inv"
game:
music_enabled: "MÚSICA ACTIVADA"

View File

@@ -125,6 +125,8 @@ scoreboard:
items: "ITEMS COLLECTED "
time: " TIME "
rooms: "ROOMS"
cheat_infinite_lives: "inf lives"
cheat_invincibility: "inv"
game:
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
67 0 103
22 23 26
127 6 34
0 40 89
148 33 106
35 73 117
214 36 17
255 38 116
0 120 153
255 132 38
255 128 164
104 174 212
16 210 117
255 209 0
191 255 60
250 253 255

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
31 32 37
46 51 77
76 82 116
124 143 178
128 77 83
191 125 133
114 51 76
192 165 169
82 103 93
108 154 154
108 154 154
226 217 228
243 200 147
229 152 125
192 165 169
226 217 228

View File

@@ -1,19 +1,19 @@
JASC-PAL
0100
16
255 255 255
109 247 193
17 173 193
96 108 129
57 52 87
30 136 117
91 179 97
161 229 90
247 228 118
249 146 82
203 77 104
106 55 113
201 36 100
203 77 104
96 108 129
30 136 117
17 173 193
155 156 130
91 179 97
249 146 82
244 140 182
247 182 158
155 156 130
161 229 90
109 247 193
247 228 118
255 255 255

View File

@@ -1,19 +1,19 @@
JASC-PAL
0100
16
209 177 135
199 123 88
174 93 64
121 68 74
75 61 68
186 145 88
146 116 65
77 69 57
119 116 59
179 165 85
210 201 165
140 171 161
75 114 110
87 72 82
121 68 74
75 114 110
174 93 64
119 116 59
146 116 65
132 120 117
199 123 88
186 145 88
171 155 142
179 165 85
140 171 161
209 177 135
210 201 165

View File

@@ -1,19 +1,19 @@
JASC-PAL
0100
16
140 143 174
88 69 99
62 33 55
154 99 72
215 155 125
245 237 186
192 199 65
100 125 52
228 148 58
157 48 59
210 100 113
112 55 127
126 196 193
52 133 157
23 67 75
31 14 28
62 33 55
23 67 75
157 48 59
112 55 127
88 69 99
154 99 72
100 125 52
52 133 157
210 100 113
140 143 174
228 148 58
215 155 125
126 196 193
192 199 65
245 237 186

View File

@@ -3,17 +3,17 @@ JASC-PAL
16
0 0 0
29 43 83
126 37 83
0 135 81
171 82 54
95 87 79
194 195 199
255 241 232
255 0 77
255 163 0
255 236 39
0 228 54
41 173 255
131 118 156
255 0 77
171 82 54
255 119 168
194 195 199
0 228 54
0 135 81
95 87 79
255 241 232
255 236 39
255 163 0
255 204 170
126 37 83

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

@@ -2,18 +2,18 @@ JASC-PAL
0100
16
17 17 37
82 75 109
176 201 196
255 252 241
36 34 114
52 112 190
159 32 98
255 94 57
150 58 191
255 94 57
159 32 98
255 105 246
176 201 196
44 126 75
160 195 95
67 152 196
147 255 229
210 133 55
254 245 107
255 252 241
82 75 109

View File

@@ -2,18 +2,18 @@ JASC-PAL
0100
16
15 11 56
97 106 130
173 180 183
249 255 236
122 87 22
40 19 160
74 107 255
160 35 17
237 23 95
115 16 147
238 20 181
115 16 147
39 139 97
157 255 38
27 105 167
71 233 223
122 87 22
27 105 167
247 229 77
173 180 183
249 255 236
97 106 130

View File

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

View File

@@ -1,19 +1,19 @@
JASC-PAL
0100
16
33 59 37
58 96 74
79 119 84
161 159 124
119 116 79
119 92 79
96 59 58
59 33 55
23 14 25
47 33 59
59 33 55
33 59 37
67 58 96
96 59 58
79 82 119
58 96 74
119 92 79
79 119 84
101 115 140
119 116 79
124 148 161
161 159 124
160 185 186
192 209 204

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
16
26 28 44
41 54 111
51 60 87
86 108 134
59 93 201
37 113 121
93 39 93
41 54 111
177 62 83
239 125 87
93 39 93
148 176 194
56 183 100
167 240 112
37 113 121
65 166 246
115 239 247
239 125 87
255 205 117
148 176 194
167 240 112
244 244 244
86 108 134

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
50 50 171
35 103 239
254 1 69
119 70 2
118 50 118
239 152 152
1 137 84
1 186 152
152 186 220
253 253 253
254 239 69
186 118 84
254 205 205
84 1 103

View File

@@ -4,17 +4,7 @@ frameWidth: 8
frameHeight: 16
animations:
- name: stand
speed: 0.1333
loop: 0
frames: [0]
- name: walk
- name: default
speed: 0.1333
loop: 0
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
animations:
- name: stand
speed: 0.1333
loop: 0
frames: [0]
- name: walk
- name: default
speed: 0.1333
loop: 0
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() {
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() {
if (Options::current_shader == Rendering::ShaderType::CRTPI) {
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
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();
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 {
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();
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,17 @@ namespace GlobalInputs {
void handleNextShader() {
Screen::get()->nextShader();
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() {
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() {
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 handleToggleIntegerScale() {
@@ -160,14 +160,16 @@ namespace GlobalInputs {
return InputAction::WINDOW_INC_ZOOM;
}
}
if (Input::get()->checkAction(InputAction::TOGGLE_SHADER, Input::DO_NOT_ALLOW_REPEAT)) {
if ((SDL_GetModState() & SDL_KMOD_CTRL) != 0U) {
return InputAction::TOGGLE_SUPERSAMPLING; // Ctrl+F4
if (Screen::get()->isHardwareAccelerated()) {
if (Input::get()->checkAction(InputAction::TOGGLE_SHADER, Input::DO_NOT_ALLOW_REPEAT)) {
if ((SDL_GetModState() & SDL_KMOD_CTRL) != 0U) {
return InputAction::TOGGLE_SUPERSAMPLING; // Ctrl+F4
}
if (Options::video.shader.enabled && ((SDL_GetModState() & SDL_KMOD_SHIFT) != 0U)) {
return InputAction::NEXT_SHADER_PRESET; // Shift+F4
}
return InputAction::TOGGLE_SHADER; // F4
}
if (Options::video.postfx && ((SDL_GetModState() & SDL_KMOD_SHIFT) != 0U)) {
return InputAction::NEXT_SHADER_PRESET; // Shift+F4
}
return InputAction::TOGGLE_SHADER; // F4
}
if (Input::get()->checkAction(InputAction::NEXT_PALETTE, Input::DO_NOT_ALLOW_REPEAT)) {
return InputAction::NEXT_PALETTE;

View File

@@ -52,7 +52,7 @@ Input::Input(std::string game_controller_db_path)
{Action::TOGGLE_VSYNC, KeyState{.scancode = SDL_SCANCODE_F10}},
{Action::PAUSE, KeyState{.scancode = SDL_SCANCODE_F11}},
{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
}

View File

@@ -2,6 +2,7 @@
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#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);
}
// [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
void Locale::destroy() {
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
void Locale::loadFromFile(const std::string& file_path) { // NOLINT(readability-convert-member-functions-to-static)
if (file_path.empty()) {

View File

@@ -8,9 +8,10 @@
// No se permite cambio de idioma en caliente.
class Locale {
public:
static void init(const std::string& file_path); // Crea e inicializa el singleton
static void destroy(); // Destruye el singleton
static auto get() -> Locale*; // Devuelve 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 auto get() -> Locale*; // Devuelve el singleton
// Devuelve la traducción de la clave dada.
// Si la clave no existe, devuelve la propia clave como fallback.
@@ -19,6 +20,7 @@ class Locale {
private:
Locale() = default;
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
static Locale* instance;

View File

@@ -0,0 +1,118 @@
#include "core/rendering/palette_manager.hpp"
#include <algorithm>
#include <cctype>
#include <string>
#include "core/rendering/surface.hpp"
#include "core/resources/resource_cache.hpp"
#include "game/defaults.hpp"
#include "game/options.hpp"
#include "utils/utils.hpp"
PaletteManager::PaletteManager(
std::vector<std::string> raw_paths,
const std::string& initial_name,
std::shared_ptr<Surface> game_surface,
std::shared_ptr<Surface> border_surface,
OnChangeCallback on_change)
: palettes_(std::move(raw_paths)),
game_surface_(std::move(game_surface)),
border_surface_(std::move(border_surface)),
on_change_(std::move(on_change)) {
current_ = findIndex(initial_name);
// Leer y aplicar paleta inicial directamente desde el archivo
// (Resource::Cache aún no está disponible en este punto del ciclo de vida)
const auto initial_palette = readPalFile(palettes_.at(current_));
game_surface_->setPalette(initial_palette);
border_surface_->setPalette(initial_palette);
// Procesar la lista: conservar solo los nombres de archivo (sin ruta)
processPathList();
}
void PaletteManager::next() {
if (++current_ == palettes_.size()) {
current_ = 0;
}
apply();
}
void PaletteManager::previous() {
current_ = (current_ > 0) ? current_ - 1 : palettes_.size() - 1;
apply();
}
auto PaletteManager::setByName(const std::string& name) -> bool {
const std::string 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::apply() {
game_surface_->loadPalette(Resource::Cache::get()->getPalette(palettes_.at(current_)));
border_surface_->loadPalette(Resource::Cache::get()->getPalette(palettes_.at(current_)));
Options::video.palette = getCurrentName();
if (on_change_) {
on_change_();
}
}
auto PaletteManager::findIndex(const std::string& name) const -> size_t {
const std::string 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);
}
}

View File

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

View File

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

View File

@@ -42,8 +42,7 @@ auto Screen::get() -> Screen* {
}
// Constructor
Screen::Screen()
: palettes_(Resource::List::get()->getListByType(Resource::List::Type::PALETTE)) {
Screen::Screen() {
// Arranca SDL VIDEO, crea la ventana y el renderizador
initSDLVideo();
if (Options::video.fullscreen) { SDL_HideCursor(); }
@@ -55,7 +54,6 @@ Screen::Screen()
// 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};
current_palette_ = findPalette(Options::video.palette);
// Define el color del borde para el modo de pantalla completa
border_color_ = static_cast<Uint8>(PaletteColor::BLACK);
@@ -76,19 +74,27 @@ Screen::Screen()
}
SDL_SetTextureScaleMode(border_texture_, SDL_SCALEMODE_NEAREST);
// Cargar la paleta una sola vez
auto initial_palette = readPalFile(palettes_.at(current_palette_));
// Crea la surface donde se dibujan los graficos del juego
// Crea las surfaces (PaletteManager aplicará la paleta inicial en su constructor)
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));
// 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_->setPalette(initial_palette);
border_surface_->clear(border_color_);
// Crea el gestor de paletas; aplica la paleta inicial a ambas surfaces
palette_manager_ = std::make_unique<PaletteManager>(
Resource::List::get()->getListByType(Resource::List::Type::PALETTE),
Options::video.palette,
game_surface_,
border_surface_,
[this]() {
// Actualizar caché ARGB del borde cuando cambia la paleta
if (border_is_solid_) {
border_surface_->toARGBBuffer(border_pixel_buffer_.data());
border_argb_color_ = border_pixel_buffer_[0];
}
});
// Cachear el color ARGB inicial del borde (borde sólido por defecto)
border_surface_->toARGBBuffer(border_pixel_buffer_.data());
border_argb_color_ = border_pixel_buffer_[0];
@@ -99,9 +105,6 @@ Screen::Screen()
// Crea el objeto de texto para la pantalla de carga
createText();
// Extrae el nombre de las paletas desde su ruta
processPaletteList();
// Renderizar una vez la textura vacía para que tenga contenido válido
// antes de inicializar los shaders (evita pantalla negra)
SDL_RenderTexture(renderer_, game_texture_, nullptr, nullptr);
@@ -194,6 +197,21 @@ auto Screen::incWindowZoom() -> bool {
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
void Screen::setBorderColor(Uint8 color) {
border_color_ = color;
@@ -221,11 +239,11 @@ void Screen::renderNotifications() const {
// Activa/desactiva todos los shaders respetando el shader actualmente seleccionado
void Screen::toggleShaders() {
Options::video.postfx = !Options::video.postfx;
Options::video.shader.enabled = !Options::video.shader.enabled;
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
if (Options::video.postfx) {
if (Options::video.shader.enabled) {
// 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);
applyCurrentCrtPiPreset();
} else {
@@ -244,10 +262,10 @@ void Screen::toggleShaders() {
// Recarga el shader del preset actual sin toggle
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
applyCurrentPostFXPreset();
} else if (Options::video.postfx) {
} else if (Options::video.shader.enabled) {
initShaders();
}
}
@@ -342,55 +360,10 @@ void Screen::setRendererSurface(const std::shared_ptr<Surface>& surface) {
}
// Cambia la paleta
void Screen::nextPalette() {
++current_palette_;
if (current_palette_ == static_cast<int>(palettes_.size())) {
current_palette_ = 0;
}
setPalete();
}
void Screen::nextPalette() { palette_manager_->next(); }
// Cambia la paleta
void Screen::previousPalette() {
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);
}
}
void Screen::previousPalette() { palette_manager_->previous(); }
// Copia la surface a la textura
void Screen::surfaceToTexture() { // NOLINT(readability-convert-member-functions-to-static)
@@ -470,17 +443,12 @@ void Screen::renderOverlays() {
if (Console::get() != nullptr) { Console::get()->render(); } // Console (encima)
}
// Localiza la paleta dentro del vector de paletas
auto Screen::findPalette(const std::string& name) -> size_t { // NOLINT(readability-convert-member-functions-to-static)
std::string upper_name = toUpper(name + ".pal");
// Cambia a una paleta por nombre (case-insensitive); devuelve false si no existe
auto Screen::setPaletteByName(const std::string& name) -> bool { return palette_manager_->setByName(name); }
for (size_t i = 0; i < palettes_.size(); ++i) {
if (toUpper(getFileName(palettes_[i])) == upper_name) {
return i;
}
}
return static_cast<size_t>(0);
}
// Devuelve los nombres de paletas disponibles (minúsculas, sin extensión .pal)
auto Screen::getPaletteNames() const -> std::vector<std::string> { return palette_manager_->getNames(); }
auto Screen::getPalettePrettyName() const -> std::string { return palette_manager_->getPrettyName(); }
// Limpia la game_surface_
void Screen::clearSurface(Uint8 index) { game_surface_->clear(index); }
@@ -537,14 +505,14 @@ auto loadData(const std::string& filepath) -> std::vector<uint8_t> {
}
void Screen::setLinearUpscale(bool linear) {
Options::video.linear_upscale = linear;
Options::video.supersampling.linear_upscale = linear;
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
shader_backend_->setLinearUpscale(linear);
}
}
void Screen::setDownscaleAlgo(int algo) {
Options::video.downscale_algo = algo;
Options::video.supersampling.downscale_algo = algo;
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
shader_backend_->setDownscaleAlgo(algo);
}
@@ -557,8 +525,8 @@ auto Screen::getSsTextureSize() const -> std::pair<int, int> {
// Activa/desactiva el supersampling global (Ctrl+F4)
void Screen::toggleSupersampling() {
Options::video.supersampling = !Options::video.supersampling;
if (Options::video.postfx && shader_backend_ && shader_backend_->isHardwareAccelerated()) {
Options::video.supersampling.enabled = !Options::video.supersampling.enabled;
if (Options::video.shader.enabled && shader_backend_ && shader_backend_->isHardwareAccelerated()) {
applyCurrentPostFXPreset();
}
}
@@ -566,11 +534,11 @@ void Screen::toggleSupersampling() {
// Aplica los parámetros del preset actual al backend de shaders
void Screen::applyCurrentPostFXPreset() { // NOLINT(readability-convert-member-functions-to-static)
if (shader_backend_ && !Options::postfx_presets.empty()) {
const auto& p = Options::postfx_presets[static_cast<size_t>(Options::current_postfx_preset)];
// Supersampling es un toggle global (Options::video.supersampling), no por 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.enabled), no por preset.
// setOversample primero: puede recrear texturas antes de que setPostFXParams
// 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};
shader_backend_->setPostFXParams(params);
}
@@ -579,7 +547,7 @@ void Screen::applyCurrentPostFXPreset() { // NOLINT(readability-convert-member-
// Aplica los parámetros del preset CrtPi actual al backend de shaders
void Screen::applyCurrentCrtPiPreset() { // NOLINT(readability-convert-member-functions-to-static)
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{
.scanline_weight = p.scanline_weight,
.scanline_gap_brightness = p.scanline_gap_brightness,
@@ -602,9 +570,9 @@ void Screen::applyCurrentCrtPiPreset() { // NOLINT(readability-convert-member-f
// Cambia el shader de post-procesado activo y aplica el preset correspondiente
void Screen::setActiveShader(Rendering::ShaderType type) {
Options::current_shader = type;
Options::video.shader.current_shader = type;
if (!shader_backend_) { return; }
if (!Options::video.postfx) {
if (!Options::video.shader.enabled) {
// Shaders desactivados: guardar preferencia pero mantener pass-through
shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
shader_backend_->setPostFXParams(Rendering::PostFXParams{});
@@ -620,7 +588,7 @@ void Screen::setActiveShader(Rendering::ShaderType type) {
// Cicla al siguiente shader disponible (preparado para futura UI)
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::POSTFX;
setActiveShader(NEXT);
@@ -634,7 +602,8 @@ void Screen::initShaders() {
if (!shader_backend_) {
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, "", "");
gpu_driver_ = shader_backend_->getDriverName();
@@ -642,10 +611,10 @@ void Screen::initShaders() {
// Propagar flags de vsync, integer scale, upscale y downscale al backend GPU
shader_backend_->setVSync(Options::video.vertical_sync);
shader_backend_->setScaleMode(Options::video.integer_scale);
shader_backend_->setLinearUpscale(Options::video.linear_upscale);
shader_backend_->setDownscaleAlgo(Options::video.downscale_algo);
shader_backend_->setLinearUpscale(Options::video.supersampling.linear_upscale);
shader_backend_->setDownscaleAlgo(Options::video.supersampling.downscale_algo);
if (Options::video.postfx) {
if (Options::video.shader.enabled) {
applyCurrentPostFXPreset();
} else {
// Pass-through: todos los efectos a 0, el shader solo copia la textura
@@ -653,8 +622,8 @@ void Screen::initShaders() {
}
// Restaurar el shader activo guardado en config (y sus parámetros CrtPi si aplica)
shader_backend_->setActiveShader(Options::current_shader);
if (Options::current_shader == Rendering::ShaderType::CRTPI) {
shader_backend_->setActiveShader(Options::video.shader.current_shader);
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
applyCurrentCrtPiPreset();
}
}

View File

@@ -9,8 +9,9 @@
#include <utility> // Para std::pair
#include <vector> // Para vector
#include "core/rendering/shader_backend.hpp" // Para Rendering::ShaderType, ShaderBackend
#include "utils/utils.hpp" // Para Color
#include "core/rendering/palette_manager.hpp" // Para PaletteManager
#include "core/rendering/shader_backend.hpp" // Para Rendering::ShaderType, ShaderBackend
#include "utils/utils.hpp" // Para Color
class Surface;
class Text;
@@ -35,14 +36,15 @@ class Screen {
void update(float delta_time); // Actualiza la lógica de la clase
// Video y ventana
void setVideoMode(bool mode); // Establece el modo de video
void toggleVideoMode(); // Cambia entre pantalla completa y ventana
void toggleIntegerScale(); // Alterna entre activar y desactivar el escalado entero
void toggleVSync(); // Alterna entre activar y desactivar el V-Sync
auto decWindowZoom() -> bool; // Reduce el tamaño de la ventana
auto incWindowZoom() -> bool; // Aumenta el tamaño de la ventana
void show(); // Muestra la ventana
void hide(); // Oculta la ventana
void setVideoMode(bool mode); // Establece el modo de video
void toggleVideoMode(); // Cambia entre pantalla completa y ventana
void toggleIntegerScale(); // Alterna entre activar y desactivar el escalado entero
void toggleVSync(); // Alterna entre activar y desactivar el V-Sync
auto decWindowZoom() -> bool; // Reduce 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 hide(); // Oculta la ventana
// Borde
void setBorderColor(Uint8 color); // Cambia el color del borde
@@ -52,17 +54,19 @@ class Screen {
void toggleBorder(); // Cambia entre borde visible y no visible
// Paletas y PostFX
void nextPalette(); // Cambia a la siguiente paleta
void previousPalette(); // Cambia a la paleta anterior
void setPalete(); // Establece la paleta actual
void toggleShaders(); // Activa/desactiva todos los shaders respetando current_shader
void toggleSupersampling(); // Activa/desactiva el supersampling global
void reloadPostFX(); // Recarga el shader del preset actual sin toggle
void reloadCrtPi(); // Recarga el shader CrtPi del preset actual sin toggle
void setLinearUpscale(bool linear); // Upscale NEAREST (false) o LINEAR (true) en el paso SS
void setDownscaleAlgo(int algo); // 0=bilinear legacy, 1=Lanczos2, 2=Lanczos3
void setActiveShader(Rendering::ShaderType type); // Cambia el shader de post-procesado activo
void nextShader(); // Cicla al siguiente shader disponible (para futura UI)
void nextPalette(); // Cambia a la siguiente paleta
void previousPalette(); // Cambia a la paleta anterior
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 toggleShaders(); // Activa/desactiva todos los shaders respetando current_shader
void toggleSupersampling(); // Activa/desactiva el supersampling global
void reloadPostFX(); // Recarga el shader del preset actual sin toggle
void reloadCrtPi(); // Recarga el shader CrtPi del preset actual sin toggle
void setLinearUpscale(bool linear); // Upscale NEAREST (false) o LINEAR (true) en el paso SS
void setDownscaleAlgo(int algo); // 0=bilinear legacy, 1=Lanczos2, 2=Lanczos3
void setActiveShader(Rendering::ShaderType type); // Cambia el shader de post-procesado activo
void nextShader(); // Cicla al siguiente shader disponible (para futura UI)
// Surfaces y notificaciones
void setRendererSurface(const std::shared_ptr<Surface>& surface = nullptr); // Establece el renderizador para las surfaces
@@ -77,8 +81,10 @@ class Screen {
[[nodiscard]] auto getText() const -> std::shared_ptr<Text> { return text_; }
[[nodiscard]] auto getGameSurfaceDstRect() const -> SDL_FRect { return game_surface_dstrect_; }
[[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 getZoomFactor() const -> float { return zoom_factor_; }
[[nodiscard]] auto getMaxZoom() const -> int;
[[nodiscard]] auto getSsTextureSize() const -> std::pair<int, int>;
private:
@@ -116,20 +122,18 @@ class Screen {
static Screen* screen;
// Métodos privados
void renderNotifications() const; // Dibuja las notificaciones
void adjustWindowSize(); // Calcula el tamaño de la ventana
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 textureToRenderer(); // Copia la textura al renderizador
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 applyCurrentPostFXPreset(); // Aplica los parámetros del preset PostFX actual al backend
void applyCurrentCrtPiPreset(); // Aplica los parámetros del preset CrtPi actual al backend
void getDisplayInfo(); // Obtiene información sobre la pantalla
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
void createText(); // Crea el objeto de texto
void renderNotifications() const; // Dibuja las notificaciones
void adjustWindowSize(); // Calcula el tamaño de la ventana
void adjustRenderLogicalSize(); // Ajusta el tamaño lógico del renderizador
void surfaceToTexture(); // Copia la surface a la textura
void textureToRenderer(); // Copia la textura al renderizador
void renderOverlays(); // Renderiza todos los overlays
void initShaders(); // Inicializa los shaders
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 getDisplayInfo(); // Obtiene información sobre la pantalla
auto initSDLVideo() -> bool; // Arranca SDL VIDEO y crea la ventana
void createText(); // Crea el objeto de texto
// Constructor y destructor
Screen();
@@ -163,9 +167,8 @@ class Screen {
SDL_FRect game_surface_dstrect_; // Coordenadas donde se dibuja la textura del juego
// Paletas y colores
Uint8 border_color_{0}; // Color del borde
std::vector<std::string> palettes_; // Listado de ficheros de paleta disponibles
Uint8 current_palette_{0}; // Índice para el vector de paletas
Uint8 border_color_{0}; // Color del borde
std::unique_ptr<PaletteManager> palette_manager_; // Gestor de paletas de color
// Estado y configuración
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") {
SDL_Log("SDL3GPUShader: GPU disabled by config, using SDL_Renderer fallback");
driver_name_ = "none";
driver_name_ = ""; // vacío → RenderInfo mostrará "sdl"
return false;
}
if (device_ == nullptr) {

View File

@@ -1,59 +1,633 @@
#pragma once
#include <cstdint>
#include <cstddef>
#include <cstdint>
static const uint8_t kupscale_frag_spv[] = {
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0b, 0x00, 0x0d, 0x00,
0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00,
0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30,
0x00, 0x00, 0x00, 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
};
0x03,
0x02,
0x23,
0x07,
0x00,
0x00,
0x01,
0x00,
0x0b,
0x00,
0x0d,
0x00,
0x14,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x11,
0x00,
0x02,
0x00,
0x01,
0x00,
0x00,
0x00,
0x0b,
0x00,
0x06,
0x00,
0x01,
0x00,
0x00,
0x00,
0x47,
0x4c,
0x53,
0x4c,
0x2e,
0x73,
0x74,
0x64,
0x2e,
0x34,
0x35,
0x30,
0x00,
0x00,
0x00,
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;

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 y_end = std::min(rect->y + rect->h, surface_data_->height);
// Recorrer cada píxel dentro del rectángulo directamente
for (int y = y_start; y < y_end; ++y) {
for (int x = x_start; x < x_end; ++x) {
const int INDEX = x + (y * surface_data_->width);
surface_data_->data.get()[INDEX] = color;
}
// Rellenar fila a fila con memset (memoria contigua por fila)
Uint8* data_ptr = surface_data_->data.get();
const int surf_width = static_cast<int>(surface_data_->width);
const int row_width = static_cast<int>(x_end) - static_cast<int>(x_start);
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 y_end = std::min(rect->y + rect->h, surface_data_->height);
// Dibujar bordes horizontales
for (int x = x_start; x < x_end; ++x) {
// Borde superior
const int TOP_INDEX = x + (y_start * surface_data_->width);
surface_data_->data.get()[TOP_INDEX] = color;
// Borde inferior
const int BOTTOM_INDEX = x + ((y_end - 1) * surface_data_->width);
surface_data_->data.get()[BOTTOM_INDEX] = color;
}
// Dibujar bordes horizontales con memset (líneas contiguas en memoria)
Uint8* data_ptr = surface_data_->data.get();
const int surf_width = static_cast<int>(surface_data_->width);
const int row_width = static_cast<int>(x_end) - static_cast<int>(x_start);
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);
// Dibujar bordes verticales
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);
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 ix = 0; ix < w; ++ix) {
// 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_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_)) {
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);
// 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 ix = 0; ix < w; ++ix) {
// 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
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
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_)) {
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)
int row_stride = pitch / sizeof(Uint32);
for (int y = 0; y < surface_data_->height; ++y) {
for (int x = 0; x < surface_data_->width; ++x) {
// Calcular la posición correcta en la textura teniendo en cuenta el stride
int texture_index = (y * row_stride) + x;
int surface_index = (y * surface_data_->width) + x;
pixels[texture_index] = palette_[surface_data_->data.get()[surface_index]];
// Cachear punteros fuera del bucle para permitir autovectorización SIMD
const Uint8* src = surface_data_->data.get();
const Uint32* pal = palette_.data();
const int width = surface_data_->width;
const int height = surface_data_->height;
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]];
}
}
@@ -613,12 +616,16 @@ void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FR
int row_stride = pitch / sizeof(Uint32);
for (int y = 0; y < surface_data_->height; ++y) {
for (int x = 0; x < surface_data_->width; ++x) {
int texture_index = (y * row_stride) + x;
int surface_index = (y * surface_data_->width) + x;
pixels[texture_index] = palette_[surface_data_->data.get()[surface_index]];
// Cachear punteros fuera del bucle para permitir autovectorización SIMD
const Uint8* src = surface_data_->data.get();
const Uint32* pal = palette_.data();
const int width = surface_data_->width;
const int height = surface_data_->height;
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)
#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
Locale::init(Resource::List::get()->get(Options::language + ".yaml")); // NOLINT(readability-static-accessed-through-instance)
#endif

View File

@@ -25,13 +25,14 @@ namespace Defaults::Video {
constexpr bool FULLSCREEN = false; // Modo de pantalla completa por defecto (false = ventana)
constexpr Screen::Filter FILTER = Screen::Filter::NEAREST; // Filtro 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 INTEGER_SCALE = true; // Escalado entero activado por defecto
constexpr bool KEEP_ASPECT = true; // Mantener aspecto activado por defecto
constexpr const char* PALETTE_NAME = "zx-spectrum"; // Paleta por defecto
constexpr bool LINEAR_UPSCALE = false; // Upscale NEAREST 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::Border {
@@ -100,5 +101,6 @@ namespace Defaults::Game::Player {
constexpr int SPAWN_X = 25 * Tile::SIZE; // Posición X inicial
constexpr int SPAWN_Y = 13 * Tile::SIZE; // Posición Y 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

View File

@@ -620,20 +620,26 @@ auto Player::handleKillingTiles() -> bool {
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) {
if (color != 0) {
color_ = color;
return;
}
if (Options::cheats.invincible == Options::Cheat::State::ENABLED) {
color_ = static_cast<Uint8>(PaletteColor::CYAN);
} else if (Options::cheats.infinite_lives == Options::Cheat::State::ENABLED) {
color_ = static_cast<Uint8>(PaletteColor::YELLOW);
// Color personalizado desde opciones
if (Options::game.player_color >= 0) {
color_ = static_cast<Uint8>(Options::game.player_color);
} else {
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
@@ -765,11 +771,18 @@ void Player::applySpawnValues(const SpawnData& spawn) {
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
void Player::setSkin(int skin_num) {
void Player::setSkin(const std::string& skin_name) {
const auto FLIP = sprite_->getFlip();
const std::string PATH = (skin_num == 2) ? "player2.yaml" : "player.yaml";
initSprite(PATH);
initSprite(skinToAnimationPath(skin_name));
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_->setWidth(WIDTH);
sprite_->setHeight(HEIGHT);
sprite_->setCurrentAnimation("walk");
sprite_->setCurrentAnimation("default");
}
// 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
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;
}

View File

@@ -100,12 +100,14 @@ class Player {
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
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
//[[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
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)
[[nodiscard]] auto getIgnoreInput() const -> bool { return ignore_input_; }
#ifdef _DEBUG
// --- Funciones de debug ---

View File

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

View File

@@ -10,6 +10,7 @@
#include "core/rendering/surface.hpp" // Para Surface
#include "core/rendering/text.hpp" // Para Text
#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 "utils/defines.hpp" // Para BLOCK
#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;
// 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_->setCurrentAnimation("walk_menu");
player_sprite_->setCurrentAnimation("default");
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};
@@ -49,9 +51,6 @@ void Scoreboard::update(float delta_time) {
// Acumular tiempo para animaciones
time_accumulator_ += delta_time;
// Actualizar sprite con delta time
player_sprite_->update(delta_time);
// Actualiza el color de la cantidad de items recogidos
updateItemsColor(delta_time);
@@ -77,6 +76,14 @@ auto Scoreboard::getTime() -> Scoreboard::ClockData { // NOLINT(readability-con
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
void Scoreboard::setPaused(bool value) {
if (is_paused_ == value) {
@@ -130,18 +137,14 @@ void Scoreboard::fillTexture() {
// Limpia la textura
surface_->clear(stringToColor("black"));
// Anclas
constexpr int LINE1 = Tile::SIZE;
constexpr int LINE2 = 3 * Tile::SIZE;
// Dibuja las vidas
// Calcular desplazamiento basado en tiempo
const int DESP = static_cast<int>(time_accumulator_ / SPRITE_WALK_CYCLE_DURATION) % 8;
const int FRAME = DESP % SPRITE_WALK_FRAMES;
const int WALK_FRAMES = player_sprite_->getCurrentAnimationSize();
const int DESP = static_cast<int>(time_accumulator_ / SPRITE_WALK_CYCLE_DURATION) % (WALK_FRAMES * 2);
const int FRAME = DESP % WALK_FRAMES;
player_sprite_->setCurrentAnimationFrame(FRAME);
player_sprite_->setPosY(LINE2);
player_sprite_->setPosY(LINE2_Y);
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();
player_sprite_->render(1, color_.at(INDEX));
}
@@ -150,21 +153,30 @@ void Scoreboard::fillTexture() {
if (data_->music) {
const Uint8 C = data_->color;
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
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 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(17 * Tile::SIZE, LINE1, 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(26 * Tile::SIZE, LINE1, TIME_TEXT, stringToColor("white"));
text->writeColored(ITEMS_LABEL_X, LINE1_Y, Locale::get()->get("scoreboard.items"), data_->color); // NOLINT(readability-static-accessed-through-instance)
text->writeColored(ITEMS_VALUE_X, LINE1_Y, ITEMS_TEXT, items_color_);
text->writeColored(TIME_LABEL_X, LINE1_Y, Locale::get()->get("scoreboard.time"), data_->color); // NOLINT(readability-static-accessed-through-instance)
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);
text->writeColored(22 * Tile::SIZE, LINE2, 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_LABEL_X, LINE2_Y, Locale::get()->get("scoreboard.rooms"), stringToColor("white")); // NOLINT(readability-static-accessed-through-instance)
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
Screen::get()->setRendererSurface(previuos_renderer);

View File

@@ -28,6 +28,7 @@ class Scoreboard {
void render(); // Pinta el objeto en pantalla
void update(float delta_time); // Actualiza las variables del objeto
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
private:
@@ -42,7 +43,23 @@ class Scoreboard {
// 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 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
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 "external/fkyaml_node.hpp" // Para fkyaml::node
#include "game/defaults.hpp" // Para GameDefaults::VERSION
#include "utils/utils.hpp" // Para toLower
namespace Options {
@@ -177,24 +178,6 @@ namespace Options {
{"LEFT_STICK_LEFT", 200},
{"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
auto filterToString(Screen::Filter filter) -> std::string {
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() ---
// Carga configuración de ventana desde YAML
@@ -309,20 +286,91 @@ 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) {
if (!vid.contains("palette")) { return; }
try {
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 (...) {
video.palette = Defaults::Video::PALETTE_NAME;
}
}
// 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) {
// fullscreen (antes era "mode")
if (vid.contains("fullscreen")) {
try {
video.fullscreen = vid["fullscreen"].get_value<bool>();
@@ -331,7 +379,6 @@ namespace Options {
}
}
// filter (ahora es string)
if (vid.contains("filter")) {
try {
auto filter_str = vid["filter"].get_value<std::string>();
@@ -341,47 +388,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")) {
try {
video.vertical_sync = vid["vertical_sync"].get_value<bool>();
@@ -406,30 +412,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);
}
@@ -439,10 +421,18 @@ namespace Options {
const auto& vid = yaml["video"];
loadBasicVideoFieldsFromYaml(vid);
// Lee border
if (vid.contains("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 +510,19 @@ namespace Options {
const auto& player_node = yaml["player"];
if (player_node.contains("skin")) {
try {
int skin = player_node["skin"].get_value<int>();
game.player_skin = (skin == 2) ? 2 : Defaults::Game::Player::SKIN;
game.player_skin = player_node["skin"].get_value<std::string>();
} catch (...) {
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 +678,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
auto saveToFile() -> bool {
// Abre el fichero para escritura
@@ -727,23 +743,27 @@ namespace Options {
file << "# VIDEO \n";
file << "video:\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 << " integer_scale: " << (video.integer_scale ? "true" : "false") << "\n";
file << " keep_aspect: " << (video.keep_aspect ? "true" : "false") << "\n";
file << " linear_upscale: " << (video.linear_upscale ? "true" : "false") << "\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 << " filter: " << filterToString(video.filter) << " # filter: nearest (pixel perfect) | linear (smooth)\n";
file << " palette: " << video.palette << "\n";
file << " border:\n";
file << " enabled: " << (video.border.enabled ? "true" : "false") << "\n";
file << " width: " << video.border.width << "\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";
// KEYBOARD CONTROLS
@@ -765,7 +785,8 @@ namespace Options {
file << "\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 << "# KIOSK MODE\n";
@@ -787,6 +808,40 @@ namespace Options {
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
void setPostFXFile(const std::string& path) {
postfx_file_path = path;
@@ -838,14 +893,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()) {
current_postfx_preset = std::clamp(
current_postfx_preset,
0,
static_cast<int>(postfx_presets.size()) - 1);
resolvePostFXPresetName();
} else {
current_postfx_preset = 0;
video.shader.current_postfx_preset = 0;
}
std::cout << "PostFX file loaded: " << postfx_presets.size() << " preset(s)\n";
@@ -950,7 +1002,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({"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});
current_postfx_preset = 0;
video.shader.current_postfx_preset = 0;
return true;
}
@@ -981,7 +1033,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({"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});
current_crtpi_preset = 0;
video.shader.current_crtpi_preset = 0;
return true;
}
out << "# JailDoctor's Dilemma - CrtPi Shader Presets\n";
@@ -1062,7 +1114,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({"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});
current_crtpi_preset = 0;
video.shader.current_crtpi_preset = 0;
return true;
}
@@ -1121,13 +1173,11 @@ namespace Options {
}
}
// Resolver el nombre del preset a índice
if (!crtpi_presets.empty()) {
current_crtpi_preset = std::clamp(
current_crtpi_preset,
0,
static_cast<int>(crtpi_presets.size()) - 1);
resolveCrtPiPresetName();
} else {
current_crtpi_preset = 0;
video.shader.current_crtpi_preset = 0;
}
std::cout << "CrtPi file loaded: " << crtpi_presets.size() << " preset(s)\n";
@@ -1138,7 +1188,7 @@ namespace Options {
// Cargar defaults en memoria en caso de error
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});
current_crtpi_preset = 0;
video.shader.current_crtpi_preset = 0;
return false;
}
}

View File

@@ -75,21 +75,42 @@ namespace Options {
float height{Defaults::Border::HEIGHT}; // Alto del borde
};
// Estructura para las opciones de video
struct Video {
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
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 keep_aspect{Defaults::Video::KEEP_ASPECT}; // Indica si se ha de mantener la relación de aspecto al poner el modo a pantalla completa
// 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
Border border{}; // Borde de la pantalla
std::string palette{Defaults::Video::PALETTE_NAME}; // Paleta de colores a usar en el juego
std::string info; // Información sobre el modo de deo
std::string gpu_preferred_driver; // Driver GPU preferido; vacío = auto. Aplica en el próximo arranque.
};
// 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
struct Video {
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
bool vertical_sync{Defaults::Video::VERTICAL_SYNC}; // Indica si se quiere usar vsync o no
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
std::string palette{Defaults::Video::PALETTE_NAME}; // Paleta de colores a usar en el juego
std::string info; // Información sobre el modo de vídeo
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
@@ -114,9 +135,10 @@ namespace Options {
// Estructura para las opciones de juego
struct Game {
float width{Defaults::Canvas::WIDTH}; // Ancho 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)
float width{Defaults::Canvas::WIDTH}; // Ancho de la resolucion del juego
float height{Defaults::Canvas::HEIGHT}; // Alto de la resolucion del juego
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
@@ -171,17 +193,12 @@ namespace Options {
// --- Variables 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
// --- Variables CrtPi ---
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
// --- Shader activo ---
inline Rendering::ShaderType current_shader{Rendering::ShaderType::POSTFX}; // Shader de post-procesado activo
// --- Funciones públicas ---
void setConfigFile(const std::string& path); // Establece la ruta del fichero de configuración
auto loadFromFile() -> bool; // Carga las opciones desde el fichero configurado
@@ -192,4 +209,7 @@ namespace Options {
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)
void resolvePostFXPresetName(); // Resuelve el nombre del preset PostFX a índice
void resolveCrtPiPresetName(); // Resuelve el nombre del preset CrtPi a índice
} // namespace Options

View File

@@ -391,7 +391,7 @@ void Ending2::placeSprites() const {
const float X = (Options::game.width - sprites_.back()->getWidth()) / 2;
const float Y = sprites_.back()->getPosY() + (sprite_max_height_ * 2);
sprites_.back()->setPos(X, Y);
sprites_.back()->setCurrentAnimation("walk");
sprites_.back()->setCurrentAnimation("default");
}
// 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()->clearUnobtainableState();
GameControl::refresh_player_color = [this]() -> void { player_->setColor(); };
Console::get()->on_toggle = [this](bool open) { player_->setIgnoreInput(open); };
GameControl::change_player_skin = [this](int skin_num) -> void {
Options::game.player_skin = skin_num;
player_->setSkin(skin_num);
if (Console::get()->isActive()) { player_->setIgnoreInput(true); }
GameControl::change_player_skin = [this](const std::string& skin_name) -> void {
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
@@ -79,9 +84,17 @@ Game::Game(Mode mode)
Options::stats.items = count;
};
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();
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();
scoreboard_data_->music = !Debug::get()->isEnabled();
scoreboard_data_->music ? Audio::get()->resumeMusic() : Audio::get()->pauseMusic();
@@ -115,8 +128,8 @@ Game::Game(Mode mode)
Game::~Game() {
ItemTracker::destroy();
GameControl::refresh_player_color = nullptr;
GameControl::change_player_skin = nullptr;
GameControl::change_player_color = nullptr;
Console::get()->on_toggle = nullptr;
#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)
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();
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();
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();
scoreboard_data_->music = !Debug::get()->isEnabled();
scoreboard_data_->music ? Audio::get()->resumeMusic() : Audio::get()->pauseMusic();
break;
}
default:
break;
@@ -637,6 +659,9 @@ auto Game::changeRoom(const std::string& room_path) -> bool {
// Pasa la nueva habitación al jugador
player_->setRoom(room_);
// Recalcula el color del jugador (evita coincidir con el fondo)
player_->setColor();
// Cambia la habitación actual
current_room_ = room_path;
@@ -882,9 +907,11 @@ void Game::checkEndGameCheevos() { // NOLINT(readability-convert-member-functio
// Inicializa al jugador
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)};
player_ = std::make_shared<Player>(PLAYER);
if (IGNORE_INPUT) { player_->setIgnoreInput(true); }
}
// 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
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)
// Estado previo de invencibilidad antes de entrar en modo debug
bool invincible_before_debug_{false};
#endif
};

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,9 @@
#include <functional> // Para function
#include <memory> // Para shared_ptr
#include <string> // Para string
#include <vector> // Para vector
#include "game/ui/console_commands.hpp" // Para CommandRegistry
class Surface;
class Sprite;
@@ -44,15 +47,16 @@ class Console {
static constexpr Uint8 BG_COLOR = 0; // PaletteColor::BLACK
static constexpr Uint8 BORDER_COLOR = 9; // PaletteColor::BRIGHT_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
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_HISTORY_SIZE = 20;
static constexpr float CURSOR_ON_TIME = 0.5F;
static constexpr float CURSOR_OFF_TIME = 0.3F;
static constexpr float TYPEWRITER_CHAR_DELAY = 0.01F; // segundos entre letra y letra
// [SINGLETON]
static Console* console;
@@ -62,9 +66,10 @@ class Console {
~Console() = default;
// Métodos privados
void buildSurface(); // Crea la Surface con el aspecto visual
void redrawText(); // Redibuja el texto dinámico (msg + input + cursor)
void processCommand(); // Procesa el comando introducido por el usuario
void buildSurface(); // Crea la Surface con el aspecto visual
void redrawText(); // Redibuja el texto dinámico (msg + input + cursor)
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
std::shared_ptr<Text> text_;
@@ -77,13 +82,28 @@ class Console {
float height_{0.0F}; // Altura del panel
// 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_;
float cursor_timer_{0.0F};
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)
std::deque<std::string> history_;
int history_index_{-1}; // -1 = en la entrada actual (presente)
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 {
constexpr const char* WINDOW_CAPTION = "© 2022 JailDoctor's Dilemma — 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
// Tamaño de bloque

View File

@@ -414,6 +414,13 @@ auto toUpper(const std::string& str) -> std::string {
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
auto getFileName(const std::string& path) -> std::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 toLower(const std::string& str) -> std::string; // String a minú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
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()