Compare commits
124 Commits
1ecb427106
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6faa80eef4 | |||
| 9ffd29bea8 | |||
| 7287d65ca3 | |||
| cea5492abc | |||
| cccb2359cf | |||
| b5e822c65f | |||
| c14774478c | |||
| 3c3e012386 | |||
| 44b6f6830d | |||
| 0d12591925 | |||
| 22d6ac2fbf | |||
| acaf434e5c | |||
| 5f25562d52 | |||
| fc28586940 | |||
| 273d9304dc | |||
| 3a5b16346b | |||
| 0e61b94848 | |||
| eca5a55d3c | |||
| dccd0d41e4 | |||
| 20bac58814 | |||
| a6fae7b001 | |||
| b31346830f | |||
| b6fec3eba7 | |||
| 606388227c | |||
| a2caf95005 | |||
| 0bfb535d4d | |||
| 405f2248ec | |||
| 93b1cd80b7 | |||
| b53bf87730 | |||
| 015a9cc4e1 | |||
| 3bd13b72cd | |||
| c94adf39af | |||
| 950eeffb07 | |||
| b37b62ef1e | |||
| 0c8aa5fe50 | |||
| fe520dd341 | |||
| ec9a9aff81 | |||
| f9c1c4843d | |||
| a804ad1368 | |||
| c689507982 | |||
| 417643018f | |||
| 2ed7316948 | |||
| 2228153d59 | |||
| 7315032ff2 | |||
| 3fc6795593 | |||
| 16924cf503 | |||
| 705a9fc7cd | |||
| b164c11ba7 | |||
| 1817d00881 | |||
| 8dcf473f31 | |||
| 8f191f02fa | |||
| 1077c13fd0 | |||
| d5a4caa86e | |||
| f3bad9f4ed | |||
| 32f22c99db | |||
| cd14ae22c5 | |||
| 1fdc29e9d2 | |||
| 0a740a5be2 | |||
| 9e1b2b8960 | |||
| ed3724193e | |||
| 98f01a6dde | |||
| e13a28f69a | |||
| 301e0145fb | |||
| 3b233f0e12 | |||
| fd9be2066d | |||
| 7551115912 | |||
| b986778bb4 | |||
| fd2e2f1014 | |||
| 77b844065e | |||
| 2fe79de1d8 | |||
| d6ecadfd3a | |||
| ad467847b9 | |||
| 1bb2142d19 | |||
| f5a82229fe | |||
| 145bab037f | |||
| 754ad2de49 | |||
| a9b7c3f025 | |||
| 4db92d423c | |||
| 6717c1e307 | |||
| 9282d661aa | |||
| a21f530dd4 | |||
| 7483bf63c8 | |||
| 268763f162 | |||
| 71c7b8e553 | |||
| 854a5f04b2 | |||
| ed21a47f92 | |||
| 637df23bc7 | |||
| ea421b4e17 | |||
| 3a5ff06dab | |||
| dfb0d2134f | |||
| b459e2106f | |||
| 065f66d40e | |||
| f15658a767 | |||
| 21c8d1e8ca | |||
| a06eb8c8e9 | |||
| 6b73a76d31 | |||
| 348a76090b | |||
| 02c1bf647e | |||
| a7f0a18e6d | |||
| 8355c266a6 | |||
| 7bad27d686 | |||
| d39622c7e2 | |||
| 4910d201f9 | |||
| e85800c5ed | |||
| f25ee18329 | |||
| 3712f0c8d9 | |||
| c063488e8e | |||
| deb0a8677f | |||
| c5a7c9e70d | |||
| 92453a6104 | |||
| 7aff3e2109 | |||
| 8d213e7b3e | |||
| c6d409c303 | |||
| 6914f7df93 | |||
| 1dbfff2c17 | |||
| 3493636954 | |||
| 8ff1073e4a | |||
| 6497e26202 | |||
| e0e37204d7 | |||
| 6595b28790 | |||
| 0ddb6c85e1 | |||
| f84007902e | |||
| 49ae2ae41f | |||
| c701421a8f |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
||||
.cache/
|
||||
.vscode/
|
||||
|
||||
*data/config/config.yaml
|
||||
*stats.txt
|
||||
*.DS_Store
|
||||
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json"
|
||||
}
|
||||
170
CHANGELOG.md
Normal file
170
CHANGELOG.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to JailDoctor's Dilemma are documented here.
|
||||
|
||||
---
|
||||
|
||||
## [v1.12] - 2026-04-02
|
||||
|
||||
### Novedades
|
||||
- **Color del jugador configurable:** se puede cambiar desde la consola (persistente), con comprobación automática para evitar que coincida con el color de fondo
|
||||
- **Skins de enemigos para el jugador:** posibilidad de usar skins de enemigos en el jugador, con cambio en caliente en el marcador
|
||||
- **Indicador de trucos mejorado:** ya no usa el color del jugador, ahora se muestra en el marcador
|
||||
- **Shader presets por nombre:** se puede establecer un shader preset directamente por nombre desde la consola, con autocompletado
|
||||
- **Comandos externos en consola:** la consola lee los comandos desde un fichero externo
|
||||
- **Gestión de paletas mejorada:**
|
||||
- Nuevas paletas añadidas
|
||||
- Restaurado el orden original de las paletas
|
||||
- Opción de reordenar paletas automáticamente por luminosidad o parecido a la paleta de Spectrum
|
||||
- Nombres "pretty" para las paletas (cambia los `-` por ` `)
|
||||
- Eliminadas responsabilidades de `Options` sobre las paletas
|
||||
- Nueva herramienta en Python para reordenar paletas
|
||||
- **Aceleración hardware configurable:** posibilidad de desactivar la aceleración hardware desde el fichero de configuración; si no hay aceleración, se deshabilitan teclas y comandos de shaders
|
||||
- **Autocompletado mejorado:** shader preset y palette autocompletan con la lista de nombres
|
||||
- Reestructuración de comandos de consola
|
||||
- Reestructuración del apartado de vídeo en `config.yaml`
|
||||
- Optimizaciones en `Surface`
|
||||
|
||||
### Correcciones
|
||||
- Fix: entrar y salir del modo debug mantiene el estado previo del jugador
|
||||
- Corregido Makefile: migración completa a cmake, detección automática de SO para release
|
||||
|
||||
---
|
||||
|
||||
## [v1.11] - 2026-03-31
|
||||
|
||||
### Novedades
|
||||
- **PaletteManager:** refactorización de `Screen`, responsabilidades de gestión de paletas extraídas a clase propia
|
||||
- **Consola 2.1:** la consola puede cambiar de paleta por nombre (`Screen` devuelve lista de paletas)
|
||||
- **Zoom configurable:** `Screen` permite establecer el nivel de zoom directamente desde consola
|
||||
- **Autocompletar en consola:** autocompletado de comandos con Tab (incluyendo soporte para armadura de lagarto)
|
||||
- Generación automática de tabla de tab-completions en la consola
|
||||
|
||||
### Correcciones
|
||||
- Fix: al entrar a GAME con la consola abierta, el jugador no tenía los inputs deshabilitados
|
||||
- Fix: al hacer restart con la música del attract mode sonando, la música no paraba al ir al logo
|
||||
- Fix: en modo debug, protección para que el jugador no caiga infinitamente si sale de pantalla
|
||||
- Corregido el case en algunas respuestas de la consola
|
||||
- Corregido Makefile
|
||||
|
||||
---
|
||||
|
||||
## [v1.10] - 2026-03-30
|
||||
|
||||
### Novedades
|
||||
- **Consola 2.0:** rediseño completo de la consola de desarrollador
|
||||
- Efecto typewriter al mostrar texto
|
||||
- Separación de líneas automática
|
||||
- Cambio de skin
|
||||
- Historial y navegación mejorada
|
||||
- Comandos para cheats, control de escena, debug, audio y shaders
|
||||
- Teclas de función operativas con la consola abierta
|
||||
- Límite de caracteres ampliado
|
||||
- La consola ya no pausa al jugador
|
||||
- Reorganización del sistema de comandos y aliases (`show info`, `hide info`, etc.)
|
||||
- **RenderInfo:** nueva clase con animación para mostrar info de renderizado en pantalla
|
||||
- **Soporte multi-shader:** comandos y teclas para manejar el nuevo diseño de shaders (SPIRV/SPIR-V)
|
||||
- **Modo kiosko:** defaults y restricciones de comandos para modo kiosk
|
||||
- **Supersampling Lanczos:** implementación de escalado Lanczos en el supersampling
|
||||
- **Driver GPU configurable:** permite elegir driver de GPU o ninguno desde consola
|
||||
- Cheats accesibles desde la consola
|
||||
- Cambio y reinicio de escena desde la consola
|
||||
- Posición e habitación inicial de debug configurables desde consola y fichero
|
||||
- `Debug` carga posición e habitación inicial desde fichero
|
||||
- Comandos de audio configurables desde consola
|
||||
- Renderizado del dispositivo GPU en info_debug
|
||||
- `Screen` optimizado (`textureToRenderer()`)
|
||||
- Eliminado soporte para argumentos de línea de comandos
|
||||
- Eliminado `Options::console`
|
||||
- Help de consola organizado
|
||||
|
||||
### Correcciones
|
||||
- Fix: vsync off no funcionaba en Wayland
|
||||
- Fix: en TITLE, la consola no bloqueaba la pulsación del 1 al 4 y entraba a opciones
|
||||
- Fix: dos logs de consola con formato incorrecto
|
||||
- Fix: lógica para abrir y entrar a la jail (ahora usa número de habitación, no nombre)
|
||||
- Corregido `compile_spirv.cmake` y la `system_folder` para shaders
|
||||
- Corregido carácter de caret que se había perdido
|
||||
- Eliminados acentos en títulos de habitaciones que causaban problemas con fuentes
|
||||
- Revisadas y corregidas traducciones
|
||||
- Corregidos ficheros `.fnt`
|
||||
- Corrección en `Screen` para `std::setprecision()` (faltaba `#include <iomanip>`)
|
||||
|
||||
---
|
||||
|
||||
## [v1.09] - 2025-03-01
|
||||
|
||||
### Novedades
|
||||
- **Refactorización a singletons:** `Screen`, `Input`, `Audio`, `Resource::Cache`, `Resource::List`, `Director`, `Cheevos`, `Debug` convertidos a singletons thread-safe
|
||||
- **Smart pointers:** uso de `std::shared_ptr` y `std::unique_ptr` para gestión de recursos y sprites
|
||||
- **Surfaces 8-bit indexadas:** nuevo sistema de renderizado con color indexado y paletas intercambiables
|
||||
- **Sistema de notificaciones rediseñado:** nuevo engine de notificaciones con control de offset
|
||||
- **Modos de vídeo mejorados:** la ventana mantiene posición al cambiar tamaño o activar borde; puede crecer según el escritorio
|
||||
- **ItemTracker:** nuevo singleton para rastrear ítems recogidos
|
||||
- **globalEvents:** nuevo sistema de eventos globales SDL
|
||||
- **Barra de progreso en carga de recursos** (actualización cada 5 ítems para mayor rendimiento con vsync)
|
||||
- **Métodos show/hide ventana:** métodos para mostrar u ocultar la ventana
|
||||
- Afinada la clase `Options`
|
||||
- Actualizada a la última versión de `jail_audio`
|
||||
- Implementados shaders
|
||||
- Nueva tipografía añadida
|
||||
- Parametros de ficheros `.ani` migrados a snake_case
|
||||
- Música de Title y attract mode restaurada
|
||||
- Eliminado sistema online completo
|
||||
|
||||
### Correcciones
|
||||
- Fix: notificaciones ya no ensucian la pantalla de carga
|
||||
- Fix: no pintaba el efecto de carga del borde en `LoadingScreen`
|
||||
- Fix: bug con el puntero a `ScoreboardData`
|
||||
- Fix: carga de opciones y recursos corregida
|
||||
- Eliminados acentos problemáticos
|
||||
|
||||
---
|
||||
|
||||
## [v1.08] - 2024-02-22
|
||||
|
||||
### Novedades
|
||||
- Posibilidad de saltar la pantalla de carga ya completada desde el menú de título
|
||||
- El `gamestate_title` puede empezar en diferentes estados
|
||||
- Pantalla de carga con fade de paleta
|
||||
- GIF loader: dibujado correcto de GIFs en pantalla
|
||||
- Añadida `paleta.cpp`/`.h` y `gif.c`
|
||||
|
||||
### Correcciones
|
||||
- Corregido bug en el fade de paleta (el canal azul no se propagaba)
|
||||
- Arreglada la separación entre el título y el fade
|
||||
- Online deshabilitado por defecto al crear el fichero de configuración
|
||||
- Tiempo de la pantalla de carga aumentado
|
||||
|
||||
---
|
||||
|
||||
## [v1.07] - 2022-12-02
|
||||
|
||||
### Novedades
|
||||
- El nombre de la habitación se pinta a partir de una textura
|
||||
- Añadido Batman a FEEL THE HEAT
|
||||
- Cielo de la Jail actualizado
|
||||
- Retocada la pantalla de título
|
||||
- Sprite de PACO modificado
|
||||
- Nombre del enemigo diskette cambiado a floppy
|
||||
- Cambios cosméticos en algunas habitaciones (BE CAREFUL WITH THE FUSE renombrado)
|
||||
- El color de fondo de la habitación se pinta en la textura del mapa
|
||||
- Optimizaciones en intro y title
|
||||
- Preparación para compatibilidad con consolas
|
||||
- Actualizado `jail_audio` a la última versión
|
||||
- Eliminados la mayor parte de accesos a `vector::at()`
|
||||
|
||||
### Correcciones
|
||||
- Corregido bug: en la jail se rellenaban las vidas mientras estaba activa la pausa
|
||||
- Corregido memory leak en `texture.cpp`
|
||||
- Corregido bug en apertura de la Jail
|
||||
|
||||
---
|
||||
|
||||
## [v1.0] - 2022-11-13
|
||||
|
||||
Versión de lanzamiento inicial.
|
||||
|
||||
---
|
||||
|
||||
*El formato de este changelog sigue [Keep a Changelog](https://keepachangelog.com/).*
|
||||
154
CLAUDE.md
154
CLAUDE.md
@@ -53,7 +53,7 @@ cmake --build build --clean-first
|
||||
./jaildoctors_dilemma
|
||||
```
|
||||
|
||||
**Important:** The build directory is `/Users/sergio/Gitea/jaildoctors_dilemma/build` and the output executable is placed in the project root directory.
|
||||
**Important:** The build directory is `build/` relative to the project root and the output executable is placed in the project root directory.
|
||||
|
||||
### Testing in Headless Environment (SSH/Remote Server)
|
||||
|
||||
@@ -67,17 +67,12 @@ sudo apt-get install xvfb
|
||||
|
||||
#### Running the Game in Headless Mode
|
||||
|
||||
**Option 1: Using the wrapper script (RECOMMENDED)**
|
||||
```bash
|
||||
./run_headless.sh
|
||||
```
|
||||
|
||||
**Option 2: Using xvfb-run directly**
|
||||
**Option 1: Using xvfb-run directly (RECOMMENDED)**
|
||||
```bash
|
||||
xvfb-run -a ./jaildoctors_dilemma
|
||||
```
|
||||
|
||||
**Option 3: Custom display configuration**
|
||||
**Option 2: Custom display configuration**
|
||||
```bash
|
||||
xvfb-run -a -s "-screen 0 1280x720x24" ./jaildoctors_dilemma
|
||||
```
|
||||
@@ -238,26 +233,39 @@ The architecture follows a **layered, modular design** with clear separation of
|
||||
source/
|
||||
├── core/ # Core engine systems
|
||||
│ ├── audio/ # Audio management
|
||||
│ │ └── audio.hpp/cpp # Audio singleton (music, sounds, volumes)
|
||||
│ │ ├── audio.hpp/cpp # Audio singleton (music, sounds, volumes)
|
||||
│ │ └── jail_audio.hpp # Custom jail_audio library wrapper
|
||||
│ ├── input/ # Input handling
|
||||
│ │ ├── input.hpp/cpp # Input manager (keyboard, gamepad)
|
||||
│ │ ├── input_types.hpp # Input type definitions
|
||||
│ │ ├── global_inputs.hpp # Global input state
|
||||
│ │ └── mouse.hpp # Mouse input
|
||||
│ ├── locale/ # Localization
|
||||
│ │ └── locale.hpp # Locale/language support
|
||||
│ ├── rendering/ # Graphics rendering
|
||||
│ │ ├── screen.hpp/cpp # Screen/window singleton, SDL renderer
|
||||
│ │ ├── surface.hpp/cpp # 8-bit indexed color surface abstraction
|
||||
│ │ ├── surface_sprite.hpp # Static sprite rendering
|
||||
│ │ ├── surface_animated_sprite.hpp # Animated sprite with frame data
|
||||
│ │ ├── surface_moving_sprite.hpp # Moving sprite with velocity
|
||||
│ │ ├── texture.hpp/cpp # SDL texture wrapper
|
||||
│ │ ├── sprite/ # Sprite rendering classes
|
||||
│ │ │ ├── sprite.hpp # Static sprite rendering
|
||||
│ │ │ ├── animated_sprite.hpp # Animated sprite with frame data
|
||||
│ │ │ ├── moving_sprite.hpp # Moving sprite with velocity
|
||||
│ │ │ └── dissolve_sprite.hpp # Dissolve transition sprite
|
||||
│ │ ├── text.hpp/cpp # Text rendering system
|
||||
│ │ ├── gif.hpp/cpp # GIF image loader
|
||||
│ │ ├── opengl/ # OpenGL shader backend
|
||||
│ │ │ └── opengl_shader.hpp/cpp # CRT shader effects
|
||||
│ │ ├── pixel_reveal.hpp # Pixel reveal effect
|
||||
│ │ ├── render_info.hpp # Render information data
|
||||
│ │ ├── palette_manager.hpp # Palette management
|
||||
│ │ ├── sdl3gpu/ # SDL3 GPU shader backend
|
||||
│ │ │ ├── sdl3gpu_shader.hpp/cpp # CRT/post-processing shader effects
|
||||
│ │ │ └── *_spv.h # Pre-compiled SPIR-V shader headers
|
||||
│ │ └── shader_backend.hpp # Abstract shader interface
|
||||
│ ├── resources/ # Asset & Resource management
|
||||
│ │ ├── asset.hpp/cpp # Asset registry (file path mapping)
|
||||
│ │ └── resource.hpp/cpp # Resource singleton (loads/caches assets)
|
||||
│ │ ├── resource_list.hpp # Asset path registry (O(1) lookups)
|
||||
│ │ ├── resource_cache.hpp # Resource caching singleton
|
||||
│ │ ├── resource_loader.hpp # Asset loading logic
|
||||
│ │ ├── resource_pack.hpp # Resource pack handling
|
||||
│ │ ├── resource_helper.hpp # Resource utility functions
|
||||
│ │ └── resource_types.hpp # Resource type definitions
|
||||
│ └── system/ # System management
|
||||
│ ├── director.hpp/cpp # Main application controller
|
||||
│ ├── debug.hpp/cpp # Debug info overlay
|
||||
@@ -268,8 +276,13 @@ source/
|
||||
│ │ ├── enemy.hpp/cpp # Enemy entities
|
||||
│ │ └── item.hpp/cpp # Collectible items
|
||||
│ ├── gameplay/ # Core gameplay systems
|
||||
│ │ ├── room.hpp/cpp # Room/level logic, tilemap, collision
|
||||
│ │ ├── room.hpp/cpp # Room/level logic, collision
|
||||
│ │ ├── room_loader.hpp/cpp # Room loading from YAML files
|
||||
│ │ ├── room_tracker.hpp/cpp # Tracks visited rooms
|
||||
│ │ ├── collision_map.hpp/cpp # Collision map data
|
||||
│ │ ├── tilemap_renderer.hpp/cpp # Tilemap rendering
|
||||
│ │ ├── enemy_manager.hpp/cpp # Enemy lifecycle management
|
||||
│ │ ├── item_manager.hpp/cpp # Item lifecycle management
|
||||
│ │ ├── scoreboard.hpp/cpp # Score display & data
|
||||
│ │ ├── item_tracker.hpp/cpp # Tracks collected items
|
||||
│ │ ├── stats.hpp/cpp # Game statistics
|
||||
@@ -284,18 +297,20 @@ source/
|
||||
│ │ ├── ending2.hpp/cpp # Ending sequence 2
|
||||
│ │ └── credits.hpp/cpp # Credits screen
|
||||
│ ├── ui/ # User interface
|
||||
│ │ └── notifier.hpp/cpp # Achievement/notification display
|
||||
│ │ ├── notifier.hpp/cpp # Achievement/notification display
|
||||
│ │ ├── console.hpp/cpp # In-game debug console
|
||||
│ │ └── console_commands.hpp/cpp # Console command definitions
|
||||
│ ├── options.hpp/cpp # Game configuration/options
|
||||
│ ├── game_control.hpp # Game control logic
|
||||
│ ├── scene_manager.hpp # Scene flow state machine
|
||||
│ ├── defaults.hpp # Game defaults constants
|
||||
│ └── gameplay.hpp # Gameplay constants
|
||||
│ └── defaults.hpp # Game defaults constants
|
||||
├── external/ # Third-party libraries
|
||||
│ ├── jail_audio.hpp/cpp # Custom audio library
|
||||
│ ├── jail_audio.h # C interface for jail_audio
|
||||
│ ├── fkyaml_node.hpp # YAML parsing library
|
||||
│ ├── stb_image.h # Image loading library
|
||||
│ └── stb_vorbis.h # OGG Vorbis audio decoding
|
||||
├── utils/ # Utility code
|
||||
│ ├── delta_timer.hpp/cpp # Frame-rate independent timing
|
||||
│ ├── easing_functions.hpp # Easing/interpolation functions
|
||||
│ ├── defines.hpp # Game constants (resolutions, block sizes)
|
||||
│ └── utils.hpp/cpp # Helper functions (colors, math)
|
||||
└── main.cpp # Application entry point
|
||||
@@ -360,7 +375,7 @@ The game uses a scene manager to control application flow:
|
||||
// namespace SceneManager
|
||||
enum class Scene {
|
||||
LOGO, LOADING_SCREEN, TITLE, CREDITS, GAME, DEMO,
|
||||
GAME_OVER, ENDING, ENDING2, QUIT
|
||||
GAME_OVER, ENDING, ENDING2, RESTART_CURRENT, QUIT
|
||||
};
|
||||
|
||||
inline Scene current = Scene::LOGO; // Global scene state
|
||||
@@ -397,9 +412,10 @@ Display
|
||||
|
||||
**Key Components:**
|
||||
- `Surface` - 8-bit indexed pixel buffer with palette support
|
||||
- `SurfaceSprite` - Renders a fixed region of a surface
|
||||
- `SurfaceAnimatedSprite` - Frame-based animation on top of sprite
|
||||
- `SurfaceMovingSprite` - Adds velocity/position to animated sprite
|
||||
- `Sprite` - Renders a fixed region of a surface
|
||||
- `AnimatedSprite` - Frame-based animation on top of sprite
|
||||
- `MovingSprite` - Adds velocity/position to animated sprite
|
||||
- `DissolveSprite` - Dissolve transition effect sprite
|
||||
- Supports color replacement, palette swapping, and shader effects (CRT)
|
||||
|
||||
### 3.5 Tile-Based Collision System
|
||||
@@ -439,8 +455,8 @@ struct AnimationData {
|
||||
int counter;
|
||||
};
|
||||
|
||||
// Loaded from .ani files (list of animation names)
|
||||
// Rendered with SurfaceAnimatedSprite
|
||||
// Loaded from .yaml animation definition files
|
||||
// Rendered with AnimatedSprite
|
||||
```
|
||||
|
||||
---
|
||||
@@ -454,7 +470,7 @@ main()
|
||||
↓
|
||||
Director::Director() [Initialization]
|
||||
├─ Resource::List::init() - Initialize asset registry singleton
|
||||
├─ Director::setFileList() - Load assets.yaml configuration (no verification)
|
||||
├─ Director::setFileList() - Load assets.yaml via Resource::List (no verification)
|
||||
├─ Options::loadFromFile() - Load game configuration
|
||||
├─ Audio::init() - Initialize SDL audio
|
||||
├─ Screen::init() - Create window, SDL renderer
|
||||
@@ -510,7 +526,7 @@ Game::run() {
|
||||
|
||||
```
|
||||
Director::setFileList()
|
||||
└─ Asset::loadFromFile(config_path, PREFIX, system_folder_)
|
||||
└─ Resource::List::loadFromFile(config_path, PREFIX, system_folder_)
|
||||
├─ Read config/assets.yaml - Parse text configuration file
|
||||
├─ Parse YAML structure: assets grouped by category
|
||||
├─ Replace variables (${PREFIX}, ${SYSTEM_FOLDER})
|
||||
@@ -570,7 +586,7 @@ Game code
|
||||
|
||||
**Classes:**
|
||||
- `PascalCase` for classes: `Player`, `Room`, `Screen`, `Director`
|
||||
- Suffix `Sprite` for sprite classes: `SurfaceSprite`, `SurfaceAnimatedSprite`
|
||||
- Suffix `Sprite` for sprite classes: `Sprite`, `AnimatedSprite`, `MovingSprite`
|
||||
|
||||
**Methods:**
|
||||
- `get*()` for getters: `getWidth()`, `getRect()`
|
||||
@@ -613,8 +629,9 @@ Provides **time scaling** for slow-motion effects.
|
||||
### 5.4 Palette System
|
||||
|
||||
- 8-bit indexed color (256 colors per palette)
|
||||
- Multiple palettes can be loaded and swapped
|
||||
- `Surface::setPalette()` changes rendering colors
|
||||
- Multiple palettes can be loaded and swapped via `PaletteManager`
|
||||
- `Screen::setPaletteByName()` changes the active palette
|
||||
- Supports palette sort modes (by luminosity, similarity to Spectrum palette, etc.)
|
||||
- Supports color replacement per-render: `renderWithColorReplace()`
|
||||
- CRT shader effects can modify colors in real-time
|
||||
|
||||
@@ -664,7 +681,7 @@ Achievements trigger notifications on unlock.
|
||||
| Component | Technology | Version | Role |
|
||||
|-----------|-----------|---------|------|
|
||||
| **Graphics** | SDL3 | Latest | Window, rendering, input |
|
||||
| **GPU Rendering** | OpenGL | 3.2+ | Shader effects (CRT) |
|
||||
| **GPU Rendering** | SDL3 GPU | Latest | Shader effects (CRT, post-processing via SPIR-V) |
|
||||
| **Audio** | SDL3 Audio | Latest | Audio device, mixing |
|
||||
| **Audio Decoding** | jail_audio (custom) | 1.x | OGG/WAV playback |
|
||||
| **Image Loading** | stb_image | v2.x | PNG/GIF image loading |
|
||||
@@ -700,6 +717,7 @@ Achievements trigger notifications on unlock.
|
||||
| `Audio` | Music and SFX playback | Singleton |
|
||||
| `Resource::Cache` | Asset caching and loading (with detailed error messages) | Singleton |
|
||||
| `Resource::List` | Asset path registry from config/assets.yaml, O(1) lookups, variable substitution | Singleton |
|
||||
| `PaletteManager` | Palette loading, switching, and sorting | Managed by Screen |
|
||||
| `Debug` | Debug overlay information | Singleton |
|
||||
| `globalEvents` | Global SDL event handling (quit, device reset, mouse) | Namespace |
|
||||
|
||||
@@ -709,7 +727,12 @@ Achievements trigger notifications on unlock.
|
||||
|-------|---------|
|
||||
| `Game` | Main gameplay scene, orchestrates update/render |
|
||||
| `Player` | Player entity with physics and animation |
|
||||
| `Room` | Level data, collision detection, tilemap rendering |
|
||||
| `Room` | Level data, collision detection |
|
||||
| `RoomLoader` | Room loading from YAML files |
|
||||
| `TilemapRenderer` | Tilemap rendering |
|
||||
| `CollisionMap` | Collision map data |
|
||||
| `EnemyManager` | Enemy lifecycle management |
|
||||
| `ItemManager` | Item lifecycle management |
|
||||
| `Enemy` | Enemy entity behavior and rendering |
|
||||
| `Item` | Collectible items |
|
||||
| `Scoreboard` | HUD display data |
|
||||
@@ -722,17 +745,20 @@ Achievements trigger notifications on unlock.
|
||||
| Class | Purpose |
|
||||
|-------|---------|
|
||||
| `Surface` | 8-bit indexed color pixel buffer with palette |
|
||||
| `SurfaceSprite` | Renders a sprite region |
|
||||
| `SurfaceAnimatedSprite` | Frame-based animation rendering |
|
||||
| `SurfaceMovingSprite` | Sprite with velocity/position |
|
||||
| `Sprite` | Renders a sprite region |
|
||||
| `AnimatedSprite` | Frame-based animation rendering |
|
||||
| `MovingSprite` | Sprite with velocity/position |
|
||||
| `DissolveSprite` | Dissolve transition sprite |
|
||||
| `Text` | Text rendering system |
|
||||
| `OpenGLShader` | Shader compilation and effects |
|
||||
| `Sdl3gpuShader` | SDL3 GPU shader compilation and effects |
|
||||
| `PaletteManager` | Palette loading and management |
|
||||
|
||||
### Utility Classes
|
||||
|
||||
| Class | Purpose |
|
||||
| Class/Header | Purpose |
|
||||
|-------|---------|
|
||||
| `DeltaTimer` | Frame-rate independent timing |
|
||||
| `easing_functions.hpp` | Easing/interpolation functions for animations |
|
||||
|
||||
---
|
||||
|
||||
@@ -848,7 +874,7 @@ assets:
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/font/smb2.gif
|
||||
- type: FONT
|
||||
path: ${PREFIX}/data/font/smb2.txt
|
||||
path: ${PREFIX}/data/font/smb2.fnt
|
||||
|
||||
# PLAYER
|
||||
player:
|
||||
@@ -892,23 +918,26 @@ assets:
|
||||
- `${PREFIX}` - Replaced with `/../Resources` on macOS bundles, empty otherwise
|
||||
- `${SYSTEM_FOLDER}` - Replaced with user's system config folder
|
||||
|
||||
### Animation Files (.ani)
|
||||
List of animation names, one per line:
|
||||
```
|
||||
default
|
||||
jump
|
||||
run
|
||||
fall
|
||||
### Animation Files (.yaml)
|
||||
YAML-based animation definitions with frame data:
|
||||
```yaml
|
||||
animations:
|
||||
- name: default
|
||||
frameWidth: 16
|
||||
frameHeight: 16
|
||||
speed: 100
|
||||
loop: 0
|
||||
frames: [...]
|
||||
```
|
||||
|
||||
### Room Data Files (.room)
|
||||
Key-value pairs defining room properties:
|
||||
```
|
||||
number=01
|
||||
name=Starting Room
|
||||
bg_color=0x000000
|
||||
border_color=0xFF00FF
|
||||
...
|
||||
### Room Data Files (.yaml)
|
||||
YAML-based room definitions:
|
||||
```yaml
|
||||
room:
|
||||
name_en: Starting Room
|
||||
bgColor: 0x000000
|
||||
connections: [...]
|
||||
tilemap: [...]
|
||||
```
|
||||
|
||||
### Tilemap Files (.tmx)
|
||||
@@ -923,7 +952,8 @@ Binary 256-color palette format (256 × 4 bytes RGBA).
|
||||
|
||||
### For Graphics Issues
|
||||
- `Screen::render()` - Main rendering method
|
||||
- `Screen::setPalete()` - Palette application
|
||||
- `Screen::setPaletteByName()` - Palette switching
|
||||
- `PaletteManager` - Palette loading and sorting
|
||||
- `Surface` class - Pixel buffer operations
|
||||
|
||||
### For Input Issues
|
||||
@@ -942,10 +972,10 @@ Binary 256-color palette format (256 × 4 bytes RGBA).
|
||||
|
||||
### For Asset Management
|
||||
- `config/assets.yaml` - Asset configuration file (text-based, no recompilation needed)
|
||||
- `Asset::loadFromFile()` - Loads assets from config file
|
||||
- `Resource::List::loadFromFile()` - Loads asset registry from config file
|
||||
- `Resource::List::get()` - Retrieves asset path (O(1) lookup with unordered_map)
|
||||
- `Resource::load()` - Asset loading
|
||||
- `Director::setFileList()` - Calls `Asset::loadFromFile()` with PREFIX and system_folder
|
||||
- `Resource::Cache` - Asset loading and caching
|
||||
- `Director::setFileList()` - Calls `Resource::List::loadFromFile()` with PREFIX and system_folder
|
||||
|
||||
---
|
||||
|
||||
@@ -985,6 +1015,6 @@ Binary 256-color palette format (256 × 4 bytes RGBA).
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** November 2022 (per README)
|
||||
**Last Updated:** April 2026
|
||||
**Original Author:** JailDesigner
|
||||
**Repository:** Gitea (internal)
|
||||
|
||||
@@ -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/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/surface_animated_sprite.cpp
|
||||
source/core/rendering/surface_dissolve_sprite.cpp
|
||||
source/core/rendering/surface_moving_sprite.cpp
|
||||
source/core/rendering/surface_sprite.cpp
|
||||
source/core/rendering/text.cpp
|
||||
source/core/rendering/texture.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,13 +90,22 @@ 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 - Editor (debug only, guarded by #ifdef _DEBUG in source)
|
||||
source/game/editor/map_editor.cpp
|
||||
source/game/editor/editor_statusbar.cpp
|
||||
source/game/editor/room_saver.cpp
|
||||
source/game/editor/tile_picker.cpp
|
||||
source/game/editor/mini_map.cpp
|
||||
|
||||
# Game - UI
|
||||
source/game/ui/console.cpp
|
||||
source/game/ui/console_commands.cpp
|
||||
source/game/ui/notifier.cpp
|
||||
|
||||
# Utils
|
||||
@@ -124,32 +134,62 @@ message(STATUS "SDL3 encontrado: ${SDL3_INCLUDE_DIRS}")
|
||||
if(NOT APPLE)
|
||||
find_program(GLSLC_EXE NAMES glslc)
|
||||
|
||||
set(SHADER_VERT_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.vert")
|
||||
set(SHADER_FRAG_SRC "${CMAKE_SOURCE_DIR}/data/shaders/postfx.frag")
|
||||
set(SHADER_VERT_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/postfx_vert_spv.h")
|
||||
set(SHADER_FRAG_H "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu/postfx_frag_spv.h")
|
||||
set(SHADERS_DIR "${CMAKE_SOURCE_DIR}/data/shaders")
|
||||
set(HEADERS_DIR "${CMAKE_SOURCE_DIR}/source/core/rendering/sdl3gpu")
|
||||
|
||||
set(SHADER_POSTFX_VERT_SRC "${SHADERS_DIR}/postfx.vert")
|
||||
set(SHADER_POSTFX_FRAG_SRC "${SHADERS_DIR}/postfx.frag")
|
||||
set(SHADER_UPSCALE_FRAG_SRC "${SHADERS_DIR}/upscale.frag")
|
||||
set(SHADER_DOWNSCALE_FRAG_SRC "${SHADERS_DIR}/downscale.frag")
|
||||
set(SHADER_CRTPI_FRAG_SRC "${SHADERS_DIR}/crtpi_frag.glsl")
|
||||
|
||||
set(SHADER_POSTFX_VERT_H "${HEADERS_DIR}/postfx_vert_spv.h")
|
||||
set(SHADER_POSTFX_FRAG_H "${HEADERS_DIR}/postfx_frag_spv.h")
|
||||
set(SHADER_UPSCALE_FRAG_H "${HEADERS_DIR}/upscale_frag_spv.h")
|
||||
set(SHADER_DOWNSCALE_FRAG_H "${HEADERS_DIR}/downscale_frag_spv.h")
|
||||
set(SHADER_CRTPI_FRAG_H "${HEADERS_DIR}/crtpi_frag_spv.h")
|
||||
|
||||
set(ALL_SHADER_HEADERS
|
||||
"${SHADER_POSTFX_VERT_H}"
|
||||
"${SHADER_POSTFX_FRAG_H}"
|
||||
"${SHADER_UPSCALE_FRAG_H}"
|
||||
"${SHADER_DOWNSCALE_FRAG_H}"
|
||||
"${SHADER_CRTPI_FRAG_H}"
|
||||
)
|
||||
set(ALL_SHADER_SOURCES
|
||||
"${SHADER_POSTFX_VERT_SRC}"
|
||||
"${SHADER_POSTFX_FRAG_SRC}"
|
||||
"${SHADER_UPSCALE_FRAG_SRC}"
|
||||
"${SHADER_DOWNSCALE_FRAG_SRC}"
|
||||
"${SHADER_CRTPI_FRAG_SRC}"
|
||||
)
|
||||
|
||||
if(GLSLC_EXE)
|
||||
add_custom_command(
|
||||
OUTPUT "${SHADER_VERT_H}" "${SHADER_FRAG_H}"
|
||||
COMMAND "${CMAKE_SOURCE_DIR}/tools/shaders/compile_spirv.sh"
|
||||
DEPENDS "${SHADER_VERT_SRC}" "${SHADER_FRAG_SRC}"
|
||||
OUTPUT ${ALL_SHADER_HEADERS}
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-D GLSLC=${GLSLC_EXE}
|
||||
-D SHADERS_DIR=${SHADERS_DIR}
|
||||
-D HEADERS_DIR=${HEADERS_DIR}
|
||||
-P ${CMAKE_SOURCE_DIR}/tools/shaders/compile_spirv.cmake
|
||||
DEPENDS ${ALL_SHADER_SOURCES}
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
COMMENT "Compilando shaders SPIR-V..."
|
||||
)
|
||||
add_custom_target(shaders DEPENDS "${SHADER_VERT_H}" "${SHADER_FRAG_H}")
|
||||
add_custom_target(shaders DEPENDS ${ALL_SHADER_HEADERS})
|
||||
message(STATUS "glslc encontrado: shaders se compilarán automáticamente")
|
||||
else()
|
||||
if(NOT EXISTS "${SHADER_VERT_H}" OR NOT EXISTS "${SHADER_FRAG_H}")
|
||||
foreach(HDR ${ALL_SHADER_HEADERS})
|
||||
if(NOT EXISTS "${HDR}")
|
||||
message(FATAL_ERROR
|
||||
"glslc no encontrado y headers SPIR-V no existen.\n"
|
||||
"glslc no encontrado y header SPIR-V no existe: ${HDR}\n"
|
||||
" Instala glslc: sudo apt install glslang-tools (Linux)\n"
|
||||
" choco install vulkan-sdk (Windows)\n"
|
||||
" O genera los headers manualmente: tools/shaders/compile_spirv.sh"
|
||||
)
|
||||
else()
|
||||
message(STATUS "glslc no encontrado - usando headers SPIR-V precompilados")
|
||||
endif()
|
||||
endforeach()
|
||||
message(STATUS "glslc no encontrado - usando headers SPIR-V precompilados")
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "macOS: shaders SPIR-V omitidos (usa Metal)")
|
||||
@@ -192,7 +232,17 @@ if(WIN32)
|
||||
elseif(APPLE)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE MACOS_BUILD)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE -Wno-deprecated)
|
||||
if(NOT CMAKE_OSX_ARCHITECTURES)
|
||||
set(CMAKE_OSX_ARCHITECTURES "arm64")
|
||||
endif()
|
||||
if(MACOS_BUNDLE)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE MACOS_BUNDLE)
|
||||
target_link_options(${PROJECT_NAME} PRIVATE
|
||||
-framework SDL3
|
||||
-F ${CMAKE_SOURCE_DIR}/release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64
|
||||
-rpath @executable_path/../Frameworks/
|
||||
)
|
||||
endif()
|
||||
elseif(UNIX AND NOT APPLE)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE LINUX_BUILD)
|
||||
endif()
|
||||
|
||||
311
Makefile
311
Makefile
@@ -18,25 +18,18 @@ RELEASE_FILE := $(RELEASE_FOLDER)/$(TARGET_NAME)
|
||||
RESOURCE_FILE := release/windows/jdd.res
|
||||
|
||||
# ==============================================================================
|
||||
# PACKING TOOL
|
||||
# ==============================================================================
|
||||
ifeq ($(OS),Windows_NT)
|
||||
PACK_TOOL := $(DIR_TOOLS)pack_resources/pack_resources.exe
|
||||
PACK_CXX := $(CXX)
|
||||
else
|
||||
PACK_TOOL := $(DIR_TOOLS)pack_resources/pack_resources
|
||||
PACK_CXX := $(CXX)
|
||||
endif
|
||||
PACK_SOURCES := $(DIR_TOOLS)pack_resources/pack_resources.cpp source/core/resources/resource_pack.cpp
|
||||
PACK_INCLUDES := -Isource
|
||||
|
||||
# ==============================================================================
|
||||
# SHADERS
|
||||
# TOOLS
|
||||
# ==============================================================================
|
||||
DIR_PACK_TOOL := $(DIR_TOOLS)pack_resources
|
||||
SHADER_SCRIPT := $(DIR_ROOT)tools/shaders/compile_spirv.sh
|
||||
SHADER_VERT_H := $(DIR_ROOT)source/core/rendering/sdl3gpu/postfx_vert_spv.h
|
||||
SHADER_FRAG_H := $(DIR_ROOT)source/core/rendering/sdl3gpu/postfx_frag_spv.h
|
||||
GLSLC := $(shell command -v glslc 2>/dev/null)
|
||||
SHADER_CMAKE := $(DIR_ROOT)tools/shaders/compile_spirv.cmake
|
||||
SHADERS_DIR := $(DIR_ROOT)data/shaders
|
||||
HEADERS_DIR := $(DIR_ROOT)source/core/rendering/sdl3gpu
|
||||
ifeq ($(OS),Windows_NT)
|
||||
GLSLC := $(shell where glslc 2>NUL)
|
||||
else
|
||||
GLSLC := $(shell command -v glslc 2>/dev/null)
|
||||
endif
|
||||
|
||||
# ==============================================================================
|
||||
# VERSION (extracted from defines.hpp)
|
||||
@@ -47,6 +40,14 @@ else
|
||||
VERSION := v$(shell grep 'constexpr const char\* VERSION' source/utils/defines.hpp | sed -E 's/.*VERSION = "([^"]+)".*/\1/')
|
||||
endif
|
||||
|
||||
# ==============================================================================
|
||||
# SHELL (Windows usa cmd.exe para que las recetas con powershell funcionen igual
|
||||
# desde cualquier terminal: PowerShell, cmd o git-bash)
|
||||
# ==============================================================================
|
||||
ifeq ($(OS),Windows_NT)
|
||||
SHELL := cmd.exe
|
||||
endif
|
||||
|
||||
# ==============================================================================
|
||||
# WINDOWS-SPECIFIC VARIABLES
|
||||
# ==============================================================================
|
||||
@@ -67,213 +68,116 @@ MACOS_APPLE_SILICON_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-macos-apple
|
||||
LINUX_RELEASE := $(DIST_DIR)/$(TARGET_NAME)-$(VERSION)-linux.tar.gz
|
||||
|
||||
# ==============================================================================
|
||||
# SOURCE FILES
|
||||
# PLATAFORMA
|
||||
# ==============================================================================
|
||||
APP_SOURCES := \
|
||||
source/main.cpp \
|
||||
source/core/audio/audio.cpp \
|
||||
source/core/input/input.cpp \
|
||||
source/core/input/input_types.cpp \
|
||||
source/core/input/mouse.cpp \
|
||||
source/core/input/global_inputs.cpp \
|
||||
source/core/rendering/screen.cpp \
|
||||
source/core/rendering/surface.cpp \
|
||||
source/core/rendering/surface_sprite.cpp \
|
||||
source/core/rendering/surface_animated_sprite.cpp \
|
||||
source/core/rendering/surface_moving_sprite.cpp \
|
||||
source/core/rendering/text.cpp \
|
||||
source/core/rendering/texture.cpp \
|
||||
source/core/rendering/gif.cpp \
|
||||
source/core/rendering/pixel_reveal.cpp \
|
||||
source/core/rendering/surface_dissolve_sprite.cpp \
|
||||
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp \
|
||||
source/core/resources/resource_list.cpp \
|
||||
source/core/resources/resource_cache.cpp \
|
||||
source/core/resources/resource_helper.cpp \
|
||||
source/core/resources/resource_loader.cpp \
|
||||
source/core/resources/resource_pack.cpp \
|
||||
source/core/system/director.cpp \
|
||||
source/core/system/debug.cpp \
|
||||
source/core/system/global_events.cpp \
|
||||
source/game/options.cpp \
|
||||
source/game/entities/player.cpp \
|
||||
source/game/entities/enemy.cpp \
|
||||
source/game/entities/item.cpp \
|
||||
source/game/gameplay/room.cpp \
|
||||
source/game/gameplay/collision_map.cpp \
|
||||
source/game/gameplay/enemy_manager.cpp \
|
||||
source/game/gameplay/item_manager.cpp \
|
||||
source/game/gameplay/room_loader.cpp \
|
||||
source/game/gameplay/tilemap_renderer.cpp \
|
||||
source/game/gameplay/scoreboard.cpp \
|
||||
source/game/gameplay/cheevos.cpp \
|
||||
source/game/gameplay/item_tracker.cpp \
|
||||
source/game/gameplay/room_tracker.cpp \
|
||||
source/game/gameplay/stats.cpp \
|
||||
source/game/scenes/logo.cpp \
|
||||
source/game/scenes/loading_screen.cpp \
|
||||
source/game/scenes/title.cpp \
|
||||
source/game/scenes/game.cpp \
|
||||
source/game/scenes/game_over.cpp \
|
||||
source/game/scenes/ending.cpp \
|
||||
source/game/scenes/ending2.cpp \
|
||||
source/game/scenes/credits.cpp \
|
||||
source/game/ui/notifier.cpp \
|
||||
source/utils/utils.cpp \
|
||||
source/utils/delta_timer.cpp
|
||||
|
||||
# All sources combined
|
||||
ALL_SOURCES := $(APP_SOURCES)
|
||||
|
||||
# ==============================================================================
|
||||
# INCLUDES
|
||||
# ==============================================================================
|
||||
INCLUDES := -Isource
|
||||
|
||||
# ==============================================================================
|
||||
# COMPILER FLAGS (OS-specific)
|
||||
# ==============================================================================
|
||||
CPP_STANDARD := c++20
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
FixPath = $(subst /,\\,$1)
|
||||
CXXFLAGS := -std=$(CPP_STANDARD) -Wall -Os -ffunction-sections -fdata-sections \
|
||||
-Wl,--gc-sections -static-libstdc++ -static-libgcc \
|
||||
-Wl,-subsystem,windows -DWINDOWS_BUILD
|
||||
CXXFLAGS_DEBUG := -std=$(CPP_STANDARD) -Wall -g -D_DEBUG -DWINDOWS_BUILD
|
||||
LDFLAGS := -lmingw32 -lws2_32 -lSDL3
|
||||
RM := del /Q
|
||||
MKDIR := mkdir
|
||||
else
|
||||
FixPath = $1
|
||||
CXXFLAGS := -std=$(CPP_STANDARD) -Wall -Os -ffunction-sections -fdata-sections
|
||||
CXXFLAGS_DEBUG := -std=$(CPP_STANDARD) -Wall -g -D_DEBUG
|
||||
LDFLAGS := -lSDL3
|
||||
RMFILE := rm -f
|
||||
RMDIR := rm -rdf
|
||||
MKDIR := mkdir -p
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
CXXFLAGS += -DLINUX_BUILD
|
||||
endif
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
CXXFLAGS += -DMACOS_BUILD
|
||||
CXXFLAGS_DEBUG += -DMACOS_BUILD
|
||||
# Configurar arquitectura (por defecto arm64)
|
||||
CXXFLAGS += -arch arm64
|
||||
CXXFLAGS_DEBUG += -arch arm64
|
||||
endif
|
||||
endif
|
||||
|
||||
# ==============================================================================
|
||||
# REGLAS PARA COMPILACIÓN DE SHADERS
|
||||
# COMPILACIÓN CON CMAKE
|
||||
# ==============================================================================
|
||||
all:
|
||||
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
@cmake --build build
|
||||
|
||||
debug:
|
||||
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
|
||||
@cmake --build build
|
||||
|
||||
# ==============================================================================
|
||||
# RELEASE AUTOMÁTICO (detecta SO)
|
||||
# ==============================================================================
|
||||
release:
|
||||
ifeq ($(OS),Windows_NT)
|
||||
@"$(MAKE)" windows_release
|
||||
else
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
@$(MAKE) macos_release
|
||||
else
|
||||
@$(MAKE) linux_release
|
||||
endif
|
||||
endif
|
||||
|
||||
# ==============================================================================
|
||||
# REGLAS PARA COMPILACIÓN DE SHADERS (multiplataforma via cmake)
|
||||
# ==============================================================================
|
||||
compile_shaders:
|
||||
ifdef GLSLC
|
||||
@echo "Compilando shaders SPIR-V..."
|
||||
@$(SHADER_SCRIPT)
|
||||
@echo "✓ Shaders compilados"
|
||||
@cmake -D GLSLC=$(GLSLC) -D SHADERS_DIR=$(SHADERS_DIR) -D HEADERS_DIR=$(HEADERS_DIR) -P $(SHADER_CMAKE)
|
||||
else
|
||||
@if [ -f "$(SHADER_VERT_H)" ] && [ -f "$(SHADER_FRAG_H)" ]; then \
|
||||
echo "⚠ glslc no encontrado - usando headers SPIR-V precompilados"; \
|
||||
else \
|
||||
echo "ERROR: glslc no encontrado y headers SPIR-V no existen."; \
|
||||
echo " Instala glslc: sudo apt install glslang-tools"; \
|
||||
echo " O ejecuta manualmente: tools/shaders/compile_spirv.sh"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo "glslc no encontrado - asegurate de que los headers SPIR-V precompilados existen"
|
||||
endif
|
||||
|
||||
# ==============================================================================
|
||||
# REGLAS PARA HERRAMIENTA DE EMPAQUETADO Y RESOURCES.PACK
|
||||
# ==============================================================================
|
||||
$(PACK_TOOL): FORCE
|
||||
@echo "Compilando herramienta de empaquetado..."
|
||||
$(PACK_CXX) -std=$(CPP_STANDARD) -Wall -Os $(PACK_INCLUDES) $(PACK_SOURCES) -o $(PACK_TOOL)
|
||||
@echo "✓ Herramienta de empaquetado lista: $(PACK_TOOL)"
|
||||
pack_tool:
|
||||
@$(MAKE) -C $(DIR_PACK_TOOL)
|
||||
|
||||
pack_tool: $(PACK_TOOL)
|
||||
|
||||
resources.pack: $(PACK_TOOL)
|
||||
@echo "Generando resources.pack desde directorio data/..."
|
||||
$(PACK_TOOL) data resources.pack
|
||||
@echo "✓ resources.pack generado exitosamente"
|
||||
resources.pack: pack_tool
|
||||
@$(MAKE) -C $(DIR_PACK_TOOL) pack
|
||||
|
||||
# ==============================================================================
|
||||
# COMPILACIÓN PARA WINDOWS
|
||||
# COMPILACIÓN PARA WINDOWS (RELEASE)
|
||||
# ==============================================================================
|
||||
windows:
|
||||
@echo off
|
||||
@echo Generando version.h...
|
||||
@powershell -Command "$$GIT_HASH = (git rev-parse --short=7 HEAD 2>$$null); if (-not $$GIT_HASH) { $$GIT_HASH = 'unknown' }; (Get-Content source/version.h.in) -replace '@GIT_HASH@', $$GIT_HASH | Set-Content source/version.h"
|
||||
@echo Compilando para Windows con nombre: "$(WIN_TARGET_FILE).exe"
|
||||
windres release/windows/jdd.rc -O coff -o $(RESOURCE_FILE)
|
||||
g++ $(ALL_SOURCES) $(RESOURCE_FILE) $(INCLUDES) $(CXXFLAGS) $(LDFLAGS) -o "$(WIN_TARGET_FILE).exe"
|
||||
strip -s -R .comment -R .gnu.version "$(WIN_TARGET_FILE).exe" --strip-unneeded
|
||||
|
||||
windows_release:
|
||||
@$(MAKE) compile_shaders
|
||||
@$(MAKE) resources.pack
|
||||
@echo off
|
||||
@echo Creando release para Windows - Version: $(VERSION)
|
||||
|
||||
# Generate version.h from version.h.in
|
||||
@echo "Generando version.h..."
|
||||
@powershell -Command "$$GIT_HASH = (git rev-parse --short=7 HEAD 2>$$null); if (-not $$GIT_HASH) { $$GIT_HASH = 'unknown' }; (Get-Content source/version.h.in) -replace '@GIT_HASH@', $$GIT_HASH | Set-Content source/version.h"
|
||||
# Compila con cmake (genera shaders, resources.pack y ejecutable)
|
||||
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
@cmake --build build
|
||||
|
||||
# Crea carpeta de distribución y carpeta temporal 'RELEASE_FOLDER'
|
||||
powershell if (-not (Test-Path "$(DIST_DIR)")) {New-Item "$(DIST_DIR)" -ItemType Directory}
|
||||
powershell if (Test-Path "$(RELEASE_FOLDER)") {Remove-Item "$(RELEASE_FOLDER)" -Recurse -Force}
|
||||
powershell if (-not (Test-Path "$(RELEASE_FOLDER)")) {New-Item "$(RELEASE_FOLDER)" -ItemType Directory}
|
||||
@powershell -Command "if (-not (Test-Path '$(DIST_DIR)')) {New-Item '$(DIST_DIR)' -ItemType Directory}"
|
||||
@powershell -Command "if (Test-Path '$(RELEASE_FOLDER)') {Remove-Item '$(RELEASE_FOLDER)' -Recurse -Force}"
|
||||
@powershell -Command "if (-not (Test-Path '$(RELEASE_FOLDER)')) {New-Item '$(RELEASE_FOLDER)' -ItemType Directory}"
|
||||
|
||||
# Copia el archivo 'resources.pack'
|
||||
powershell Copy-Item -Path "resources.pack" -Destination "$(RELEASE_FOLDER)"
|
||||
|
||||
# Copia los ficheros que están en la raíz del proyecto
|
||||
powershell Copy-Item "LICENSE" -Destination "$(RELEASE_FOLDER)"
|
||||
powershell Copy-Item "README.md" -Destination "$(RELEASE_FOLDER)"
|
||||
powershell Copy-Item "gamecontrollerdb.txt" -Destination "$(RELEASE_FOLDER)"
|
||||
powershell Copy-Item "release\windows\dll\*.dll" -Destination "$(RELEASE_FOLDER)"
|
||||
|
||||
# Compila (con icono)
|
||||
windres release/windows/jdd.rc -O coff -o $(RESOURCE_FILE)
|
||||
g++ $(ALL_SOURCES) $(RESOURCE_FILE) $(INCLUDES) -DRELEASE_BUILD $(CXXFLAGS) $(LDFLAGS) -o "$(WIN_RELEASE_FILE).exe"
|
||||
# Copia ficheros
|
||||
@powershell -Command "Copy-Item -Path 'resources.pack' -Destination '$(RELEASE_FOLDER)'"
|
||||
@powershell -Command "Copy-Item 'LICENSE' -Destination '$(RELEASE_FOLDER)'"
|
||||
@powershell -Command "Copy-Item 'README.md' -Destination '$(RELEASE_FOLDER)'"
|
||||
@powershell -Command "Copy-Item 'gamecontrollerdb.txt' -Destination '$(RELEASE_FOLDER)'"
|
||||
@powershell -Command "Copy-Item 'release\windows\dll\*.dll' -Destination '$(RELEASE_FOLDER)'"
|
||||
@powershell -Command "Copy-Item -Path '$(TARGET_FILE)' -Destination '\"$(WIN_RELEASE_FILE).exe\"'"
|
||||
strip -s -R .comment -R .gnu.version "$(WIN_RELEASE_FILE).exe" --strip-unneeded
|
||||
|
||||
# Crea el fichero .zip
|
||||
powershell if (Test-Path "$(WINDOWS_RELEASE)") {Remove-Item "$(WINDOWS_RELEASE)"}
|
||||
powershell Compress-Archive -Path "$(RELEASE_FOLDER)"/* -DestinationPath "$(WINDOWS_RELEASE)"
|
||||
@powershell -Command "if (Test-Path '$(WINDOWS_RELEASE)') {Remove-Item '$(WINDOWS_RELEASE)'}"
|
||||
@powershell -Command "Compress-Archive -Path '$(RELEASE_FOLDER)/*' -DestinationPath '$(WINDOWS_RELEASE)'"
|
||||
@echo Release creado: $(WINDOWS_RELEASE)
|
||||
|
||||
# Elimina la carpeta temporal 'RELEASE_FOLDER'
|
||||
powershell if (Test-Path "$(RELEASE_FOLDER)") {Remove-Item "$(RELEASE_FOLDER)" -Recurse -Force}
|
||||
@powershell -Command "if (Test-Path '$(RELEASE_FOLDER)') {Remove-Item '$(RELEASE_FOLDER)' -Recurse -Force}"
|
||||
|
||||
# ==============================================================================
|
||||
# COMPILACIÓN PARA MACOS
|
||||
# COMPILACIÓN PARA MACOS (RELEASE)
|
||||
# ==============================================================================
|
||||
macos:
|
||||
@GIT_HASH=$$(git rev-parse --short=7 HEAD 2>/dev/null || echo "unknown"); \
|
||||
sed "s/@GIT_HASH@/$$GIT_HASH/g" source/version.h.in > source/version.h
|
||||
@echo "Compilando para macOS: $(TARGET_NAME)"
|
||||
clang++ $(ALL_SOURCES) $(INCLUDES) $(CXXFLAGS) $(LDFLAGS) -o "$(TARGET_FILE)"
|
||||
|
||||
macos_release:
|
||||
@$(MAKE) compile_shaders
|
||||
@$(MAKE) resources.pack
|
||||
@echo "Creando release para macOS - Version: $(VERSION)"
|
||||
|
||||
# Verificar e instalar create-dmg si es necesario
|
||||
@which create-dmg > /dev/null || (echo "Instalando create-dmg..." && brew install create-dmg)
|
||||
|
||||
# Generate version.h from version.h.in
|
||||
@echo "Generando version.h..."
|
||||
@GIT_HASH=$$(git rev-parse --short=7 HEAD 2>/dev/null || echo "unknown"); \
|
||||
sed "s/@GIT_HASH@/$$GIT_HASH/g" source/version.h.in > source/version.h
|
||||
# Compila la versión para procesadores Intel con cmake (genera shaders y resources.pack)
|
||||
@cmake -S . -B build/intel -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 -DMACOS_BUNDLE=ON
|
||||
@cmake --build build/intel
|
||||
|
||||
# Elimina datos de compilaciones anteriores
|
||||
$(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"
|
||||
@@ -295,8 +199,8 @@ macos_release:
|
||||
sed -i '' '/<key>CFBundleShortVersionString<\/key>/{n;s|<string>.*</string>|<string>'"$$RAW_VERSION"'</string>|;}' "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Info.plist"; \
|
||||
sed -i '' '/<key>CFBundleVersion<\/key>/{n;s|<string>.*</string>|<string>'"$$RAW_VERSION"'</string>|;}' "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/Info.plist"
|
||||
|
||||
# Compila la versión para procesadores Intel
|
||||
clang++ $(ALL_SOURCES) $(INCLUDES) -DMACOS_BUNDLE -DRELEASE_BUILD -std=$(CPP_STANDARD) -Wall -Os -framework SDL3 -F release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64 -ffunction-sections -fdata-sections -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target x86_64-apple-macos10.15
|
||||
# Copia el ejecutable Intel al bundle
|
||||
cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)"
|
||||
|
||||
# Firma la aplicación
|
||||
codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app"
|
||||
@@ -318,8 +222,10 @@ macos_release:
|
||||
"$(RELEASE_FOLDER)" || true
|
||||
@echo "Release Intel creado: $(MACOS_INTEL_RELEASE)"
|
||||
|
||||
# Compila la versión para procesadores Apple Silicon
|
||||
clang++ $(ALL_SOURCES) $(INCLUDES) -DMACOS_BUNDLE -DRELEASE_BUILD -std=$(CPP_STANDARD) -Wall -Os -framework SDL3 -F release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64 -ffunction-sections -fdata-sections -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target arm64-apple-macos11
|
||||
# Compila la versión para procesadores Apple Silicon con cmake
|
||||
@cmake -S . -B build/arm -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 -DMACOS_BUNDLE=ON
|
||||
@cmake --build build/arm
|
||||
cp "$(TARGET_FILE)" "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)"
|
||||
|
||||
# Firma la aplicación
|
||||
codesign --deep --force --sign - --timestamp=none "$(RELEASE_FOLDER)/$(APP_NAME).app"
|
||||
@@ -343,27 +249,19 @@ macos_release:
|
||||
|
||||
# Elimina las carpetas temporales
|
||||
$(RMDIR) "$(RELEASE_FOLDER)"
|
||||
$(RMDIR) build/intel
|
||||
$(RMDIR) build/arm
|
||||
$(RMFILE) "$(DIST_DIR)"/rw.*
|
||||
|
||||
# ==============================================================================
|
||||
# COMPILACIÓN PARA LINUX
|
||||
# COMPILACIÓN PARA LINUX (RELEASE)
|
||||
# ==============================================================================
|
||||
linux:
|
||||
@GIT_HASH=$$(git rev-parse --short=7 HEAD 2>/dev/null || echo "unknown"); \
|
||||
sed "s/@GIT_HASH@/$$GIT_HASH/g" source/version.h.in > source/version.h
|
||||
@echo "Compilando para Linux: $(TARGET_NAME)"
|
||||
g++ $(ALL_SOURCES) $(INCLUDES) $(CXXFLAGS) $(LDFLAGS) -o "$(TARGET_FILE)"
|
||||
strip -s -R .comment -R .gnu.version "$(TARGET_FILE)" --strip-unneeded
|
||||
|
||||
linux_release:
|
||||
@$(MAKE) compile_shaders
|
||||
@$(MAKE) resources.pack
|
||||
@echo "Creando release para Linux - Version: $(VERSION)"
|
||||
|
||||
# Generate version.h from version.h.in
|
||||
@echo "Generando version.h..."
|
||||
@GIT_HASH=$$(git rev-parse --short=7 HEAD 2>/dev/null || echo "unknown"); \
|
||||
sed "s/@GIT_HASH@/$$GIT_HASH/g" source/version.h.in > source/version.h
|
||||
# Compila con cmake (genera shaders, resources.pack y ejecutable)
|
||||
@cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
@cmake --build build
|
||||
|
||||
# Elimina carpeta temporal previa y la recrea (crea dist/ si no existe)
|
||||
$(RMDIR) "$(RELEASE_FOLDER)"
|
||||
@@ -374,9 +272,7 @@ linux_release:
|
||||
cp LICENSE "$(RELEASE_FOLDER)"
|
||||
cp README.md "$(RELEASE_FOLDER)"
|
||||
cp gamecontrollerdb.txt "$(RELEASE_FOLDER)"
|
||||
|
||||
# Compila
|
||||
g++ $(ALL_SOURCES) $(INCLUDES) -DRELEASE_BUILD $(CXXFLAGS) $(LDFLAGS) -o "$(RELEASE_FILE)"
|
||||
cp "$(TARGET_FILE)" "$(RELEASE_FILE)"
|
||||
strip -s -R .comment -R .gnu.version "$(RELEASE_FILE)" --strip-unneeded
|
||||
|
||||
# Empaqueta ficheros
|
||||
@@ -398,17 +294,24 @@ show_version:
|
||||
help:
|
||||
@echo "Makefile para JailDoctor's Dilemma"
|
||||
@echo "Comandos disponibles:"
|
||||
@echo " windows - Compilar para Windows"
|
||||
@echo " windows_release - Crear release completo para Windows"
|
||||
@echo " linux - Compilar para Linux"
|
||||
@echo " linux_release - Crear release completo para Linux"
|
||||
@echo " macos - Compilar para macOS"
|
||||
@echo " macos_release - Crear release completo para macOS"
|
||||
@echo " pack_tool - Compilar herramienta de empaquetado"
|
||||
@echo " resources.pack - Generar pack de recursos desde data/"
|
||||
@echo " show_version - Mostrar version actual ($(VERSION))"
|
||||
@echo " help - Mostrar esta ayuda"
|
||||
@echo ""
|
||||
@echo " Compilacion:"
|
||||
@echo " make - Compilar con cmake (Release)"
|
||||
@echo " make debug - Compilar con cmake (Debug)"
|
||||
@echo ""
|
||||
@echo " Release:"
|
||||
@echo " make release - Crear release (detecta SO automaticamente)"
|
||||
@echo " make windows_release - Crear release para Windows"
|
||||
@echo " make linux_release - Crear release para Linux"
|
||||
@echo " make macos_release - Crear release para macOS"
|
||||
@echo ""
|
||||
@echo " Herramientas:"
|
||||
@echo " make compile_shaders - Compilar shaders SPIR-V"
|
||||
@echo " make pack_tool - Compilar herramienta de empaquetado"
|
||||
@echo " make resources.pack - Generar pack de recursos desde data/"
|
||||
@echo ""
|
||||
@echo " Otros:"
|
||||
@echo " make show_version - Mostrar version actual ($(VERSION))"
|
||||
@echo " make help - Mostrar esta ayuda"
|
||||
|
||||
FORCE:
|
||||
|
||||
.PHONY: windows windows_release macos macos_release linux linux_release compile_shaders pack_tool resources.pack show_version help
|
||||
.PHONY: all debug release windows_release macos_release linux_release compile_shaders pack_tool resources.pack show_version help
|
||||
|
||||
@@ -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:
|
||||
@@ -74,6 +92,14 @@ assets:
|
||||
path: ${SYSTEM_FOLDER}/config.yaml
|
||||
required: false
|
||||
absolute: true
|
||||
- type: DATA
|
||||
path: ${SYSTEM_FOLDER}/debug.yaml
|
||||
required: false
|
||||
absolute: true
|
||||
- type: DATA
|
||||
path: ${SYSTEM_FOLDER}/editor.yaml
|
||||
required: false
|
||||
absolute: true
|
||||
- type: DATA
|
||||
path: ${SYSTEM_FOLDER}/stats_buffer.csv
|
||||
required: false
|
||||
@@ -87,9 +113,18 @@ assets:
|
||||
required: false
|
||||
absolute: true
|
||||
- type: DATA
|
||||
path: ${SYSTEM_FOLDER}/postfx.yaml
|
||||
path: ${SYSTEM_FOLDER}/shaders/postfx.yaml
|
||||
required: false
|
||||
absolute: true
|
||||
- type: DATA
|
||||
path: ${SYSTEM_FOLDER}/shaders/crtpi.yaml
|
||||
required: false
|
||||
absolute: true
|
||||
|
||||
# CONSOLE
|
||||
console:
|
||||
- type: DATA
|
||||
path: ${PREFIX}/data/console/commands.yaml
|
||||
|
||||
# ROOMS
|
||||
rooms:
|
||||
|
||||
287
data/console/commands.yaml
Normal file
287
data/console/commands.yaml
Normal file
@@ -0,0 +1,287 @@
|
||||
# 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
|
||||
# scope - (optional) Visibility scope: global, game, editor, debug (default: global)
|
||||
# Can be a string or a list. Categories can set a default scope for all commands.
|
||||
|
||||
categories:
|
||||
- name: VIDEO
|
||||
scope: game
|
||||
commands:
|
||||
- keyword: SS
|
||||
handler: cmd_ss
|
||||
description: Supersampling
|
||||
usage: "SS [ON|OFF|SIZE|UPSCALE [NEAREST|LINEAR]|DOWNSCALE [BILINEAR|LANCZOS2|LANCZOS3]]"
|
||||
completions:
|
||||
SS: [ON, OFF, SIZE, UPSCALE, DOWNSCALE]
|
||||
SS UPSCALE: [NEAREST, LINEAR]
|
||||
SS DOWNSCALE: [BILINEAR, LANCZOS2, LANCZOS3]
|
||||
|
||||
- keyword: SHADER
|
||||
handler: cmd_shader
|
||||
description: "Toggle/select shader (F4)"
|
||||
usage: "SHADER [ON|OFF|NEXT|POSTFX|CRTPI|PRESET [NEXT|PREV|<name>]]"
|
||||
completions:
|
||||
SHADER: [ON, OFF, NEXT, POSTFX, CRTPI, PRESET]
|
||||
dynamic_completions: true
|
||||
|
||||
- keyword: BORDER
|
||||
handler: cmd_border
|
||||
description: "Decorative border (B)"
|
||||
usage: "BORDER [ON|OFF]"
|
||||
completions:
|
||||
BORDER: [ON, OFF]
|
||||
|
||||
- keyword: FULLSCREEN
|
||||
handler: cmd_fullscreen
|
||||
description: "Fullscreen mode (F3)"
|
||||
usage: "FULLSCREEN [ON|OFF]"
|
||||
completions:
|
||||
FULLSCREEN: [ON, OFF]
|
||||
|
||||
- keyword: ZOOM
|
||||
handler: cmd_zoom
|
||||
description: "Window zoom (F1/F2)"
|
||||
usage: "ZOOM [UP|DOWN|<1-N>]"
|
||||
completions:
|
||||
ZOOM: [UP, DOWN]
|
||||
|
||||
- keyword: INTSCALE
|
||||
handler: cmd_intscale
|
||||
description: "Integer scaling (F7)"
|
||||
usage: "INTSCALE [ON|OFF]"
|
||||
completions:
|
||||
INTSCALE: [ON, OFF]
|
||||
|
||||
- keyword: VSYNC
|
||||
handler: cmd_vsync
|
||||
description: "Vertical sync"
|
||||
usage: "VSYNC [ON|OFF]"
|
||||
completions:
|
||||
VSYNC: [ON, OFF]
|
||||
|
||||
- keyword: DRIVER
|
||||
handler: cmd_driver
|
||||
description: "GPU driver (restart to apply)"
|
||||
usage: "DRIVER [LIST|AUTO|NONE|<name>]"
|
||||
completions:
|
||||
DRIVER: [LIST, AUTO, NONE]
|
||||
|
||||
- keyword: PALETTE
|
||||
handler: cmd_palette
|
||||
description: "Color palette (F5/F6)"
|
||||
usage: "PALETTE [NEXT|PREV|SORT [ORIGINAL|LUMINANCE|SPECTRUM]|DEFAULT|<name>]"
|
||||
completions:
|
||||
PALETTE SORT: [ORIGINAL, LUMINANCE, SPECTRUM]
|
||||
dynamic_completions: true
|
||||
|
||||
- name: AUDIO
|
||||
scope: game
|
||||
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
|
||||
scope: 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
|
||||
scope: global
|
||||
|
||||
- keyword: QUIT
|
||||
handler: cmd_quit
|
||||
description: Quit application
|
||||
usage: QUIT
|
||||
instant: true
|
||||
help_hidden: true
|
||||
scope: global
|
||||
|
||||
- name: INFO
|
||||
scope: global
|
||||
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 [<command>]"
|
||||
dynamic_completions: true
|
||||
|
||||
- keyword: "?"
|
||||
handler: cmd_help
|
||||
help_hidden: true
|
||||
|
||||
- name: DEBUG
|
||||
debug_only: true
|
||||
scope: debug
|
||||
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|LEFT|RIGHT|UP|DOWN"
|
||||
scope: [debug, editor]
|
||||
completions:
|
||||
ROOM: [NEXT, PREV, LEFT, RIGHT, UP, DOWN, NEW, DELETE]
|
||||
ROOM NEW: [LEFT, RIGHT, UP, DOWN]
|
||||
|
||||
- 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]
|
||||
|
||||
- keyword: EDIT
|
||||
handler: cmd_edit
|
||||
description: "Map editor mode (GAME only)"
|
||||
usage: "EDIT [ON|OFF|REVERT|SHOW|HIDE|MAPBG|MAPCONN] [...]"
|
||||
scope: [debug, editor]
|
||||
dynamic_completions: true
|
||||
completions:
|
||||
EDIT: [ON, OFF, REVERT, SHOW, HIDE, MAPBG, MAPCONN]
|
||||
EDIT SHOW: [INFO, GRID]
|
||||
EDIT HIDE: [INFO, GRID]
|
||||
|
||||
- name: EDITOR
|
||||
debug_only: true
|
||||
scope: editor
|
||||
commands:
|
||||
- keyword: ENEMY
|
||||
handler: cmd_enemy
|
||||
description: "Add, delete or duplicate enemy"
|
||||
usage: "ENEMY <ADD|DELETE|DUPLICATE>"
|
||||
completions:
|
||||
ENEMY: [ADD, DELETE, DUPLICATE]
|
||||
|
||||
- keyword: ITEM
|
||||
handler: cmd_item
|
||||
description: "Add, delete or duplicate item"
|
||||
usage: "ITEM <ADD|DELETE|DUPLICATE>"
|
||||
completions:
|
||||
ITEM: [ADD, DELETE, DUPLICATE]
|
||||
|
||||
- keyword: SET
|
||||
handler: cmd_set
|
||||
description: "Set property (enemy, item or room)"
|
||||
usage: "SET <property> <value>"
|
||||
dynamic_completions: true
|
||||
completions:
|
||||
SET: [ANIMATION, COLOR, VX, VY, FLIP, MIRROR, BGCOLOR, BORDER, ITEMCOLOR1, ITEMCOLOR2, CONVEYOR, TILESET, UP, DOWN, LEFT, RIGHT, TILE, COUNTER]
|
||||
SET FLIP: [ON, OFF]
|
||||
SET MIRROR: [ON, OFF]
|
||||
SET CONVEYOR: [LEFT, NONE, RIGHT]
|
||||
|
||||
- name: CHEATS
|
||||
scope: [game, editor]
|
||||
commands:
|
||||
- keyword: CHEAT
|
||||
handler: cmd_cheat
|
||||
description: "Game cheats (GAME only)"
|
||||
usage: "CHEAT [INFINITE LIVES|INVINCIBILITY|OPEN THE JAIL|CLOSE THE JAIL]"
|
||||
hidden: true
|
||||
help_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
|
||||
help_hidden: false
|
||||
@@ -105,24 +105,24 @@ columns 15
|
||||
193 6 # Á
|
||||
200 6 # È
|
||||
201 6 # É
|
||||
204 6 # Ì
|
||||
205 6 # Í
|
||||
207 6 # Ï
|
||||
210 6 # Ò
|
||||
211 6 # Ó
|
||||
219 6 # Ù
|
||||
218 6 # Ú
|
||||
220 6 # Ü
|
||||
209 6 # Ñ
|
||||
199 6 # Ç
|
||||
224 5 # à
|
||||
225 5 # á
|
||||
232 5 # è
|
||||
233 5 # é
|
||||
236 4 # ì
|
||||
237 4 # í
|
||||
239 4 # ï
|
||||
242 5 # ò
|
||||
243 5 # ó
|
||||
249 5 # ù
|
||||
250 5 # ú
|
||||
252 5 # ü
|
||||
241 5 # ñ
|
||||
231 5 # ç
|
||||
161 2 # ¡
|
||||
|
||||
@@ -101,24 +101,24 @@ columns 15
|
||||
193 6 # Á
|
||||
200 7 # È
|
||||
201 7 # É
|
||||
204 6 # Ì
|
||||
205 6 # Í
|
||||
207 6 # Ï
|
||||
210 7 # Ò
|
||||
211 7 # Ó
|
||||
217 6 # Ù
|
||||
218 6 # Ú
|
||||
220 6 # Ü
|
||||
209 7 # Ñ
|
||||
199 7 # Ç
|
||||
224 6 # à
|
||||
225 6 # á
|
||||
232 7 # è
|
||||
233 7 # é
|
||||
236 6 # ì
|
||||
237 6 # í
|
||||
239 6 # ï
|
||||
242 7 # ò
|
||||
243 7 # ó
|
||||
249 6 # ù
|
||||
250 6 # ú
|
||||
252 6 # ü
|
||||
241 7 # ñ
|
||||
231 7 # ç
|
||||
161 2 # ¡
|
||||
|
||||
@@ -6,24 +6,24 @@ box_height 8
|
||||
columns 15
|
||||
|
||||
# codepoint_decimal ancho_visual
|
||||
32 8 # U+0020
|
||||
33 5 # !
|
||||
34 6 # "
|
||||
32 7 # U+0020
|
||||
33 7 # !
|
||||
34 7 # "
|
||||
35 7 # #
|
||||
36 7 # $
|
||||
37 7 # %
|
||||
38 7 # &
|
||||
39 4 # '
|
||||
40 6 # (
|
||||
41 5 # )
|
||||
39 7 # '
|
||||
40 7 # (
|
||||
41 7 # )
|
||||
42 7 # *
|
||||
43 7 # +
|
||||
44 4 # ,
|
||||
44 7 # ,
|
||||
45 7 # -
|
||||
46 4 # .
|
||||
46 7 # .
|
||||
47 7 # /
|
||||
48 7 # 0
|
||||
49 6 # 1
|
||||
49 7 # 1
|
||||
50 7 # 2
|
||||
51 7 # 3
|
||||
52 7 # 4
|
||||
@@ -32,11 +32,11 @@ columns 15
|
||||
55 7 # 7
|
||||
56 7 # 8
|
||||
57 7 # 9
|
||||
58 4 # :
|
||||
59 4 # ;
|
||||
60 6 # <
|
||||
61 6 # =
|
||||
62 6 # >
|
||||
58 7 # :
|
||||
59 7 # ;
|
||||
60 7 # <
|
||||
61 7 # =
|
||||
62 7 # >
|
||||
63 7 # ?
|
||||
64 7 # @
|
||||
65 7 # A
|
||||
@@ -47,7 +47,7 @@ columns 15
|
||||
70 7 # F
|
||||
71 7 # G
|
||||
72 7 # H
|
||||
73 6 # I
|
||||
73 7 # I
|
||||
74 7 # J
|
||||
75 7 # K
|
||||
76 7 # L
|
||||
@@ -65,12 +65,12 @@ columns 15
|
||||
88 7 # X
|
||||
89 7 # Y
|
||||
90 7 # Z
|
||||
91 6 # [
|
||||
91 7 # [
|
||||
92 7 # \
|
||||
93 5 # ]
|
||||
94 6 # ^
|
||||
93 7 # ]
|
||||
94 7 # ^
|
||||
95 7 # _
|
||||
96 4 # `
|
||||
96 7 # `
|
||||
97 7 # a
|
||||
98 7 # b
|
||||
99 7 # c
|
||||
@@ -79,7 +79,7 @@ columns 15
|
||||
102 7 # f
|
||||
103 7 # g
|
||||
104 7 # h
|
||||
105 6 # i
|
||||
105 7 # i
|
||||
106 7 # j
|
||||
107 7 # k
|
||||
108 7 # l
|
||||
@@ -97,36 +97,37 @@ columns 15
|
||||
120 7 # x
|
||||
121 7 # y
|
||||
122 7 # z
|
||||
123 6 # {
|
||||
124 5 # |
|
||||
125 5 # }
|
||||
123 7 # {
|
||||
124 7 # |
|
||||
125 7 # }
|
||||
126 7 # ~
|
||||
192 7 # À
|
||||
193 7 # Á
|
||||
200 7 # È
|
||||
201 7 # É
|
||||
205 6 # Í
|
||||
207 6 # Ï
|
||||
204 7 # Ì
|
||||
205 7 # Í
|
||||
210 7 # Ò
|
||||
211 7 # Ó
|
||||
217 7 # Ù
|
||||
218 7 # Ú
|
||||
220 7 # Ü
|
||||
209 7 # Ñ
|
||||
199 7 # Ç
|
||||
224 7 # à
|
||||
225 7 # á
|
||||
232 7 # è
|
||||
233 7 # é
|
||||
237 6 # í
|
||||
239 6 # ï
|
||||
236 7 # ì
|
||||
237 7 # í
|
||||
242 7 # ò
|
||||
243 7 # ó
|
||||
249 7 # ù
|
||||
250 7 # ú
|
||||
252 7 # ü
|
||||
241 7 # ñ
|
||||
231 7 # ç
|
||||
161 5 # ¡
|
||||
161 7 # ¡
|
||||
191 7 # ¿
|
||||
171 7 # «
|
||||
187 7 # »
|
||||
183 4 # ·
|
||||
183 7 # ·
|
||||
228 7 # ä (corazón)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 968 B After Width: | Height: | Size: 1001 B |
@@ -105,24 +105,24 @@ columns 15
|
||||
193 5 # Á
|
||||
200 4 # È
|
||||
201 4 # É
|
||||
204 1 # Ì
|
||||
205 1 # Í
|
||||
207 1 # Ï
|
||||
210 5 # Ò
|
||||
211 5 # Ó
|
||||
217 5 # Ù
|
||||
218 5 # Ú
|
||||
220 5 # Ü
|
||||
209 5 # Ñ
|
||||
199 5 # Ç
|
||||
224 4 # à
|
||||
225 4 # á
|
||||
232 4 # è
|
||||
233 4 # é
|
||||
236 1 # ì
|
||||
237 1 # í
|
||||
239 1 # ï
|
||||
242 4 # ò
|
||||
243 4 # ó
|
||||
249 4 # ù
|
||||
250 4 # ú
|
||||
252 4 # ü
|
||||
241 4 # ñ
|
||||
231 3 # ç
|
||||
161 1 # ¡
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
# lang: ca
|
||||
|
||||
title:
|
||||
marquee: "EH JAILEROS!! ESTEM EN 2022 I ENCARA HO PETEM COM EN 1998!! QUE, HO HEU SENTIT O NO? ELS JAILGAMES HAN TORNAT!! SÍ, COLLONS, HAN TORNAT!! MÉS DE 10 TÍTOLS QUE EL JAILDOC TÉ A FOC LENT!! AIXÒ ÉS UNA BARBARITAT, PERÒ... QUIN EIXIRÀ PRIMER? I ATENCIÓ, QUE HI HA UN APARELLET NOU QUE VOS FARÀ VOLAR EL CAP: EL P.A.C.O.! PERÒ UN MOMENT... QUÈ ÉS AQUELL ENCANTET QUE VE ALLÀ? OOOH, AQUELLA MINIASCII ÉS AMOR DEL BO!! LI PEGARIA UNA MOSSEGADA A CADA BYTE! OSTRES! I NO VOS OBLIDEU DE PUJAR AQUELLS JAILGAMES VELLS I PANXUTS DE MS-DOS A GITHUB, QUE SI NO ES PERDRAN!! QUIN SERÀ EL PROPER PROJECTE DEL JAILDOC? QUÈ PRENDRA VIDA? AI MARE... NI IDEA, PERÒ ACÍ PODEU SABER-HO SI RESOLGUEU EL DILEMA DEL JAILDOCTOR... VOS ATREVIU O QUÈ?"
|
||||
marquee: "EI JAILERS!! ESTEM EN 2022 I ENCARA HO PETEM COM EN 1998!! QUÉ, HO HEU SENTIT O NO? ELS JAILGAMES HAN TORNAT!! SÍ, COLLONS, HAN TORNAT!! MÉS DE 10 TÍTOLS QUE EL JAILDOC TÉ EN LA CUINA A FOC LENT!! MOLT LENT!! AIXÒ ÉS UNA BARBARITAT, PERÒ... QUIN EIXIRÀ PRIMER? I ATENCIÓ, QUE HI HA UN APARELLET NOU QUE VOS FARÀ VOLAR EL CAP: EL P.A.C.O.! PERÒ UN MOMENT... QUÈ ÉS AQUELLA COSETA QUE VE PER ALLÀ? OOOH, AQUELLA MINIASCII ÉS AMOR DEL BO!! LI PEGARIA UNA LLEPAETA A CADA BYTE! OSTRES! I NO VOS OBLIDEU DE PUJAR AQUELLS JAILGAMES VELLS I PANXUTS DE MS-DOS A GITHUB, QUE SI NO ES PERDRAN!! QUIN SERÀ EL PRÒXIM PROJECTE DE JAILDOC? SERÀ UN PROJECTE DE MERDA? AI MARE... NI IDEA, PERÒ ACÍ PODEU SABER-HO SI RESOLEU EL DILEMA DEL JAILDOCTOR... VOS ATREVIU O QUÈ? VAAAAA!!!"
|
||||
menu:
|
||||
play: "1. JUGAR"
|
||||
keyboard: "2. REDEFINIR TECLAT"
|
||||
keyboard: "2. REDEFINIR TECLES"
|
||||
joystick: "3. REDEFINIR MANDO"
|
||||
projects: "4. PROJECTES"
|
||||
keys:
|
||||
@@ -42,11 +42,11 @@ ending:
|
||||
t6: "FOREN ALLIBERATS"
|
||||
t7: "HI HAVIA FINS I TOT BARRULLS"
|
||||
t8: "I BEGGINERS ENTRE LA GENT"
|
||||
t9: "BRY ESTAVA FENT LLAGRIMETA..."
|
||||
t9: "BRY ESTAVA PLORANT..."
|
||||
t10: "PERÒ DE SOBTE ALGUNA COSA"
|
||||
t11: "LI VA CRIDAR L'ATENCIÓ"
|
||||
t12: "UN MUNT DE FERRALLA!"
|
||||
t13: "PLE D'ANDROMINES QUE NI ANAVEN!!"
|
||||
t13: "PLE DE TRASTOS QUE NI ANAVEN!!"
|
||||
t14: "I ALESHORES,"
|
||||
t15: "QUARANTA PROJECTES NOUS"
|
||||
t16: "VAN NÀIXER..."
|
||||
@@ -71,7 +71,7 @@ credits:
|
||||
f9: "F9 VORA DE LA PANTALLA"
|
||||
author: "UN JOC DE JAILDESIGNER"
|
||||
date: "FET A L'ESTIU/TARDOR DEL 2022"
|
||||
love: "M'ENCANTEN ELS JAILGAMES!"
|
||||
love: "I LOVE JAILGAMES! "
|
||||
|
||||
achievements:
|
||||
header: "ASSOLIMENT DESBLOQUEJAT!"
|
||||
@@ -108,12 +108,15 @@ ui:
|
||||
fullscreen_enabled: "PANTALLA COMPLETA ACTIVADA"
|
||||
fullscreen_disabled: "PANTALLA COMPLETA DESACTIVADA"
|
||||
window_zoom: "ZOOM FINESTRA x"
|
||||
postfx_enabled: "POSTFX ACTIVAT"
|
||||
postfx_disabled: "POSTFX DESACTIVAT"
|
||||
shaders_enabled: "SHADERS ACTIVATS"
|
||||
shaders_disabled: "SHADERS DESACTIVATS"
|
||||
shader: "SHADER"
|
||||
postfx: "POSTFX"
|
||||
crtpi: "CRTPI"
|
||||
supersampling_enabled: "SUPERMOSTREIG ACTIVAT"
|
||||
supersampling_disabled: "SUPERMOSTREIG DESACTIVAT"
|
||||
palette: "PALETA"
|
||||
palette_sort: "ORDENACIÓ PALETA"
|
||||
integer_scale_enabled: "ESCALAT SENCER ACTIVAT"
|
||||
integer_scale_disabled: "ESCALAT SENCER DESACTIVAT"
|
||||
vsync_enabled: "V-SYNC ACTIVAT"
|
||||
@@ -123,6 +126,8 @@ scoreboard:
|
||||
items: "TRESORS PILLATS "
|
||||
time: " HORA "
|
||||
rooms: "SALES"
|
||||
cheat_infinite_lives: "vides inf"
|
||||
cheat_invincibility: "inv"
|
||||
|
||||
game:
|
||||
music_enabled: "MÚSICA ACTIVADA"
|
||||
@@ -136,3 +141,5 @@ game:
|
||||
cheat_jail_open: "JAIL OBERTA"
|
||||
debug_enabled: "DEBUG ACTIVAT"
|
||||
debug_disabled: "DEBUG DESACTIVAT"
|
||||
editor_enabled: "EDITOR ACTIVAT"
|
||||
editor_disabled: "EDITOR DESACTIVAT"
|
||||
|
||||
@@ -108,12 +108,15 @@ ui:
|
||||
fullscreen_enabled: "FULLSCREEN ENABLED"
|
||||
fullscreen_disabled: "FULLSCREEN DISABLED"
|
||||
window_zoom: "WINDOW ZOOM x"
|
||||
postfx_enabled: "POSTFX ENABLED"
|
||||
postfx_disabled: "POSTFX DISABLED"
|
||||
shaders_enabled: "SHADERS ON"
|
||||
shaders_disabled: "SHADERS OFF"
|
||||
shader: "SHADER"
|
||||
postfx: "POSTFX"
|
||||
crtpi: "CRTPI"
|
||||
supersampling_enabled: "SUPERSAMPLING ON"
|
||||
supersampling_disabled: "SUPERSAMPLING OFF"
|
||||
palette: "PALETTE"
|
||||
palette_sort: "PALETTE SORT"
|
||||
integer_scale_enabled: "INTEGER SCALE ENABLED"
|
||||
integer_scale_disabled: "INTEGER SCALE DISABLED"
|
||||
vsync_enabled: "V-SYNC ENABLED"
|
||||
@@ -123,6 +126,8 @@ scoreboard:
|
||||
items: "ITEMS COLLECTED "
|
||||
time: " TIME "
|
||||
rooms: "ROOMS"
|
||||
cheat_infinite_lives: "inf lives"
|
||||
cheat_invincibility: "inv"
|
||||
|
||||
game:
|
||||
music_enabled: "MUSIC ENABLED"
|
||||
@@ -136,3 +141,5 @@ game:
|
||||
cheat_jail_open: "JAIL IS OPEN"
|
||||
debug_enabled: "DEBUG ENABLED"
|
||||
debug_disabled: "DEBUG DISABLED"
|
||||
editor_enabled: "EDITOR ENABLED"
|
||||
editor_disabled: "EDITOR DISABLED"
|
||||
|
||||
19
data/palette/aged-terracotta.pal
Normal file
19
data/palette/aged-terracotta.pal
Normal 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
|
||||
19
data/palette/antiquity.pal
Normal file
19
data/palette/antiquity.pal
Normal 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
|
||||
19
data/palette/bubblegum.pal
Normal file
19
data/palette/bubblegum.pal
Normal file
@@ -0,0 +1,19 @@
|
||||
JASC-PAL
|
||||
0100
|
||||
16
|
||||
22 23 26
|
||||
127 6 34
|
||||
214 36 17
|
||||
255 132 38
|
||||
255 209 0
|
||||
250 253 255
|
||||
255 128 164
|
||||
255 38 116
|
||||
148 33 106
|
||||
67 0 103
|
||||
35 73 117
|
||||
104 174 212
|
||||
191 255 60
|
||||
16 210 117
|
||||
0 120 153
|
||||
0 40 89
|
||||
19
data/palette/darkseed.pal
Normal file
19
data/palette/darkseed.pal
Normal 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
19
data/palette/h16da.pal
Normal file
@@ -0,0 +1,19 @@
|
||||
JASC-PAL
|
||||
0100
|
||||
16
|
||||
226 217 228
|
||||
108 154 154
|
||||
82 103 93
|
||||
55 64 59
|
||||
243 200 147
|
||||
229 152 125
|
||||
203 94 92
|
||||
114 51 76
|
||||
192 165 169
|
||||
191 125 133
|
||||
128 77 83
|
||||
64 48 56
|
||||
124 143 178
|
||||
76 82 116
|
||||
46 51 77
|
||||
31 32 37
|
||||
19
data/palette/psychic-fibre.pal
Normal file
19
data/palette/psychic-fibre.pal
Normal 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
|
||||
19
data/palette/shido-cyberneon.pal
Normal file
19
data/palette/shido-cyberneon.pal
Normal file
@@ -0,0 +1,19 @@
|
||||
JASC-PAL
|
||||
0100
|
||||
16
|
||||
0 3 60
|
||||
0 82 96
|
||||
0 157 74
|
||||
10 255 82
|
||||
0 56 132
|
||||
0 138 197
|
||||
0 247 255
|
||||
255 92 255
|
||||
172 41 206
|
||||
96 0 136
|
||||
177 5 133
|
||||
255 0 78
|
||||
42 46 121
|
||||
78 110 168
|
||||
173 212 250
|
||||
255 255 255
|
||||
@@ -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
|
||||
@@ -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
|
||||
177 62 83
|
||||
56 183 100
|
||||
167 240 112
|
||||
65 166 246
|
||||
115 239 247
|
||||
239 125 87
|
||||
255 205 117
|
||||
148 176 194
|
||||
167 240 112
|
||||
56 183 100
|
||||
37 113 121
|
||||
41 54 111
|
||||
59 93 201
|
||||
65 166 246
|
||||
115 239 247
|
||||
244 244 244
|
||||
148 176 194
|
||||
86 108 134
|
||||
51 60 87
|
||||
19
data/palette/vanilla-milkshake.pal
Normal file
19
data/palette/vanilla-milkshake.pal
Normal 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
|
||||
19
data/palette/winds-seed-pc98.pal
Normal file
19
data/palette/winds-seed-pc98.pal
Normal file
@@ -0,0 +1,19 @@
|
||||
JASC-PAL
|
||||
0100
|
||||
16
|
||||
0 0 0
|
||||
50 1 50
|
||||
84 1 103
|
||||
118 50 118
|
||||
50 50 171
|
||||
35 103 239
|
||||
152 186 220
|
||||
253 253 253
|
||||
1 186 152
|
||||
1 137 84
|
||||
254 239 69
|
||||
119 70 2
|
||||
186 118 84
|
||||
254 1 69
|
||||
239 152 152
|
||||
254 205 205
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# BIG JUMP
|
||||
room:
|
||||
name_en: "BIG JUMP"
|
||||
name_ca: "GRAN SALT"
|
||||
name_ca: "EL GRAN BOT"
|
||||
bgColor: black
|
||||
border: red
|
||||
tileSetFile: standard.gif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# WELCOME TO MY ABBEY
|
||||
room:
|
||||
name_en: "WELCOME TO MY ABBEY"
|
||||
name_ca: "BENVINGUT A LA MEVA ABADIA"
|
||||
name_ca: "BENVINGUT A LA MEUA ABADIA"
|
||||
bgColor: blue
|
||||
border: yellow
|
||||
tileSetFile: standard.gif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# TREE TOP
|
||||
room:
|
||||
name_en: "TREE TOP"
|
||||
name_ca: "AMUNT DE L'ARBRE"
|
||||
name_ca: "DALT DE L'ARBRE"
|
||||
bgColor: bright_black
|
||||
border: blue
|
||||
tileSetFile: standard.gif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# LAZY ROOM
|
||||
room:
|
||||
name_en: "LAZY ROOM"
|
||||
name_ca: "SALA DE LA PEREA"
|
||||
name_ca: "LA SALA GOSSA"
|
||||
bgColor: black
|
||||
border: blue
|
||||
tileSetFile: standard.gif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# KILLING SPREE
|
||||
room:
|
||||
name_en: "KILLING SPREE"
|
||||
name_ca: "MATANCA INDISCRIMINADA"
|
||||
name_ca: "MATANÇA INDISCRIMINADA"
|
||||
bgColor: black
|
||||
border: blue
|
||||
tileSetFile: standard.gif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# NOW THIS IS THE BATCAVE!
|
||||
room:
|
||||
name_en: "NOW THIS IS THE BATCAVE!"
|
||||
name_ca: "AQUESTA SI QUE ES LA BATCOVA!"
|
||||
name_ca: "ESTA SI QUE ES LA BATCOVA!"
|
||||
bgColor: black
|
||||
border: black
|
||||
tileSetFile: standard.gif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# THE FRIDGE
|
||||
room:
|
||||
name_en: "THE FRIDGE"
|
||||
name_ca: "LA NEVERA"
|
||||
name_ca: "EL FRIGO"
|
||||
bgColor: blue
|
||||
border: blue
|
||||
tileSetFile: standard.gif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# I DID NOT COPY THIS ONE
|
||||
room:
|
||||
name_en: "I DID NOT COPY THIS ONE"
|
||||
name_ca: "ESTA NO LA HE COPIADA, NO"
|
||||
name_ca: "ESTA NO LA HE COPIADA"
|
||||
bgColor: black
|
||||
border: magenta
|
||||
tileSetFile: standard.gif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# THIS CAN'T BE THE BATCAVE
|
||||
room:
|
||||
name_en: "THIS CAN'T BE THE BATCAVE"
|
||||
name_ca: "AQUESTA NO POT SER LA BATCOVA"
|
||||
name_ca: "ESTA NO POT SER LA BATCOVA"
|
||||
bgColor: black
|
||||
border: cyan
|
||||
tileSetFile: standard.gif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ENTER PAKU SIMBEL
|
||||
room:
|
||||
name_en: "ENTER PAKU SIMBEL"
|
||||
name_ca: "ENTRANT A PAKU SIMBEL"
|
||||
name_ca: "ACCEDINT A PAKU SIMBEL"
|
||||
bgColor: bright_black
|
||||
border: yellow
|
||||
tileSetFile: standard.gif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# } WE ALL LOVE JAILGAMES }
|
||||
room:
|
||||
name_en: "} WE ALL LOVE JAILGAMES }"
|
||||
name_ca: "} AMOR PELS JAILGAMES }"
|
||||
name_en: "ä WE ALL LOVE JAILGAMES ä"
|
||||
name_ca: "ä AMOR PELS JAILGAMES ä"
|
||||
bgColor: black
|
||||
border: bright_black
|
||||
tileSetFile: standard.gif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# PREVENT THE CRISIS
|
||||
room:
|
||||
name_en: "PREVENT THE CRISIS"
|
||||
name_ca: "PREVEU LA CRISI"
|
||||
name_ca: "EVITA LA CRISI"
|
||||
bgColor: black
|
||||
border: bright_magenta
|
||||
tileSetFile: standard.gif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# AROUND WITH ME
|
||||
room:
|
||||
name_en: "AROUND WITH ME"
|
||||
name_ca: "VOLTA AMB MI"
|
||||
name_ca: "AROUNDA AMB MI"
|
||||
bgColor: black
|
||||
border: blue
|
||||
tileSetFile: standard.gif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# SANDWITCH AND COUNTER
|
||||
room:
|
||||
name_en: "SANDWITCH AND COUNTER"
|
||||
name_ca: "SANDVITX I COUNTER S."
|
||||
name_ca: "SANDVITX I COUNTER STRIKE"
|
||||
bgColor: black
|
||||
border: cyan
|
||||
tileSetFile: standard.gif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# FEEL THE HEAT
|
||||
room:
|
||||
name_en: "FEEL THE HEAT"
|
||||
name_ca: "NOTA LA CALOR"
|
||||
name_ca: "NOTA EL CALORET"
|
||||
bgColor: bright_black
|
||||
border: bright_yellow
|
||||
tileSetFile: standard.gif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# WE NEED A ROBOT
|
||||
room:
|
||||
name_en: "WE NEED A ROBOT"
|
||||
name_ca: "NECESSITEM UN ROBOT"
|
||||
name_en: "WE NEED A JAILROBOT"
|
||||
name_ca: "NECESSITEM UN JAILROBOT"
|
||||
bgColor: black
|
||||
border: red
|
||||
tileSetFile: standard.gif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# STORED JAILGAMES
|
||||
room:
|
||||
name_en: "STORED JAILGAMES"
|
||||
name_ca: "JAILGAMES EMMAGATZEMATS"
|
||||
name_ca: "EL MAGATZEM DE JAILGAMES"
|
||||
bgColor: black
|
||||
border: blue
|
||||
tileSetFile: standard.gif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# P.A.C.O. WORKSHOP
|
||||
room:
|
||||
name_en: "P.A.C.O. WORKSHOP"
|
||||
name_ca: "TALLER DE P.A.C.O."
|
||||
name_ca: "EL TALLER DE P.A.C.O."
|
||||
bgColor: black
|
||||
border: yellow
|
||||
tileSetFile: standard.gif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# CHIRPING
|
||||
room:
|
||||
name_en: "CHIRPING DEVELOPMENT"
|
||||
name_ca: "DESENVOLUPANT CHIRPING"
|
||||
name_en: "CHIRPING"
|
||||
name_ca: "CHIRPING"
|
||||
bgColor: black
|
||||
border: magenta
|
||||
tileSetFile: standard.gif
|
||||
|
||||
157
data/shaders/crtpi.glsl
Normal file
157
data/shaders/crtpi.glsl
Normal file
@@ -0,0 +1,157 @@
|
||||
#version 330 core
|
||||
|
||||
// Configuración
|
||||
#define SCANLINES
|
||||
#define MULTISAMPLE
|
||||
#define GAMMA
|
||||
//#define FAKE_GAMMA
|
||||
//#define CURVATURE
|
||||
//#define SHARPER
|
||||
#define MASK_TYPE 2
|
||||
|
||||
#define CURVATURE_X 0.05
|
||||
#define CURVATURE_Y 0.1
|
||||
#define MASK_BRIGHTNESS 0.80
|
||||
#define SCANLINE_WEIGHT 6.0
|
||||
#define SCANLINE_GAP_BRIGHTNESS 0.12
|
||||
#define BLOOM_FACTOR 3.5
|
||||
#define INPUT_GAMMA 2.4
|
||||
#define OUTPUT_GAMMA 2.2
|
||||
|
||||
// Inputs desde vertex shader
|
||||
in vec2 vTexCoord;
|
||||
in float vFilterWidth;
|
||||
#if defined(CURVATURE)
|
||||
in vec2 vScreenScale;
|
||||
#endif
|
||||
|
||||
// Output
|
||||
out vec4 FragColor;
|
||||
|
||||
// Uniforms
|
||||
uniform sampler2D Texture;
|
||||
uniform vec2 TextureSize;
|
||||
|
||||
#if defined(CURVATURE)
|
||||
vec2 Distort(vec2 coord)
|
||||
{
|
||||
vec2 CURVATURE_DISTORTION = vec2(CURVATURE_X, CURVATURE_Y);
|
||||
vec2 barrelScale = 1.0 - (0.23 * CURVATURE_DISTORTION);
|
||||
coord *= vScreenScale;
|
||||
coord -= vec2(0.5);
|
||||
float rsq = coord.x * coord.x + coord.y * coord.y;
|
||||
coord += coord * (CURVATURE_DISTORTION * rsq);
|
||||
coord *= barrelScale;
|
||||
if (abs(coord.x) >= 0.5 || abs(coord.y) >= 0.5)
|
||||
coord = vec2(-1.0);
|
||||
else
|
||||
{
|
||||
coord += vec2(0.5);
|
||||
coord /= vScreenScale;
|
||||
}
|
||||
return coord;
|
||||
}
|
||||
#endif
|
||||
|
||||
float CalcScanLineWeight(float dist)
|
||||
{
|
||||
return max(1.0 - dist * dist * SCANLINE_WEIGHT, SCANLINE_GAP_BRIGHTNESS);
|
||||
}
|
||||
|
||||
float CalcScanLine(float dy)
|
||||
{
|
||||
float scanLineWeight = CalcScanLineWeight(dy);
|
||||
#if defined(MULTISAMPLE)
|
||||
scanLineWeight += CalcScanLineWeight(dy - vFilterWidth);
|
||||
scanLineWeight += CalcScanLineWeight(dy + vFilterWidth);
|
||||
scanLineWeight *= 0.3333333;
|
||||
#endif
|
||||
return scanLineWeight;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
#if defined(CURVATURE)
|
||||
vec2 texcoord = Distort(vTexCoord);
|
||||
if (texcoord.x < 0.0) {
|
||||
FragColor = vec4(0.0);
|
||||
return;
|
||||
}
|
||||
#else
|
||||
vec2 texcoord = vTexCoord;
|
||||
#endif
|
||||
|
||||
vec2 texcoordInPixels = texcoord * TextureSize;
|
||||
|
||||
#if defined(SHARPER)
|
||||
vec2 tempCoord = floor(texcoordInPixels) + 0.5;
|
||||
vec2 coord = tempCoord / TextureSize;
|
||||
vec2 deltas = texcoordInPixels - tempCoord;
|
||||
float scanLineWeight = CalcScanLine(deltas.y);
|
||||
vec2 signs = sign(deltas);
|
||||
deltas.x *= 2.0;
|
||||
deltas = deltas * deltas;
|
||||
deltas.y = deltas.y * deltas.y;
|
||||
deltas.x *= 0.5;
|
||||
deltas.y *= 8.0;
|
||||
deltas /= TextureSize;
|
||||
deltas *= signs;
|
||||
vec2 tc = coord + deltas;
|
||||
#else
|
||||
float tempY = floor(texcoordInPixels.y) + 0.5;
|
||||
float yCoord = tempY / TextureSize.y;
|
||||
float dy = texcoordInPixels.y - tempY;
|
||||
float scanLineWeight = CalcScanLine(dy);
|
||||
float signY = sign(dy);
|
||||
dy = dy * dy;
|
||||
dy = dy * dy;
|
||||
dy *= 8.0;
|
||||
dy /= TextureSize.y;
|
||||
dy *= signY;
|
||||
vec2 tc = vec2(texcoord.x, yCoord + dy);
|
||||
#endif
|
||||
|
||||
vec3 colour = texture(Texture, tc).rgb;
|
||||
|
||||
#if defined(SCANLINES)
|
||||
#if defined(GAMMA)
|
||||
#if defined(FAKE_GAMMA)
|
||||
colour = colour * colour;
|
||||
#else
|
||||
colour = pow(colour, vec3(INPUT_GAMMA));
|
||||
#endif
|
||||
#endif
|
||||
scanLineWeight *= BLOOM_FACTOR;
|
||||
colour *= scanLineWeight;
|
||||
|
||||
#if defined(GAMMA)
|
||||
#if defined(FAKE_GAMMA)
|
||||
colour = sqrt(colour);
|
||||
#else
|
||||
colour = pow(colour, vec3(1.0 / OUTPUT_GAMMA));
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if MASK_TYPE == 0
|
||||
FragColor = vec4(colour, 1.0);
|
||||
#elif MASK_TYPE == 1
|
||||
float whichMask = fract(gl_FragCoord.x * 0.5);
|
||||
vec3 mask;
|
||||
if (whichMask < 0.5)
|
||||
mask = vec3(MASK_BRIGHTNESS, 1.0, MASK_BRIGHTNESS);
|
||||
else
|
||||
mask = vec3(1.0, MASK_BRIGHTNESS, 1.0);
|
||||
FragColor = vec4(colour * mask, 1.0);
|
||||
#elif MASK_TYPE == 2
|
||||
float whichMask = fract(gl_FragCoord.x * 0.3333333);
|
||||
vec3 mask = vec3(MASK_BRIGHTNESS, MASK_BRIGHTNESS, MASK_BRIGHTNESS);
|
||||
if (whichMask < 0.3333333)
|
||||
mask.x = 1.0;
|
||||
else if (whichMask < 0.6666666)
|
||||
mask.y = 1.0;
|
||||
else
|
||||
mask.z = 1.0;
|
||||
FragColor = vec4(colour * mask, 1.0);
|
||||
#endif
|
||||
}
|
||||
48
data/shaders/crtpi.vert
Normal file
48
data/shaders/crtpi.vert
Normal file
@@ -0,0 +1,48 @@
|
||||
#version 330 core
|
||||
|
||||
// Configuración
|
||||
#define SCANLINES
|
||||
#define MULTISAMPLE
|
||||
#define GAMMA
|
||||
//#define FAKE_GAMMA
|
||||
//#define CURVATURE
|
||||
//#define SHARPER
|
||||
#define MASK_TYPE 2
|
||||
|
||||
#define CURVATURE_X 0.05
|
||||
#define CURVATURE_Y 0.1
|
||||
#define MASK_BRIGHTNESS 0.80
|
||||
#define SCANLINE_WEIGHT 6.0
|
||||
#define SCANLINE_GAP_BRIGHTNESS 0.12
|
||||
#define BLOOM_FACTOR 3.5
|
||||
#define INPUT_GAMMA 2.4
|
||||
#define OUTPUT_GAMMA 2.2
|
||||
|
||||
// Inputs (desde VAO)
|
||||
layout(location = 0) in vec2 aPosition;
|
||||
layout(location = 1) in vec2 aTexCoord;
|
||||
|
||||
// Outputs al fragment shader
|
||||
out vec2 vTexCoord;
|
||||
out float vFilterWidth;
|
||||
#if defined(CURVATURE)
|
||||
out vec2 vScreenScale;
|
||||
#endif
|
||||
|
||||
// Uniforms
|
||||
uniform vec2 TextureSize;
|
||||
|
||||
void main()
|
||||
{
|
||||
#if defined(CURVATURE)
|
||||
vScreenScale = vec2(1.0, 1.0);
|
||||
#endif
|
||||
// Calcula filterWidth dinámicamente basándose en la altura de la textura
|
||||
vFilterWidth = (768.0 / TextureSize.y) / 3.0;
|
||||
|
||||
// Pasar coordenadas de textura (invertir Y para SDL)
|
||||
vTexCoord = vec2(aTexCoord.x, 1.0 - aTexCoord.y) * 1.0001;
|
||||
|
||||
// Posición del vértice (ya en espacio de clip [-1, 1])
|
||||
gl_Position = vec4(aPosition, 0.0, 1.0);
|
||||
}
|
||||
152
data/shaders/crtpi_frag.glsl
Normal file
152
data/shaders/crtpi_frag.glsl
Normal file
@@ -0,0 +1,152 @@
|
||||
#version 450
|
||||
|
||||
// Vulkan GLSL fragment shader — CRT-Pi PostFX
|
||||
// Algoritmo de scanlines continuas con pesos gaussianos, bloom y máscara de fósforo.
|
||||
// Basado en el shader CRT-Pi original (GLSL 3.3), portado a GLSL 4.50 con parámetros uniformes.
|
||||
//
|
||||
// Compile: glslc -fshader-stage=frag --target-env=vulkan1.0 crtpi_frag.glsl -o crtpi_frag.spv
|
||||
// xxd -i crtpi_frag.spv > ../../source/core/rendering/sdl3gpu/crtpi_frag_spv.h
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
layout(set = 2, binding = 0) uniform sampler2D Texture;
|
||||
|
||||
layout(set = 3, binding = 0) uniform CrtPiBlock {
|
||||
// vec4 #0
|
||||
float scanline_weight; // Ajuste gaussiano de scanlines (default 6.0)
|
||||
float scanline_gap_brightness; // Brillo mínimo entre scanlines (default 0.12)
|
||||
float bloom_factor; // Factor de brillo en zonas iluminadas (default 3.5)
|
||||
float input_gamma; // Gamma de entrada — linealización (default 2.4)
|
||||
// vec4 #1
|
||||
float output_gamma; // Gamma de salida — codificación (default 2.2)
|
||||
float mask_brightness; // Brillo sub-píxeles de la máscara (default 0.80)
|
||||
float curvature_x; // Distorsión barrel eje X (default 0.05)
|
||||
float curvature_y; // Distorsión barrel eje Y (default 0.10)
|
||||
// vec4 #2
|
||||
int mask_type; // 0=ninguna, 1=verde/magenta, 2=RGB fósforo
|
||||
int enable_scanlines; // 0 = off, 1 = on
|
||||
int enable_multisample; // 0 = off, 1 = on (antialiasing analítico de scanlines)
|
||||
int enable_gamma; // 0 = off, 1 = on
|
||||
// vec4 #3
|
||||
int enable_curvature; // 0 = off, 1 = on
|
||||
int enable_sharper; // 0 = off, 1 = on
|
||||
float texture_width; // Ancho del canvas lógico en píxeles
|
||||
float texture_height; // Alto del canvas lógico en píxeles
|
||||
} u;
|
||||
|
||||
// Distorsión barrel CRT
|
||||
vec2 distort(vec2 coord, vec2 screen_scale) {
|
||||
vec2 curvature = vec2(u.curvature_x, u.curvature_y);
|
||||
vec2 barrel_scale = 1.0 - (0.23 * curvature);
|
||||
coord *= screen_scale;
|
||||
coord -= vec2(0.5);
|
||||
float rsq = coord.x * coord.x + coord.y * coord.y;
|
||||
coord += coord * (curvature * rsq);
|
||||
coord *= barrel_scale;
|
||||
if (abs(coord.x) >= 0.5 || abs(coord.y) >= 0.5) {
|
||||
return vec2(-1.0); // fuera de pantalla
|
||||
}
|
||||
coord += vec2(0.5);
|
||||
coord /= screen_scale;
|
||||
return coord;
|
||||
}
|
||||
|
||||
float calcScanLineWeight(float dist) {
|
||||
return max(1.0 - dist * dist * u.scanline_weight, u.scanline_gap_brightness);
|
||||
}
|
||||
|
||||
float calcScanLine(float dy, float filter_width) {
|
||||
float weight = calcScanLineWeight(dy);
|
||||
if (u.enable_multisample != 0) {
|
||||
weight += calcScanLineWeight(dy - filter_width);
|
||||
weight += calcScanLineWeight(dy + filter_width);
|
||||
weight *= 0.3333333;
|
||||
}
|
||||
return weight;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 tex_size = vec2(u.texture_width, u.texture_height);
|
||||
|
||||
// filterWidth: equivalente al original (768.0 / TextureSize.y) / 3.0
|
||||
float filter_width = (768.0 / u.texture_height) / 3.0;
|
||||
|
||||
vec2 texcoord = v_uv;
|
||||
|
||||
// Curvatura barrel opcional
|
||||
if (u.enable_curvature != 0) {
|
||||
texcoord = distort(texcoord, vec2(1.0, 1.0));
|
||||
if (texcoord.x < 0.0) {
|
||||
out_color = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
vec2 texcoord_in_pixels = texcoord * tex_size;
|
||||
vec2 tc;
|
||||
float scan_line_weight;
|
||||
|
||||
if (u.enable_sharper != 0) {
|
||||
// Modo SHARPER: filtrado bicúbico-like con subpixel sharpen
|
||||
vec2 temp_coord = floor(texcoord_in_pixels) + 0.5;
|
||||
tc = temp_coord / tex_size;
|
||||
vec2 deltas = texcoord_in_pixels - temp_coord;
|
||||
scan_line_weight = calcScanLine(deltas.y, filter_width);
|
||||
vec2 signs = sign(deltas);
|
||||
deltas.x *= 2.0;
|
||||
deltas = deltas * deltas;
|
||||
deltas.y = deltas.y * deltas.y;
|
||||
deltas.x *= 0.5;
|
||||
deltas.y *= 8.0;
|
||||
deltas /= tex_size;
|
||||
deltas *= signs;
|
||||
tc = tc + deltas;
|
||||
} else {
|
||||
// Modo estándar
|
||||
float temp_y = floor(texcoord_in_pixels.y) + 0.5;
|
||||
float y_coord = temp_y / tex_size.y;
|
||||
float dy = texcoord_in_pixels.y - temp_y;
|
||||
scan_line_weight = calcScanLine(dy, filter_width);
|
||||
float sign_y = sign(dy);
|
||||
dy = dy * dy;
|
||||
dy = dy * dy;
|
||||
dy *= 8.0;
|
||||
dy /= tex_size.y;
|
||||
dy *= sign_y;
|
||||
tc = vec2(texcoord.x, y_coord + dy);
|
||||
}
|
||||
|
||||
vec3 colour = texture(Texture, tc).rgb;
|
||||
|
||||
if (u.enable_scanlines != 0) {
|
||||
if (u.enable_gamma != 0) {
|
||||
colour = pow(colour, vec3(u.input_gamma));
|
||||
}
|
||||
colour *= scan_line_weight * u.bloom_factor;
|
||||
if (u.enable_gamma != 0) {
|
||||
colour = pow(colour, vec3(1.0 / u.output_gamma));
|
||||
}
|
||||
}
|
||||
|
||||
// Máscara de fósforo
|
||||
if (u.mask_type == 1) {
|
||||
float which_mask = fract(gl_FragCoord.x * 0.5);
|
||||
vec3 mask = (which_mask < 0.5)
|
||||
? vec3(u.mask_brightness, 1.0, u.mask_brightness)
|
||||
: vec3(1.0, u.mask_brightness, 1.0);
|
||||
colour *= mask;
|
||||
} else if (u.mask_type == 2) {
|
||||
float which_mask = fract(gl_FragCoord.x * 0.3333333);
|
||||
vec3 mask = vec3(u.mask_brightness);
|
||||
if (which_mask < 0.3333333)
|
||||
mask.x = 1.0;
|
||||
else if (which_mask < 0.6666666)
|
||||
mask.y = 1.0;
|
||||
else
|
||||
mask.z = 1.0;
|
||||
colour *= mask;
|
||||
}
|
||||
|
||||
out_color = vec4(colour, 1.0);
|
||||
}
|
||||
BIN
data/shaders/crtpi_frag.spv
Normal file
BIN
data/shaders/crtpi_frag.spv
Normal file
Binary file not shown.
48
data/shaders/downscale.frag
Normal file
48
data/shaders/downscale.frag
Normal file
@@ -0,0 +1,48 @@
|
||||
#version 450
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
layout(set = 2, binding = 0) uniform sampler2D source;
|
||||
|
||||
layout(set = 3, binding = 0) uniform DownscaleUniforms {
|
||||
int algorithm; // 0 = Lanczos2 (ventana 2, ±2 taps), 1 = Lanczos3 (ventana 3, ±3 taps)
|
||||
float pad0;
|
||||
float pad1;
|
||||
float pad2;
|
||||
} u;
|
||||
|
||||
// Kernel Lanczos normalizado: sinc(t) * sinc(t/a) para |t| < a, 0 fuera.
|
||||
float lanczos(float t, float a) {
|
||||
t = abs(t);
|
||||
if (t < 0.0001) { return 1.0; }
|
||||
if (t >= a) { return 0.0; }
|
||||
const float PI = 3.14159265358979;
|
||||
float pt = PI * t;
|
||||
return (a * sin(pt) * sin(pt / a)) / (pt * pt);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 src_size = vec2(textureSize(source, 0));
|
||||
// Posición en coordenadas de texel (centros de texel en N+0.5)
|
||||
vec2 p = v_uv * src_size;
|
||||
vec2 p_floor = floor(p);
|
||||
|
||||
float a = (u.algorithm == 0) ? 2.0 : 3.0;
|
||||
int win = int(a);
|
||||
|
||||
vec4 color = vec4(0.0);
|
||||
float weight_sum = 0.0;
|
||||
|
||||
for (int j = -win; j <= win; j++) {
|
||||
for (int i = -win; i <= win; i++) {
|
||||
// Centro del texel (i,j) relativo a p_floor
|
||||
vec2 tap_center = p_floor + vec2(float(i), float(j)) + 0.5;
|
||||
vec2 offset = tap_center - p;
|
||||
float w = lanczos(offset.x, a) * lanczos(offset.y, a);
|
||||
color += texture(source, tap_center / src_size) * w;
|
||||
weight_sum += w;
|
||||
}
|
||||
}
|
||||
|
||||
out_color = (weight_sum > 0.0) ? (color / weight_sum) : vec4(0.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
@@ -23,9 +23,9 @@ layout(set = 3, binding = 0) uniform PostFXUniforms {
|
||||
float curvature;
|
||||
float bleeding;
|
||||
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
|
||||
float time; // seconds since SDL init (for future animated effects)
|
||||
float time; // seconds since SDL init
|
||||
float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS)
|
||||
float pad1; // padding — 48 bytes total (3 × 16)
|
||||
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz — 48 bytes total (3 × 16)
|
||||
} u;
|
||||
|
||||
// YCbCr helpers for NTSC bleeding
|
||||
@@ -85,8 +85,8 @@ void main() {
|
||||
colour = base;
|
||||
}
|
||||
|
||||
// Aberración cromática
|
||||
float ca = u.chroma_strength * 0.005;
|
||||
// Aberración cromática (drift animado con time para efecto NTSC real)
|
||||
float ca = u.chroma_strength * 0.005 * (1.0 + 0.15 * sin(u.time * 7.3));
|
||||
colour.r = texture(scene, uv + vec2(ca, 0.0)).r;
|
||||
colour.b = texture(scene, uv - vec2(ca, 0.0)).b;
|
||||
|
||||
@@ -96,17 +96,22 @@ void main() {
|
||||
colour = mix(colour, lin, u.gamma_strength);
|
||||
}
|
||||
|
||||
// Scanlines — 1 pixel físico oscuro por fila lógica.
|
||||
// Usa uv.y (independiente del offset de letterbox) con pixel_scale para
|
||||
// calcular la posición dentro de la fila en coordenadas físicas.
|
||||
// 3x: 1 dark + 2 bright. 4x: 1 dark + 3 bright.
|
||||
// bright=3.5×, dark floor=0.42 (mantiene aspecto CRT original).
|
||||
// Scanlines — proporción 2/3 brillantes + 1/3 oscuras por fila lógica.
|
||||
// Casos especiales: 1 subfila → sin efecto; 2 subfilas → 1+1 (50/50).
|
||||
// Constantes ajustables:
|
||||
const float SCAN_DARK_RATIO = 0.333; // fracción de subfilas oscuras (ps >= 3)
|
||||
const float SCAN_DARK_FLOOR = 0.42; // multiplicador de brillo de subfilas oscuras
|
||||
if (u.scanline_strength > 0.0) {
|
||||
float ps = max(1.0, round(u.pixel_scale));
|
||||
float frac_in_row = fract(uv.y * u.screen_height);
|
||||
float row_pos = floor(frac_in_row * ps);
|
||||
float is_dark = step(ps - 1.0, row_pos);
|
||||
float scan = mix(3.5, 0.42, is_dark);
|
||||
// bright_rows: cuántas subfilas son brillantes
|
||||
// ps==1 → ps (todo brillante → is_dark nunca se activa)
|
||||
// ps==2 → 1 brillante + 1 oscura
|
||||
// ps>=3 → floor(ps * (1 - DARK_RATIO)) brillantes
|
||||
float bright_rows = (ps < 2.0) ? ps : ((ps < 3.0) ? 1.0 : floor(ps * (1.0 - SCAN_DARK_RATIO)));
|
||||
float is_dark = step(bright_rows, row_pos);
|
||||
float scan = mix(1.0, SCAN_DARK_FLOOR, is_dark);
|
||||
colour *= mix(1.0, scan, u.scanline_strength);
|
||||
}
|
||||
|
||||
@@ -134,5 +139,11 @@ void main() {
|
||||
colour = mix(colour, colour * mask, u.mask_strength);
|
||||
}
|
||||
|
||||
// Parpadeo de fósforo CRT (~50 Hz)
|
||||
if (u.flicker > 0.0) {
|
||||
float flicker_wave = sin(u.time * 100.0) * 0.5 + 0.5;
|
||||
colour *= 1.0 - u.flicker * 0.04 * flicker_wave;
|
||||
}
|
||||
|
||||
out_color = vec4(colour, 1.0);
|
||||
}
|
||||
|
||||
BIN
data/shaders/postfx.frag.spv
Normal file
BIN
data/shaders/postfx.frag.spv
Normal file
Binary file not shown.
15
data/shaders/upscale.frag
Normal file
15
data/shaders/upscale.frag
Normal file
@@ -0,0 +1,15 @@
|
||||
#version 450
|
||||
|
||||
// Vulkan GLSL fragment shader — Nearest-neighbour upscale pass
|
||||
// Used as the first render pass when supersampling is active.
|
||||
// Compile: glslc upscale.frag -o upscale.frag.spv
|
||||
// xxd -i upscale.frag.spv > ../../source/core/rendering/sdl3gpu/upscale_frag_spv.h
|
||||
|
||||
layout(location = 0) in vec2 v_uv;
|
||||
layout(location = 0) out vec4 out_color;
|
||||
|
||||
layout(set = 2, binding = 0) uniform sampler2D scene;
|
||||
|
||||
void main() {
|
||||
out_color = texture(scene, v_uv);
|
||||
}
|
||||
BIN
data/shaders/upscale.frag.spv
Normal file
BIN
data/shaders/upscale.frag.spv
Normal file
Binary file not shown.
@@ -41,7 +41,7 @@ void Audio::update() {
|
||||
}
|
||||
|
||||
// Reproduce la música
|
||||
void Audio::playMusic(const std::string& name, const int loop) {
|
||||
void Audio::playMusic(const std::string& name, const int loop) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
bool new_loop = (loop != 0);
|
||||
|
||||
// Si ya está sonando exactamente la misma pista y mismo modo loop, no hacemos nada
|
||||
@@ -71,7 +71,7 @@ void Audio::playMusic(const std::string& name, const int loop) {
|
||||
}
|
||||
|
||||
// Pausa la música
|
||||
void Audio::pauseMusic() {
|
||||
void Audio::pauseMusic() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
if (music_enabled_ && music_.state == MusicState::PLAYING) {
|
||||
JA_PauseMusic();
|
||||
music_.state = MusicState::PAUSED;
|
||||
@@ -79,7 +79,7 @@ void Audio::pauseMusic() {
|
||||
}
|
||||
|
||||
// Continua la música pausada
|
||||
void Audio::resumeMusic() {
|
||||
void Audio::resumeMusic() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
if (music_enabled_ && music_.state == MusicState::PAUSED) {
|
||||
JA_ResumeMusic();
|
||||
music_.state = MusicState::PLAYING;
|
||||
@@ -87,7 +87,7 @@ void Audio::resumeMusic() {
|
||||
}
|
||||
|
||||
// Detiene la música
|
||||
void Audio::stopMusic() {
|
||||
void Audio::stopMusic() { // NOLINT(readability-make-member-function-const)
|
||||
if (music_enabled_) {
|
||||
JA_StopMusic();
|
||||
music_.state = MusicState::STOPPED;
|
||||
@@ -177,6 +177,18 @@ void Audio::initSDLAudio() {
|
||||
JA_Init(FREQUENCY, SDL_AUDIO_S16LE, 2);
|
||||
enable(Options::audio.enabled);
|
||||
|
||||
// Aplicar estado de música y sonido guardado en las opciones.
|
||||
// enable() ya aplica los volúmenes, pero no toca music_enabled_/sound_enabled_.
|
||||
// Si alguno está desactivado, hay que forzar el volumen a 0 en el backend.
|
||||
if (!Options::audio.music.enabled) {
|
||||
setMusicVolume(0.0F); // music_enabled_=true aún → llega a JA
|
||||
enableMusic(false);
|
||||
}
|
||||
if (!Options::audio.sound.enabled) {
|
||||
setSoundVolume(0.0F); // sound_enabled_=true aún → llega a JA
|
||||
enableSound(false);
|
||||
}
|
||||
|
||||
std::cout << "\n** AUDIO SYSTEM **\n";
|
||||
std::cout << "Audio system initialized successfully\n";
|
||||
}
|
||||
|
||||
@@ -7,9 +7,11 @@
|
||||
|
||||
#include "core/input/input.hpp" // Para Input, InputAction, Input::DO_NOT_ALLOW_REPEAT
|
||||
#include "core/locale/locale.hpp" // Para Locale
|
||||
#include "core/rendering/render_info.hpp" // Para RenderInfo
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "game/options.hpp" // Para Options, options, OptionsVideo, Section
|
||||
#include "game/scene_manager.hpp" // Para SceneManager
|
||||
#include "game/ui/console.hpp" // Para Console
|
||||
#include "game/ui/notifier.hpp" // Para Notifier, NotificationText
|
||||
#include "utils/utils.hpp" // Para stringInVector
|
||||
|
||||
@@ -24,7 +26,7 @@ namespace GlobalInputs {
|
||||
if (stringInVector(Notifier::get()->getCodes(), CODE)) {
|
||||
SceneManager::current = SceneManager::Scene::TITLE;
|
||||
} else {
|
||||
Notifier::get()->show({Locale::get()->get("ui.press_again_menu")}, Notifier::Style::DEFAULT, -1, true, CODE);
|
||||
Notifier::get()->show({Locale::get()->get("ui.press_again_menu")}, Notifier::Style::DEFAULT, -1, true, CODE); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -44,7 +46,7 @@ namespace GlobalInputs {
|
||||
if (stringInVector(Notifier::get()->getCodes(), CODE)) {
|
||||
SceneManager::current = SceneManager::Scene::QUIT;
|
||||
} else {
|
||||
Notifier::get()->show({Locale::get()->get("ui.press_again_exit")}, Notifier::Style::DEFAULT, -1, true, CODE);
|
||||
Notifier::get()->show({Locale::get()->get("ui.press_again_exit")}, Notifier::Style::DEFAULT, -1, true, CODE); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,66 +70,79 @@ namespace GlobalInputs {
|
||||
|
||||
void handleToggleBorder() {
|
||||
Screen::get()->toggleBorder();
|
||||
Notifier::get()->show({Locale::get()->get(Options::video.border.enabled ? "ui.border_enabled" : "ui.border_disabled")});
|
||||
Notifier::get()->show({Locale::get()->get(Options::video.border.enabled ? "ui.border_enabled" : "ui.border_disabled")}); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
|
||||
void handleToggleVideoMode() {
|
||||
Screen::get()->toggleVideoMode();
|
||||
Notifier::get()->show({Locale::get()->get(static_cast<int>(Options::video.fullscreen) == 0 ? "ui.fullscreen_disabled" : "ui.fullscreen_enabled")});
|
||||
Notifier::get()->show({Locale::get()->get(static_cast<int>(Options::video.fullscreen) == 0 ? "ui.fullscreen_disabled" : "ui.fullscreen_enabled")}); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
|
||||
void handleDecWindowZoom() {
|
||||
if (Screen::get()->decWindowZoom()) {
|
||||
Notifier::get()->show({Locale::get()->get("ui.window_zoom") + std::to_string(Options::window.zoom)});
|
||||
Notifier::get()->show({Locale::get()->get("ui.window_zoom") + std::to_string(Options::window.zoom)}); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
}
|
||||
|
||||
void handleIncWindowZoom() {
|
||||
if (Screen::get()->incWindowZoom()) {
|
||||
Notifier::get()->show({Locale::get()->get("ui.window_zoom") + std::to_string(Options::window.zoom)});
|
||||
Notifier::get()->show({Locale::get()->get("ui.window_zoom") + std::to_string(Options::window.zoom)}); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
}
|
||||
|
||||
void handleTogglePostFX() {
|
||||
Screen::get()->togglePostFX();
|
||||
Notifier::get()->show({Locale::get()->get(Options::video.postfx ? "ui.postfx_enabled" : "ui.postfx_disabled")});
|
||||
void handleToggleShaders() {
|
||||
Screen::get()->toggleShaders();
|
||||
Notifier::get()->show({Locale::get()->get(Options::video.shader.enabled ? "ui.shaders_enabled" : "ui.shaders_disabled")}); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
|
||||
void handleToggleSupersampling() {
|
||||
Screen::get()->toggleSupersampling();
|
||||
Notifier::get()->show({Locale::get()->get(Options::video.supersampling ? "ui.supersampling_enabled" : "ui.supersampling_disabled")});
|
||||
void handleNextShaderPreset() {
|
||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
if (!Options::crtpi_presets.empty()) {
|
||||
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") + " " + prettyName(Options::crtpi_presets[static_cast<size_t>(Options::video.shader.current_crtpi_preset)].name)}); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
|
||||
void handleNextPostFXPreset() {
|
||||
} 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});
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleNextShader() {
|
||||
Screen::get()->nextShader();
|
||||
Notifier::get()->show({Locale::get()->get("ui.shader") + " " + // NOLINT(readability-static-accessed-through-instance)
|
||||
(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});
|
||||
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});
|
||||
Notifier::get()->show({Locale::get()->get("ui.palette") + " " + toUpper(Screen::get()->getPalettePrettyName())}); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
|
||||
void handleNextPaletteSortMode() {
|
||||
Screen::get()->nextPaletteSortMode();
|
||||
Notifier::get()->show({Locale::get()->get("ui.palette_sort") + " " + toUpper(Screen::get()->getPaletteSortModeName())}); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
|
||||
void handleToggleIntegerScale() {
|
||||
Screen::get()->toggleIntegerScale();
|
||||
Screen::get()->setVideoMode(Options::video.fullscreen);
|
||||
Notifier::get()->show({Locale::get()->get(Options::video.integer_scale ? "ui.integer_scale_enabled" : "ui.integer_scale_disabled")});
|
||||
Notifier::get()->show({Locale::get()->get(Options::video.integer_scale ? "ui.integer_scale_enabled" : "ui.integer_scale_disabled")}); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
|
||||
void handleToggleVSync() {
|
||||
Screen::get()->toggleVSync();
|
||||
Notifier::get()->show({Locale::get()->get(Options::video.vertical_sync ? "ui.vsync_enabled" : "ui.vsync_disabled")});
|
||||
Notifier::get()->show({Locale::get()->get(Options::video.vertical_sync ? "ui.vsync_enabled" : "ui.vsync_disabled")}); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
|
||||
|
||||
// Detecta qué acción global ha sido presionada (si alguna)
|
||||
auto getPressedAction() -> InputAction {
|
||||
if (Input::get()->checkAction(InputAction::EXIT, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
@@ -150,20 +165,25 @@ namespace GlobalInputs {
|
||||
return InputAction::WINDOW_INC_ZOOM;
|
||||
}
|
||||
}
|
||||
if (Input::get()->checkAction(InputAction::TOGGLE_POSTFX, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
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.postfx && ((SDL_GetModState() & SDL_KMOD_SHIFT) != 0U)) {
|
||||
return InputAction::NEXT_POSTFX_PRESET; // Shift+F4
|
||||
if (Options::video.shader.enabled && ((SDL_GetModState() & SDL_KMOD_SHIFT) != 0U)) {
|
||||
return InputAction::NEXT_SHADER_PRESET; // Shift+F4
|
||||
}
|
||||
return InputAction::TOGGLE_SHADER; // F4
|
||||
}
|
||||
return InputAction::TOGGLE_POSTFX; // F4
|
||||
}
|
||||
if (Input::get()->checkAction(InputAction::NEXT_PALETTE, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
return InputAction::NEXT_PALETTE;
|
||||
if ((SDL_GetModState() & SDL_KMOD_CTRL) != 0U) {
|
||||
return InputAction::PREVIOUS_PALETTE; // Ctrl+F5
|
||||
}
|
||||
if (Input::get()->checkAction(InputAction::PREVIOUS_PALETTE, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
return InputAction::PREVIOUS_PALETTE;
|
||||
return InputAction::NEXT_PALETTE; // F5
|
||||
}
|
||||
if (Input::get()->checkAction(InputAction::NEXT_PALETTE_SORT, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
return InputAction::NEXT_PALETTE_SORT; // F6
|
||||
}
|
||||
if (Input::get()->checkAction(InputAction::TOGGLE_INTEGER_SCALE, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
return InputAction::TOGGLE_INTEGER_SCALE;
|
||||
@@ -171,11 +191,11 @@ namespace GlobalInputs {
|
||||
if (Input::get()->checkAction(InputAction::TOGGLE_VSYNC, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
return InputAction::TOGGLE_VSYNC;
|
||||
}
|
||||
if (Input::get()->checkAction(InputAction::TOGGLE_DEBUG, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
return InputAction::TOGGLE_DEBUG;
|
||||
if (Input::get()->checkAction(InputAction::TOGGLE_INFO, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
return InputAction::TOGGLE_INFO;
|
||||
}
|
||||
if (Input::get()->checkAction(InputAction::SHOW_DEBUG_INFO, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
return InputAction::SHOW_DEBUG_INFO;
|
||||
if (Input::get()->checkAction(InputAction::TOGGLE_CONSOLE, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
return InputAction::TOGGLE_CONSOLE;
|
||||
}
|
||||
return InputAction::NONE;
|
||||
}
|
||||
@@ -186,6 +206,16 @@ namespace GlobalInputs {
|
||||
|
||||
// Comprueba los inputs que se pueden introducir en cualquier sección del juego
|
||||
void handle() {
|
||||
const bool CONSOLE_ACTIVE = Console::get() != nullptr && Console::get()->isActive();
|
||||
|
||||
if (CONSOLE_ACTIVE) {
|
||||
// TAB/ESC cierran la consola en lugar de ejecutar sus acciones normales
|
||||
if (Input::get()->checkAction(InputAction::TOGGLE_CONSOLE, Input::DO_NOT_ALLOW_REPEAT) ||
|
||||
Input::get()->checkAction(InputAction::EXIT, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
Console::get()->toggle();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Salida de administrador en modo kiosko (Ctrl+Shift+Alt+Q)
|
||||
if (Options::kiosk.enabled) {
|
||||
SDL_Keymod mod = SDL_GetModState();
|
||||
@@ -195,10 +225,16 @@ namespace GlobalInputs {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Detectar qué acción global está siendo presionada
|
||||
InputAction action = getPressedAction();
|
||||
|
||||
// Con consola activa, ACCEPT (saltar sección) y EXIT están bloqueados
|
||||
if (CONSOLE_ACTIVE && (action == InputAction::ACCEPT || action == InputAction::EXIT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ejecutar el handler correspondiente usando switch statement
|
||||
switch (action) {
|
||||
case InputAction::EXIT:
|
||||
@@ -225,16 +261,16 @@ namespace GlobalInputs {
|
||||
handleIncWindowZoom();
|
||||
break;
|
||||
|
||||
case InputAction::TOGGLE_POSTFX:
|
||||
handleTogglePostFX();
|
||||
case InputAction::TOGGLE_SHADER:
|
||||
handleToggleShaders();
|
||||
break;
|
||||
|
||||
case InputAction::NEXT_POSTFX_PRESET:
|
||||
handleNextPostFXPreset();
|
||||
case InputAction::NEXT_SHADER_PRESET:
|
||||
handleNextShaderPreset();
|
||||
break;
|
||||
|
||||
case InputAction::TOGGLE_SUPERSAMPLING:
|
||||
handleToggleSupersampling();
|
||||
handleNextShader();
|
||||
break;
|
||||
|
||||
case InputAction::NEXT_PALETTE:
|
||||
@@ -245,6 +281,10 @@ namespace GlobalInputs {
|
||||
handlePreviousPalette();
|
||||
break;
|
||||
|
||||
case InputAction::NEXT_PALETTE_SORT:
|
||||
handleNextPaletteSortMode();
|
||||
break;
|
||||
|
||||
case InputAction::TOGGLE_INTEGER_SCALE:
|
||||
handleToggleIntegerScale();
|
||||
break;
|
||||
@@ -253,14 +293,13 @@ namespace GlobalInputs {
|
||||
handleToggleVSync();
|
||||
break;
|
||||
|
||||
#ifdef _DEBUG
|
||||
case InputAction::TOGGLE_DEBUG:
|
||||
Screen::get()->toggleFPS();
|
||||
case InputAction::TOGGLE_CONSOLE:
|
||||
if (Console::get() != nullptr) { Console::get()->toggle(); }
|
||||
break;
|
||||
|
||||
case InputAction::SHOW_DEBUG_INFO:
|
||||
case InputAction::TOGGLE_INFO:
|
||||
if (RenderInfo::get() != nullptr) { RenderInfo::get()->toggle(); }
|
||||
break;
|
||||
#endif
|
||||
|
||||
case InputAction::NONE:
|
||||
default:
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
Input* Input::instance = nullptr;
|
||||
|
||||
// Inicializa la instancia única del singleton
|
||||
void Input::init(const std::string& game_controller_db_path) {
|
||||
void Input::init(const std::string& game_controller_db_path) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
Input::instance = new Input(game_controller_db_path);
|
||||
}
|
||||
|
||||
@@ -43,15 +43,16 @@ Input::Input(std::string game_controller_db_path)
|
||||
{Action::WINDOW_DEC_ZOOM, KeyState{.scancode = SDL_SCANCODE_F1}},
|
||||
{Action::WINDOW_INC_ZOOM, KeyState{.scancode = SDL_SCANCODE_F2}},
|
||||
{Action::TOGGLE_FULLSCREEN, KeyState{.scancode = SDL_SCANCODE_F3}},
|
||||
{Action::TOGGLE_POSTFX, KeyState{.scancode = SDL_SCANCODE_F4}},
|
||||
{Action::TOGGLE_SHADER, KeyState{.scancode = SDL_SCANCODE_F4}},
|
||||
{Action::NEXT_PALETTE, KeyState{.scancode = SDL_SCANCODE_F5}},
|
||||
{Action::PREVIOUS_PALETTE, KeyState{.scancode = SDL_SCANCODE_F6}},
|
||||
{Action::NEXT_PALETTE_SORT, KeyState{.scancode = SDL_SCANCODE_F6}},
|
||||
{Action::TOGGLE_INTEGER_SCALE, KeyState{.scancode = SDL_SCANCODE_F7}},
|
||||
{Action::TOGGLE_MUSIC, KeyState{.scancode = SDL_SCANCODE_F8}},
|
||||
{Action::TOGGLE_IN_GAME_MUSIC, KeyState{.scancode = SDL_SCANCODE_F8}},
|
||||
{Action::TOGGLE_BORDER, KeyState{.scancode = SDL_SCANCODE_F9}},
|
||||
{Action::TOGGLE_VSYNC, KeyState{.scancode = SDL_SCANCODE_F10}},
|
||||
{Action::PAUSE, KeyState{.scancode = SDL_SCANCODE_F11}},
|
||||
{Action::TOGGLE_DEBUG, KeyState{.scancode = SDL_SCANCODE_F12}}};
|
||||
{Action::TOGGLE_INFO, KeyState{.scancode = SDL_SCANCODE_F12}},
|
||||
{Action::TOGGLE_CONSOLE, KeyState{.scancode = SDL_SCANCODE_GRAVE}}};
|
||||
|
||||
initSDLGamePad(); // Inicializa el subsistema SDL_INIT_GAMEPAD
|
||||
}
|
||||
@@ -69,7 +70,7 @@ void Input::applyKeyboardBindingsFromOptions() {
|
||||
}
|
||||
|
||||
// Aplica configuración de botones del gamepad desde Options al primer gamepad conectado
|
||||
void Input::applyGamepadBindingsFromOptions() {
|
||||
void Input::applyGamepadBindingsFromOptions() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
// Si no hay gamepads conectados, no hay nada que hacer
|
||||
if (gamepads_.empty()) {
|
||||
return;
|
||||
@@ -90,21 +91,21 @@ void Input::applyGamepadBindingsFromOptions() {
|
||||
}
|
||||
|
||||
// Asigna inputs a botones del mando
|
||||
void Input::bindGameControllerButton(const std::shared_ptr<Gamepad>& gamepad, Action action, SDL_GamepadButton button) {
|
||||
void Input::bindGameControllerButton(const std::shared_ptr<Gamepad>& gamepad, Action action, SDL_GamepadButton button) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
if (gamepad != nullptr) {
|
||||
gamepad->bindings[action].button = button;
|
||||
}
|
||||
}
|
||||
|
||||
// Asigna inputs a botones del mando
|
||||
void Input::bindGameControllerButton(const std::shared_ptr<Gamepad>& gamepad, Action action_target, Action action_source) {
|
||||
void Input::bindGameControllerButton(const std::shared_ptr<Gamepad>& gamepad, Action action_target, Action action_source) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
if (gamepad != nullptr) {
|
||||
gamepad->bindings[action_target].button = gamepad->bindings[action_source].button;
|
||||
}
|
||||
}
|
||||
|
||||
// Comprueba si alguna acción está activa
|
||||
auto Input::checkAction(Action action, bool repeat, bool check_keyboard, const std::shared_ptr<Gamepad>& gamepad) -> bool {
|
||||
auto Input::checkAction(Action action, bool repeat, bool check_keyboard, const std::shared_ptr<Gamepad>& gamepad) -> bool { // NOLINT(readability-convert-member-functions-to-static)
|
||||
bool success_keyboard = false;
|
||||
bool success_controller = false;
|
||||
|
||||
@@ -142,7 +143,7 @@ auto Input::checkAction(Action action, bool repeat, bool check_keyboard, const s
|
||||
}
|
||||
|
||||
// Comprueba si hay almenos una acción activa
|
||||
auto Input::checkAnyInput(bool check_keyboard, const std::shared_ptr<Gamepad>& gamepad) -> bool {
|
||||
auto Input::checkAnyInput(bool check_keyboard, const std::shared_ptr<Gamepad>& gamepad) -> bool { // NOLINT(readability-convert-member-functions-to-static)
|
||||
// Obtenemos el número total de acciones posibles para iterar sobre ellas.
|
||||
|
||||
// --- Comprobación del Teclado ---
|
||||
@@ -179,7 +180,7 @@ auto Input::checkAnyInput(bool check_keyboard, const std::shared_ptr<Gamepad>& g
|
||||
}
|
||||
|
||||
// Comprueba si hay algún botón pulsado
|
||||
auto Input::checkAnyButton(bool repeat) -> bool {
|
||||
auto Input::checkAnyButton(bool repeat) -> bool { // NOLINT(readability-convert-member-functions-to-static)
|
||||
// Solo comprueba los botones definidos previamente
|
||||
for (auto bi : BUTTON_INPUTS) {
|
||||
// Comprueba el teclado
|
||||
@@ -219,7 +220,7 @@ auto Input::getControllerNames() const -> std::vector<std::string> {
|
||||
auto Input::getNumGamepads() const -> int { return gamepads_.size(); }
|
||||
|
||||
// Obtiene el gamepad a partir de un event.id
|
||||
auto Input::getGamepad(SDL_JoystickID id) const -> std::shared_ptr<Input::Gamepad> {
|
||||
auto Input::getGamepad(SDL_JoystickID id) const -> std::shared_ptr<Input::Gamepad> { // NOLINT(readability-convert-member-functions-to-static)
|
||||
for (const auto& gamepad : gamepads_) {
|
||||
if (gamepad->instance_id == id) {
|
||||
return gamepad;
|
||||
@@ -228,7 +229,7 @@ auto Input::getGamepad(SDL_JoystickID id) const -> std::shared_ptr<Input::Gamepa
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto Input::getGamepadByName(const std::string& name) const -> std::shared_ptr<Input::Gamepad> {
|
||||
auto Input::getGamepadByName(const std::string& name) const -> std::shared_ptr<Input::Gamepad> { // NOLINT(readability-convert-member-functions-to-static)
|
||||
for (const auto& gamepad : gamepads_) {
|
||||
if (gamepad && gamepad->name == name) {
|
||||
return gamepad;
|
||||
@@ -238,12 +239,12 @@ auto Input::getGamepadByName(const std::string& name) const -> std::shared_ptr<I
|
||||
}
|
||||
|
||||
// Obtiene el SDL_GamepadButton asignado a un action
|
||||
auto Input::getControllerBinding(const std::shared_ptr<Gamepad>& gamepad, Action action) -> SDL_GamepadButton {
|
||||
auto Input::getControllerBinding(const std::shared_ptr<Gamepad>& gamepad, Action action) -> SDL_GamepadButton { // NOLINT(readability-convert-member-functions-to-static)
|
||||
return static_cast<SDL_GamepadButton>(gamepad->bindings[action].button);
|
||||
}
|
||||
|
||||
// Comprueba el eje del mando
|
||||
auto Input::checkAxisInput(Action action, const std::shared_ptr<Gamepad>& gamepad, bool repeat) -> bool {
|
||||
auto Input::checkAxisInput(Action action, const std::shared_ptr<Gamepad>& gamepad, bool repeat) -> bool { // NOLINT(readability-convert-member-functions-to-static)
|
||||
// Obtener el binding configurado para esta acción
|
||||
auto& binding = gamepad->bindings[action];
|
||||
|
||||
@@ -286,7 +287,7 @@ auto Input::checkAxisInput(Action action, const std::shared_ptr<Gamepad>& gamepa
|
||||
}
|
||||
|
||||
// Comprueba los triggers del mando como botones digitales
|
||||
auto Input::checkTriggerInput(Action action, const std::shared_ptr<Gamepad>& gamepad, bool repeat) -> bool {
|
||||
auto Input::checkTriggerInput(Action action, const std::shared_ptr<Gamepad>& gamepad, bool repeat) -> bool { // NOLINT(readability-convert-member-functions-to-static)
|
||||
// Solo manejamos botones específicos que pueden ser triggers
|
||||
if (gamepad->bindings[action].button != static_cast<int>(SDL_GAMEPAD_BUTTON_INVALID)) {
|
||||
// Solo procesamos L2 y R2 como triggers
|
||||
@@ -333,13 +334,13 @@ auto Input::checkTriggerInput(Action action, const std::shared_ptr<Gamepad>& gam
|
||||
return false;
|
||||
}
|
||||
|
||||
void Input::addGamepadMappingsFromFile() {
|
||||
void Input::addGamepadMappingsFromFile() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
if (SDL_AddGamepadMappingsFromFile(gamepad_mappings_file_.c_str()) < 0) {
|
||||
std::cout << "Error, could not load " << gamepad_mappings_file_.c_str() << " file: " << SDL_GetError() << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
void Input::discoverGamepads() {
|
||||
void Input::discoverGamepads() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
handleEvent(event); // Comprueba mandos conectados
|
||||
@@ -375,7 +376,7 @@ void Input::resetInputStates() {
|
||||
}
|
||||
}
|
||||
|
||||
void Input::update() {
|
||||
void Input::update() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
// --- TECLADO ---
|
||||
const bool* key_states = SDL_GetKeyboardState(nullptr);
|
||||
|
||||
@@ -399,7 +400,7 @@ void Input::update() {
|
||||
}
|
||||
}
|
||||
|
||||
auto Input::handleEvent(const SDL_Event& event) -> std::string {
|
||||
auto Input::handleEvent(const SDL_Event& event) -> std::string { // NOLINT(readability-convert-member-functions-to-static)
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_GAMEPAD_ADDED:
|
||||
return addGamepad(event.gdevice.which);
|
||||
@@ -409,7 +410,7 @@ auto Input::handleEvent(const SDL_Event& event) -> std::string {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Input::addGamepad(int device_index) -> std::string {
|
||||
auto Input::addGamepad(int device_index) -> std::string { // NOLINT(readability-convert-member-functions-to-static)
|
||||
SDL_Gamepad* pad = SDL_OpenGamepad(device_index);
|
||||
if (pad == nullptr) {
|
||||
std::cerr << "Error al abrir el gamepad: " << SDL_GetError() << '\n';
|
||||
@@ -423,8 +424,8 @@ auto Input::addGamepad(int device_index) -> std::string {
|
||||
return name + " CONNECTED";
|
||||
}
|
||||
|
||||
auto Input::removeGamepad(SDL_JoystickID id) -> std::string {
|
||||
auto it = std::ranges::find_if(gamepads_, [id](const std::shared_ptr<Gamepad>& gamepad) {
|
||||
auto Input::removeGamepad(SDL_JoystickID id) -> std::string { // NOLINT(readability-convert-member-functions-to-static)
|
||||
auto it = std::ranges::find_if(gamepads_, [id](const std::shared_ptr<Gamepad>& gamepad) -> bool {
|
||||
return gamepad->instance_id == id;
|
||||
});
|
||||
|
||||
@@ -438,7 +439,7 @@ auto Input::removeGamepad(SDL_JoystickID id) -> std::string {
|
||||
return {};
|
||||
}
|
||||
|
||||
void Input::printConnectedGamepads() const {
|
||||
void Input::printConnectedGamepads() const { // NOLINT(readability-convert-member-functions-to-static)
|
||||
if (gamepads_.empty()) {
|
||||
std::cout << "No hay gamepads conectados." << '\n';
|
||||
return;
|
||||
@@ -452,7 +453,7 @@ void Input::printConnectedGamepads() const {
|
||||
}
|
||||
}
|
||||
|
||||
auto Input::findAvailableGamepadByName(const std::string& gamepad_name) -> std::shared_ptr<Input::Gamepad> {
|
||||
auto Input::findAvailableGamepadByName(const std::string& gamepad_name) -> std::shared_ptr<Input::Gamepad> { // NOLINT(readability-convert-member-functions-to-static)
|
||||
// Si no hay gamepads disponibles, devolver gamepad por defecto
|
||||
if (gamepads_.empty()) {
|
||||
return nullptr;
|
||||
|
||||
@@ -101,12 +101,12 @@ class Input {
|
||||
// --- Gestión de gamepads ---
|
||||
[[nodiscard]] auto gameControllerFound() const -> bool;
|
||||
[[nodiscard]] auto getNumGamepads() const -> int;
|
||||
auto getGamepad(SDL_JoystickID id) const -> std::shared_ptr<Gamepad>;
|
||||
auto getGamepadByName(const std::string& name) const -> std::shared_ptr<Input::Gamepad>;
|
||||
auto getGamepads() const -> const Gamepads& { return gamepads_; }
|
||||
[[nodiscard]] auto getGamepad(SDL_JoystickID id) const -> std::shared_ptr<Gamepad>;
|
||||
[[nodiscard]] auto getGamepadByName(const std::string& name) const -> std::shared_ptr<Input::Gamepad>;
|
||||
[[nodiscard]] auto getGamepads() const -> const Gamepads& { return gamepads_; }
|
||||
auto findAvailableGamepadByName(const std::string& gamepad_name) -> std::shared_ptr<Gamepad>;
|
||||
static auto getControllerName(const std::shared_ptr<Gamepad>& gamepad) -> std::string;
|
||||
auto getControllerNames() const -> std::vector<std::string>;
|
||||
[[nodiscard]] auto getControllerNames() const -> std::vector<std::string>;
|
||||
[[nodiscard]] static auto getControllerBinding(const std::shared_ptr<Gamepad>& gamepad, Action action) -> SDL_GamepadButton;
|
||||
void printConnectedGamepads() const;
|
||||
|
||||
|
||||
@@ -17,13 +17,13 @@ const std::unordered_map<InputAction, std::string> ACTION_TO_STRING = {
|
||||
{InputAction::TOGGLE_VSYNC, "TOGGLE_VSYNC"},
|
||||
{InputAction::TOGGLE_INTEGER_SCALE, "TOGGLE_INTEGER_SCALE"},
|
||||
{InputAction::TOGGLE_BORDER, "TOGGLE_BORDER"},
|
||||
{InputAction::TOGGLE_MUSIC, "TOGGLE_MUSIC"},
|
||||
{InputAction::TOGGLE_IN_GAME_MUSIC, "TOGGLE_MUSIC"},
|
||||
{InputAction::NEXT_PALETTE, "NEXT_PALETTE"},
|
||||
{InputAction::PREVIOUS_PALETTE, "PREVIOUS_PALETTE"},
|
||||
{InputAction::TOGGLE_POSTFX, "TOGGLE_POSTFX"},
|
||||
{InputAction::NEXT_POSTFX_PRESET, "NEXT_POSTFX_PRESET"},
|
||||
{InputAction::SHOW_DEBUG_INFO, "SHOW_DEBUG_INFO"},
|
||||
{InputAction::TOGGLE_DEBUG, "TOGGLE_DEBUG"},
|
||||
{InputAction::NEXT_PALETTE_SORT, "NEXT_PALETTE_SORT"},
|
||||
{InputAction::TOGGLE_SHADER, "TOGGLE_POSTFX"},
|
||||
{InputAction::NEXT_SHADER_PRESET, "NEXT_POSTFX_PRESET"},
|
||||
{InputAction::TOGGLE_INFO, "TOGGLE_DEBUG"},
|
||||
{InputAction::NONE, "NONE"}};
|
||||
|
||||
const std::unordered_map<std::string, InputAction> STRING_TO_ACTION = {
|
||||
@@ -40,13 +40,13 @@ const std::unordered_map<std::string, InputAction> STRING_TO_ACTION = {
|
||||
{"TOGGLE_VSYNC", InputAction::TOGGLE_VSYNC},
|
||||
{"TOGGLE_INTEGER_SCALE", InputAction::TOGGLE_INTEGER_SCALE},
|
||||
{"TOGGLE_BORDER", InputAction::TOGGLE_BORDER},
|
||||
{"TOGGLE_MUSIC", InputAction::TOGGLE_MUSIC},
|
||||
{"TOGGLE_MUSIC", InputAction::TOGGLE_IN_GAME_MUSIC},
|
||||
{"NEXT_PALETTE", InputAction::NEXT_PALETTE},
|
||||
{"PREVIOUS_PALETTE", InputAction::PREVIOUS_PALETTE},
|
||||
{"TOGGLE_POSTFX", InputAction::TOGGLE_POSTFX},
|
||||
{"NEXT_POSTFX_PRESET", InputAction::NEXT_POSTFX_PRESET},
|
||||
{"SHOW_DEBUG_INFO", InputAction::SHOW_DEBUG_INFO},
|
||||
{"TOGGLE_DEBUG", InputAction::TOGGLE_DEBUG},
|
||||
{"NEXT_PALETTE_SORT", InputAction::NEXT_PALETTE_SORT},
|
||||
{"TOGGLE_POSTFX", InputAction::TOGGLE_SHADER},
|
||||
{"NEXT_POSTFX_PRESET", InputAction::NEXT_SHADER_PRESET},
|
||||
{"TOGGLE_DEBUG", InputAction::TOGGLE_INFO},
|
||||
{"NONE", InputAction::NONE}};
|
||||
|
||||
const std::unordered_map<SDL_GamepadButton, std::string> BUTTON_TO_STRING = {
|
||||
|
||||
@@ -24,15 +24,16 @@ enum class InputAction : int { // Acciones de entrada posibles en el juego
|
||||
TOGGLE_FULLSCREEN,
|
||||
TOGGLE_VSYNC,
|
||||
TOGGLE_INTEGER_SCALE,
|
||||
TOGGLE_POSTFX,
|
||||
NEXT_POSTFX_PRESET,
|
||||
TOGGLE_SHADER,
|
||||
NEXT_SHADER_PRESET,
|
||||
TOGGLE_SUPERSAMPLING,
|
||||
TOGGLE_BORDER,
|
||||
TOGGLE_MUSIC,
|
||||
TOGGLE_IN_GAME_MUSIC,
|
||||
NEXT_PALETTE,
|
||||
PREVIOUS_PALETTE,
|
||||
SHOW_DEBUG_INFO,
|
||||
TOGGLE_DEBUG,
|
||||
NEXT_PALETTE_SORT,
|
||||
TOGGLE_INFO,
|
||||
TOGGLE_CONSOLE,
|
||||
|
||||
// Input obligatorio
|
||||
NONE,
|
||||
|
||||
@@ -2,20 +2,26 @@
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "external/fkyaml_node.hpp" // Para fkyaml::node
|
||||
#include "game/options.hpp" // Para Options::console
|
||||
|
||||
// [SINGLETON]
|
||||
Locale* Locale::instance = nullptr;
|
||||
|
||||
// [SINGLETON] Crea el objeto con esta función estática
|
||||
void Locale::init(const std::string& file_path) {
|
||||
void Locale::init(const std::string& file_path) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
Locale::instance = new Locale();
|
||||
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;
|
||||
@@ -28,20 +34,18 @@ auto Locale::get() -> Locale* {
|
||||
}
|
||||
|
||||
// Devuelve la traducción de la clave o la clave como fallback
|
||||
auto Locale::get(const std::string& key) const -> std::string {
|
||||
auto Locale::get(const std::string& key) const -> std::string { // NOLINT(readability-convert-member-functions-to-static)
|
||||
auto it = strings_.find(key);
|
||||
if (it != strings_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
if (Options::console) {
|
||||
std::cerr << "Locale: clave no encontrada: " << key << '\n';
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
// Aplana un nodo YAML de forma recursiva: {a: {b: "val"}} -> {"a.b" -> "val"}
|
||||
void Locale::flatten(const void* node_ptr, const std::string& prefix) {
|
||||
void Locale::flatten(const void* node_ptr, const std::string& prefix) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
const auto& node = *static_cast<const fkyaml::node*>(node_ptr);
|
||||
|
||||
for (auto itr = node.begin(); itr != node.end(); ++itr) {
|
||||
@@ -58,20 +62,34 @@ void Locale::flatten(const void* node_ptr, const std::string& prefix) {
|
||||
}
|
||||
}
|
||||
|
||||
// Carga las traducciones desde el fichero YAML indicado
|
||||
void Locale::loadFromFile(const std::string& file_path) {
|
||||
if (file_path.empty()) {
|
||||
if (Options::console) {
|
||||
std::cerr << "Locale: ruta de fichero vacía, sin traducciones cargadas\n";
|
||||
// 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()) {
|
||||
std::cerr << "Locale: ruta de fichero vacía, sin traducciones cargadas\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::ifstream file(file_path);
|
||||
if (!file.is_open()) {
|
||||
if (Options::console) {
|
||||
std::cerr << "Locale: no se puede abrir " << file_path << '\n';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -79,12 +97,8 @@ void Locale::loadFromFile(const std::string& file_path) {
|
||||
auto yaml = fkyaml::node::deserialize(file);
|
||||
flatten(&yaml, "");
|
||||
|
||||
if (Options::console) {
|
||||
std::cout << "Locale: " << strings_.size() << " traducciones cargadas desde " << file_path << '\n';
|
||||
}
|
||||
} catch (const fkyaml::exception& e) {
|
||||
if (Options::console) {
|
||||
std::cerr << "Locale: error al parsear YAML: " << e.what() << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
class Locale {
|
||||
public:
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace GIF {
|
||||
}
|
||||
|
||||
// Inicializa el diccionario LZW con los valores iniciales
|
||||
inline void initializeDictionary(std::vector<DictionaryEntry>& dictionary, int code_length, int& dictionary_ind) {
|
||||
inline void initializeDictionary(std::vector<DictionaryEntry>& dictionary, int code_length, int& dictionary_ind) { // NOLINT(readability-identifier-naming)
|
||||
int size = 1 << code_length;
|
||||
dictionary.resize(1 << (code_length + 1));
|
||||
for (dictionary_ind = 0; dictionary_ind < size; dictionary_ind++) {
|
||||
@@ -55,7 +55,7 @@ namespace GIF {
|
||||
}
|
||||
|
||||
// Agrega una nueva entrada al diccionario
|
||||
inline void addDictionaryEntry(std::vector<DictionaryEntry>& dictionary, int& dictionary_ind, int& code_length, int prev, int code) {
|
||||
inline void addDictionaryEntry(std::vector<DictionaryEntry>& dictionary, int& dictionary_ind, int& code_length, int prev, int code) { // NOLINT(readability-identifier-naming)
|
||||
uint8_t first_byte;
|
||||
if (code == dictionary_ind) {
|
||||
first_byte = findFirstByte(dictionary, prev);
|
||||
@@ -90,7 +90,7 @@ namespace GIF {
|
||||
return match_len;
|
||||
}
|
||||
|
||||
void Gif::decompress(int code_length, const uint8_t* input, int input_length, uint8_t* out) {
|
||||
void Gif::decompress(int code_length, const uint8_t* input, int input_length, uint8_t* out) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
// Verifica que el code_length tenga un rango razonable.
|
||||
if (code_length < 2 || code_length > 12) {
|
||||
throw std::runtime_error("Invalid LZW code length");
|
||||
@@ -146,7 +146,7 @@ namespace GIF {
|
||||
}
|
||||
}
|
||||
|
||||
auto Gif::readSubBlocks(const uint8_t*& buffer) -> std::vector<uint8_t> {
|
||||
auto Gif::readSubBlocks(const uint8_t*& buffer) -> std::vector<uint8_t> { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::vector<uint8_t> data;
|
||||
uint8_t block_size = *buffer;
|
||||
buffer++;
|
||||
@@ -159,7 +159,7 @@ namespace GIF {
|
||||
return data;
|
||||
}
|
||||
|
||||
auto Gif::processImageDescriptor(const uint8_t*& buffer, const std::vector<RGB>& gct, int resolution_bits) -> std::vector<uint8_t> {
|
||||
auto Gif::processImageDescriptor(const uint8_t*& buffer, const std::vector<RGB>& gct, int resolution_bits) -> std::vector<uint8_t> { // NOLINT(readability-convert-member-functions-to-static)
|
||||
ImageDescriptor image_descriptor;
|
||||
// Lee 9 bytes para el image descriptor.
|
||||
readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor));
|
||||
@@ -175,7 +175,7 @@ namespace GIF {
|
||||
return uncompressed_data;
|
||||
}
|
||||
|
||||
auto Gif::loadPalette(const uint8_t* buffer) -> std::vector<uint32_t> {
|
||||
auto Gif::loadPalette(const uint8_t* buffer) -> std::vector<uint32_t> { // NOLINT(readability-convert-member-functions-to-static)
|
||||
uint8_t header[6];
|
||||
std::memcpy(header, buffer, 6);
|
||||
buffer += 6;
|
||||
@@ -186,7 +186,7 @@ namespace GIF {
|
||||
|
||||
std::vector<uint32_t> global_color_table;
|
||||
if ((screen_descriptor.fields & 0x80) != 0) {
|
||||
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
|
||||
int global_color_table_size = 1 << ((screen_descriptor.fields & 0x07) + 1);
|
||||
global_color_table.resize(global_color_table_size);
|
||||
for (int i = 0; i < global_color_table_size; ++i) {
|
||||
uint8_t r = buffer[0];
|
||||
@@ -199,7 +199,7 @@ namespace GIF {
|
||||
return global_color_table;
|
||||
}
|
||||
|
||||
auto Gif::processGifStream(const uint8_t* buffer, uint16_t& w, uint16_t& h) -> std::vector<uint8_t> {
|
||||
auto Gif::processGifStream(const uint8_t* buffer, uint16_t& w, uint16_t& h) -> std::vector<uint8_t> { // NOLINT(readability-convert-member-functions-to-static)
|
||||
// Leer la cabecera de 6 bytes ("GIF87a" o "GIF89a")
|
||||
uint8_t header[6];
|
||||
std::memcpy(header, buffer, 6);
|
||||
@@ -222,7 +222,7 @@ namespace GIF {
|
||||
int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
|
||||
std::vector<RGB> global_color_table;
|
||||
if ((screen_descriptor.fields & 0x80) != 0) {
|
||||
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
|
||||
int global_color_table_size = 1 << ((screen_descriptor.fields & 0x07) + 1);
|
||||
global_color_table.resize(global_color_table_size);
|
||||
std::memcpy(global_color_table.data(), buffer, 3 * global_color_table_size);
|
||||
buffer += 3 * global_color_table_size;
|
||||
|
||||
276
source/core/rendering/palette_manager.cpp
Normal file
276
source/core/rendering/palette_manager.cpp
Normal file
@@ -0,0 +1,276 @@
|
||||
#include "core/rendering/palette_manager.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "core/rendering/surface.hpp"
|
||||
#include "core/resources/resource_cache.hpp"
|
||||
#include "game/defaults.hpp"
|
||||
#include "game/options.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
// ── Conversión string ↔ PaletteSortMode ──────────────────────────────────────
|
||||
|
||||
auto sortModeFromString(const std::string& str) -> PaletteSortMode {
|
||||
const std::string lower = toLower(str);
|
||||
if (lower == "luminance") { return PaletteSortMode::LUMINANCE; }
|
||||
if (lower == "spectrum") { return PaletteSortMode::SPECTRUM; }
|
||||
return PaletteSortMode::ORIGINAL;
|
||||
}
|
||||
|
||||
auto sortModeToString(PaletteSortMode mode) -> std::string {
|
||||
switch (mode) {
|
||||
case PaletteSortMode::LUMINANCE:
|
||||
return "luminance";
|
||||
case PaletteSortMode::SPECTRUM:
|
||||
return "spectrum";
|
||||
default:
|
||||
return "original";
|
||||
}
|
||||
}
|
||||
|
||||
// ── Paleta de referencia ZX Spectrum (16 colores ARGB) ───────────────────────
|
||||
|
||||
namespace {
|
||||
// Helpers para extraer componentes RGB de un color ARGB (0xAARRGGBB)
|
||||
constexpr auto redOf(Uint32 c) -> int { return static_cast<int>((c >> 16) & 0xFF); }
|
||||
constexpr auto greenOf(Uint32 c) -> int { return static_cast<int>((c >> 8) & 0xFF); }
|
||||
constexpr auto blueOf(Uint32 c) -> int { return static_cast<int>(c & 0xFF); }
|
||||
|
||||
constexpr auto makeARGB(int r, int g, int b) -> Uint32 {
|
||||
return (0xFFU << 24) | (static_cast<Uint32>(r) << 16) | (static_cast<Uint32>(g) << 8) | static_cast<Uint32>(b);
|
||||
}
|
||||
|
||||
// Paleta ZX Spectrum de referencia (misma que en tools/sort_palette/sort_palette.py)
|
||||
constexpr std::array<Uint32, 16> SPECTRUM_REFERENCE = {
|
||||
makeARGB(0, 0, 0),
|
||||
makeARGB(0, 0, 0),
|
||||
makeARGB(0, 0, 216),
|
||||
makeARGB(0, 0, 255),
|
||||
makeARGB(216, 0, 0),
|
||||
makeARGB(255, 0, 0),
|
||||
makeARGB(216, 0, 216),
|
||||
makeARGB(255, 0, 255),
|
||||
makeARGB(0, 216, 0),
|
||||
makeARGB(0, 255, 0),
|
||||
makeARGB(0, 216, 216),
|
||||
makeARGB(0, 255, 255),
|
||||
makeARGB(216, 216, 0),
|
||||
makeARGB(255, 255, 0),
|
||||
makeARGB(216, 216, 216),
|
||||
makeARGB(255, 255, 255),
|
||||
};
|
||||
|
||||
// Luminancia percibida (ITU-R BT.709)
|
||||
auto luminance(Uint32 color) -> double {
|
||||
return 0.2126 * redOf(color) + 0.7152 * greenOf(color) + 0.0722 * blueOf(color);
|
||||
}
|
||||
|
||||
// Distancia euclídea al cuadrado en espacio RGB (no necesita sqrt para comparar)
|
||||
auto rgbDistanceSq(Uint32 a, Uint32 b) -> int {
|
||||
const int dr = redOf(a) - redOf(b);
|
||||
const int dg = greenOf(a) - greenOf(b);
|
||||
const int db = blueOf(a) - blueOf(b);
|
||||
return dr * dr + dg * dg + db * db;
|
||||
}
|
||||
|
||||
// Cuenta los colores activos en la paleta (los que tienen alpha != 0)
|
||||
auto countActiveColors(const Palette& palette) -> size_t {
|
||||
size_t count = 0;
|
||||
for (const auto& c : palette) {
|
||||
if (c == 0) { break; }
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Ordenar por luminancia
|
||||
auto sortByLuminance(const Palette& palette) -> Palette {
|
||||
const size_t n = countActiveColors(palette);
|
||||
std::vector<Uint32> colors(palette.begin(), palette.begin() + static_cast<ptrdiff_t>(n));
|
||||
std::sort(colors.begin(), colors.end(), [](Uint32 a, Uint32 b) {
|
||||
return luminance(a) < luminance(b);
|
||||
});
|
||||
|
||||
Palette result{};
|
||||
result.fill(0);
|
||||
std::copy(colors.begin(), colors.end(), result.begin());
|
||||
return result;
|
||||
}
|
||||
|
||||
// Ordenar por similitud con la paleta ZX Spectrum (greedy matching)
|
||||
auto sortBySpectrum(const Palette& palette) -> Palette {
|
||||
const size_t n = countActiveColors(palette);
|
||||
std::vector<Uint32> available(palette.begin(), palette.begin() + static_cast<ptrdiff_t>(n));
|
||||
std::vector<Uint32> result;
|
||||
result.reserve(n);
|
||||
|
||||
// Para cada color de referencia del Spectrum, buscar el más cercano disponible
|
||||
const size_t refs = std::min(n, SPECTRUM_REFERENCE.size());
|
||||
for (size_t i = 0; i < refs && !available.empty(); ++i) {
|
||||
const Uint32 ref = SPECTRUM_REFERENCE[i];
|
||||
auto best = std::min_element(available.begin(), available.end(), [ref](Uint32 a, Uint32 b) {
|
||||
return rgbDistanceSq(a, ref) < rgbDistanceSq(b, ref);
|
||||
});
|
||||
result.push_back(*best);
|
||||
available.erase(best);
|
||||
}
|
||||
|
||||
// Si quedan colores sin asignar, añadirlos al final
|
||||
for (const auto& c : available) {
|
||||
result.push_back(c);
|
||||
}
|
||||
|
||||
Palette out{};
|
||||
out.fill(0);
|
||||
std::copy(result.begin(), result.end(), out.begin());
|
||||
return out;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// ── PaletteManager ───────────────────────────────────────────────────────────
|
||||
|
||||
PaletteManager::PaletteManager(
|
||||
std::vector<std::string> raw_paths,
|
||||
const std::string& initial_name,
|
||||
PaletteSortMode initial_sort_mode,
|
||||
std::shared_ptr<Surface> game_surface,
|
||||
std::shared_ptr<Surface> border_surface,
|
||||
OnChangeCallback on_change)
|
||||
: palettes_(std::move(raw_paths)),
|
||||
sort_mode_(initial_sort_mode),
|
||||
game_surface_(std::move(game_surface)),
|
||||
border_surface_(std::move(border_surface)),
|
||||
on_change_(std::move(on_change)) {
|
||||
current_ = findIndex(initial_name);
|
||||
|
||||
// Leer y aplicar paleta inicial directamente desde el archivo
|
||||
// (Resource::Cache aún no está disponible en este punto del ciclo de vida)
|
||||
const auto initial_palette = sortPalette(readPalFile(palettes_.at(current_)), sort_mode_);
|
||||
game_surface_->setPalette(initial_palette);
|
||||
border_surface_->setPalette(initial_palette);
|
||||
|
||||
// Procesar la lista: conservar solo los nombres de archivo (sin ruta)
|
||||
processPathList();
|
||||
}
|
||||
|
||||
void PaletteManager::next() {
|
||||
if (++current_ == palettes_.size()) {
|
||||
current_ = 0;
|
||||
}
|
||||
apply();
|
||||
}
|
||||
|
||||
void PaletteManager::previous() {
|
||||
current_ = (current_ > 0) ? current_ - 1 : palettes_.size() - 1;
|
||||
apply();
|
||||
}
|
||||
|
||||
auto PaletteManager::setByName(const std::string& name) -> bool {
|
||||
const std::string lower_name = toLower(name + ".pal");
|
||||
for (size_t i = 0; i < palettes_.size(); ++i) {
|
||||
if (toLower(palettes_[i]) == lower_name) {
|
||||
current_ = i;
|
||||
apply();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto PaletteManager::getNames() const -> std::vector<std::string> {
|
||||
std::vector<std::string> names;
|
||||
names.reserve(palettes_.size());
|
||||
for (const auto& p : palettes_) {
|
||||
std::string name = p;
|
||||
const size_t pos = name.find(".pal");
|
||||
if (pos != std::string::npos) { name.erase(pos, 4); }
|
||||
std::ranges::transform(name, name.begin(), ::tolower);
|
||||
names.push_back(std::move(name));
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
auto PaletteManager::getCurrentName() const -> std::string {
|
||||
std::string name = palettes_.at(current_);
|
||||
const size_t pos = name.find(".pal");
|
||||
if (pos != std::string::npos) { name.erase(pos, 4); }
|
||||
std::ranges::transform(name, name.begin(), ::tolower);
|
||||
return name;
|
||||
}
|
||||
|
||||
auto PaletteManager::getPrettyName() const -> std::string {
|
||||
std::string name = getCurrentName();
|
||||
std::ranges::replace(name, '-', ' ');
|
||||
return name;
|
||||
}
|
||||
|
||||
void PaletteManager::nextSortMode() {
|
||||
sort_mode_ = static_cast<PaletteSortMode>((static_cast<int>(sort_mode_) + 1) % static_cast<int>(PaletteSortMode::COUNT));
|
||||
Options::video.palette_sort = sortModeToString(sort_mode_);
|
||||
apply();
|
||||
}
|
||||
|
||||
void PaletteManager::setSortMode(PaletteSortMode mode) {
|
||||
sort_mode_ = mode;
|
||||
Options::video.palette_sort = sortModeToString(sort_mode_);
|
||||
apply();
|
||||
}
|
||||
|
||||
auto PaletteManager::getSortMode() const -> PaletteSortMode {
|
||||
return sort_mode_;
|
||||
}
|
||||
|
||||
auto PaletteManager::getSortModeName() const -> std::string {
|
||||
return sortModeToString(sort_mode_);
|
||||
}
|
||||
|
||||
void PaletteManager::apply() {
|
||||
Palette raw = Resource::Cache::get()->getPalette(palettes_.at(current_));
|
||||
Palette sorted = sortPalette(raw, sort_mode_);
|
||||
game_surface_->loadPalette(sorted);
|
||||
border_surface_->loadPalette(sorted);
|
||||
|
||||
Options::video.palette = getCurrentName();
|
||||
|
||||
if (on_change_) {
|
||||
on_change_();
|
||||
}
|
||||
}
|
||||
|
||||
auto PaletteManager::findIndex(const std::string& name) const -> size_t {
|
||||
const std::string lower_name = toLower(name + ".pal");
|
||||
for (size_t i = 0; i < palettes_.size(); ++i) {
|
||||
if (toLower(getFileName(palettes_[i])) == lower_name) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
// Fallback: buscar la paleta por defecto
|
||||
const std::string default_name = toLower(std::string(Defaults::Video::PALETTE_NAME) + ".pal");
|
||||
for (size_t i = 0; i < palettes_.size(); ++i) {
|
||||
if (toLower(getFileName(palettes_[i])) == default_name) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PaletteManager::processPathList() {
|
||||
for (auto& palette : palettes_) {
|
||||
palette = getFileName(palette);
|
||||
}
|
||||
}
|
||||
|
||||
auto PaletteManager::sortPalette(const Palette& palette, PaletteSortMode mode) -> Palette {
|
||||
switch (mode) {
|
||||
case PaletteSortMode::LUMINANCE:
|
||||
return sortByLuminance(palette);
|
||||
case PaletteSortMode::SPECTRUM:
|
||||
return sortBySpectrum(palette);
|
||||
default:
|
||||
return palette;
|
||||
}
|
||||
}
|
||||
64
source/core/rendering/palette_manager.hpp
Normal file
64
source/core/rendering/palette_manager.hpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Alias de paleta (igual que en surface.hpp; evita incluir todo el header)
|
||||
using Palette = std::array<Uint32, 256>;
|
||||
|
||||
class Surface;
|
||||
|
||||
// Modo de ordenación de paletas
|
||||
enum class PaletteSortMode : int {
|
||||
ORIGINAL = 0, // Paleta tal cual viene del fichero
|
||||
LUMINANCE = 1, // Ordenada por luminancia percibida
|
||||
SPECTRUM = 2, // Reordenada para imitar la paleta ZX Spectrum
|
||||
COUNT = 3
|
||||
};
|
||||
|
||||
// Conversión string ↔ PaletteSortMode
|
||||
auto sortModeFromString(const std::string& str) -> PaletteSortMode;
|
||||
auto sortModeToString(PaletteSortMode mode) -> std::string;
|
||||
|
||||
class PaletteManager {
|
||||
public:
|
||||
using OnChangeCallback = std::function<void()>;
|
||||
|
||||
PaletteManager(
|
||||
std::vector<std::string> raw_paths,
|
||||
const std::string& initial_name,
|
||||
PaletteSortMode initial_sort_mode,
|
||||
std::shared_ptr<Surface> game_surface,
|
||||
std::shared_ptr<Surface> border_surface,
|
||||
OnChangeCallback on_change = nullptr);
|
||||
|
||||
void next(); // Avanza a la siguiente paleta
|
||||
void previous(); // Retrocede a la paleta anterior
|
||||
auto setByName(const std::string& name) -> bool; // Cambia a paleta por nombre; false si no existe
|
||||
[[nodiscard]] auto getNames() const -> std::vector<std::string>; // Nombres disponibles (minúsculas, sin .pal)
|
||||
[[nodiscard]] auto getCurrentName() const -> std::string; // Nombre de la paleta actual (minúsculas, sin .pal)
|
||||
[[nodiscard]] auto getPrettyName() const -> std::string; // Nombre actual con guiones sustituidos por espacios
|
||||
|
||||
void nextSortMode(); // Cicla al siguiente modo de ordenación
|
||||
void setSortMode(PaletteSortMode mode); // Establece un modo de ordenación concreto
|
||||
[[nodiscard]] auto getSortMode() const -> PaletteSortMode; // Devuelve el modo de ordenación actual
|
||||
[[nodiscard]] auto getSortModeName() const -> std::string; // Nombre del modo actual ("ORIGINAL", etc.)
|
||||
|
||||
private:
|
||||
void apply(); // Aplica la paleta actual a ambas surfaces
|
||||
[[nodiscard]] auto findIndex(const std::string& name) const -> size_t; // Localiza paleta por nombre en el vector
|
||||
void processPathList(); // Extrae nombres de archivo de las rutas completas
|
||||
static auto sortPalette(const Palette& palette, PaletteSortMode mode) -> Palette; // Reordena una paleta según el modo
|
||||
|
||||
std::vector<std::string> palettes_;
|
||||
size_t current_{0};
|
||||
PaletteSortMode sort_mode_{PaletteSortMode::ORIGINAL};
|
||||
std::shared_ptr<Surface> game_surface_;
|
||||
std::shared_ptr<Surface> border_surface_;
|
||||
OnChangeCallback on_change_;
|
||||
};
|
||||
@@ -65,11 +65,8 @@ PixelReveal::PixelReveal(int width, int height, float pixels_per_second, float s
|
||||
}
|
||||
}
|
||||
|
||||
// Destructor
|
||||
PixelReveal::~PixelReveal() = default;
|
||||
|
||||
// Actualiza el estado del revelado
|
||||
void PixelReveal::update(float time_active) {
|
||||
void PixelReveal::update(float time_active) { // NOLINT(readability-make-member-function-const)
|
||||
// En modo normal revela (pone transparente); en modo inverso cubre (pone negro)
|
||||
const auto PIXEL_COLOR = reverse_ ? static_cast<Uint8>(PaletteColor::BLACK) : static_cast<Uint8>(PaletteColor::TRANSPARENT);
|
||||
|
||||
@@ -106,5 +103,5 @@ void PixelReveal::render(int dst_x, int dst_y) const {
|
||||
|
||||
// Indica si el revelado ha completado todas las filas
|
||||
auto PixelReveal::isComplete() const -> bool {
|
||||
return std::ranges::all_of(row_step_, [this](int s) { return s >= num_steps_; });
|
||||
return std::ranges::all_of(row_step_, [this](int s) -> bool { return s >= num_steps_; });
|
||||
}
|
||||
|
||||
@@ -16,8 +16,7 @@ class PixelReveal {
|
||||
// Constructor
|
||||
PixelReveal(int width, int height, float pixels_per_second, float step_duration, int num_steps = 4, bool reverse = false, RevealMode mode = RevealMode::RANDOM);
|
||||
|
||||
// Destructor definido en el .cpp para que unique_ptr<Surface> funcione con forward declaration
|
||||
~PixelReveal();
|
||||
~PixelReveal() = default;
|
||||
|
||||
// Actualiza el estado del revelado según el tiempo transcurrido
|
||||
void update(float time_active);
|
||||
|
||||
159
source/core/rendering/render_info.cpp
Normal file
159
source/core/rendering/render_info.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
#include "core/rendering/render_info.hpp"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm> // Para transform
|
||||
#include <cmath> // Para round, floor
|
||||
#include <iomanip> // Para setprecision
|
||||
#include <sstream> // Para ostringstream
|
||||
#include <string> // Para string
|
||||
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
#include "core/rendering/text.hpp" // Para Text
|
||||
#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;
|
||||
|
||||
// [SINGLETON] Crearemos el objeto con esta función estática
|
||||
void RenderInfo::init() {
|
||||
RenderInfo::render_info = new RenderInfo();
|
||||
}
|
||||
|
||||
// [SINGLETON] Destruiremos el objeto con esta función estática
|
||||
void RenderInfo::destroy() {
|
||||
delete RenderInfo::render_info;
|
||||
RenderInfo::render_info = nullptr;
|
||||
}
|
||||
|
||||
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
|
||||
auto RenderInfo::get() -> RenderInfo* {
|
||||
return RenderInfo::render_info;
|
||||
}
|
||||
|
||||
// Constructor: en DEBUG se activa inmediatamente (notifica a Notifier del offset)
|
||||
RenderInfo::RenderInfo() {
|
||||
#ifdef _DEBUG
|
||||
toggle();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Actualiza la animación de entrada/salida del overlay
|
||||
void RenderInfo::update(float delta_time) {
|
||||
switch (status_) {
|
||||
case Status::RISING:
|
||||
y_ += SLIDE_SPEED * delta_time;
|
||||
if (y_ >= 0.0F) {
|
||||
y_ = 0.0F;
|
||||
status_ = Status::ACTIVE;
|
||||
}
|
||||
break;
|
||||
case Status::VANISHING:
|
||||
y_ -= SLIDE_SPEED * delta_time;
|
||||
if (y_ <= static_cast<float>(-HEIGHT)) {
|
||||
y_ = static_cast<float>(-HEIGHT);
|
||||
status_ = Status::HIDDEN;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Renderiza el overlay de información por pantalla
|
||||
void RenderInfo::render() const {
|
||||
if (status_ == Status::HIDDEN) { return; }
|
||||
|
||||
// FPS
|
||||
std::string line = std::to_string(Screen::get()->getLastFPS()) + " fps";
|
||||
|
||||
// Driver GPU
|
||||
const auto& driver = Screen::get()->getGPUDriver();
|
||||
line += " | " + (driver.empty() ? std::string("sdl") : driver);
|
||||
|
||||
// Zoom calculado (alto físico / alto lógico), con coma decimal y sin ceros innecesarios
|
||||
const float ROUNDED = std::round(Screen::get()->getZoomFactor() * 100.0F) / 100.0F;
|
||||
std::string zoom_str;
|
||||
if (ROUNDED == std::floor(ROUNDED)) {
|
||||
zoom_str = std::to_string(static_cast<int>(ROUNDED));
|
||||
} else {
|
||||
std::ostringstream oss;
|
||||
oss << std::fixed << std::setprecision(2) << ROUNDED;
|
||||
zoom_str = oss.str();
|
||||
if (zoom_str.back() == '0') { zoom_str.pop_back(); }
|
||||
std::replace(zoom_str.begin(), zoom_str.end(), '.', ',');
|
||||
}
|
||||
line += " | " + zoom_str + "x";
|
||||
|
||||
// PostFX: muestra shader + preset y supersampling, o nada si está desactivado
|
||||
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 = prettyName(Options::crtpi_presets[static_cast<size_t>(Options::video.shader.current_crtpi_preset)].name);
|
||||
}
|
||||
} else {
|
||||
if (!Options::postfx_presets.empty()) {
|
||||
preset_name = prettyName(Options::postfx_presets[static_cast<size_t>(Options::video.shader.current_postfx_preset)].name);
|
||||
}
|
||||
}
|
||||
const bool SHOW_SS = Options::video.supersampling.enabled && !IS_CRTPI;
|
||||
line += " | " + SHADER_NAME + " " + preset_name + (SHOW_SS ? " (ss)" : "");
|
||||
}
|
||||
|
||||
// Todo en lowercase
|
||||
std::transform(line.begin(), line.end(), line.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
|
||||
// Constantes visuales (igual que Console)
|
||||
static constexpr Uint8 BG_COLOR = 0; // PaletteColor::BLACK
|
||||
static constexpr Uint8 MSG_COLOR = 9; // PaletteColor::BRIGHT_GREEN
|
||||
static constexpr int TEXT_SIZE = 6;
|
||||
static constexpr int PADDING_V = TEXT_SIZE / 2 - 1;
|
||||
|
||||
// Fuente: preferir la de la consola si está disponible
|
||||
auto text_obj = (Console::get() != nullptr) ? Console::get()->getText() : Screen::get()->getText();
|
||||
|
||||
// Posición Y: debajo de la consola + offset animado propio
|
||||
const int CONSOLE_Y = (Console::get() != nullptr) ? Console::get()->getVisibleHeight() : 0;
|
||||
const int Y = CONSOLE_Y + static_cast<int>(y_);
|
||||
|
||||
// Rectángulo de fondo: ancho completo, alto ajustado al texto
|
||||
const SDL_FRect RECT = {
|
||||
.x = 0.0F,
|
||||
.y = static_cast<float>(Y),
|
||||
.w = Options::game.width,
|
||||
.h = static_cast<float>(TEXT_SIZE + (PADDING_V * 2))};
|
||||
|
||||
auto game_surface = Screen::get()->getGameSurface();
|
||||
game_surface->fillRect(&RECT, BG_COLOR);
|
||||
// game_surface->drawRectBorder(&RECT, BORDER_COLOR);
|
||||
text_obj->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
|
||||
static_cast<int>(Options::game.width / 2),
|
||||
Y + PADDING_V,
|
||||
line,
|
||||
1,
|
||||
MSG_COLOR);
|
||||
}
|
||||
|
||||
// Activa o desactiva el overlay y notifica a Notifier del cambio de offset
|
||||
void RenderInfo::toggle() {
|
||||
switch (status_) {
|
||||
case Status::HIDDEN:
|
||||
status_ = Status::RISING;
|
||||
Screen::get()->updateZoomFactor();
|
||||
if (Notifier::get() != nullptr) { Notifier::get()->addYOffset(HEIGHT); }
|
||||
break;
|
||||
case Status::ACTIVE:
|
||||
status_ = Status::VANISHING;
|
||||
if (Notifier::get() != nullptr) { Notifier::get()->removeYOffset(HEIGHT); }
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
37
source/core/rendering/render_info.hpp
Normal file
37
source/core/rendering/render_info.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
class RenderInfo {
|
||||
public:
|
||||
// Singleton
|
||||
static void init();
|
||||
static void destroy();
|
||||
static auto get() -> RenderInfo*;
|
||||
|
||||
// Métodos principales
|
||||
void update(float delta_time);
|
||||
void render() const;
|
||||
void toggle();
|
||||
|
||||
// Consultas
|
||||
[[nodiscard]] auto isActive() const -> bool { return status_ != Status::HIDDEN; }
|
||||
|
||||
// Altura fija del overlay (TEXT_SIZE(6) + PADDING_V(2) * 2)
|
||||
static constexpr int HEIGHT = 10;
|
||||
static constexpr float SLIDE_SPEED = 120.0F;
|
||||
|
||||
private:
|
||||
enum class Status { HIDDEN,
|
||||
RISING,
|
||||
ACTIVE,
|
||||
VANISHING };
|
||||
|
||||
// Singleton
|
||||
static RenderInfo* render_info;
|
||||
|
||||
// Constructor y destructor privados [SINGLETON]
|
||||
RenderInfo();
|
||||
~RenderInfo() = default;
|
||||
|
||||
Status status_{Status::HIDDEN};
|
||||
float y_{static_cast<float>(-HEIGHT)};
|
||||
};
|
||||
@@ -4,12 +4,15 @@
|
||||
|
||||
#include <algorithm> // Para max, min, transform
|
||||
#include <cctype> // Para toupper
|
||||
#include <cmath> // Para round, floor
|
||||
#include <cstring> // Para memcpy
|
||||
#include <fstream> // Para basic_ostream, operator<<, endl, basic_...
|
||||
#include <iostream> // Para cerr
|
||||
#include <iterator> // Para istreambuf_iterator, operator==
|
||||
#include <string> // Para char_traits, string, operator+, operator==
|
||||
|
||||
#include "core/input/mouse.hpp" // Para updateCursorVisibility
|
||||
#include "core/rendering/render_info.hpp" // Para RenderInfo
|
||||
#include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" // Para SDL3GPUShader
|
||||
#include "core/rendering/surface.hpp" // Para Surface, readPalFile
|
||||
#include "core/rendering/text.hpp" // Para Text
|
||||
@@ -17,6 +20,7 @@
|
||||
#include "core/resources/resource_helper.hpp" // Para ResourceHelper
|
||||
#include "core/resources/resource_list.hpp" // Para Asset, AssetType
|
||||
#include "game/options.hpp" // Para Options, options, OptionsVideo, Border
|
||||
#include "game/ui/console.hpp" // Para Console
|
||||
#include "game/ui/notifier.hpp" // Para Notifier
|
||||
|
||||
// [SINGLETON]
|
||||
@@ -38,18 +42,18 @@ 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();
|
||||
}
|
||||
if (Options::video.fullscreen) { SDL_HideCursor(); }
|
||||
|
||||
// Calcular tamaños y hacer .resize() de los buffers de píxeles
|
||||
adjustWindowSize();
|
||||
adjustRenderLogicalSize();
|
||||
updateZoomFactor();
|
||||
|
||||
// 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};
|
||||
// adjustWindowSize();
|
||||
current_palette_ = findPalette(Options::video.palette);
|
||||
|
||||
// Define el color del borde para el modo de pantalla completa
|
||||
border_color_ = static_cast<Uint8>(PaletteColor::BLACK);
|
||||
@@ -58,44 +62,50 @@ Screen::Screen()
|
||||
game_texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, Options::game.width, Options::game.height);
|
||||
if (game_texture_ == nullptr) {
|
||||
// Registrar el error si está habilitado
|
||||
if (Options::console) {
|
||||
std::cerr << "Error: game_texture_ could not be created!\nSDL Error: " << SDL_GetError() << '\n';
|
||||
}
|
||||
}
|
||||
SDL_SetTextureScaleMode(game_texture_, SDL_SCALEMODE_NEAREST);
|
||||
|
||||
// Crea la textura donde se dibuja el borde que rodea el area de juego
|
||||
border_texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, Options::game.width + (Options::video.border.width * 2), Options::game.height + (Options::video.border.height * 2));
|
||||
if (border_texture_ == nullptr) {
|
||||
// Registrar el error si está habilitado
|
||||
if (Options::console) {
|
||||
std::cerr << "Error: border_texture_ could not be created!\nSDL Error: " << SDL_GetError() << '\n';
|
||||
}
|
||||
}
|
||||
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,
|
||||
sortModeFromString(Options::video.palette_sort),
|
||||
game_surface_,
|
||||
border_surface_,
|
||||
[this]() {
|
||||
// Actualizar caché ARGB del borde cuando cambia la paleta
|
||||
if (border_is_solid_) {
|
||||
border_surface_->toARGBBuffer(border_pixel_buffer_.data());
|
||||
border_argb_color_ = border_pixel_buffer_[0];
|
||||
}
|
||||
});
|
||||
|
||||
// Cachear el color ARGB inicial del borde (borde sólido por defecto)
|
||||
border_surface_->toARGBBuffer(border_pixel_buffer_.data());
|
||||
border_argb_color_ = border_pixel_buffer_[0];
|
||||
|
||||
// Establece la surface que actuará como renderer para recibir las llamadas a render()
|
||||
renderer_surface_ = std::make_shared<std::shared_ptr<Surface>>(game_surface_);
|
||||
|
||||
// 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);
|
||||
@@ -144,8 +154,10 @@ void Screen::setVideoMode(bool mode) {
|
||||
|
||||
// Configura el modo de pantalla y ajusta la ventana
|
||||
SDL_SetWindowFullscreen(window_, Options::video.fullscreen);
|
||||
SDL_SyncWindow(window_);
|
||||
adjustWindowSize();
|
||||
adjustRenderLogicalSize();
|
||||
updateZoomFactor();
|
||||
}
|
||||
|
||||
// Camibia entre pantalla completa y ventana
|
||||
@@ -186,10 +198,30 @@ 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;
|
||||
border_surface_->clear(border_color_);
|
||||
|
||||
// Actualizar caché ARGB del borde sólido (ocurre una vez por habitación, no cada frame)
|
||||
border_surface_->toARGBBuffer(border_pixel_buffer_.data());
|
||||
border_argb_color_ = border_pixel_buffer_[0];
|
||||
border_is_solid_ = true;
|
||||
}
|
||||
|
||||
// Cambia entre borde visible y no visible
|
||||
@@ -206,14 +238,21 @@ void Screen::renderNotifications() const {
|
||||
}
|
||||
}
|
||||
|
||||
// Cambia el estado del PostFX
|
||||
void Screen::togglePostFX() {
|
||||
Options::video.postfx = !Options::video.postfx;
|
||||
// Activa/desactiva todos los shaders respetando el shader actualmente seleccionado
|
||||
void Screen::toggleShaders() {
|
||||
Options::video.shader.enabled = !Options::video.shader.enabled;
|
||||
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||
if (Options::video.postfx) {
|
||||
applyCurrentPostFXPreset();
|
||||
if (Options::video.shader.enabled) {
|
||||
// Activar: usar el shader actualmente seleccionado
|
||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
shader_backend_->setActiveShader(Rendering::ShaderType::CRTPI);
|
||||
applyCurrentCrtPiPreset();
|
||||
} else {
|
||||
// Pass-through: efectos a 0, el shader copia la textura sin modificar
|
||||
applyCurrentPostFXPreset();
|
||||
}
|
||||
} else {
|
||||
// Desactivar: pass-through con POSTFX (pipeline sin efecto)
|
||||
shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
|
||||
shader_backend_->setPostFXParams(Rendering::PostFXParams{});
|
||||
}
|
||||
} else {
|
||||
@@ -224,18 +263,28 @@ void Screen::togglePostFX() {
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
// Recarga el shader CrtPi del preset actual sin toggle
|
||||
void Screen::reloadCrtPi() {
|
||||
if (!shader_backend_) { return; }
|
||||
applyCurrentCrtPiPreset();
|
||||
}
|
||||
|
||||
// Actualiza la lógica de la clase (versión nueva con delta_time para escenas migradas)
|
||||
void Screen::update(float delta_time) {
|
||||
fps_.calculate(SDL_GetTicks());
|
||||
Notifier::get()->update(delta_time);
|
||||
if (Console::get() != nullptr) {
|
||||
Console::get()->update(delta_time);
|
||||
}
|
||||
if (RenderInfo::get() != nullptr) { RenderInfo::get()->update(delta_time); }
|
||||
Mouse::updateCursorVisibility();
|
||||
}
|
||||
|
||||
@@ -244,21 +293,41 @@ void Screen::adjustWindowSize() {
|
||||
window_width_ = Options::game.width + (Options::video.border.enabled ? Options::video.border.width * 2 : 0);
|
||||
window_height_ = Options::game.height + (Options::video.border.enabled ? Options::video.border.height * 2 : 0);
|
||||
|
||||
// Establece el nuevo tamaño
|
||||
// Reservamos memoria una sola vez.
|
||||
// Si el buffer es más pequeño que la superficie, crash asegurado.
|
||||
border_pixel_buffer_.resize(static_cast<size_t>(window_width_ * window_height_));
|
||||
game_pixel_buffer_.resize(static_cast<size_t>(Options::game.width * Options::game.height));
|
||||
|
||||
// border_pixel_buffer_ es el buffer que se sube a la GPU (tamaño total ventana).
|
||||
if (Options::video.border.enabled) {
|
||||
border_pixel_buffer_.resize(static_cast<size_t>(window_width_ * window_height_));
|
||||
}
|
||||
|
||||
// Lógica de centrado y redimensionado de ventana SDL
|
||||
if (static_cast<int>(Options::video.fullscreen) == 0) {
|
||||
int old_width;
|
||||
int old_height;
|
||||
SDL_GetWindowSize(window_, &old_width, &old_height);
|
||||
int old_w, old_h;
|
||||
SDL_GetWindowSize(window_, &old_w, &old_h);
|
||||
int old_x, old_y;
|
||||
SDL_GetWindowPosition(window_, &old_x, &old_y);
|
||||
|
||||
int old_pos_x;
|
||||
int old_pos_y;
|
||||
SDL_GetWindowPosition(window_, &old_pos_x, &old_pos_y);
|
||||
const int new_w = window_width_ * Options::window.zoom;
|
||||
const int new_h = window_height_ * Options::window.zoom;
|
||||
const int NEW_X = old_x + ((old_w - new_w) / 2);
|
||||
const int NEW_Y = old_y + ((old_h - new_h) / 2);
|
||||
|
||||
const int NEW_POS_X = old_pos_x + ((old_width - (window_width_ * Options::window.zoom)) / 2);
|
||||
const int NEW_POS_Y = old_pos_y + ((old_height - (window_height_ * Options::window.zoom)) / 2);
|
||||
SDL_SetWindowSize(window_, new_w, new_h);
|
||||
|
||||
SDL_SetWindowSize(window_, window_width_ * Options::window.zoom, window_height_ * Options::window.zoom);
|
||||
SDL_SetWindowPosition(window_, std::max(NEW_POS_X, WINDOWS_DECORATIONS), std::max(NEW_POS_Y, 0));
|
||||
// En Wayland, SDL_SetWindowPosition es ignorado por el compositor (limitación de
|
||||
// protocolo: el compositor controla la posición de ventanas toplevel). Solo se
|
||||
// aplica en X11/Windows/macOS donde el posicionado funciona correctamente.
|
||||
// SDL_SyncWindow garantiza que el resize esté completado antes de reposicionar
|
||||
// (evita el race condition en X11).
|
||||
SDL_SyncWindow(window_);
|
||||
const char* driver = SDL_GetCurrentVideoDriver();
|
||||
const bool is_wayland = (driver != nullptr && SDL_strcmp(driver, "wayland") == 0);
|
||||
if (!is_wayland) {
|
||||
SDL_SetWindowPosition(window_, std::max(NEW_X, WINDOWS_DECORATIONS), std::max(NEW_Y, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,58 +336,38 @@ void Screen::adjustRenderLogicalSize() {
|
||||
SDL_SetRenderLogicalPresentation(renderer_, window_width_, window_height_, Options::video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||
}
|
||||
|
||||
// Recalcula y almacena el factor de zoom. Llamar solo cuando SDL ya ha estabilizado el estado de la ventana.
|
||||
// En ventana: Options::window.zoom (siempre entero).
|
||||
// En fullscreen: mínimo de las escalas en ambos ejes; floor si integer scale está activo.
|
||||
void Screen::updateZoomFactor() {
|
||||
if (!Options::video.fullscreen) {
|
||||
zoom_factor_ = static_cast<float>(Options::window.zoom);
|
||||
return;
|
||||
}
|
||||
if (window_width_ == 0 || window_height_ == 0) {
|
||||
zoom_factor_ = 1.0F;
|
||||
return;
|
||||
}
|
||||
int pw{0}, ph{0};
|
||||
SDL_GetRenderOutputSize(renderer_, &pw, &ph);
|
||||
const float SCALE = std::min(static_cast<float>(pw) / static_cast<float>(window_width_),
|
||||
static_cast<float>(ph) / static_cast<float>(window_height_));
|
||||
zoom_factor_ = Options::video.integer_scale ? std::floor(SCALE) : SCALE;
|
||||
}
|
||||
|
||||
// Establece el renderizador para las surfaces
|
||||
void Screen::setRendererSurface(const std::shared_ptr<Surface>& surface) {
|
||||
(surface) ? renderer_surface_ = std::make_shared<std::shared_ptr<Surface>>(surface) : renderer_surface_ = std::make_shared<std::shared_ptr<Surface>>(game_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() {
|
||||
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);
|
||||
}
|
||||
|
||||
// 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() {
|
||||
void Screen::surfaceToTexture() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
if (Options::video.border.enabled) {
|
||||
border_surface_->copyToTexture(renderer_, border_texture_);
|
||||
game_surface_->copyToTexture(renderer_, border_texture_, nullptr, &game_surface_dstrect_);
|
||||
@@ -329,83 +378,81 @@ void Screen::surfaceToTexture() {
|
||||
|
||||
// Copia la textura al renderizador (o hace el present GPU)
|
||||
void Screen::textureToRenderer() {
|
||||
SDL_Texture* texture_to_render = Options::video.border.enabled ? border_texture_ : game_texture_;
|
||||
|
||||
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||
// ---- SDL3 GPU path: convertir Surface → ARGB → upload → PostFX/pass-through → present ----
|
||||
if (Options::video.border.enabled) {
|
||||
// El border_surface_ solo tiene el color de borde; hay que componer encima el game_surface_
|
||||
const int BORDER_W = static_cast<int>(border_surface_->getWidth());
|
||||
const int BORDER_H = static_cast<int>(border_surface_->getHeight());
|
||||
pixel_buffer_.resize(static_cast<size_t>(BORDER_W * BORDER_H));
|
||||
border_surface_->toARGBBuffer(pixel_buffer_.data());
|
||||
const int GAME_W = Options::game.width;
|
||||
const int GAME_H = Options::game.height;
|
||||
|
||||
// Compositar game_surface_ en la posición correcta dentro del buffer
|
||||
const int GAME_W = static_cast<int>(game_surface_->getWidth());
|
||||
const int GAME_H = static_cast<int>(game_surface_->getHeight());
|
||||
if (Options::video.border.enabled) {
|
||||
const int BORDER_W = window_width_;
|
||||
const int BORDER_H = window_height_;
|
||||
const int OFF_X = static_cast<int>(game_surface_dstrect_.x);
|
||||
const int OFF_Y = static_cast<int>(game_surface_dstrect_.y);
|
||||
std::vector<Uint32> game_pixels(static_cast<size_t>(GAME_W * GAME_H));
|
||||
game_surface_->toARGBBuffer(game_pixels.data());
|
||||
for (int y = 0; y < GAME_H; ++y) {
|
||||
for (int x = 0; x < GAME_W; ++x) {
|
||||
pixel_buffer_[static_cast<size_t>(((OFF_Y + y) * BORDER_W) + (OFF_X + x))] = game_pixels[static_cast<size_t>((y * GAME_W) + x)];
|
||||
|
||||
if (border_is_solid_) {
|
||||
// Path A: borde sólido (gameplay normal)
|
||||
// Rellena solo el marco con el color cacheado — sin lookups de paleta.
|
||||
// El área central (juego) se deja sin tocar; el overlay la sobreescribe igualmente.
|
||||
|
||||
// Franjas superior e inferior (ancho completo)
|
||||
std::fill_n(border_pixel_buffer_.data(), OFF_Y * BORDER_W, border_argb_color_);
|
||||
std::fill_n(&border_pixel_buffer_[(OFF_Y + GAME_H) * BORDER_W],
|
||||
(BORDER_H - OFF_Y - GAME_H) * BORDER_W,
|
||||
border_argb_color_);
|
||||
// Columnas laterales en las filas del área de juego
|
||||
for (int y = OFF_Y; y < OFF_Y + GAME_H; ++y) {
|
||||
std::fill_n(&border_pixel_buffer_[y * BORDER_W], OFF_X, border_argb_color_);
|
||||
std::fill_n(&border_pixel_buffer_[y * BORDER_W + OFF_X + GAME_W],
|
||||
BORDER_W - OFF_X - GAME_W,
|
||||
border_argb_color_);
|
||||
}
|
||||
}
|
||||
shader_backend_->uploadPixels(pixel_buffer_.data(), BORDER_W, BORDER_H);
|
||||
} else {
|
||||
const int GAME_W = static_cast<int>(game_surface_->getWidth());
|
||||
const int GAME_H = static_cast<int>(game_surface_->getHeight());
|
||||
pixel_buffer_.resize(static_cast<size_t>(GAME_W * GAME_H));
|
||||
game_surface_->toARGBBuffer(pixel_buffer_.data());
|
||||
shader_backend_->uploadPixels(pixel_buffer_.data(), GAME_W, GAME_H);
|
||||
// Path B: borde dinámico (escena de carga — bandas de colores animadas)
|
||||
// Conversión completa: la escena modifica border_surface_ cada frame
|
||||
border_surface_->toARGBBuffer(border_pixel_buffer_.data());
|
||||
}
|
||||
|
||||
// Overlay del juego sobre el centro del buffer (ambos paths)
|
||||
game_surface_->toARGBBuffer(game_pixel_buffer_.data());
|
||||
for (int y = 0; y < GAME_H; ++y) {
|
||||
const Uint32* src = &game_pixel_buffer_[y * GAME_W];
|
||||
Uint32* dst = &border_pixel_buffer_[(OFF_Y + y) * BORDER_W + OFF_X];
|
||||
std::memcpy(dst, src, GAME_W * sizeof(Uint32));
|
||||
}
|
||||
|
||||
shader_backend_->uploadPixels(border_pixel_buffer_.data(), BORDER_W, BORDER_H);
|
||||
} else {
|
||||
// Caso sin borde: subida directa simplificada
|
||||
game_surface_->toARGBBuffer(game_pixel_buffer_.data());
|
||||
shader_backend_->uploadPixels(game_pixel_buffer_.data(), GAME_W, GAME_H);
|
||||
}
|
||||
|
||||
shader_backend_->render();
|
||||
} else {
|
||||
// ---- SDL_Renderer path (fallback / no-shader) ----
|
||||
// Fallback SDL_Renderer (mantiene tu lógica de texturas SDL)
|
||||
SDL_Texture* tex = Options::video.border.enabled ? border_texture_ : game_texture_;
|
||||
SDL_SetRenderTarget(renderer_, nullptr);
|
||||
SDL_SetRenderDrawColor(renderer_, 0x00, 0x00, 0x00, 0xFF);
|
||||
SDL_RenderClear(renderer_);
|
||||
SDL_RenderTexture(renderer_, texture_to_render, nullptr, nullptr);
|
||||
SDL_RenderTexture(renderer_, tex, nullptr, nullptr);
|
||||
SDL_RenderPresent(renderer_);
|
||||
}
|
||||
}
|
||||
|
||||
// Renderiza todos los overlays
|
||||
// Renderiza todos los overlays (orden: último dibujado queda encima)
|
||||
void Screen::renderOverlays() {
|
||||
renderNotifications();
|
||||
#ifdef _DEBUG
|
||||
renderInfo();
|
||||
#endif
|
||||
renderNotifications(); // Notifier (abajo)
|
||||
if (RenderInfo::get() != nullptr) { RenderInfo::get()->render(); } // RenderInfo (medio)
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
// Muestra información por pantalla
|
||||
void Screen::renderInfo() const {
|
||||
if (show_fps_ && (Resource::Cache::get() != nullptr)) {
|
||||
auto text = Resource::Cache::get()->getText("smb2");
|
||||
auto color = static_cast<Uint8>(PaletteColor::YELLOW);
|
||||
auto shadow = static_cast<Uint8>(PaletteColor::BLACK);
|
||||
|
||||
// FPS con sombra
|
||||
const std::string FPS_TEXT = std::to_string(fps_.last_value) + " FPS";
|
||||
const int FPS_X = Options::game.width - text->length(FPS_TEXT) - 1;
|
||||
|
||||
text->writeColored(FPS_X + 1, 1, FPS_TEXT, shadow);
|
||||
text->writeColored(FPS_X, 0, FPS_TEXT, color);
|
||||
}
|
||||
}
|
||||
// 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(); }
|
||||
void Screen::nextPaletteSortMode() { palette_manager_->nextSortMode(); }
|
||||
void Screen::setPaletteSortMode(PaletteSortMode mode) { palette_manager_->setSortMode(mode); }
|
||||
auto Screen::getPaletteSortModeName() const -> std::string { return palette_manager_->getSortModeName(); }
|
||||
|
||||
// Limpia la game_surface_
|
||||
void Screen::clearSurface(Uint8 index) { game_surface_->clear(index); }
|
||||
@@ -428,9 +475,6 @@ void Screen::hide() { SDL_HideWindow(window_); }
|
||||
// Establece la visibilidad de las notificaciones
|
||||
void Screen::setNotificationsEnabled(bool value) { notifications_enabled_ = value; }
|
||||
|
||||
// Activa / desactiva el contador de FPS
|
||||
void Screen::toggleFPS() { show_fps_ = !show_fps_; }
|
||||
|
||||
// Alterna entre activar y desactivar el escalado entero
|
||||
void Screen::toggleIntegerScale() {
|
||||
Options::video.integer_scale = !Options::video.integer_scale;
|
||||
@@ -438,6 +482,7 @@ void Screen::toggleIntegerScale() {
|
||||
if (shader_backend_) {
|
||||
shader_backend_->setScaleMode(Options::video.integer_scale);
|
||||
}
|
||||
updateZoomFactor();
|
||||
}
|
||||
|
||||
// Alterna entre activar y desactivar el V-Sync
|
||||
@@ -452,34 +497,107 @@ void Screen::toggleVSync() {
|
||||
// Getters
|
||||
auto Screen::getRenderer() -> SDL_Renderer* { return renderer_; }
|
||||
auto Screen::getRendererSurface() -> std::shared_ptr<Surface> { return (*renderer_surface_); }
|
||||
auto Screen::getBorderSurface() -> std::shared_ptr<Surface> { return border_surface_; }
|
||||
auto Screen::getGameSurface() -> std::shared_ptr<Surface> { return game_surface_; }
|
||||
auto Screen::getBorderSurface() -> std::shared_ptr<Surface> {
|
||||
border_is_solid_ = false; // Modificación externa → modo borde dinámico
|
||||
return border_surface_;
|
||||
}
|
||||
|
||||
auto loadData(const std::string& filepath) -> std::vector<uint8_t> {
|
||||
// Load using ResourceHelper (supports both filesystem and pack)
|
||||
return Resource::Helper::loadFile(filepath);
|
||||
}
|
||||
|
||||
void Screen::setLinearUpscale(bool linear) {
|
||||
Options::video.supersampling.linear_upscale = linear;
|
||||
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||
shader_backend_->setLinearUpscale(linear);
|
||||
}
|
||||
}
|
||||
|
||||
void Screen::setDownscaleAlgo(int algo) {
|
||||
Options::video.supersampling.downscale_algo = algo;
|
||||
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||
shader_backend_->setDownscaleAlgo(algo);
|
||||
}
|
||||
}
|
||||
|
||||
auto Screen::getSsTextureSize() const -> std::pair<int, int> {
|
||||
if (!shader_backend_) { return {0, 0}; }
|
||||
return shader_backend_->getSsTextureSize();
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
// Aplica los parámetros del preset actual al backend de shaders
|
||||
void Screen::applyCurrentPostFXPreset() {
|
||||
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);
|
||||
Rendering::PostFXParams params{.vignette = p.vignette, .scanlines = p.scanlines, .chroma = p.chroma, .mask = p.mask, .gamma = p.gamma, .curvature = p.curvature, .bleeding = p.bleeding};
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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::video.shader.current_crtpi_preset)];
|
||||
Rendering::CrtPiParams params{
|
||||
.scanline_weight = p.scanline_weight,
|
||||
.scanline_gap_brightness = p.scanline_gap_brightness,
|
||||
.bloom_factor = p.bloom_factor,
|
||||
.input_gamma = p.input_gamma,
|
||||
.output_gamma = p.output_gamma,
|
||||
.mask_brightness = p.mask_brightness,
|
||||
.curvature_x = p.curvature_x,
|
||||
.curvature_y = p.curvature_y,
|
||||
.mask_type = p.mask_type,
|
||||
.enable_scanlines = p.enable_scanlines,
|
||||
.enable_multisample = p.enable_multisample,
|
||||
.enable_gamma = p.enable_gamma,
|
||||
.enable_curvature = p.enable_curvature,
|
||||
.enable_sharper = p.enable_sharper,
|
||||
};
|
||||
shader_backend_->setCrtPiParams(params);
|
||||
}
|
||||
}
|
||||
|
||||
// Cambia el shader de post-procesado activo y aplica el preset correspondiente
|
||||
void Screen::setActiveShader(Rendering::ShaderType type) {
|
||||
Options::video.shader.current_shader = type;
|
||||
if (!shader_backend_) { return; }
|
||||
if (!Options::video.shader.enabled) {
|
||||
// Shaders desactivados: guardar preferencia pero mantener pass-through
|
||||
shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
|
||||
shader_backend_->setPostFXParams(Rendering::PostFXParams{});
|
||||
return;
|
||||
}
|
||||
shader_backend_->setActiveShader(type);
|
||||
if (type == Rendering::ShaderType::CRTPI) {
|
||||
applyCurrentCrtPiPreset();
|
||||
} else {
|
||||
applyCurrentPostFXPreset();
|
||||
}
|
||||
}
|
||||
|
||||
// Cicla al siguiente shader disponible (preparado para futura UI)
|
||||
void Screen::nextShader() {
|
||||
const Rendering::ShaderType NEXT = (Options::video.shader.current_shader == Rendering::ShaderType::POSTFX)
|
||||
? Rendering::ShaderType::CRTPI
|
||||
: Rendering::ShaderType::POSTFX;
|
||||
setActiveShader(NEXT);
|
||||
}
|
||||
|
||||
// Inicializa los shaders
|
||||
// El device GPU se crea siempre (independientemente de postfx) para evitar
|
||||
// conflictos SDL_Renderer/SDL_GPU al hacer toggle F4 en Windows/Vulkan.
|
||||
@@ -488,23 +606,34 @@ void Screen::initShaders() {
|
||||
|
||||
if (!shader_backend_) {
|
||||
shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
|
||||
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();
|
||||
|
||||
// Propagar flags de vsync e integer scale al backend GPU
|
||||
// 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.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
|
||||
shader_backend_->setPostFXParams(Rendering::PostFXParams{});
|
||||
}
|
||||
|
||||
// Restaurar el shader activo guardado en config (y sus parámetros CrtPi si aplica)
|
||||
shader_backend_->setActiveShader(Options::video.shader.current_shader);
|
||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
applyCurrentCrtPiPreset();
|
||||
}
|
||||
}
|
||||
|
||||
// Obtiene información sobre la pantalla
|
||||
void Screen::getDisplayInfo() {
|
||||
void Screen::getDisplayInfo() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::cout << "\n** VIDEO SYSTEM **\n";
|
||||
|
||||
int num_displays = 0;
|
||||
@@ -609,7 +738,7 @@ auto Screen::initSDLVideo() -> bool {
|
||||
}
|
||||
|
||||
// Crea el objeto de texto
|
||||
void Screen::createText() {
|
||||
void Screen::createText() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
// Carga la surface de la fuente directamente del archivo
|
||||
auto surface = std::make_shared<Surface>(Resource::List::get()->get("aseprite.gif"));
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
#include <cstddef> // Para size_t
|
||||
#include <memory> // Para shared_ptr, __shared_ptr_access
|
||||
#include <string> // Para string
|
||||
#include <utility> // Para std::pair
|
||||
#include <vector> // Para vector
|
||||
|
||||
#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;
|
||||
namespace Rendering {
|
||||
class ShaderBackend;
|
||||
}
|
||||
|
||||
class Screen {
|
||||
public:
|
||||
@@ -42,6 +42,7 @@ class Screen {
|
||||
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
|
||||
|
||||
@@ -55,22 +56,39 @@ class Screen {
|
||||
// Paletas y PostFX
|
||||
void nextPalette(); // Cambia a la siguiente paleta
|
||||
void previousPalette(); // Cambia a la paleta anterior
|
||||
void setPalete(); // Establece la paleta actual
|
||||
void togglePostFX(); // Cambia el estado del PostFX
|
||||
auto setPaletteByName(const std::string& name) -> bool; // Cambia a paleta por nombre; false si no existe
|
||||
[[nodiscard]] auto getPaletteNames() const -> std::vector<std::string>; // Nombres disponibles (minúsculas, sin .pal)
|
||||
[[nodiscard]] auto getPalettePrettyName() const -> std::string; // Nombre actual con guiones sustituidos por espacios
|
||||
void nextPaletteSortMode(); // Cicla al siguiente modo de ordenación de paleta
|
||||
void setPaletteSortMode(PaletteSortMode mode); // Establece modo de ordenación concreto
|
||||
[[nodiscard]] auto getPaletteSortModeName() const -> std::string; // Nombre del modo de ordenación actual
|
||||
void toggleShaders(); // Activa/desactiva todos los shaders respetando current_shader
|
||||
void 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
|
||||
void setNotificationsEnabled(bool value); // Establece la visibilidad de las notificaciones
|
||||
void toggleFPS(); // Activa o desactiva el contador de FPS
|
||||
void updateZoomFactor(); // Recalcula y almacena el factor de zoom real
|
||||
|
||||
// Getters
|
||||
auto getRenderer() -> SDL_Renderer*;
|
||||
auto getRendererSurface() -> std::shared_ptr<Surface>;
|
||||
auto getBorderSurface() -> std::shared_ptr<Surface>;
|
||||
auto getGameSurface() -> std::shared_ptr<Surface>;
|
||||
[[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:
|
||||
// Estructuras
|
||||
@@ -110,14 +128,12 @@ class Screen {
|
||||
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 actual al backend
|
||||
void renderInfo() const; // Muestra información por pantalla
|
||||
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
|
||||
@@ -139,15 +155,23 @@ class Screen {
|
||||
std::unique_ptr<Rendering::ShaderBackend> shader_backend_; // Backend de shaders (OpenGL/Metal/Vulkan)
|
||||
std::shared_ptr<Text> text_; // Objeto para escribir texto
|
||||
|
||||
// Buffers persistentes para evitar .resize() cada frame
|
||||
std::vector<Uint32> game_pixel_buffer_; // Textura de juego
|
||||
std::vector<Uint32> border_pixel_buffer_; // Textura de borde (composición final borde+juego)
|
||||
|
||||
// Caché del borde sólido (gameplay normal)
|
||||
bool border_is_solid_{true}; // true = borde de color sólido; false = borde dinámico (carga)
|
||||
Uint32 border_argb_color_{0}; // Color ARGB pre-convertido del borde sólido
|
||||
|
||||
// Configuración de ventana y pantalla
|
||||
int window_width_{0}; // Ancho de la pantalla o ventana
|
||||
int window_height_{0}; // Alto de la pantalla o ventana
|
||||
float zoom_factor_{1.0f}; // Factor de zoom calculado (alto físico / alto lógico)
|
||||
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
|
||||
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
|
||||
@@ -156,11 +180,5 @@ class Screen {
|
||||
|
||||
// Shaders
|
||||
std::string info_resolution_; // Texto con la información de la pantalla
|
||||
std::vector<Uint32> pixel_buffer_; // Buffer intermedio para SDL3GPU path (surface → ARGB)
|
||||
|
||||
#ifdef _DEBUG
|
||||
bool show_fps_{true}; // Indica si ha de mostrar el contador de FPS
|
||||
#else
|
||||
bool show_fps_{false}; // Indica si ha de mostrar el contador de FPS
|
||||
#endif
|
||||
std::string gpu_driver_; // Nombre del driver GPU (SDL3GPU), capturado en initShaders()
|
||||
};
|
||||
10362
source/core/rendering/sdl3gpu/crtpi_frag_spv.h
Normal file
10362
source/core/rendering/sdl3gpu/crtpi_frag_spv.h
Normal file
File diff suppressed because it is too large
Load Diff
4253
source/core/rendering/sdl3gpu/downscale_frag_spv.h
Normal file
4253
source/core/rendering/sdl3gpu/downscale_frag_spv.h
Normal file
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
@@ -20,7 +20,42 @@ struct PostFXUniforms {
|
||||
float pixel_scale; // physical pixels per logical pixel (vh / tex_height_)
|
||||
float time; // seconds since SDL init (SDL_GetTicks() / 1000.0f)
|
||||
float oversample; // supersampling factor (1.0 = off, 3.0 = 3×SS)
|
||||
float pad1; // padding — keep struct at 48 bytes (3 × 16)
|
||||
float flicker; // 0 = off, 1 = phosphor flicker ~50 Hz — keep struct at 48 bytes (3 × 16)
|
||||
};
|
||||
|
||||
// CrtPi uniforms pushed to fragment stage each frame.
|
||||
// Must match the MSL struct and GLSL uniform block layout.
|
||||
// 14 fields (8 floats + 6 ints) + 2 floats (texture size) = 16 fields = 64 bytes — 4 × 16-byte alignment.
|
||||
struct CrtPiUniforms {
|
||||
// vec4 #0
|
||||
float scanline_weight; // Ajuste gaussiano (default 6.0)
|
||||
float scanline_gap_brightness; // Brillo mínimo entre scanlines (default 0.12)
|
||||
float bloom_factor; // Factor brillo zonas iluminadas (default 3.5)
|
||||
float input_gamma; // Gamma de entrada (default 2.4)
|
||||
// vec4 #1
|
||||
float output_gamma; // Gamma de salida (default 2.2)
|
||||
float mask_brightness; // Brillo sub-píxeles máscara (default 0.80)
|
||||
float curvature_x; // Distorsión barrel X (default 0.05)
|
||||
float curvature_y; // Distorsión barrel Y (default 0.10)
|
||||
// vec4 #2
|
||||
int mask_type; // 0=ninguna, 1=verde/magenta, 2=RGB fósforo
|
||||
int enable_scanlines; // 0 = off, 1 = on
|
||||
int enable_multisample; // 0 = off, 1 = on (antialiasing analítico)
|
||||
int enable_gamma; // 0 = off, 1 = on
|
||||
// vec4 #3
|
||||
int enable_curvature; // 0 = off, 1 = on
|
||||
int enable_sharper; // 0 = off, 1 = on
|
||||
float texture_width; // Ancho del canvas en píxeles (inyectado en render)
|
||||
float texture_height; // Alto del canvas en píxeles (inyectado en render)
|
||||
};
|
||||
|
||||
// Downscale uniforms pushed to the Lanczos downscale fragment stage.
|
||||
// 1 int + 3 floats = 16 bytes — meets Metal/Vulkan alignment.
|
||||
struct DownscaleUniforms {
|
||||
int algorithm; // 0 = Lanczos2 (ventana 2), 1 = Lanczos3 (ventana 3)
|
||||
float pad0;
|
||||
float pad1;
|
||||
float pad2;
|
||||
};
|
||||
|
||||
namespace Rendering {
|
||||
@@ -47,6 +82,10 @@ namespace Rendering {
|
||||
void cleanup() final; // Libera pipeline/texturas pero mantiene el device vivo
|
||||
void destroy(); // Limpieza completa (device + swapchain); llamar solo al cerrar
|
||||
[[nodiscard]] auto isHardwareAccelerated() const -> bool override { return is_initialized_; }
|
||||
[[nodiscard]] auto getDriverName() const -> std::string override { return driver_name_; }
|
||||
|
||||
// Establece el driver GPU preferido (vacío = auto). Debe llamarse antes de init().
|
||||
void setPreferredDriver(const std::string& driver) override { preferred_driver_ = driver; }
|
||||
|
||||
// Sube píxeles ARGB8888 desde CPU; llamado antes de render()
|
||||
void uploadPixels(const Uint32* pixels, int width, int height) override;
|
||||
@@ -63,6 +102,24 @@ namespace Rendering {
|
||||
// Establece factor de supersampling (1 = off, 3 = 3×SS)
|
||||
void setOversample(int factor) override;
|
||||
|
||||
// Activa/desactiva interpolación LINEAR en el upscale (false = NEAREST)
|
||||
void setLinearUpscale(bool linear) override;
|
||||
|
||||
// Selecciona algoritmo de downscale: 0=bilinear legacy, 1=Lanczos2, 2=Lanczos3
|
||||
void setDownscaleAlgo(int algo) override;
|
||||
|
||||
// Devuelve las dimensiones de la textura de supersampling (0,0 si SS desactivado)
|
||||
[[nodiscard]] auto getSsTextureSize() const -> std::pair<int, int> override;
|
||||
|
||||
// Selecciona el shader de post-procesado activo (POSTFX o CRTPI)
|
||||
void setActiveShader(ShaderType type) override;
|
||||
|
||||
// Actualiza los parámetros del shader CRT-Pi
|
||||
void setCrtPiParams(const CrtPiParams& p) override;
|
||||
|
||||
// Devuelve el shader activo
|
||||
[[nodiscard]] auto getActiveShader() const -> ShaderType override { return active_shader_; }
|
||||
|
||||
private:
|
||||
static auto createShaderMSL(SDL_GPUDevice* device,
|
||||
const char* msl_source,
|
||||
@@ -80,27 +137,42 @@ namespace Rendering {
|
||||
Uint32 num_uniform_buffers) -> SDL_GPUShader*;
|
||||
|
||||
auto createPipeline() -> bool;
|
||||
auto reinitTexturesAndBuffer() -> bool; // Recrea textura y buffer con oversample actual
|
||||
auto createCrtPiPipeline() -> bool; // Pipeline dedicado para el shader CrtPi
|
||||
auto reinitTexturesAndBuffer() -> bool; // Recrea scene_texture_ y upload_buffer_
|
||||
auto recreateScaledTexture(int factor) -> bool; // Recrea scaled_texture_ para factor dado
|
||||
static auto calcSsFactor(float zoom) -> int; // Primer múltiplo de 3 >= zoom (mín 3)
|
||||
// Devuelve el mejor present mode disponible: IMMEDIATE > MAILBOX > VSYNC
|
||||
[[nodiscard]] auto bestPresentMode(bool vsync) const -> SDL_GPUPresentMode;
|
||||
|
||||
SDL_Window* window_ = nullptr;
|
||||
SDL_GPUDevice* device_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* pipeline_ = nullptr;
|
||||
SDL_GPUTexture* scene_texture_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* pipeline_ = nullptr; // PostFX pass (→ swapchain o → postfx_texture_)
|
||||
SDL_GPUGraphicsPipeline* crtpi_pipeline_ = nullptr; // CrtPi pass (→ swapchain directo, sin SS)
|
||||
SDL_GPUGraphicsPipeline* postfx_offscreen_pipeline_ = nullptr; // PostFX → postfx_texture_ (B8G8R8A8, solo con Lanczos)
|
||||
SDL_GPUGraphicsPipeline* upscale_pipeline_ = nullptr; // Upscale pass (solo con SS)
|
||||
SDL_GPUGraphicsPipeline* downscale_pipeline_ = nullptr; // Lanczos downscale (solo con SS + algo > 0)
|
||||
SDL_GPUTexture* scene_texture_ = nullptr; // Canvas del juego (game_width_ × game_height_)
|
||||
SDL_GPUTexture* scaled_texture_ = nullptr; // Upscale target (game×factor), solo con SS
|
||||
SDL_GPUTexture* postfx_texture_ = nullptr; // PostFX output a resolución escalada, solo con Lanczos
|
||||
SDL_GPUTransferBuffer* upload_buffer_ = nullptr;
|
||||
SDL_GPUSampler* sampler_ = nullptr; // NEAREST — para path sin supersampling
|
||||
SDL_GPUSampler* linear_sampler_ = nullptr; // LINEAR — para path con supersampling
|
||||
SDL_GPUSampler* sampler_ = nullptr; // NEAREST
|
||||
SDL_GPUSampler* linear_sampler_ = nullptr; // LINEAR
|
||||
|
||||
PostFXUniforms uniforms_{.vignette_strength = 0.6F, .chroma_strength = 0.15F, .scanline_strength = 0.7F, .screen_height = 192.0F, .pixel_scale = 1.0F, .oversample = 1.0F};
|
||||
CrtPiUniforms crtpi_uniforms_{.scanline_weight = 6.0F, .scanline_gap_brightness = 0.12F, .bloom_factor = 3.5F, .input_gamma = 2.4F, .output_gamma = 2.2F, .mask_brightness = 0.80F, .curvature_x = 0.05F, .curvature_y = 0.10F, .mask_type = 2, .enable_scanlines = 1, .enable_multisample = 1, .enable_gamma = 1};
|
||||
ShaderType active_shader_ = ShaderType::POSTFX; // Shader de post-procesado activo
|
||||
|
||||
int game_width_ = 0; // Dimensiones originales del canvas (sin SS)
|
||||
int game_width_ = 0; // Dimensiones originales del canvas
|
||||
int game_height_ = 0;
|
||||
int tex_width_ = 0; // Dimensiones de la textura GPU (game × oversample_)
|
||||
int tex_height_ = 0;
|
||||
int oversample_ = 1; // Factor SS actual (1 o 3)
|
||||
float baked_scanline_strength_ = 0.0F; // Guardado para hornear en CPU
|
||||
int ss_factor_ = 0; // Factor SS activo (3, 6, 9...) o 0 si SS desactivado
|
||||
int oversample_ = 1; // SS on/off (1 = off, >1 = on)
|
||||
int downscale_algo_ = 1; // 0 = bilinear legacy, 1 = Lanczos2, 2 = Lanczos3
|
||||
std::string driver_name_;
|
||||
std::string preferred_driver_; // Driver preferido; vacío = auto (SDL elige)
|
||||
bool is_initialized_ = false;
|
||||
bool vsync_ = true;
|
||||
bool integer_scale_ = false;
|
||||
bool linear_upscale_ = false; // Upscale NEAREST (false) o LINEAR (true)
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
|
||||
633
source/core/rendering/sdl3gpu/upscale_frag_spv.h
Normal file
633
source/core/rendering/sdl3gpu/upscale_frag_spv.h
Normal file
@@ -0,0 +1,633 @@
|
||||
#pragma once
|
||||
#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};
|
||||
static const size_t kupscale_frag_spv_size = 628;
|
||||
@@ -3,9 +3,14 @@
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace Rendering {
|
||||
|
||||
/** @brief Identificador del shader de post-procesado activo */
|
||||
enum class ShaderType { POSTFX,
|
||||
CRTPI };
|
||||
|
||||
/**
|
||||
* @brief Parámetros de intensidad de los efectos PostFX
|
||||
* Definido a nivel de namespace para facilitar el uso desde subclases y screen.cpp
|
||||
@@ -18,6 +23,28 @@ namespace Rendering {
|
||||
float gamma = 0.0F; // Corrección gamma (blend 0=off, 1=full)
|
||||
float curvature = 0.0F; // Curvatura barrel CRT
|
||||
float bleeding = 0.0F; // Sangrado de color NTSC
|
||||
float flicker = 0.0F; // Parpadeo de fósforo CRT ~50 Hz
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Parámetros del shader CRT-Pi (algoritmo de scanlines continuas)
|
||||
* Diferente al PostFX: usa pesos gaussianos por distancia subpixel y bloom.
|
||||
*/
|
||||
struct CrtPiParams {
|
||||
float scanline_weight{6.0F}; // Ajuste gaussiano (mayor = scanlines más estrechas)
|
||||
float scanline_gap_brightness{0.12F}; // Brillo mínimo en las ranuras entre scanlines
|
||||
float bloom_factor{3.5F}; // Factor de brillo para zonas iluminadas
|
||||
float input_gamma{2.4F}; // Gamma de entrada (linealización)
|
||||
float output_gamma{2.2F}; // Gamma de salida (codificación)
|
||||
float mask_brightness{0.80F}; // Sub-píxeles tenues en la máscara de fósforo
|
||||
float curvature_x{0.05F}; // Distorsión barrel eje X
|
||||
float curvature_y{0.10F}; // Distorsión barrel eje Y
|
||||
int mask_type{2}; // 0=ninguna, 1=verde/magenta, 2=RGB fósforo
|
||||
bool enable_scanlines{true}; // Activar efecto de scanlines
|
||||
bool enable_multisample{true}; // Antialiasing analítico de scanlines
|
||||
bool enable_gamma{true}; // Corrección gamma
|
||||
bool enable_curvature{false}; // Distorsión barrel CRT
|
||||
bool enable_sharper{false}; // Submuestreo más nítido (modo SHARPER)
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -89,11 +116,60 @@ namespace Rendering {
|
||||
*/
|
||||
virtual void setOversample(int /*factor*/) {}
|
||||
|
||||
/**
|
||||
* @brief Activa/desactiva interpolación LINEAR en el paso de upscale (SS).
|
||||
* Por defecto NEAREST (false). Solo tiene efecto con supersampling activo.
|
||||
*/
|
||||
virtual void setLinearUpscale(bool /*linear*/) {}
|
||||
[[nodiscard]] virtual auto isLinearUpscale() const -> bool { return false; }
|
||||
|
||||
/**
|
||||
* @brief Selecciona el algoritmo de downscale tras el PostFX (SS activo).
|
||||
* 0 = bilinear legacy (comportamiento actual, sin textura intermedia),
|
||||
* 1 = Lanczos2 (ventana 2, ~25 muestras), 2 = Lanczos3 (ventana 3, ~49 muestras).
|
||||
*/
|
||||
virtual void setDownscaleAlgo(int /*algo*/) {}
|
||||
[[nodiscard]] virtual auto getDownscaleAlgo() const -> int { return 0; }
|
||||
|
||||
/**
|
||||
* @brief Devuelve las dimensiones de la textura de supersampling.
|
||||
* @return Par (ancho, alto) en píxeles; (0, 0) si SS está desactivado.
|
||||
*/
|
||||
[[nodiscard]] virtual auto getSsTextureSize() const -> std::pair<int, int> { return {0, 0}; }
|
||||
|
||||
/**
|
||||
* @brief Verifica si el backend está usando aceleración por hardware
|
||||
* @return true si usa aceleración (OpenGL/Metal/Vulkan)
|
||||
*/
|
||||
[[nodiscard]] virtual auto isHardwareAccelerated() const -> bool = 0;
|
||||
|
||||
/**
|
||||
* @brief Nombre del driver GPU activo (p.ej. "vulkan", "metal", "direct3d12")
|
||||
* @return Cadena vacía si no disponible
|
||||
*/
|
||||
[[nodiscard]] virtual auto getDriverName() const -> std::string { return {}; }
|
||||
|
||||
/**
|
||||
* @brief Establece el driver GPU preferido antes de init().
|
||||
* Vacío = selección automática de SDL. Implementado en SDL3GPUShader.
|
||||
*/
|
||||
virtual void setPreferredDriver(const std::string& /*driver*/) {}
|
||||
|
||||
/**
|
||||
* @brief Selecciona el shader de post-procesado activo (POSTFX o CRTPI).
|
||||
* Debe llamarse antes de render(). No recrea pipelines.
|
||||
*/
|
||||
virtual void setActiveShader(ShaderType /*type*/) {}
|
||||
|
||||
/**
|
||||
* @brief Establece los parámetros del shader CRT-Pi.
|
||||
*/
|
||||
virtual void setCrtPiParams(const CrtPiParams& /*p*/) {}
|
||||
|
||||
/**
|
||||
* @brief Devuelve el shader de post-procesado activo.
|
||||
*/
|
||||
[[nodiscard]] virtual auto getActiveShader() const -> ShaderType { return ShaderType::POSTFX; }
|
||||
};
|
||||
|
||||
} // namespace Rendering
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "core/rendering/surface_animated_sprite.hpp"
|
||||
#include "core/rendering/sprite/animated_sprite.hpp"
|
||||
|
||||
#include <cstddef> // Para size_t
|
||||
#include <fstream> // Para basic_ostream, basic_istream, operator<<, basic...
|
||||
@@ -16,7 +16,7 @@
|
||||
// Helper: Convierte un nodo YAML de frames (array) a vector de SDL_FRect
|
||||
auto convertYAMLFramesToRects(const fkyaml::node& frames_node, float frame_width, float frame_height, int frames_per_row, int max_tiles) -> std::vector<SDL_FRect> {
|
||||
std::vector<SDL_FRect> frames;
|
||||
SDL_FRect rect = {0.0F, 0.0F, frame_width, frame_height};
|
||||
SDL_FRect rect = {.x = 0.0F, .y = 0.0F, .w = frame_width, .h = frame_height};
|
||||
|
||||
for (const auto& frame_index_node : frames_node) {
|
||||
const int NUM_TILE = frame_index_node.get_value<int>();
|
||||
@@ -31,7 +31,7 @@ auto convertYAMLFramesToRects(const fkyaml::node& frames_node, float frame_width
|
||||
}
|
||||
|
||||
// Carga las animaciones desde un fichero YAML
|
||||
auto SurfaceAnimatedSprite::loadAnimationsFromYAML(const std::string& file_path, std::shared_ptr<Surface>& surface, float& frame_width, float& frame_height) -> std::vector<AnimationData> {
|
||||
auto AnimatedSprite::loadAnimationsFromYAML(const std::string& file_path, std::shared_ptr<Surface>& surface, float& frame_width, float& frame_height) -> std::vector<AnimationData> { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::vector<AnimationData> animations;
|
||||
|
||||
// Extract filename for logging
|
||||
@@ -124,7 +124,7 @@ auto SurfaceAnimatedSprite::loadAnimationsFromYAML(const std::string& file_path,
|
||||
}
|
||||
|
||||
// Constructor con bytes YAML del cache (parsing lazy)
|
||||
SurfaceAnimatedSprite::SurfaceAnimatedSprite(const AnimationResource& cached_data) {
|
||||
AnimatedSprite::AnimatedSprite(const AnimationResource& cached_data) {
|
||||
// Parsear YAML desde los bytes cargados en cache
|
||||
std::string yaml_content(cached_data.yaml_data.begin(), cached_data.yaml_data.end());
|
||||
|
||||
@@ -215,8 +215,8 @@ SurfaceAnimatedSprite::SurfaceAnimatedSprite(const AnimationResource& cached_dat
|
||||
}
|
||||
|
||||
// Constructor per a subclasses amb surface directa (sense YAML)
|
||||
SurfaceAnimatedSprite::SurfaceAnimatedSprite(std::shared_ptr<Surface> surface, SDL_FRect pos)
|
||||
: SurfaceMovingSprite(std::move(surface), pos) {
|
||||
AnimatedSprite::AnimatedSprite(std::shared_ptr<Surface> surface, SDL_FRect pos)
|
||||
: MovingSprite(std::move(surface), pos) {
|
||||
// animations_ queda buit (protegit per el guard de animate())
|
||||
if (surface_) {
|
||||
clip_ = {.x = 0, .y = 0, .w = surface_->getWidth(), .h = surface_->getHeight()};
|
||||
@@ -224,7 +224,7 @@ SurfaceAnimatedSprite::SurfaceAnimatedSprite(std::shared_ptr<Surface> surface, S
|
||||
}
|
||||
|
||||
// Obtiene el indice de la animación a partir del nombre
|
||||
auto SurfaceAnimatedSprite::getIndex(const std::string& name) -> int {
|
||||
auto AnimatedSprite::getIndex(const std::string& name) -> int { // NOLINT(readability-convert-member-functions-to-static)
|
||||
auto index = -1;
|
||||
|
||||
for (const auto& a : animations_) {
|
||||
@@ -238,7 +238,7 @@ auto SurfaceAnimatedSprite::getIndex(const std::string& name) -> int {
|
||||
}
|
||||
|
||||
// Calcula el frame correspondiente a la animación (time-based)
|
||||
void SurfaceAnimatedSprite::animate(float delta_time) {
|
||||
void AnimatedSprite::animate(float delta_time) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
if (animations_.empty()) { return; }
|
||||
if (animations_[current_animation_].speed <= 0.0F) {
|
||||
return;
|
||||
@@ -288,12 +288,12 @@ void SurfaceAnimatedSprite::animate(float delta_time) {
|
||||
}
|
||||
|
||||
// Comprueba si ha terminado la animación
|
||||
auto SurfaceAnimatedSprite::animationIsCompleted() -> bool {
|
||||
auto AnimatedSprite::animationIsCompleted() -> bool {
|
||||
return animations_[current_animation_].completed;
|
||||
}
|
||||
|
||||
// Establece la animacion actual
|
||||
void SurfaceAnimatedSprite::setCurrentAnimation(const std::string& name) {
|
||||
void AnimatedSprite::setCurrentAnimation(const std::string& name) {
|
||||
const auto NEW_ANIMATION = getIndex(name);
|
||||
if (current_animation_ != NEW_ANIMATION) {
|
||||
current_animation_ = NEW_ANIMATION;
|
||||
@@ -305,7 +305,7 @@ void SurfaceAnimatedSprite::setCurrentAnimation(const std::string& name) {
|
||||
}
|
||||
|
||||
// Establece la animacion actual
|
||||
void SurfaceAnimatedSprite::setCurrentAnimation(int index) {
|
||||
void AnimatedSprite::setCurrentAnimation(int index) {
|
||||
const auto NEW_ANIMATION = index;
|
||||
if (current_animation_ != NEW_ANIMATION) {
|
||||
current_animation_ = NEW_ANIMATION;
|
||||
@@ -317,20 +317,20 @@ void SurfaceAnimatedSprite::setCurrentAnimation(int index) {
|
||||
}
|
||||
|
||||
// Actualiza las variables del objeto (time-based)
|
||||
void SurfaceAnimatedSprite::update(float delta_time) {
|
||||
void AnimatedSprite::update(float delta_time) {
|
||||
animate(delta_time);
|
||||
SurfaceMovingSprite::update(delta_time);
|
||||
MovingSprite::update(delta_time);
|
||||
}
|
||||
|
||||
// Reinicia la animación
|
||||
void SurfaceAnimatedSprite::resetAnimation() {
|
||||
void AnimatedSprite::resetAnimation() {
|
||||
animations_[current_animation_].current_frame = 0;
|
||||
animations_[current_animation_].accumulated_time = 0.0F;
|
||||
animations_[current_animation_].completed = false;
|
||||
}
|
||||
|
||||
// Establece el frame actual de la animación
|
||||
void SurfaceAnimatedSprite::setCurrentAnimationFrame(int num) {
|
||||
void AnimatedSprite::setCurrentAnimationFrame(int num) {
|
||||
// Descarta valores fuera de rango
|
||||
if (num < 0 || num >= static_cast<int>(animations_[current_animation_].frames.size())) {
|
||||
num = 0;
|
||||
@@ -7,12 +7,12 @@
|
||||
#include <utility>
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "core/rendering/surface_moving_sprite.hpp" // Para SMovingSprite
|
||||
#include "core/rendering/sprite/moving_sprite.hpp" // Para SMovingSprite
|
||||
#include "core/resources/resource_types.hpp" // Para AnimationResource
|
||||
|
||||
class Surface;
|
||||
|
||||
class SurfaceAnimatedSprite : public SurfaceMovingSprite {
|
||||
class AnimatedSprite : public MovingSprite {
|
||||
public:
|
||||
using Animations = std::vector<std::string>; // Tipo para lista de animaciones
|
||||
|
||||
@@ -31,9 +31,9 @@ class SurfaceAnimatedSprite : public SurfaceMovingSprite {
|
||||
static auto loadAnimationsFromYAML(const std::string& file_path, std::shared_ptr<Surface>& surface, float& frame_width, float& frame_height) -> std::vector<AnimationData>; // Carga las animaciones desde fichero YAML
|
||||
|
||||
// Constructores
|
||||
explicit SurfaceAnimatedSprite(const AnimationResource& cached_data); // Constructor con datos pre-cargados del cache
|
||||
explicit AnimatedSprite(const AnimationResource& cached_data); // Constructor con datos pre-cargados del cache
|
||||
|
||||
~SurfaceAnimatedSprite() override = default; // Destructor
|
||||
~AnimatedSprite() override = default; // Destructor
|
||||
|
||||
void update(float delta_time) override; // Actualiza las variables del objeto (time-based)
|
||||
|
||||
@@ -47,13 +47,11 @@ class SurfaceAnimatedSprite : public SurfaceMovingSprite {
|
||||
void setCurrentAnimation(int index = 0); // Establece la animación actual por índice
|
||||
void resetAnimation(); // Reinicia la animación
|
||||
void setCurrentAnimationFrame(int num); // Establece el frame actual de la animación
|
||||
void animate(float delta_time); // Calcula el frame correspondiente a la animación actual (time-based)
|
||||
|
||||
protected:
|
||||
// Constructor per a ús de subclasses que gestionen la surface directament (sense YAML)
|
||||
SurfaceAnimatedSprite(std::shared_ptr<Surface> surface, SDL_FRect pos);
|
||||
|
||||
// Métodos protegidos
|
||||
void animate(float delta_time); // Calcula el frame correspondiente a la animación actual (time-based)
|
||||
AnimatedSprite(std::shared_ptr<Surface> surface, SDL_FRect pos);
|
||||
|
||||
private:
|
||||
// Variables miembro
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "core/rendering/surface_dissolve_sprite.hpp"
|
||||
#include "core/rendering/sprite/dissolve_sprite.hpp"
|
||||
|
||||
#include <algorithm> // Para min
|
||||
#include <cstdint> // Para uint32_t
|
||||
@@ -15,7 +15,7 @@ static auto pixelRank(int col, int row) -> float {
|
||||
}
|
||||
|
||||
// Rang per a un píxel tenint en compte direcció (70% direccional + 30% aleatori)
|
||||
auto SurfaceDissolveSprite::computePixelRank(int col, int row, int frame_h, DissolveDirection dir) -> float {
|
||||
auto DissolveSprite::computePixelRank(int col, int row, int frame_h, DissolveDirection dir) -> float {
|
||||
const float RANDOM = pixelRank(col, row);
|
||||
if (dir == DissolveDirection::NONE || frame_h <= 0) {
|
||||
return RANDOM;
|
||||
@@ -32,8 +32,8 @@ auto SurfaceDissolveSprite::computePixelRank(int col, int row, int frame_h, Diss
|
||||
}
|
||||
|
||||
// Constructor per a surface directa (sense AnimationResource)
|
||||
SurfaceDissolveSprite::SurfaceDissolveSprite(std::shared_ptr<Surface> surface, SDL_FRect pos)
|
||||
: SurfaceAnimatedSprite(std::move(surface), pos) {
|
||||
DissolveSprite::DissolveSprite(std::shared_ptr<Surface> surface, SDL_FRect pos)
|
||||
: AnimatedSprite(std::move(surface), pos) {
|
||||
if (surface_) {
|
||||
const int W = static_cast<int>(surface_->getWidth());
|
||||
const int H = static_cast<int>(surface_->getHeight());
|
||||
@@ -44,8 +44,8 @@ SurfaceDissolveSprite::SurfaceDissolveSprite(std::shared_ptr<Surface> surface, S
|
||||
}
|
||||
|
||||
// Constructor
|
||||
SurfaceDissolveSprite::SurfaceDissolveSprite(const AnimationResource& data)
|
||||
: SurfaceAnimatedSprite(data) {
|
||||
DissolveSprite::DissolveSprite(const AnimationResource& data)
|
||||
: AnimatedSprite(data) {
|
||||
if (surface_) {
|
||||
const int W = static_cast<int>(surface_->getWidth());
|
||||
const int H = static_cast<int>(surface_->getHeight());
|
||||
@@ -57,7 +57,7 @@ SurfaceDissolveSprite::SurfaceDissolveSprite(const AnimationResource& data)
|
||||
}
|
||||
|
||||
// Reconstrueix la surface_display_ filtrant píxels per progress_
|
||||
void SurfaceDissolveSprite::rebuildDisplaySurface() {
|
||||
void DissolveSprite::rebuildDisplaySurface() {
|
||||
if (!surface_ || !surface_display_) {
|
||||
return;
|
||||
}
|
||||
@@ -109,9 +109,9 @@ void SurfaceDissolveSprite::rebuildDisplaySurface() {
|
||||
}
|
||||
|
||||
// Actualitza animació, moviment i transició temporal
|
||||
void SurfaceDissolveSprite::update(float delta_time) {
|
||||
void DissolveSprite::update(float delta_time) {
|
||||
const SDL_FRect OLD_CLIP = clip_;
|
||||
SurfaceAnimatedSprite::update(delta_time);
|
||||
AnimatedSprite::update(delta_time);
|
||||
|
||||
// Detecta canvi de frame d'animació
|
||||
if (clip_.x != OLD_CLIP.x || clip_.y != OLD_CLIP.y ||
|
||||
@@ -136,22 +136,22 @@ void SurfaceDissolveSprite::update(float delta_time) {
|
||||
}
|
||||
|
||||
// Renderitza: usa surface_display_ (amb color replace) si disponible
|
||||
void SurfaceDissolveSprite::render() {
|
||||
void DissolveSprite::render() {
|
||||
if (!surface_display_) {
|
||||
SurfaceAnimatedSprite::render();
|
||||
AnimatedSprite::render();
|
||||
return;
|
||||
}
|
||||
surface_display_->render(static_cast<int>(pos_.x), static_cast<int>(pos_.y), &clip_, flip_);
|
||||
}
|
||||
|
||||
// Estableix el progrés manualment
|
||||
void SurfaceDissolveSprite::setProgress(float progress) {
|
||||
void DissolveSprite::setProgress(float progress) {
|
||||
progress_ = std::min(std::max(progress, 0.0F), 1.0F);
|
||||
needs_rebuild_ = true;
|
||||
}
|
||||
|
||||
// Inicia dissolució temporal (visible → invisible)
|
||||
void SurfaceDissolveSprite::startDissolve(float duration_ms, DissolveDirection dir) {
|
||||
void DissolveSprite::startDissolve(float duration_ms, DissolveDirection dir) {
|
||||
direction_ = dir;
|
||||
transition_mode_ = TransitionMode::DISSOLVING;
|
||||
transition_duration_ = duration_ms;
|
||||
@@ -161,7 +161,7 @@ void SurfaceDissolveSprite::startDissolve(float duration_ms, DissolveDirection d
|
||||
}
|
||||
|
||||
// Inicia generació temporal (invisible → visible)
|
||||
void SurfaceDissolveSprite::startGenerate(float duration_ms, DissolveDirection dir) {
|
||||
void DissolveSprite::startGenerate(float duration_ms, DissolveDirection dir) {
|
||||
direction_ = dir;
|
||||
transition_mode_ = TransitionMode::GENERATING;
|
||||
transition_duration_ = duration_ms;
|
||||
@@ -171,17 +171,17 @@ void SurfaceDissolveSprite::startGenerate(float duration_ms, DissolveDirection d
|
||||
}
|
||||
|
||||
// Atura la transició temporal
|
||||
void SurfaceDissolveSprite::stopTransition() {
|
||||
void DissolveSprite::stopTransition() {
|
||||
transition_mode_ = TransitionMode::NONE;
|
||||
}
|
||||
|
||||
// Retorna si la transició ha acabat
|
||||
auto SurfaceDissolveSprite::isTransitionDone() const -> bool {
|
||||
auto DissolveSprite::isTransitionDone() const -> bool {
|
||||
return transition_mode_ == TransitionMode::NONE;
|
||||
}
|
||||
|
||||
// Configura substitució de color per a la reconstrucció
|
||||
void SurfaceDissolveSprite::setColorReplace(Uint8 source, Uint8 target) {
|
||||
void DissolveSprite::setColorReplace(Uint8 source, Uint8 target) {
|
||||
source_color_ = source;
|
||||
target_color_ = target;
|
||||
needs_rebuild_ = true;
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
|
||||
#include "core/rendering/surface_animated_sprite.hpp" // Para SurfaceAnimatedSprite
|
||||
#include "core/rendering/sprite/animated_sprite.hpp" // Para SurfaceAnimatedSprite
|
||||
|
||||
class Surface;
|
||||
|
||||
@@ -15,11 +15,11 @@ enum class DissolveDirection { NONE,
|
||||
|
||||
// Sprite que pot dissoldre's o generar-se de forma aleatòria en X mil·lisegons.
|
||||
// progress_ va de 0.0 (totalment visible) a 1.0 (totalment invisible).
|
||||
class SurfaceDissolveSprite : public SurfaceAnimatedSprite {
|
||||
class DissolveSprite : public AnimatedSprite {
|
||||
public:
|
||||
explicit SurfaceDissolveSprite(const AnimationResource& data);
|
||||
SurfaceDissolveSprite(std::shared_ptr<Surface> surface, SDL_FRect pos);
|
||||
~SurfaceDissolveSprite() override = default;
|
||||
explicit DissolveSprite(const AnimationResource& data);
|
||||
DissolveSprite(std::shared_ptr<Surface> surface, SDL_FRect pos);
|
||||
~DissolveSprite() override = default;
|
||||
|
||||
void update(float delta_time) override;
|
||||
void render() override;
|
||||
@@ -52,7 +52,7 @@ class SurfaceDissolveSprite : public SurfaceAnimatedSprite {
|
||||
TransitionMode transition_mode_{TransitionMode::NONE};
|
||||
float transition_duration_{0.0F};
|
||||
float transition_elapsed_{0.0F};
|
||||
SDL_FRect prev_clip_{0, 0, 0, 0};
|
||||
SDL_FRect prev_clip_{.x = 0, .y = 0, .w = 0, .h = 0};
|
||||
bool needs_rebuild_{false};
|
||||
Uint8 source_color_{255}; // 255 = transparent = sense replace per defecte
|
||||
Uint8 target_color_{0};
|
||||
@@ -1,28 +1,28 @@
|
||||
#include "core/rendering/surface_moving_sprite.hpp"
|
||||
#include "core/rendering/sprite/moving_sprite.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
|
||||
// Constructor
|
||||
SurfaceMovingSprite::SurfaceMovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos, SDL_FlipMode flip)
|
||||
: SurfaceSprite(std::move(surface), pos),
|
||||
MovingSprite::MovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos, SDL_FlipMode flip)
|
||||
: Sprite(std::move(surface), pos),
|
||||
x_(pos.x),
|
||||
y_(pos.y),
|
||||
flip_(flip) { SurfaceSprite::pos_ = pos; }
|
||||
flip_(flip) { Sprite::pos_ = pos; }
|
||||
|
||||
SurfaceMovingSprite::SurfaceMovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos)
|
||||
: SurfaceSprite(std::move(surface), pos),
|
||||
MovingSprite::MovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos)
|
||||
: Sprite(std::move(surface), pos),
|
||||
x_(pos.x),
|
||||
y_(pos.y) { SurfaceSprite::pos_ = pos; }
|
||||
y_(pos.y) { Sprite::pos_ = pos; }
|
||||
|
||||
SurfaceMovingSprite::SurfaceMovingSprite() { SurfaceSprite::clear(); }
|
||||
MovingSprite::MovingSprite() { Sprite::clear(); }
|
||||
|
||||
SurfaceMovingSprite::SurfaceMovingSprite(std::shared_ptr<Surface> surface)
|
||||
: SurfaceSprite(std::move(surface)) { SurfaceSprite::clear(); }
|
||||
MovingSprite::MovingSprite(std::shared_ptr<Surface> surface)
|
||||
: Sprite(std::move(surface)) { Sprite::clear(); }
|
||||
|
||||
// Reinicia todas las variables
|
||||
void SurfaceMovingSprite::clear() {
|
||||
void MovingSprite::clear() {
|
||||
// Resetea posición
|
||||
x_ = 0.0F;
|
||||
y_ = 0.0F;
|
||||
@@ -38,13 +38,13 @@ void SurfaceMovingSprite::clear() {
|
||||
// Resetea flip
|
||||
flip_ = SDL_FLIP_NONE;
|
||||
|
||||
SurfaceSprite::clear();
|
||||
Sprite::clear();
|
||||
}
|
||||
|
||||
// Mueve el sprite (time-based)
|
||||
// Nota: vx_, vy_ ahora se interpretan como pixels/segundo
|
||||
// Nota: ax_, ay_ ahora se interpretan como pixels/segundo²
|
||||
void SurfaceMovingSprite::move(float delta_time) {
|
||||
void MovingSprite::move(float delta_time) {
|
||||
// Aplica aceleración a velocidad (time-based)
|
||||
vx_ += ax_ * delta_time;
|
||||
vy_ += ay_ * delta_time;
|
||||
@@ -59,22 +59,22 @@ void SurfaceMovingSprite::move(float delta_time) {
|
||||
}
|
||||
|
||||
// Actualiza las variables internas del objeto (time-based)
|
||||
void SurfaceMovingSprite::update(float delta_time) {
|
||||
void MovingSprite::update(float delta_time) {
|
||||
move(delta_time);
|
||||
}
|
||||
|
||||
// Muestra el sprite por pantalla
|
||||
void SurfaceMovingSprite::render() {
|
||||
void MovingSprite::render() {
|
||||
surface_->render(pos_.x, pos_.y, &clip_, flip_);
|
||||
}
|
||||
|
||||
// Muestra el sprite por pantalla
|
||||
void SurfaceMovingSprite::render(Uint8 source_color, Uint8 target_color) {
|
||||
void MovingSprite::render(Uint8 source_color, Uint8 target_color) {
|
||||
surface_->renderWithColorReplace(pos_.x, pos_.y, source_color, target_color, &clip_, flip_);
|
||||
}
|
||||
|
||||
// Establece la posición y_ el tamaño del objeto
|
||||
void SurfaceMovingSprite::setPos(SDL_FRect rect) {
|
||||
void MovingSprite::setPos(SDL_FRect rect) {
|
||||
x_ = rect.x;
|
||||
y_ = rect.y;
|
||||
|
||||
@@ -82,7 +82,7 @@ void SurfaceMovingSprite::setPos(SDL_FRect rect) {
|
||||
}
|
||||
|
||||
// Establece el valor de las variables
|
||||
void SurfaceMovingSprite::setPos(float x, float y) {
|
||||
void MovingSprite::setPos(float x, float y) {
|
||||
x_ = x;
|
||||
y_ = y;
|
||||
|
||||
@@ -91,13 +91,13 @@ void SurfaceMovingSprite::setPos(float x, float y) {
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void SurfaceMovingSprite::setPosX(float value) {
|
||||
void MovingSprite::setPosX(float value) {
|
||||
x_ = value;
|
||||
pos_.x = static_cast<int>(x_);
|
||||
}
|
||||
|
||||
// Establece el valor de la variable
|
||||
void SurfaceMovingSprite::setPosY(float value) {
|
||||
void MovingSprite::setPosY(float value) {
|
||||
y_ = value;
|
||||
pos_.y = static_cast<int>(y_);
|
||||
}
|
||||
@@ -4,18 +4,18 @@
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
|
||||
#include "core/rendering/surface_sprite.hpp" // Para SSprite
|
||||
#include "core/rendering/sprite/sprite.hpp" // Para SSprite
|
||||
class Surface; // lines 8-8
|
||||
|
||||
// Clase SMovingSprite. Añade movimiento y flip al sprite
|
||||
class SurfaceMovingSprite : public SurfaceSprite {
|
||||
class MovingSprite : public Sprite {
|
||||
public:
|
||||
// Constructores
|
||||
SurfaceMovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos, SDL_FlipMode flip);
|
||||
SurfaceMovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos);
|
||||
explicit SurfaceMovingSprite();
|
||||
explicit SurfaceMovingSprite(std::shared_ptr<Surface> surface);
|
||||
~SurfaceMovingSprite() override = default;
|
||||
MovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos, SDL_FlipMode flip);
|
||||
MovingSprite(std::shared_ptr<Surface> surface, SDL_FRect pos);
|
||||
explicit MovingSprite();
|
||||
explicit MovingSprite(std::shared_ptr<Surface> surface);
|
||||
~MovingSprite() override = default;
|
||||
|
||||
// Actualización y renderizado
|
||||
void update(float delta_time) override; // Actualiza variables internas (time-based)
|
||||
@@ -1,37 +1,37 @@
|
||||
#include "core/rendering/surface_sprite.hpp"
|
||||
#include "core/rendering/sprite/sprite.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
|
||||
// Constructor
|
||||
SurfaceSprite::SurfaceSprite(std::shared_ptr<Surface> surface, float x, float y, float w, float h)
|
||||
Sprite::Sprite(std::shared_ptr<Surface> surface, float x, float y, float w, float h)
|
||||
: surface_(std::move(surface)),
|
||||
pos_{x, y, w, h},
|
||||
clip_{0.0F, 0.0F, pos_.w, pos_.h} {}
|
||||
pos_{.x = x, .y = y, .w = w, .h = h},
|
||||
clip_{.x = 0.0F, .y = 0.0F, .w = pos_.w, .h = pos_.h} {}
|
||||
|
||||
SurfaceSprite::SurfaceSprite(std::shared_ptr<Surface> surface, SDL_FRect rect)
|
||||
Sprite::Sprite(std::shared_ptr<Surface> surface, SDL_FRect rect)
|
||||
: surface_(std::move(surface)),
|
||||
pos_(rect),
|
||||
clip_{0.0F, 0.0F, pos_.w, pos_.h} {}
|
||||
clip_{.x = 0.0F, .y = 0.0F, .w = pos_.w, .h = pos_.h} {}
|
||||
|
||||
SurfaceSprite::SurfaceSprite() = default;
|
||||
Sprite::Sprite() = default;
|
||||
|
||||
SurfaceSprite::SurfaceSprite(std::shared_ptr<Surface> surface)
|
||||
Sprite::Sprite(std::shared_ptr<Surface> surface)
|
||||
: surface_(std::move(surface)),
|
||||
pos_{0.0F, 0.0F, surface_->getWidth(), surface_->getHeight()},
|
||||
clip_(pos_) {}
|
||||
|
||||
// Muestra el sprite por pantalla
|
||||
void SurfaceSprite::render() {
|
||||
void Sprite::render() {
|
||||
surface_->render(pos_.x, pos_.y, &clip_);
|
||||
}
|
||||
|
||||
void SurfaceSprite::render(Uint8 source_color, Uint8 target_color) {
|
||||
void Sprite::render(Uint8 source_color, Uint8 target_color) {
|
||||
surface_->renderWithColorReplace(pos_.x, pos_.y, source_color, target_color, &clip_);
|
||||
}
|
||||
|
||||
void SurfaceSprite::renderWithVerticalFade(int fade_h, int canvas_height) {
|
||||
void Sprite::renderWithVerticalFade(int fade_h, int canvas_height) {
|
||||
surface_->renderWithVerticalFade(
|
||||
static_cast<int>(pos_.x),
|
||||
static_cast<int>(pos_.y),
|
||||
@@ -40,7 +40,7 @@ void SurfaceSprite::renderWithVerticalFade(int fade_h, int canvas_height) {
|
||||
&clip_);
|
||||
}
|
||||
|
||||
void SurfaceSprite::renderWithVerticalFade(int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color) {
|
||||
void Sprite::renderWithVerticalFade(int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color) {
|
||||
surface_->renderWithVerticalFade(
|
||||
static_cast<int>(pos_.x),
|
||||
static_cast<int>(pos_.y),
|
||||
@@ -52,25 +52,25 @@ void SurfaceSprite::renderWithVerticalFade(int fade_h, int canvas_height, Uint8
|
||||
}
|
||||
|
||||
// Establece la posición del objeto
|
||||
void SurfaceSprite::setPosition(float x, float y) {
|
||||
void Sprite::setPosition(float x, float y) {
|
||||
pos_.x = x;
|
||||
pos_.y = y;
|
||||
}
|
||||
|
||||
// Establece la posición del objeto
|
||||
void SurfaceSprite::setPosition(SDL_FPoint p) {
|
||||
void Sprite::setPosition(SDL_FPoint p) {
|
||||
pos_.x = p.x;
|
||||
pos_.y = p.y;
|
||||
}
|
||||
|
||||
// Reinicia las variables a cero
|
||||
void SurfaceSprite::clear() {
|
||||
void Sprite::clear() {
|
||||
pos_ = {.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F};
|
||||
clip_ = {.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F};
|
||||
}
|
||||
|
||||
// Actualiza el estado del sprite (time-based)
|
||||
void SurfaceSprite::update(float delta_time) {
|
||||
void Sprite::update(float delta_time) {
|
||||
// Base implementation does nothing (static sprites)
|
||||
(void)delta_time; // Evita warning de parámetro no usado
|
||||
}
|
||||
@@ -7,16 +7,16 @@
|
||||
class Surface; // lines 5-5
|
||||
|
||||
// Clase SurfaceSprite
|
||||
class SurfaceSprite {
|
||||
class Sprite {
|
||||
public:
|
||||
// Constructores
|
||||
SurfaceSprite(std::shared_ptr<Surface>, float x, float y, float w, float h);
|
||||
SurfaceSprite(std::shared_ptr<Surface>, SDL_FRect rect);
|
||||
SurfaceSprite();
|
||||
explicit SurfaceSprite(std::shared_ptr<Surface>);
|
||||
Sprite(std::shared_ptr<Surface>, float x, float y, float w, float h);
|
||||
Sprite(std::shared_ptr<Surface>, SDL_FRect rect);
|
||||
Sprite();
|
||||
explicit Sprite(std::shared_ptr<Surface>);
|
||||
|
||||
// Destructor
|
||||
virtual ~SurfaceSprite() = default;
|
||||
virtual ~Sprite() = default;
|
||||
|
||||
// Actualización y renderizado
|
||||
virtual void update(float delta_time); // Actualiza el estado del sprite (time-based)
|
||||
@@ -51,12 +51,12 @@ class SurfaceSprite {
|
||||
|
||||
// Modificación de clip y surface
|
||||
void setClip(SDL_FRect rect) { clip_ = rect; }
|
||||
void setClip(float x, float y, float w, float h) { clip_ = SDL_FRect{x, y, w, h}; }
|
||||
void setClip(float x, float y, float w, float h) { clip_ = SDL_FRect{.x = x, .y = y, .w = w, .h = h}; }
|
||||
void setSurface(std::shared_ptr<Surface> surface) { surface_ = std::move(surface); }
|
||||
|
||||
protected:
|
||||
// Variables miembro
|
||||
std::shared_ptr<Surface> surface_{nullptr}; // Surface donde estan todos los dibujos del sprite
|
||||
SDL_FRect pos_{0.0F, 0.0F, 0.0F, 0.0F}; // Posición y tamaño donde dibujar el sprite
|
||||
SDL_FRect clip_{0.0F, 0.0F, 0.0F, 0.0F}; // Rectangulo de origen de la surface que se dibujará en pantalla
|
||||
SDL_FRect pos_{.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F}; // Posición y tamaño donde dibujar el sprite
|
||||
SDL_FRect clip_{.x = 0.0F, .y = 0.0F, .w = 0.0F, .h = 0.0F}; // Rectangulo de origen de la surface que se dibujará en pantalla
|
||||
};
|
||||
@@ -104,7 +104,7 @@ Surface::Surface(const std::string& file_path)
|
||||
}
|
||||
|
||||
// Carga una superficie desde un archivo
|
||||
auto Surface::loadSurface(const std::string& file_path) -> SurfaceData {
|
||||
auto Surface::loadSurface(const std::string& file_path) -> SurfaceData { // NOLINT(readability-convert-member-functions-to-static)
|
||||
// Load file using ResourceHelper (supports both filesystem and pack)
|
||||
std::vector<Uint8> buffer = Resource::Helper::loadFile(file_path);
|
||||
if (buffer.empty()) {
|
||||
@@ -148,14 +148,14 @@ void Surface::setColor(int index, Uint32 color) {
|
||||
}
|
||||
|
||||
// Rellena la superficie con un color
|
||||
void Surface::clear(Uint8 color) {
|
||||
void Surface::clear(Uint8 color) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
const size_t TOTAL_PIXELS = surface_data_->width * surface_data_->height;
|
||||
Uint8* data_ptr = surface_data_->data.get();
|
||||
std::fill(data_ptr, data_ptr + TOTAL_PIXELS, color);
|
||||
}
|
||||
|
||||
// Pone un pixel en la SurfaceData
|
||||
void Surface::putPixel(int x, int y, Uint8 color) {
|
||||
void Surface::putPixel(int x, int y, Uint8 color) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
if (x < 0 || y < 0 || x >= surface_data_->width || y >= surface_data_->height) {
|
||||
return; // Coordenadas fuera de rango
|
||||
}
|
||||
@@ -168,40 +168,36 @@ void Surface::putPixel(int x, int y, Uint8 color) {
|
||||
auto Surface::getPixel(int x, int y) -> Uint8 { return surface_data_->data.get()[x + (y * static_cast<int>(surface_data_->width))]; }
|
||||
|
||||
// Dibuja un rectangulo relleno
|
||||
void Surface::fillRect(const SDL_FRect* rect, Uint8 color) {
|
||||
void Surface::fillRect(const SDL_FRect* rect, Uint8 color) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
// Limitar los valores del rectángulo al tamaño de la superficie
|
||||
float x_start = std::max(0.0F, rect->x);
|
||||
float y_start = std::max(0.0F, rect->y);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja el borde de un rectangulo
|
||||
void Surface::drawRectBorder(const SDL_FRect* rect, Uint8 color) {
|
||||
void Surface::drawRectBorder(const SDL_FRect* rect, Uint8 color) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
// Limitar los valores del rectángulo al tamaño de la superficie
|
||||
float x_start = std::max(0.0F, rect->x);
|
||||
float y_start = std::max(0.0F, rect->y);
|
||||
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) {
|
||||
@@ -216,7 +212,7 @@ void Surface::drawRectBorder(const SDL_FRect* rect, Uint8 color) {
|
||||
}
|
||||
|
||||
// Dibuja una linea
|
||||
void Surface::drawLine(float x1, float y1, float x2, float y2, Uint8 color) {
|
||||
void Surface::drawLine(float x1, float y1, float x2, float y2, Uint8 color) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
// Calcula las diferencias
|
||||
float dx = std::abs(x2 - x1);
|
||||
float dy = std::abs(y2 - y1);
|
||||
@@ -250,7 +246,7 @@ void Surface::drawLine(float x1, float y1, float x2, float y2, Uint8 color) {
|
||||
}
|
||||
}
|
||||
|
||||
void Surface::render(float dx, float dy, float sx, float sy, float w, float h) {
|
||||
void Surface::render(float dx, float dy, float sx, float sy, float w, float h) { // NOLINT(readability-make-member-function-const)
|
||||
auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();
|
||||
|
||||
// Limitar la región para evitar accesos fuera de rango en origen
|
||||
@@ -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))];
|
||||
if (color != transparent_color_) {
|
||||
surface_data->data.get()[static_cast<size_t>(dest_x + (dest_y * surface_data->width))] = sub_palette_[color];
|
||||
Uint8 color = src_ptr[static_cast<size_t>(src_x + (src_y * surface_data_->width))];
|
||||
if (color != static_cast<Uint8>(transparent_color_)) {
|
||||
dst_ptr[static_cast<size_t>(dest_x + (dest_y * surface_data->width))] = sub_palette_[color];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,14 +277,14 @@ void Surface::render(float dx, float dy, float sx, float sy, float w, float h) {
|
||||
}
|
||||
}
|
||||
|
||||
void Surface::render(int x, int y, SDL_FRect* src_rect, SDL_FlipMode flip) {
|
||||
void Surface::render(int x, int y, SDL_FRect* src_rect, SDL_FlipMode flip) { // NOLINT(readability-make-member-function-const)
|
||||
auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData();
|
||||
|
||||
// Determina la región de origen (clip) a renderizar
|
||||
float sx = ((src_rect) != nullptr) ? src_rect->x : 0;
|
||||
float sy = ((src_rect) != nullptr) ? src_rect->y : 0;
|
||||
float w = ((src_rect) != nullptr) ? src_rect->w : surface_data_->width;
|
||||
float h = ((src_rect) != nullptr) ? src_rect->h : surface_data_->height;
|
||||
float sx = (src_rect != nullptr) ? src_rect->x : 0;
|
||||
float sy = (src_rect != nullptr) ? src_rect->y : 0;
|
||||
float w = (src_rect != nullptr) ? src_rect->w : surface_data_->width;
|
||||
float h = (src_rect != nullptr) ? src_rect->h : surface_data_->height;
|
||||
|
||||
// Limitar la región para evitar accesos fuera de rango en origen
|
||||
w = std::min(w, surface_data_->width - sx);
|
||||
@@ -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))];
|
||||
if (color != transparent_color_) {
|
||||
surface_data_dest->data[dest_x + (dest_y * surface_data_dest->width)] = sub_palette_[color];
|
||||
Uint8 color = src_ptr[static_cast<size_t>(src_x + (src_y * surface_data_->width))];
|
||||
if (color != static_cast<Uint8>(transparent_color_)) {
|
||||
dst_ptr[static_cast<size_t>(dest_x + (dest_y * surface_data_dest->width))] = sub_palette_[color];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -334,7 +334,7 @@ void Surface::copyPixelIfNotTransparent(Uint8* dest_data, int dest_x, int dest_y
|
||||
}
|
||||
|
||||
Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + (src_y * surface_data_->width))];
|
||||
if (color != transparent_color_) {
|
||||
if (color != static_cast<Uint8>(transparent_color_)) {
|
||||
dest_data[dest_x + (dest_y * dest_width)] = sub_palette_[color];
|
||||
}
|
||||
}
|
||||
@@ -344,16 +344,16 @@ void Surface::render(SDL_FRect* src_rect, SDL_FRect* dst_rect, SDL_FlipMode flip
|
||||
auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();
|
||||
|
||||
// Si srcRect es nullptr, tomar toda la superficie fuente
|
||||
float sx = ((src_rect) != nullptr) ? src_rect->x : 0;
|
||||
float sy = ((src_rect) != nullptr) ? src_rect->y : 0;
|
||||
float sw = ((src_rect) != nullptr) ? src_rect->w : surface_data_->width;
|
||||
float sh = ((src_rect) != nullptr) ? src_rect->h : surface_data_->height;
|
||||
float sx = (src_rect != nullptr) ? src_rect->x : 0;
|
||||
float sy = (src_rect != nullptr) ? src_rect->y : 0;
|
||||
float sw = (src_rect != nullptr) ? src_rect->w : surface_data_->width;
|
||||
float sh = (src_rect != nullptr) ? src_rect->h : surface_data_->height;
|
||||
|
||||
// Si dstRect es nullptr, asignar las mismas dimensiones que srcRect
|
||||
float dx = ((dst_rect) != nullptr) ? dst_rect->x : 0;
|
||||
float dy = ((dst_rect) != nullptr) ? dst_rect->y : 0;
|
||||
float dw = ((dst_rect) != nullptr) ? dst_rect->w : sw;
|
||||
float dh = ((dst_rect) != nullptr) ? dst_rect->h : sh;
|
||||
float dx = (dst_rect != nullptr) ? dst_rect->x : 0;
|
||||
float dy = (dst_rect != nullptr) ? dst_rect->y : 0;
|
||||
float dw = (dst_rect != nullptr) ? dst_rect->w : sw;
|
||||
float dh = (dst_rect != nullptr) ? dst_rect->h : sh;
|
||||
|
||||
// Asegurarse de que srcRect y dstRect tienen las mismas dimensiones
|
||||
if (sw != dw || sh != dh) {
|
||||
@@ -389,14 +389,14 @@ void Surface::render(SDL_FRect* src_rect, SDL_FRect* dst_rect, SDL_FlipMode flip
|
||||
}
|
||||
|
||||
// Copia una región de la SurfaceData de origen a la SurfaceData de destino reemplazando un color por otro
|
||||
void Surface::renderWithColorReplace(int x, int y, Uint8 source_color, Uint8 target_color, SDL_FRect* src_rect, SDL_FlipMode flip) {
|
||||
void Surface::renderWithColorReplace(int x, int y, Uint8 source_color, Uint8 target_color, SDL_FRect* src_rect, SDL_FlipMode flip) const {
|
||||
auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();
|
||||
|
||||
// Determina la región de origen (clip) a renderizar
|
||||
float sx = ((src_rect) != nullptr) ? src_rect->x : 0;
|
||||
float sy = ((src_rect) != nullptr) ? src_rect->y : 0;
|
||||
float w = ((src_rect) != nullptr) ? src_rect->w : surface_data_->width;
|
||||
float h = ((src_rect) != nullptr) ? src_rect->h : surface_data_->height;
|
||||
float sx = (src_rect != nullptr) ? src_rect->x : 0;
|
||||
float sy = (src_rect != nullptr) ? src_rect->y : 0;
|
||||
float w = (src_rect != nullptr) ? src_rect->w : surface_data_->width;
|
||||
float h = (src_rect != nullptr) ? src_rect->h : surface_data_->height;
|
||||
|
||||
// Limitar la región para evitar accesos fuera de rango
|
||||
w = std::min(w, surface_data_->width - sx);
|
||||
@@ -420,7 +420,7 @@ void Surface::renderWithColorReplace(int x, int y, Uint8 source_color, Uint8 tar
|
||||
|
||||
// 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))];
|
||||
if (color != transparent_color_) {
|
||||
if (color != static_cast<Uint8>(transparent_color_)) {
|
||||
surface_data->data[dest_x + (dest_y * surface_data->width)] =
|
||||
(color == source_color) ? target_color : color;
|
||||
}
|
||||
@@ -449,7 +449,7 @@ static auto computeFadeDensity(int screen_y, int fade_h, int canvas_height) -> f
|
||||
}
|
||||
|
||||
// Render amb dissolució als cantons superior/inferior (hash 2D, sense parpelleig)
|
||||
void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, SDL_FRect* src_rect) {
|
||||
void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, SDL_FRect* src_rect) const {
|
||||
const int SX = (src_rect != nullptr) ? static_cast<int>(src_rect->x) : 0;
|
||||
const int SY = (src_rect != nullptr) ? static_cast<int>(src_rect->y) : 0;
|
||||
const int SW = (src_rect != nullptr) ? static_cast<int>(src_rect->w) : static_cast<int>(surface_data_->width);
|
||||
@@ -472,7 +472,7 @@ void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height
|
||||
}
|
||||
|
||||
const Uint8 COLOR = surface_data_->data[((SY + row) * static_cast<int>(surface_data_->width)) + (SX + col)];
|
||||
if (static_cast<int>(COLOR) == transparent_color_) {
|
||||
if (COLOR == static_cast<Uint8>(transparent_color_)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -486,7 +486,7 @@ void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height
|
||||
}
|
||||
|
||||
// Idem però reemplaçant un color índex
|
||||
void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, SDL_FRect* src_rect) {
|
||||
void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, SDL_FRect* src_rect) const {
|
||||
const int SX = (src_rect != nullptr) ? static_cast<int>(src_rect->x) : 0;
|
||||
const int SY = (src_rect != nullptr) ? static_cast<int>(src_rect->y) : 0;
|
||||
const int SW = (src_rect != nullptr) ? static_cast<int>(src_rect->w) : static_cast<int>(surface_data_->width);
|
||||
@@ -509,7 +509,7 @@ void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height
|
||||
}
|
||||
|
||||
const Uint8 COLOR = surface_data_->data[((SY + row) * static_cast<int>(surface_data_->width)) + (SX + col)];
|
||||
if (static_cast<int>(COLOR) == transparent_color_) {
|
||||
if (COLOR == static_cast<Uint8>(transparent_color_)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -525,19 +525,29 @@ void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height
|
||||
|
||||
// Vuelca los píxeles como ARGB8888 a un buffer externo (sin SDL_Texture ni SDL_Renderer)
|
||||
void Surface::toARGBBuffer(Uint32* buffer) const {
|
||||
if (!surface_data_ || (surface_data_->data == nullptr)) { return; }
|
||||
if (!surface_data_ || !surface_data_->data || !buffer) { return; }
|
||||
|
||||
const int WIDTH = static_cast<int>(surface_data_->width);
|
||||
const int HEIGHT = static_cast<int>(surface_data_->height);
|
||||
const Uint8* src = surface_data_->data.get();
|
||||
for (int y = 0; y < HEIGHT; ++y) {
|
||||
for (int x = 0; x < WIDTH; ++x) {
|
||||
buffer[(y * WIDTH) + x] = palette_[src[(y * WIDTH) + x]];
|
||||
|
||||
// Obtenemos el tamaño de la paleta para evitar accesos fuera de rango
|
||||
const size_t PAL_SIZE = palette_.size();
|
||||
|
||||
for (int i = 0; i < WIDTH * HEIGHT; ++i) {
|
||||
Uint8 color_index = src[i];
|
||||
|
||||
// Verificación de seguridad: ¿El índice existe en la paleta?
|
||||
if (color_index < PAL_SIZE) {
|
||||
buffer[i] = palette_[color_index];
|
||||
} else {
|
||||
buffer[i] = 0xFF000000; // Negro opaco si el índice es erróneo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Vuelca la superficie a una textura
|
||||
void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture) {
|
||||
void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
if ((renderer == nullptr) || (texture == nullptr) || !surface_data_) {
|
||||
throw std::runtime_error("Renderer or texture is null.");
|
||||
}
|
||||
@@ -557,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]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,7 +589,7 @@ void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture) {
|
||||
}
|
||||
|
||||
// Vuelca la superficie a una textura
|
||||
void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FRect* src_rect, SDL_FRect* dest_rect) {
|
||||
void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FRect* src_rect, SDL_FRect* dest_rect) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
if ((renderer == nullptr) || (texture == nullptr) || !surface_data_) {
|
||||
throw std::runtime_error("Renderer or texture is null.");
|
||||
}
|
||||
@@ -603,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]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -621,7 +638,7 @@ void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FR
|
||||
}
|
||||
|
||||
// Realiza un efecto de fundido en la paleta principal
|
||||
auto Surface::fadePalette() -> bool {
|
||||
auto Surface::fadePalette() -> bool { // NOLINT(readability-convert-member-functions-to-static)
|
||||
// Verificar que el tamaño mínimo de palette_ sea adecuado
|
||||
static constexpr int PALETTE_SIZE = 19;
|
||||
if (sizeof(palette_) / sizeof(palette_[0]) < PALETTE_SIZE) {
|
||||
@@ -641,7 +658,7 @@ auto Surface::fadePalette() -> bool {
|
||||
}
|
||||
|
||||
// Realiza un efecto de fundido en la paleta secundaria
|
||||
auto Surface::fadeSubPalette(Uint32 delay) -> bool {
|
||||
auto Surface::fadeSubPalette(Uint32 delay) -> bool { // NOLINT(readability-convert-member-functions-to-static)
|
||||
// Variable estática para almacenar el último tick
|
||||
static Uint32 last_tick_ = 0;
|
||||
|
||||
@@ -675,4 +692,4 @@ auto Surface::fadeSubPalette(Uint32 delay) -> bool {
|
||||
}
|
||||
|
||||
// Restaura la sub paleta a su estado original
|
||||
void Surface::resetSubPalette() { initializeSubPalette(sub_palette_); }
|
||||
void Surface::resetSubPalette() { initializeSubPalette(sub_palette_); } // NOLINT(readability-convert-member-functions-to-static)
|
||||
|
||||
@@ -82,13 +82,13 @@ class Surface {
|
||||
void render(SDL_FRect* src_rect = nullptr, SDL_FRect* dst_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
|
||||
|
||||
// Copia una región de la SurfaceData de origen a la SurfaceData de destino reemplazando un color por otro
|
||||
void renderWithColorReplace(int x, int y, Uint8 source_color = 0, Uint8 target_color = 0, SDL_FRect* src_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
|
||||
void renderWithColorReplace(int x, int y, Uint8 source_color = 0, Uint8 target_color = 0, SDL_FRect* src_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE) const;
|
||||
|
||||
// Render amb dissolució als cantons superior/inferior (hash 2D, sense parpelleig)
|
||||
void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, SDL_FRect* src_rect = nullptr);
|
||||
void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, SDL_FRect* src_rect = nullptr) const;
|
||||
|
||||
// Idem però reemplaçant un color índex (per a sprites sobre fons del mateix color)
|
||||
void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, SDL_FRect* src_rect = nullptr);
|
||||
void renderWithVerticalFade(int x, int y, int fade_h, int canvas_height, Uint8 source_color, Uint8 target_color, SDL_FRect* src_rect = nullptr) const;
|
||||
|
||||
// Establece un color en la paleta
|
||||
void setColor(int index, Uint32 color);
|
||||
@@ -139,6 +139,7 @@ class Surface {
|
||||
|
||||
// Paleta
|
||||
void setPalette(const std::array<Uint32, 256>& palette) { palette_ = palette; }
|
||||
[[nodiscard]] auto getPaletteColor(Uint8 index) const -> Uint32 { return palette_[index]; }
|
||||
|
||||
// Inicializa la sub paleta
|
||||
static void initializeSubPalette(SubPalette& palette) { std::iota(palette.begin(), palette.end(), 0); }
|
||||
|
||||
@@ -8,23 +8,37 @@
|
||||
#include <stdexcept> // Para runtime_error
|
||||
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/rendering/sprite/sprite.hpp" // Para SSprite
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
#include "core/rendering/surface_sprite.hpp" // Para SSprite
|
||||
#include "core/resources/resource_helper.hpp" // Para ResourceHelper
|
||||
#include "utils/utils.hpp" // Para getFileName, stringToColor, printWithDots
|
||||
|
||||
// Extrae el siguiente codepoint UTF-8 de la cadena, avanzando 'pos' al byte siguiente
|
||||
auto Text::nextCodepoint(const std::string& s, size_t& pos) -> uint32_t {
|
||||
auto Text::nextCodepoint(const std::string& s, size_t& pos) -> uint32_t { // NOLINT(readability-convert-member-functions-to-static)
|
||||
auto c = static_cast<unsigned char>(s[pos]);
|
||||
uint32_t cp = 0;
|
||||
size_t extra = 0;
|
||||
|
||||
if (c < 0x80) { cp = c; extra = 0; }
|
||||
else if (c < 0xC0) { pos++; return 0xFFFD; } // byte de continuación suelto
|
||||
else if (c < 0xE0) { cp = c & 0x1F; extra = 1; }
|
||||
else if (c < 0xF0) { cp = c & 0x0F; extra = 2; }
|
||||
else if (c < 0xF8) { cp = c & 0x07; extra = 3; }
|
||||
else { pos++; return 0xFFFD; }
|
||||
if (c < 0x80) {
|
||||
cp = c;
|
||||
extra = 0;
|
||||
} else if (c < 0xC0) {
|
||||
pos++;
|
||||
return 0xFFFD;
|
||||
} // byte de continuación suelto
|
||||
else if (c < 0xE0) {
|
||||
cp = c & 0x1F;
|
||||
extra = 1;
|
||||
} else if (c < 0xF0) {
|
||||
cp = c & 0x0F;
|
||||
extra = 2;
|
||||
} else if (c < 0xF8) {
|
||||
cp = c & 0x07;
|
||||
extra = 3;
|
||||
} else {
|
||||
pos++;
|
||||
return 0xFFFD;
|
||||
}
|
||||
|
||||
pos++;
|
||||
for (size_t i = 0; i < extra && pos < s.size(); ++i, ++pos) {
|
||||
@@ -36,7 +50,7 @@ auto Text::nextCodepoint(const std::string& s, size_t& pos) -> uint32_t {
|
||||
}
|
||||
|
||||
// Convierte un codepoint Unicode a una cadena UTF-8
|
||||
auto Text::codepointToUtf8(uint32_t cp) -> std::string {
|
||||
auto Text::codepointToUtf8(uint32_t cp) -> std::string { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::string result;
|
||||
if (cp < 0x80) {
|
||||
result += static_cast<char>(cp);
|
||||
@@ -58,7 +72,7 @@ auto Text::codepointToUtf8(uint32_t cp) -> std::string {
|
||||
|
||||
// Carga un fichero de definición de fuente .fnt
|
||||
// Formato: líneas "clave valor", comentarios con #, gliphos como "codepoint ancho"
|
||||
auto Text::loadTextFile(const std::string& file_path) -> std::shared_ptr<File> {
|
||||
auto Text::loadTextFile(const std::string& file_path) -> std::shared_ptr<File> { // NOLINT(readability-convert-member-functions-to-static)
|
||||
auto tf = std::make_shared<File>();
|
||||
|
||||
auto file_data = Resource::Helper::loadFile(file_path);
|
||||
@@ -101,9 +115,9 @@ auto Text::loadTextFile(const std::string& file_path) -> std::shared_ptr<File> {
|
||||
continue; // línea mal formateada, ignorar
|
||||
}
|
||||
Offset off{};
|
||||
const int row_sp = tf->row_spacing > 0 ? tf->row_spacing : tf->cell_spacing;
|
||||
off.x = (glyph_index % tf->columns) * (tf->box_width + tf->cell_spacing) + tf->cell_spacing;
|
||||
off.y = (glyph_index / tf->columns) * (tf->box_height + row_sp) + tf->cell_spacing;
|
||||
const int ROW_SP = tf->row_spacing > 0 ? tf->row_spacing : tf->cell_spacing;
|
||||
off.x = ((glyph_index % tf->columns) * (tf->box_width + tf->cell_spacing)) + tf->cell_spacing;
|
||||
off.y = ((glyph_index / tf->columns) * (tf->box_height + ROW_SP)) + tf->cell_spacing;
|
||||
off.w = width;
|
||||
tf->offset[codepoint] = off;
|
||||
++glyph_index;
|
||||
@@ -122,19 +136,19 @@ Text::Text(const std::shared_ptr<Surface>& surface, const std::string& text_file
|
||||
box_width_ = tf->box_width;
|
||||
offset_ = tf->offset;
|
||||
|
||||
sprite_ = std::make_unique<SurfaceSprite>(surface, (SDL_FRect){0.0F, 0.0F, static_cast<float>(box_width_), static_cast<float>(box_height_)});
|
||||
sprite_ = std::make_unique<Sprite>(surface, SDL_FRect{.x = 0.0F, .y = 0.0F, .w = static_cast<float>(box_width_), .h = static_cast<float>(box_height_)});
|
||||
}
|
||||
|
||||
// Constructor desde estructura precargada
|
||||
Text::Text(const std::shared_ptr<Surface>& surface, const std::shared_ptr<File>& text_file)
|
||||
: sprite_(std::make_unique<SurfaceSprite>(surface, (SDL_FRect){0.0F, 0.0F, static_cast<float>(text_file->box_width), static_cast<float>(text_file->box_height)})),
|
||||
: sprite_(std::make_unique<Sprite>(surface, SDL_FRect{.x = 0.0F, .y = 0.0F, .w = static_cast<float>(text_file->box_width), .h = static_cast<float>(text_file->box_height)})),
|
||||
box_width_(text_file->box_width),
|
||||
box_height_(text_file->box_height),
|
||||
offset_(text_file->offset) {
|
||||
}
|
||||
|
||||
// Escribe texto en pantalla
|
||||
void Text::write(int x, int y, const std::string& text, int kerning, int lenght) {
|
||||
void Text::write(int x, int y, const std::string& text, int kerning, int lenght) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
int shift = 0;
|
||||
int glyphs_done = 0;
|
||||
size_t pos = 0;
|
||||
@@ -156,7 +170,7 @@ void Text::write(int x, int y, const std::string& text, int kerning, int lenght)
|
||||
}
|
||||
|
||||
// Escribe el texto en una surface
|
||||
auto Text::writeToSurface(const std::string& text, int zoom, int kerning) -> std::shared_ptr<Surface> {
|
||||
auto Text::writeToSurface(const std::string& text, int zoom, int kerning) -> std::shared_ptr<Surface> { // NOLINT(readability-make-member-function-const)
|
||||
auto width = length(text, kerning) * zoom;
|
||||
auto height = box_height_ * zoom;
|
||||
auto surface = std::make_shared<Surface>(width, height);
|
||||
@@ -170,7 +184,7 @@ auto Text::writeToSurface(const std::string& text, int zoom, int kerning) -> std
|
||||
}
|
||||
|
||||
// Escribe el texto con extras en una surface
|
||||
auto Text::writeDXToSurface(Uint8 flags, const std::string& text, int kerning, Uint8 text_color, Uint8 shadow_distance, Uint8 shadow_color, int lenght) -> std::shared_ptr<Surface> {
|
||||
auto Text::writeDXToSurface(Uint8 flags, const std::string& text, int kerning, Uint8 text_color, Uint8 shadow_distance, Uint8 shadow_color, int lenght) -> std::shared_ptr<Surface> { // NOLINT(readability-make-member-function-const)
|
||||
auto width = Text::length(text, kerning) + shadow_distance;
|
||||
auto height = box_height_ + shadow_distance;
|
||||
auto surface = std::make_shared<Surface>(width, height);
|
||||
@@ -184,7 +198,7 @@ auto Text::writeDXToSurface(Uint8 flags, const std::string& text, int kerning, U
|
||||
}
|
||||
|
||||
// Escribe el texto con colores
|
||||
void Text::writeColored(int x, int y, const std::string& text, Uint8 color, int kerning, int lenght) {
|
||||
void Text::writeColored(int x, int y, const std::string& text, Uint8 color, int kerning, int lenght) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
int shift = 0;
|
||||
int glyphs_done = 0;
|
||||
size_t pos = 0;
|
||||
@@ -218,7 +232,7 @@ void Text::writeCentered(int x, int y, const std::string& text, int kerning, int
|
||||
}
|
||||
|
||||
// Escribe texto con extras
|
||||
void Text::writeDX(Uint8 flags, int x, int y, const std::string& text, int kerning, Uint8 text_color, Uint8 shadow_distance, Uint8 shadow_color, int lenght) {
|
||||
void Text::writeDX(Uint8 flags, int x, int y, const std::string& text, int kerning, Uint8 text_color, Uint8 shadow_distance, Uint8 shadow_color, int lenght) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
const auto CENTERED = ((flags & CENTER_FLAG) == CENTER_FLAG);
|
||||
const auto SHADOWED = ((flags & SHADOW_FLAG) == SHADOW_FLAG);
|
||||
const auto COLORED = ((flags & COLOR_FLAG) == COLOR_FLAG);
|
||||
@@ -233,7 +247,8 @@ void Text::writeDX(Uint8 flags, int x, int y, const std::string& text, int kerni
|
||||
}
|
||||
|
||||
if (STROKED) {
|
||||
for (int dist = 1; dist <= shadow_distance; ++dist) {
|
||||
const int MAX_DIST = static_cast<int>(shadow_distance);
|
||||
for (int dist = 1; dist <= MAX_DIST; ++dist) {
|
||||
for (int dy = -dist; dy <= dist; ++dy) {
|
||||
for (int dx = -dist; dx <= dist; ++dx) {
|
||||
writeColored(x + dx, y + dy, text, shadow_color, kerning, lenght);
|
||||
@@ -250,7 +265,7 @@ void Text::writeDX(Uint8 flags, int x, int y, const std::string& text, int kerni
|
||||
}
|
||||
|
||||
// Obtiene la longitud en pixels de una cadena UTF-8
|
||||
auto Text::length(const std::string& text, int kerning) const -> int {
|
||||
auto Text::length(const std::string& text, int kerning) const -> int { // NOLINT(readability-convert-member-functions-to-static)
|
||||
int shift = 0;
|
||||
size_t pos = 0;
|
||||
|
||||
@@ -267,7 +282,7 @@ auto Text::length(const std::string& text, int kerning) const -> int {
|
||||
}
|
||||
|
||||
// Devuelve el ancho en pixels de un glifo dado su codepoint Unicode
|
||||
auto Text::glyphWidth(uint32_t codepoint, int kerning) const -> int {
|
||||
auto Text::glyphWidth(uint32_t codepoint, int kerning) const -> int { // NOLINT(readability-convert-member-functions-to-static)
|
||||
auto it = offset_.find(codepoint);
|
||||
if (it == offset_.end()) { it = offset_.find('?'); }
|
||||
if (it != offset_.end()) { return it->second.w + kerning; }
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <string> // Para string
|
||||
#include <unordered_map> // Para unordered_map
|
||||
|
||||
#include "core/rendering/surface_sprite.hpp" // Para SSprite
|
||||
#include "core/rendering/sprite/sprite.hpp" // Para SSprite
|
||||
class Surface; // Forward declaration
|
||||
|
||||
// Clase texto. Pinta texto en pantalla a partir de un bitmap con soporte UTF-8
|
||||
@@ -52,7 +52,7 @@ class Text {
|
||||
[[nodiscard]] auto getCharacterSize() const -> int; // Devuelve el tamaño del caracter
|
||||
[[nodiscard]] auto glyphWidth(uint32_t codepoint, int kerning = 0) const -> int; // Devuelve el ancho en pixels de un glifo
|
||||
[[nodiscard]] auto getGlyphClip(uint32_t codepoint) const -> SDL_FRect; // Devuelve el clip rect del glifo
|
||||
[[nodiscard]] auto getSprite() const -> SurfaceSprite* { return sprite_.get(); } // Acceso al sprite interno
|
||||
[[nodiscard]] auto getSprite() const -> Sprite* { return sprite_.get(); } // Acceso al sprite interno
|
||||
|
||||
void setFixedWidth(bool value); // Establece si se usa un tamaño fijo de letra
|
||||
|
||||
@@ -62,7 +62,7 @@ class Text {
|
||||
|
||||
private:
|
||||
// Objetos y punteros
|
||||
std::unique_ptr<SurfaceSprite> sprite_ = nullptr; // Objeto con los graficos para el texto
|
||||
std::unique_ptr<Sprite> sprite_ = nullptr; // Objeto con los graficos para el texto
|
||||
|
||||
// Variables
|
||||
int box_width_ = 0; // Anchura de la caja de cada caracter en el png
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
|
||||
#include "core/rendering/texture.hpp"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <iostream> // Para basic_ostream, operator<<, endl, cout
|
||||
#include <stdexcept> // Para runtime_error
|
||||
#include <string> // Para char_traits, operator<<, string, opera...
|
||||
#include <utility>
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "utils/utils.hpp" // Para getFileName, Color, printWithDots
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "external/stb_image.h" // para stbi_failure_reason, stbi_image_free
|
||||
|
||||
// Constructor
|
||||
Texture::Texture(SDL_Renderer* renderer, std::string path)
|
||||
: renderer_(renderer),
|
||||
path_(std::move(path)) {
|
||||
// Carga el fichero en la textura
|
||||
if (!path_.empty()) {
|
||||
// Obtiene la extensión
|
||||
const std::string EXTENSION = path_.substr(path_.find_last_of('.') + 1);
|
||||
|
||||
// .png
|
||||
if (EXTENSION == "png") {
|
||||
loadFromFile(path_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Destructor
|
||||
Texture::~Texture() {
|
||||
unloadTexture();
|
||||
palettes_.clear();
|
||||
}
|
||||
|
||||
// Carga una imagen desde un fichero
|
||||
auto Texture::loadFromFile(const std::string& file_path) -> bool {
|
||||
if (file_path.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int req_format = STBI_rgb_alpha;
|
||||
int width;
|
||||
int height;
|
||||
int orig_format;
|
||||
unsigned char* data = stbi_load(file_path.c_str(), &width, &height, &orig_format, req_format);
|
||||
if (data == nullptr) {
|
||||
std::cerr << "Error: Fichero no encontrado " << getFileName(file_path) << '\n';
|
||||
throw std::runtime_error("Fichero no encontrado: " + getFileName(file_path));
|
||||
}
|
||||
printWithDots("Image : ", getFileName(file_path), "[ LOADED ]");
|
||||
|
||||
int pitch;
|
||||
SDL_PixelFormat pixel_format;
|
||||
// STBI_rgb_alpha (RGBA)
|
||||
pitch = 4 * width;
|
||||
pixel_format = SDL_PIXELFORMAT_RGBA32;
|
||||
|
||||
// Limpia
|
||||
unloadTexture();
|
||||
|
||||
// La textura final
|
||||
SDL_Texture* new_texture = nullptr;
|
||||
|
||||
// Carga la imagen desde una ruta específica
|
||||
auto* loaded_surface = SDL_CreateSurfaceFrom(width, height, pixel_format, static_cast<void*>(data), pitch);
|
||||
if (loaded_surface == nullptr) {
|
||||
std::cout << "Unable to load image " << file_path << '\n';
|
||||
} else {
|
||||
// Crea la textura desde los pixels de la surface
|
||||
new_texture = SDL_CreateTextureFromSurface(renderer_, loaded_surface);
|
||||
if (new_texture == nullptr) {
|
||||
std::cout << "Unable to create texture from " << file_path << "! SDL Error: " << SDL_GetError() << '\n';
|
||||
} else {
|
||||
// Obtiene las dimensiones de la imagen
|
||||
width_ = loaded_surface->w;
|
||||
height_ = loaded_surface->h;
|
||||
}
|
||||
|
||||
// Elimina la textura cargada
|
||||
SDL_DestroySurface(loaded_surface);
|
||||
}
|
||||
|
||||
// Return success
|
||||
stbi_image_free(data);
|
||||
texture_ = new_texture;
|
||||
return texture_ != nullptr;
|
||||
}
|
||||
|
||||
// Crea una textura en blanco
|
||||
auto Texture::createBlank(int width, int height, SDL_PixelFormat format, SDL_TextureAccess access) -> bool {
|
||||
// Crea una textura sin inicializar
|
||||
texture_ = SDL_CreateTexture(renderer_, format, access, width, height);
|
||||
if (texture_ == nullptr) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create blank texture! SDL Error: %s", SDL_GetError());
|
||||
} else {
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
}
|
||||
|
||||
return texture_ != nullptr;
|
||||
}
|
||||
|
||||
// Libera la memoria de la textura
|
||||
void Texture::unloadTexture() {
|
||||
// Libera la textura
|
||||
if (texture_ != nullptr) {
|
||||
SDL_DestroyTexture(texture_);
|
||||
texture_ = nullptr;
|
||||
width_ = 0;
|
||||
height_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Establece el color para la modulacion
|
||||
void Texture::setColor(Uint8 red, Uint8 green, Uint8 blue) { SDL_SetTextureColorMod(texture_, red, green, blue); }
|
||||
void Texture::setColor(Color color) { SDL_SetTextureColorMod(texture_, color.r, color.g, color.b); }
|
||||
|
||||
// Establece el blending
|
||||
void Texture::setBlendMode(SDL_BlendMode blending) { SDL_SetTextureBlendMode(texture_, blending); }
|
||||
|
||||
// Establece el alpha para la modulación
|
||||
void Texture::setAlpha(Uint8 alpha) { SDL_SetTextureAlphaMod(texture_, alpha); }
|
||||
|
||||
// Renderiza la textura en un punto específico
|
||||
void Texture::render(float x, float y, SDL_FRect* clip, float zoom_w, float zoom_h, double angle, SDL_FPoint* center, SDL_FlipMode flip) {
|
||||
// Establece el destino de renderizado en la pantalla
|
||||
SDL_FRect render_quad = {x, y, width_, height_};
|
||||
|
||||
// Obtiene las dimesiones del clip de renderizado
|
||||
if (clip != nullptr) {
|
||||
render_quad.w = clip->w;
|
||||
render_quad.h = clip->h;
|
||||
}
|
||||
|
||||
// Calcula el zoom y las coordenadas
|
||||
if (zoom_h != 1.0F || zoom_w != 1.0F) {
|
||||
render_quad.x = render_quad.x + (render_quad.w / 2);
|
||||
render_quad.y = render_quad.y + (render_quad.h / 2);
|
||||
render_quad.w = render_quad.w * zoom_w;
|
||||
render_quad.h = render_quad.h * zoom_h;
|
||||
render_quad.x = render_quad.x - (render_quad.w / 2);
|
||||
render_quad.y = render_quad.y - (render_quad.h / 2);
|
||||
}
|
||||
|
||||
// Renderiza a pantalla
|
||||
SDL_RenderTextureRotated(renderer_, texture_, clip, &render_quad, angle, center, flip);
|
||||
}
|
||||
|
||||
// Establece la textura como objetivo de renderizado
|
||||
void Texture::setAsRenderTarget(SDL_Renderer* renderer) { SDL_SetRenderTarget(renderer, texture_); }
|
||||
|
||||
// Recarga la textura
|
||||
auto Texture::reLoad() -> bool { return loadFromFile(path_); }
|
||||
|
||||
// Obtiene la textura
|
||||
auto Texture::getSDLTexture() -> SDL_Texture* { return texture_; }
|
||||
|
||||
// Obtiene el renderizador
|
||||
auto Texture::getRenderer() -> SDL_Renderer* { return renderer_; }
|
||||
@@ -1,43 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
struct Color; // lines 11-11
|
||||
|
||||
class Texture {
|
||||
public:
|
||||
explicit Texture(SDL_Renderer* renderer, std::string path = std::string()); // Constructor
|
||||
~Texture(); // Destructor
|
||||
|
||||
auto loadFromFile(const std::string& path) -> bool; // Carga una imagen desde un fichero
|
||||
auto createBlank(int width, int height, SDL_PixelFormat format = SDL_PIXELFORMAT_RGBA8888, SDL_TextureAccess access = SDL_TEXTUREACCESS_STREAMING) -> bool; // Crea una textura en blanco
|
||||
auto reLoad() -> bool; // Recarga la textura
|
||||
|
||||
void setColor(Uint8 red, Uint8 green, Uint8 blue); // Establece el color para la modulacion
|
||||
void setColor(Color color); // Establece el color para la modulacion
|
||||
void setBlendMode(SDL_BlendMode blending); // Establece el blending
|
||||
void setAlpha(Uint8 alpha); // Establece el alpha para la modulación
|
||||
void setAsRenderTarget(SDL_Renderer* renderer); // Establece la textura como objetivo de renderizado
|
||||
|
||||
void render(float x, float y, SDL_FRect* clip = nullptr, float zoom_w = 1, float zoom_h = 1, double angle = 0.0, SDL_FPoint* center = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE); // Renderiza la textura en un punto específico
|
||||
|
||||
[[nodiscard]] auto getWidth() const -> int { return width_; } // Obtiene el ancho de la imagen
|
||||
[[nodiscard]] auto getHeight() const -> int { return height_; } // Obtiene el alto de la imagen
|
||||
auto getSDLTexture() -> SDL_Texture*; // Obtiene la textura
|
||||
auto getRenderer() -> SDL_Renderer*; // Obtiene el renderizador
|
||||
|
||||
private:
|
||||
void unloadTexture(); // Libera la memoria de la textura
|
||||
|
||||
// Objetos y punteros
|
||||
SDL_Renderer* renderer_; // Renderizador donde dibujar la textura
|
||||
SDL_Texture* texture_ = nullptr; // La textura
|
||||
|
||||
// Variables
|
||||
std::string path_; // Ruta de la imagen de la textura
|
||||
float width_ = 0.0F; // Ancho de la imagen
|
||||
float height_ = 0.0F; // Alto de la imagen
|
||||
std::vector<std::vector<Uint32>> palettes_; // Vector con las diferentes paletas
|
||||
};
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <algorithm> // Para find_if
|
||||
#include <cstdlib> // Para exit, size_t
|
||||
#include <fstream> // Para ifstream, istreambuf_iterator
|
||||
#include <iostream> // Para basic_ostream, operator<<, endl, cout
|
||||
#include <stdexcept> // Para runtime_error
|
||||
#include <utility>
|
||||
@@ -15,6 +16,7 @@
|
||||
#include "core/resources/resource_list.hpp" // Para List, List::Type
|
||||
#include "game/defaults.hpp" // Para Defaults namespace
|
||||
#include "game/gameplay/room.hpp" // Para RoomData, loadRoomFile, loadRoomTileFile
|
||||
#include "game/gameplay/room_loader.hpp" // Para RoomLoader::loadFromString
|
||||
#include "game/options.hpp" // Para Options, OptionsGame, options
|
||||
#include "utils/defines.hpp" // Para WINDOW_CAPTION
|
||||
#include "utils/utils.hpp" // Para getFileName, printWithDots, PaletteColor
|
||||
@@ -55,6 +57,8 @@ namespace Resource {
|
||||
|
||||
// Carga todos los recursos
|
||||
void Cache::load() {
|
||||
// Nota: el overlay de debug (RenderInfo) se inicializa después de esta carga,
|
||||
// por lo que updateZoomFactor() se llamará correctamente en RenderInfo::init().
|
||||
calculateTotal();
|
||||
Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK));
|
||||
std::cout << "\n** LOADING RESOURCES" << '\n';
|
||||
@@ -76,8 +80,8 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Obtiene el sonido a partir de un nombre
|
||||
auto Cache::getSound(const std::string& name) -> JA_Sound_t* {
|
||||
auto it = std::ranges::find_if(sounds_, [&name](const auto& s) { return s.name == name; });
|
||||
auto Cache::getSound(const std::string& name) -> JA_Sound_t* { // NOLINT(readability-convert-member-functions-to-static)
|
||||
auto it = std::ranges::find_if(sounds_, [&name](const auto& s) -> bool { return s.name == name; });
|
||||
|
||||
if (it != sounds_.end()) {
|
||||
return it->sound;
|
||||
@@ -88,8 +92,8 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Obtiene la música a partir de un nombre
|
||||
auto Cache::getMusic(const std::string& name) -> JA_Music_t* {
|
||||
auto it = std::ranges::find_if(musics_, [&name](const auto& m) { return m.name == name; });
|
||||
auto Cache::getMusic(const std::string& name) -> JA_Music_t* { // NOLINT(readability-convert-member-functions-to-static)
|
||||
auto it = std::ranges::find_if(musics_, [&name](const auto& m) -> bool { return m.name == name; });
|
||||
|
||||
if (it != musics_.end()) {
|
||||
return it->music;
|
||||
@@ -100,8 +104,8 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Obtiene la surface a partir de un nombre
|
||||
auto Cache::getSurface(const std::string& name) -> std::shared_ptr<Surface> {
|
||||
auto it = std::ranges::find_if(surfaces_, [&name](const auto& t) { return t.name == name; });
|
||||
auto Cache::getSurface(const std::string& name) -> std::shared_ptr<Surface> { // NOLINT(readability-convert-member-functions-to-static)
|
||||
auto it = std::ranges::find_if(surfaces_, [&name](const auto& t) -> bool { return t.name == name; });
|
||||
|
||||
if (it != surfaces_.end()) {
|
||||
return it->surface;
|
||||
@@ -112,8 +116,8 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Obtiene la paleta a partir de un nombre
|
||||
auto Cache::getPalette(const std::string& name) -> Palette {
|
||||
auto it = std::ranges::find_if(palettes_, [&name](const auto& t) { return t.name == name; });
|
||||
auto Cache::getPalette(const std::string& name) -> Palette { // NOLINT(readability-convert-member-functions-to-static)
|
||||
auto it = std::ranges::find_if(palettes_, [&name](const auto& t) -> bool { return t.name == name; });
|
||||
|
||||
if (it != palettes_.end()) {
|
||||
return it->palette;
|
||||
@@ -124,8 +128,8 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Obtiene el fichero de texto a partir de un nombre
|
||||
auto Cache::getTextFile(const std::string& name) -> std::shared_ptr<Text::File> {
|
||||
auto it = std::ranges::find_if(text_files_, [&name](const auto& t) { return t.name == name; });
|
||||
auto Cache::getTextFile(const std::string& name) -> std::shared_ptr<Text::File> { // NOLINT(readability-convert-member-functions-to-static)
|
||||
auto it = std::ranges::find_if(text_files_, [&name](const auto& t) -> bool { return t.name == name; });
|
||||
|
||||
if (it != text_files_.end()) {
|
||||
return it->text_file;
|
||||
@@ -136,8 +140,8 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Obtiene el objeto de texto a partir de un nombre
|
||||
auto Cache::getText(const std::string& name) -> std::shared_ptr<Text> {
|
||||
auto it = std::ranges::find_if(texts_, [&name](const auto& t) { return t.name == name; });
|
||||
auto Cache::getText(const std::string& name) -> std::shared_ptr<Text> { // NOLINT(readability-convert-member-functions-to-static)
|
||||
auto it = std::ranges::find_if(texts_, [&name](const auto& t) -> bool { return t.name == name; });
|
||||
|
||||
if (it != texts_.end()) {
|
||||
return it->text;
|
||||
@@ -148,8 +152,8 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Obtiene los datos de animación parseados a partir de un nombre
|
||||
auto Cache::getAnimationData(const std::string& name) -> const AnimationResource& {
|
||||
auto it = std::ranges::find_if(animations_, [&name](const auto& a) { return a.name == name; });
|
||||
auto Cache::getAnimationData(const std::string& name) -> const AnimationResource& { // NOLINT(readability-convert-member-functions-to-static)
|
||||
auto it = std::ranges::find_if(animations_, [&name](const auto& a) -> bool { return a.name == name; });
|
||||
|
||||
if (it != animations_.end()) {
|
||||
return *it;
|
||||
@@ -160,8 +164,8 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Obtiene la habitación a partir de un nombre
|
||||
auto Cache::getRoom(const std::string& name) -> std::shared_ptr<Room::Data> {
|
||||
auto it = std::ranges::find_if(rooms_, [&name](const auto& r) { return r.name == name; });
|
||||
auto Cache::getRoom(const std::string& name) -> std::shared_ptr<Room::Data> { // NOLINT(readability-convert-member-functions-to-static)
|
||||
auto it = std::ranges::find_if(rooms_, [&name](const auto& r) -> bool { return r.name == name; });
|
||||
|
||||
if (it != rooms_.end()) {
|
||||
return it->room;
|
||||
@@ -171,13 +175,41 @@ namespace Resource {
|
||||
throw std::runtime_error("Habitación no encontrada: " + name);
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Recarga una habitación desde disco (para el editor de mapas)
|
||||
// Lee directamente del filesystem (no del resource pack) para obtener los cambios del editor
|
||||
void Cache::reloadRoom(const std::string& name) {
|
||||
auto file_path = List::get()->get(name);
|
||||
if (file_path.empty()) {
|
||||
std::cerr << "reloadRoom: Cannot resolve path for " << name << '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
// Leer directamente del filesystem (evita el resource pack que tiene datos antiguos)
|
||||
std::ifstream file(file_path);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "reloadRoom: Cannot open " << file_path << '\n';
|
||||
return;
|
||||
}
|
||||
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
file.close();
|
||||
|
||||
// Parsear y actualizar el cache
|
||||
auto it = std::ranges::find_if(rooms_, [&name](const auto& r) -> bool { return r.name == name; });
|
||||
if (it != rooms_.end()) {
|
||||
*(it->room) = RoomLoader::loadFromString(content, name);
|
||||
std::cout << "reloadRoom: " << name << " reloaded from filesystem\n";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Obtiene todas las habitaciones
|
||||
auto Cache::getRooms() -> std::vector<RoomResource>& {
|
||||
return rooms_;
|
||||
}
|
||||
|
||||
// Helper para lanzar errores de carga con formato consistente
|
||||
[[noreturn]] void Cache::throwLoadError(const std::string& asset_type, const std::string& file_path, const std::exception& e) {
|
||||
[[noreturn]] void Cache::throwLoadError(const std::string& asset_type, const std::string& file_path, const std::exception& e) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::cerr << "\n[ ERROR ] Failed to load " << asset_type << ": " << getFileName(file_path) << '\n';
|
||||
std::cerr << "[ ERROR ] Path: " << file_path << '\n';
|
||||
std::cerr << "[ ERROR ] Reason: " << e.what() << '\n';
|
||||
@@ -186,7 +218,7 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Carga los sonidos
|
||||
void Cache::loadSounds() {
|
||||
void Cache::loadSounds() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::cout << "\n>> SOUND FILES" << '\n';
|
||||
auto list = List::get()->getListByType(List::Type::SOUND);
|
||||
sounds_.clear();
|
||||
@@ -221,7 +253,7 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Carga las musicas
|
||||
void Cache::loadMusics() {
|
||||
void Cache::loadMusics() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::cout << "\n>> MUSIC FILES" << '\n';
|
||||
auto list = List::get()->getListByType(List::Type::MUSIC);
|
||||
musics_.clear();
|
||||
@@ -256,7 +288,7 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Carga las texturas
|
||||
void Cache::loadSurfaces() {
|
||||
void Cache::loadSurfaces() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::cout << "\n>> SURFACES" << '\n';
|
||||
auto list = List::get()->getListByType(List::Type::BITMAP);
|
||||
surfaces_.clear();
|
||||
@@ -283,7 +315,7 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Carga las paletas
|
||||
void Cache::loadPalettes() {
|
||||
void Cache::loadPalettes() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::cout << "\n>> PALETTES" << '\n';
|
||||
auto list = List::get()->getListByType(List::Type::PALETTE);
|
||||
palettes_.clear();
|
||||
@@ -300,7 +332,7 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Carga los ficheros de texto
|
||||
void Cache::loadTextFiles() {
|
||||
void Cache::loadTextFiles() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::cout << "\n>> TEXT FILES" << '\n';
|
||||
auto list = List::get()->getListByType(List::Type::FONT);
|
||||
text_files_.clear();
|
||||
@@ -317,7 +349,7 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Carga las animaciones
|
||||
void Cache::loadAnimations() {
|
||||
void Cache::loadAnimations() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::cout << "\n>> ANIMATIONS" << '\n';
|
||||
auto list = List::get()->getListByType(List::Type::ANIMATION);
|
||||
animations_.clear();
|
||||
@@ -343,7 +375,7 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Carga las habitaciones desde archivos YAML
|
||||
void Cache::loadRooms() {
|
||||
void Cache::loadRooms() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::cout << "\n>> ROOMS" << '\n';
|
||||
auto list = List::get()->getListByType(List::Type::ROOM);
|
||||
rooms_.clear();
|
||||
@@ -360,7 +392,7 @@ namespace Resource {
|
||||
}
|
||||
}
|
||||
|
||||
void Cache::createText() {
|
||||
void Cache::createText() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
struct ResourceInfo {
|
||||
std::string key; // Identificador del recurso
|
||||
std::string texture_file; // Nombre del archivo de textura
|
||||
@@ -461,12 +493,12 @@ namespace Resource {
|
||||
|
||||
// Draw progress bar border
|
||||
const float WIRED_BAR_WIDTH = Options::game.width - (X_PADDING * 2);
|
||||
SDL_FRect rect_wired = {X_PADDING, BAR_POSITION, WIRED_BAR_WIDTH, BAR_HEIGHT};
|
||||
SDL_FRect rect_wired = {.x = X_PADDING, .y = BAR_POSITION, .w = WIRED_BAR_WIDTH, .h = BAR_HEIGHT};
|
||||
surface->drawRectBorder(&rect_wired, BAR_COLOR);
|
||||
|
||||
// Draw progress bar fill
|
||||
const float FULL_BAR_WIDTH = WIRED_BAR_WIDTH * count_.getPercentage();
|
||||
SDL_FRect rect_full = {X_PADDING, BAR_POSITION, FULL_BAR_WIDTH, BAR_HEIGHT};
|
||||
SDL_FRect rect_full = {.x = X_PADDING, .y = BAR_POSITION, .w = FULL_BAR_WIDTH, .h = BAR_HEIGHT};
|
||||
surface->fillRect(&rect_full, BAR_COLOR);
|
||||
|
||||
Screen::get()->render();
|
||||
|
||||
@@ -26,6 +26,9 @@ namespace Resource {
|
||||
auto getRooms() -> std::vector<RoomResource>&;
|
||||
|
||||
void reload(); // Recarga todos los recursos
|
||||
#ifdef _DEBUG
|
||||
void reloadRoom(const std::string& name); // Recarga una habitación desde disco
|
||||
#endif
|
||||
|
||||
private:
|
||||
// Estructura para llevar la cuenta de los recursos cargados
|
||||
|
||||
@@ -19,11 +19,11 @@ namespace Resource {
|
||||
// Singleton
|
||||
List* List::instance = nullptr;
|
||||
|
||||
void List::init(const std::string& executable_path) {
|
||||
void List::init(const std::string& executable_path) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
List::instance = new List(executable_path);
|
||||
}
|
||||
|
||||
void List::destroy() {
|
||||
void List::destroy() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
delete List::instance;
|
||||
}
|
||||
|
||||
@@ -32,12 +32,12 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Añade un elemento al mapa (función auxiliar)
|
||||
void List::addToMap(const std::string& file_path, Type type, bool required, bool absolute) {
|
||||
void List::addToMap(const std::string& file_path, Type type, bool required, bool absolute) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::string full_path = absolute ? file_path : executable_path_ + file_path;
|
||||
std::string filename = getFileName(full_path);
|
||||
|
||||
// Verificar si ya existe el archivo
|
||||
if (file_list_.find(filename) != file_list_.end()) {
|
||||
if (file_list_.contains(filename)) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Warning: Asset '%s' already exists, overwriting",
|
||||
filename.c_str());
|
||||
@@ -52,7 +52,7 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Carga recursos desde un archivo de configuración con soporte para variables
|
||||
void List::loadFromFile(const std::string& config_file_path, const std::string& prefix, const std::string& system_folder) {
|
||||
void List::loadFromFile(const std::string& config_file_path, const std::string& prefix, const std::string& system_folder) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::ifstream file(config_file_path);
|
||||
if (!file.is_open()) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@@ -71,7 +71,7 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Carga recursos desde un string de configuración (para release con pack)
|
||||
void List::loadFromString(const std::string& config_content, const std::string& prefix, const std::string& system_folder) {
|
||||
void List::loadFromString(const std::string& config_content, const std::string& prefix, const std::string& system_folder) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
try {
|
||||
// Parsear YAML
|
||||
auto yaml = fkyaml::node::deserialize(config_content);
|
||||
@@ -156,7 +156,7 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Devuelve la ruta completa a un fichero (búsqueda O(1))
|
||||
auto List::get(const std::string& filename) const -> std::string {
|
||||
auto List::get(const std::string& filename) const -> std::string { // NOLINT(readability-convert-member-functions-to-static)
|
||||
auto it = file_list_.find(filename);
|
||||
if (it != file_list_.end()) {
|
||||
return it->second.file;
|
||||
@@ -167,7 +167,7 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Carga datos del archivo
|
||||
auto List::loadData(const std::string& filename) const -> std::vector<uint8_t> {
|
||||
auto List::loadData(const std::string& filename) const -> std::vector<uint8_t> { // NOLINT(readability-convert-member-functions-to-static)
|
||||
auto it = file_list_.find(filename);
|
||||
if (it != file_list_.end()) {
|
||||
std::ifstream file(it->second.file, std::ios::binary);
|
||||
@@ -197,11 +197,11 @@ namespace Resource {
|
||||
|
||||
// Verifica si un recurso existe
|
||||
auto List::exists(const std::string& filename) const -> bool {
|
||||
return file_list_.find(filename) != file_list_.end();
|
||||
return file_list_.contains(filename);
|
||||
}
|
||||
|
||||
// Parsea string a Type
|
||||
auto List::parseAssetType(const std::string& type_str) -> Type {
|
||||
auto List::parseAssetType(const std::string& type_str) -> Type { // NOLINT(readability-convert-member-functions-to-static)
|
||||
if (type_str == "DATA") {
|
||||
return Type::DATA;
|
||||
}
|
||||
@@ -235,7 +235,7 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Devuelve el nombre del tipo de recurso
|
||||
auto List::getTypeName(Type type) -> std::string {
|
||||
auto List::getTypeName(Type type) -> std::string { // NOLINT(readability-convert-member-functions-to-static)
|
||||
switch (type) {
|
||||
case Type::DATA:
|
||||
return "DATA";
|
||||
@@ -259,7 +259,7 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Devuelve la lista de recursos de un tipo
|
||||
auto List::getListByType(Type type) const -> std::vector<std::string> {
|
||||
auto List::getListByType(Type type) const -> std::vector<std::string> { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::vector<std::string> list;
|
||||
|
||||
for (const auto& [filename, item] : file_list_) {
|
||||
@@ -275,7 +275,7 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Reemplaza variables en las rutas
|
||||
auto List::replaceVariables(const std::string& path, const std::string& prefix, const std::string& system_folder) -> std::string {
|
||||
auto List::replaceVariables(const std::string& path, const std::string& prefix, const std::string& system_folder) -> std::string { // NOLINT(readability-convert-member-functions-to-static)
|
||||
std::string result = path;
|
||||
|
||||
// Reemplazar ${PREFIX}
|
||||
@@ -296,7 +296,7 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Parsea las opciones de una línea de configuración
|
||||
auto List::parseOptions(const std::string& options, bool& required, bool& absolute) -> void {
|
||||
auto List::parseOptions(const std::string& options, bool& required, bool& absolute) -> void { // NOLINT(readability-convert-member-functions-to-static)
|
||||
if (options.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user