Compare commits
26 Commits
v1.11
...
0e61b94848
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
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)
|
||||
|
||||
@@ -96,8 +96,14 @@ set(APP_SOURCES
|
||||
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
|
||||
|
||||
# Game - UI
|
||||
source/game/ui/console.cpp
|
||||
source/game/ui/console_commands.cpp
|
||||
source/game/ui/notifier.cpp
|
||||
|
||||
# Utils
|
||||
@@ -224,7 +230,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()
|
||||
|
||||
280
Makefile
280
Makefile
@@ -18,24 +18,13 @@ 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
|
||||
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
|
||||
@@ -79,190 +68,87 @@ 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/global_inputs.cpp \
|
||||
source/core/input/input_types.cpp \
|
||||
source/core/input/input.cpp \
|
||||
source/core/input/mouse.cpp \
|
||||
source/core/locale/locale.cpp \
|
||||
source/core/rendering/gif.cpp \
|
||||
source/core/rendering/palette_manager.cpp \
|
||||
source/core/rendering/pixel_reveal.cpp \
|
||||
source/core/rendering/render_info.cpp \
|
||||
source/core/rendering/screen.cpp \
|
||||
source/core/rendering/sdl3gpu/sdl3gpu_shader.cpp \
|
||||
source/core/rendering/sprite/animated_sprite.cpp \
|
||||
source/core/rendering/sprite/dissolve_sprite.cpp \
|
||||
source/core/rendering/sprite/moving_sprite.cpp \
|
||||
source/core/rendering/sprite/sprite.cpp \
|
||||
source/core/rendering/surface.cpp \
|
||||
source/core/rendering/text.cpp \
|
||||
source/core/resources/resource_cache.cpp \
|
||||
source/core/resources/resource_helper.cpp \
|
||||
source/core/resources/resource_list.cpp \
|
||||
source/core/resources/resource_loader.cpp \
|
||||
source/core/resources/resource_pack.cpp \
|
||||
source/core/system/debug.cpp \
|
||||
source/core/system/director.cpp \
|
||||
source/core/system/global_events.cpp \
|
||||
source/game/entities/enemy.cpp \
|
||||
source/game/entities/item.cpp \
|
||||
source/game/entities/player.cpp \
|
||||
source/game/gameplay/cheevos.cpp \
|
||||
source/game/gameplay/collision_map.cpp \
|
||||
source/game/gameplay/enemy_manager.cpp \
|
||||
source/game/gameplay/item_manager.cpp \
|
||||
source/game/gameplay/item_tracker.cpp \
|
||||
source/game/gameplay/room_loader.cpp \
|
||||
source/game/gameplay/room_tracker.cpp \
|
||||
source/game/gameplay/room.cpp \
|
||||
source/game/gameplay/scoreboard.cpp \
|
||||
source/game/gameplay/stats.cpp \
|
||||
source/game/gameplay/tilemap_renderer.cpp \
|
||||
source/game/options.cpp \
|
||||
source/game/scenes/credits.cpp \
|
||||
source/game/scenes/ending.cpp \
|
||||
source/game/scenes/ending2.cpp \
|
||||
source/game/scenes/game_over.cpp \
|
||||
source/game/scenes/game.cpp \
|
||||
source/game/scenes/loading_screen.cpp \
|
||||
source/game/scenes/logo.cpp \
|
||||
source/game/scenes/title.cpp \
|
||||
source/game/ui/console.cpp \
|
||||
source/game/ui/notifier.cpp \
|
||||
source/utils/delta_timer.cpp \
|
||||
source/utils/utils.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
|
||||
|
||||
# ==============================================================================
|
||||
# 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)
|
||||
CXXFLAGS += -DMACOS_BUILD
|
||||
CXXFLAGS_DEBUG += -DMACOS_BUILD
|
||||
# Configurar arquitectura (por defecto arm64)
|
||||
CXXFLAGS += -arch arm64
|
||||
CXXFLAGS_DEBUG += -arch arm64
|
||||
# Usar Apple Clang explícitamente para evitar LLVM de Homebrew
|
||||
MACOS_CXX := /usr/bin/clang++
|
||||
@$(MAKE) macos_release
|
||||
else
|
||||
@$(MAKE) linux_release
|
||||
endif
|
||||
endif
|
||||
|
||||
# ==============================================================================
|
||||
# REGLAS PARA COMPILACIÓN DE SHADERS
|
||||
# REGLAS PARA COMPILACIÓN DE SHADERS (multiplataforma via cmake)
|
||||
# ==============================================================================
|
||||
compile_shaders:
|
||||
ifdef GLSLC
|
||||
ifeq ($(OS),Windows_NT)
|
||||
@powershell -Command "if ((Test-Path '$(SHADER_VERT_H)') -and (Test-Path '$(SHADER_FRAG_H)')) { Write-Host 'Shaders SPIR-V precompilados OK' } else { Write-Host 'Compilando shaders SPIR-V...'; bash '$(SHADER_SCRIPT)'; Write-Host 'Shaders compilados' }"
|
||||
@cmake -D GLSLC=$(GLSLC) -D SHADERS_DIR=$(SHADERS_DIR) -D HEADERS_DIR=$(HEADERS_DIR) -P $(SHADER_CMAKE)
|
||||
else
|
||||
@echo "Compilando shaders SPIR-V..."
|
||||
@$(SHADER_SCRIPT)
|
||||
@echo "✓ Shaders compilados"
|
||||
endif
|
||||
else
|
||||
ifeq ($(OS),Windows_NT)
|
||||
@powershell -Command "if ((Test-Path '$(SHADER_VERT_H)') -and (Test-Path '$(SHADER_FRAG_H)')) { Write-Host 'glslc no encontrado - usando headers SPIR-V precompilados' } else { Write-Host 'ERROR: glslc no encontrado y headers SPIR-V no existen.'; Write-Host ' Instala glslc o ejecuta: tools/shaders/compile_spirv.sh'; exit 1 }"
|
||||
else
|
||||
@if [ -f "$(SHADER_VERT_H)" ] && [ -f "$(SHADER_FRAG_H)" ]; then \
|
||||
echo "⚠ glslc no encontrado - usando headers SPIR-V precompilados"; \
|
||||
else \
|
||||
echo "ERROR: glslc no encontrado y headers SPIR-V no existen."; \
|
||||
echo " Instala glslc: sudo apt install glslang-tools"; \
|
||||
echo " O ejecuta manualmente: tools/shaders/compile_spirv.sh"; \
|
||||
exit 1; \
|
||||
fi
|
||||
endif
|
||||
@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 -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'
|
||||
# Copia ficheros
|
||||
@powershell -Command "Copy-Item -Path 'resources.pack' -Destination '$(RELEASE_FOLDER)'"
|
||||
|
||||
# Copia los ficheros que están en la raíz del proyecto
|
||||
@powershell -Command "Copy-Item 'LICENSE' -Destination '$(RELEASE_FOLDER)'"
|
||||
@powershell -Command "Copy-Item '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)'"
|
||||
|
||||
# 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"
|
||||
@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
|
||||
@@ -274,26 +160,17 @@ windows_release:
|
||||
@powershell -Command "if (Test-Path '$(RELEASE_FOLDER)') {Remove-Item '$(RELEASE_FOLDER)' -Recurse -Force}"
|
||||
|
||||
# ==============================================================================
|
||||
# COMPILACIÓN PARA MACOS
|
||||
# COMPILACIÓN PARA MACOS (RELEASE)
|
||||
# ==============================================================================
|
||||
macos:
|
||||
@GIT_HASH=$$(git rev-parse --short=7 HEAD 2>/dev/null || echo "unknown"); \
|
||||
sed "s/@GIT_HASH@/$$GIT_HASH/g" source/version.h.in > source/version.h
|
||||
@echo "Compilando para macOS: $(TARGET_NAME)"
|
||||
$(MACOS_CXX) $(ALL_SOURCES) $(INCLUDES) $(CXXFLAGS) $(LDFLAGS) -o "$(TARGET_FILE)"
|
||||
|
||||
macos_release:
|
||||
@$(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)"
|
||||
@@ -322,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
|
||||
$(MACOS_CXX) $(ALL_SOURCES) $(INCLUDES) -DMACOS_BUNDLE -DRELEASE_BUILD -std=$(CPP_STANDARD) -Wall -Os -framework SDL3 -F release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64 -ffunction-sections -fdata-sections -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target x86_64-apple-macos10.15
|
||||
# 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"
|
||||
@@ -345,8 +222,10 @@ macos_release:
|
||||
"$(RELEASE_FOLDER)" || true
|
||||
@echo "Release Intel creado: $(MACOS_INTEL_RELEASE)"
|
||||
|
||||
# Compila la versión para procesadores Apple Silicon
|
||||
$(MACOS_CXX) $(ALL_SOURCES) $(INCLUDES) -DMACOS_BUNDLE -DRELEASE_BUILD -std=$(CPP_STANDARD) -Wall -Os -framework SDL3 -F release/macos/frameworks/SDL3.xcframework/macos-arm64_x86_64 -ffunction-sections -fdata-sections -o "$(RELEASE_FOLDER)/$(APP_NAME).app/Contents/MacOS/$(TARGET_NAME)" -rpath @executable_path/../Frameworks/ -target arm64-apple-macos11
|
||||
# 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"
|
||||
@@ -370,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)"
|
||||
@@ -401,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
|
||||
@@ -425,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:
|
||||
@@ -99,6 +117,11 @@ assets:
|
||||
required: false
|
||||
absolute: true
|
||||
|
||||
# CONSOLE
|
||||
console:
|
||||
- type: DATA
|
||||
path: ${PREFIX}/data/console/commands.yaml
|
||||
|
||||
# ROOMS
|
||||
rooms:
|
||||
- type: ROOM
|
||||
|
||||
239
data/console/commands.yaml
Normal file
239
data/console/commands.yaml
Normal file
@@ -0,0 +1,239 @@
|
||||
# JailDoctor's Dilemma - Console Commands
|
||||
# Metadata for the in-game console command system.
|
||||
# Execution logic stays in C++; this file defines metadata only.
|
||||
#
|
||||
# Fields:
|
||||
# keyword - Command name (uppercase)
|
||||
# handler - C++ handler function identifier
|
||||
# description - Short description for help output
|
||||
# usage - Full usage string for terminal help
|
||||
# instant - (optional) Skip typewriter effect (default: false)
|
||||
# hidden - (optional) Hide from TAB completion (default: false)
|
||||
# debug_only - (optional) Only available in debug builds (default: false)
|
||||
# help_hidden - (optional) Don't show in help output (default: false)
|
||||
# dynamic_completions - (optional) Completions generated at runtime (default: false)
|
||||
# completions - (optional) Static TAB completion tree
|
||||
# debug_extras - (optional) Overrides applied in debug builds
|
||||
|
||||
categories:
|
||||
- name: VIDEO
|
||||
commands:
|
||||
- keyword: SS
|
||||
handler: cmd_ss
|
||||
description: Supersampling
|
||||
usage: "SS [ON|OFF|SIZE|UPSCALE [NEAREST|LINEAR]|DOWNSCALE [BILINEAR|LANCZOS2|LANCZOS3]]"
|
||||
completions:
|
||||
SS: [ON, OFF, SIZE, UPSCALE, DOWNSCALE]
|
||||
SS UPSCALE: [NEAREST, LINEAR]
|
||||
SS DOWNSCALE: [BILINEAR, LANCZOS2, LANCZOS3]
|
||||
|
||||
- keyword: SHADER
|
||||
handler: cmd_shader
|
||||
description: "Toggle/select shader (F4)"
|
||||
usage: "SHADER [ON|OFF|NEXT|POSTFX|CRTPI|PRESET [NEXT|PREV|<name>]]"
|
||||
completions:
|
||||
SHADER: [ON, OFF, NEXT, POSTFX, CRTPI, PRESET]
|
||||
dynamic_completions: true
|
||||
|
||||
- keyword: BORDER
|
||||
handler: cmd_border
|
||||
description: "Decorative border (B)"
|
||||
usage: "BORDER [ON|OFF]"
|
||||
completions:
|
||||
BORDER: [ON, OFF]
|
||||
|
||||
- keyword: FULLSCREEN
|
||||
handler: cmd_fullscreen
|
||||
description: "Fullscreen mode (F3)"
|
||||
usage: "FULLSCREEN [ON|OFF]"
|
||||
completions:
|
||||
FULLSCREEN: [ON, OFF]
|
||||
|
||||
- keyword: ZOOM
|
||||
handler: cmd_zoom
|
||||
description: "Window zoom (F1/F2)"
|
||||
usage: "ZOOM [UP|DOWN|<1-N>]"
|
||||
completions:
|
||||
ZOOM: [UP, DOWN]
|
||||
|
||||
- keyword: INTSCALE
|
||||
handler: cmd_intscale
|
||||
description: "Integer scaling (F7)"
|
||||
usage: "INTSCALE [ON|OFF]"
|
||||
completions:
|
||||
INTSCALE: [ON, OFF]
|
||||
|
||||
- keyword: VSYNC
|
||||
handler: cmd_vsync
|
||||
description: "Vertical sync"
|
||||
usage: "VSYNC [ON|OFF]"
|
||||
completions:
|
||||
VSYNC: [ON, OFF]
|
||||
|
||||
- keyword: DRIVER
|
||||
handler: cmd_driver
|
||||
description: "GPU driver (restart to apply)"
|
||||
usage: "DRIVER [LIST|AUTO|NONE|<name>]"
|
||||
completions:
|
||||
DRIVER: [LIST, AUTO, NONE]
|
||||
|
||||
- keyword: PALETTE
|
||||
handler: cmd_palette
|
||||
description: "Color palette (F5/F6)"
|
||||
usage: "PALETTE [NEXT|PREV|SORT [ORIGINAL|LUMINANCE|SPECTRUM]|DEFAULT|<name>]"
|
||||
completions:
|
||||
PALETTE SORT: [ORIGINAL, LUMINANCE, SPECTRUM]
|
||||
dynamic_completions: true
|
||||
|
||||
- name: AUDIO
|
||||
commands:
|
||||
- keyword: AUDIO
|
||||
handler: cmd_audio
|
||||
description: Audio master
|
||||
usage: "AUDIO [ON|OFF|VOL <0-100>]"
|
||||
completions:
|
||||
AUDIO: [ON, OFF, VOL]
|
||||
|
||||
- keyword: MUSIC
|
||||
handler: cmd_music
|
||||
description: Music volume
|
||||
usage: "MUSIC [ON|OFF|VOL <0-100>]"
|
||||
completions:
|
||||
MUSIC: [ON, OFF, VOL]
|
||||
|
||||
- keyword: SOUND
|
||||
handler: cmd_sound
|
||||
description: Sound volume
|
||||
usage: "SOUND [ON|OFF|VOL <0-100>]"
|
||||
completions:
|
||||
SOUND: [ON, OFF, VOL]
|
||||
|
||||
- name: GAME
|
||||
commands:
|
||||
- keyword: PLAYER
|
||||
handler: cmd_player
|
||||
description: "Player skin and color"
|
||||
usage: "PLAYER SKIN <name> | PLAYER COLOR <0-15>|DEFAULT"
|
||||
completions:
|
||||
PLAYER: [SKIN, COLOR]
|
||||
PLAYER SKIN: [DEFAULT, ABAD, BATMAN, CHIP, CONGO, JEANNINE, MUMMY, UPV_STUDENT]
|
||||
PLAYER COLOR: [DEFAULT]
|
||||
|
||||
- keyword: RESTART
|
||||
handler: cmd_restart
|
||||
description: Restart from the beginning
|
||||
usage: RESTART
|
||||
instant: true
|
||||
|
||||
- keyword: KIOSK
|
||||
handler: cmd_kiosk
|
||||
description: Enable kiosk mode
|
||||
usage: "KIOSK [ON]"
|
||||
completions:
|
||||
KIOSK: [ON]
|
||||
|
||||
- keyword: EXIT
|
||||
handler: cmd_exit
|
||||
description: Quit application
|
||||
usage: EXIT
|
||||
instant: true
|
||||
|
||||
- keyword: QUIT
|
||||
handler: cmd_quit
|
||||
description: Quit application
|
||||
usage: QUIT
|
||||
instant: true
|
||||
help_hidden: true
|
||||
|
||||
- name: INFO
|
||||
commands:
|
||||
- keyword: SHOW
|
||||
handler: cmd_show
|
||||
description: Show info overlay
|
||||
usage: "SHOW [INFO]"
|
||||
completions:
|
||||
SHOW: [INFO]
|
||||
debug_extras:
|
||||
description: "Show overlay/test notification"
|
||||
usage: "SHOW [INFO|NOTIFICATION|CHEEVO]"
|
||||
completions:
|
||||
SHOW: [INFO, NOTIFICATION, CHEEVO]
|
||||
|
||||
- keyword: HIDE
|
||||
handler: cmd_hide
|
||||
description: Hide info overlay
|
||||
usage: "HIDE [INFO]"
|
||||
completions:
|
||||
HIDE: [INFO]
|
||||
|
||||
- keyword: SIZE
|
||||
handler: cmd_size
|
||||
description: Window size in pixels
|
||||
usage: SIZE
|
||||
|
||||
- keyword: HELP
|
||||
handler: cmd_help
|
||||
description: "Show this help"
|
||||
usage: "HELP / ?"
|
||||
|
||||
- keyword: "?"
|
||||
handler: cmd_help
|
||||
help_hidden: true
|
||||
|
||||
- name: DEBUG
|
||||
debug_only: true
|
||||
commands:
|
||||
- keyword: DEBUG
|
||||
handler: cmd_debug
|
||||
description: "Debug mode and start options (F12)"
|
||||
usage: "DEBUG [MODE [ON|OFF]|START [HERE|ROOM|POS|SCENE <name>]]"
|
||||
completions:
|
||||
DEBUG: [MODE, START]
|
||||
DEBUG MODE: [ON, OFF]
|
||||
DEBUG START: [HERE, ROOM, POS, SCENE]
|
||||
DEBUG START SCENE: [LOGO, LOADING, TITLE, CREDITS, GAME, ENDING, ENDING2]
|
||||
|
||||
- keyword: ITEMS
|
||||
handler: cmd_items
|
||||
description: "Set item count (GAME only)"
|
||||
usage: "ITEMS <0-200>"
|
||||
|
||||
- keyword: ROOM
|
||||
handler: cmd_room
|
||||
description: "Change to room number (GAME only)"
|
||||
usage: "ROOM <1-60>|NEXT|PREV|LEFT|RIGHT|UP|DOWN"
|
||||
completions:
|
||||
ROOM: [NEXT, PREV, 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]"
|
||||
completions:
|
||||
EDIT: [ON, OFF, REVERT]
|
||||
|
||||
- name: CHEATS
|
||||
commands:
|
||||
- keyword: CHEAT
|
||||
handler: cmd_cheat
|
||||
description: "Game cheats (GAME only)"
|
||||
usage: "CHEAT [INFINITE LIVES|INVINCIBILITY|OPEN THE JAIL|CLOSE THE JAIL]"
|
||||
hidden: true
|
||||
completions:
|
||||
CHEAT: [INFINITE, INVINCIBILITY, OPEN, CLOSE]
|
||||
CHEAT INFINITE: [LIVES]
|
||||
CHEAT INFINITE LIVES: [ON, OFF]
|
||||
CHEAT INVINCIBILITY: [ON, OFF]
|
||||
CHEAT OPEN: [THE]
|
||||
CHEAT OPEN THE: [JAIL]
|
||||
CHEAT CLOSE: [THE]
|
||||
CHEAT CLOSE THE: [JAIL]
|
||||
debug_extras:
|
||||
hidden: false
|
||||
@@ -116,6 +116,7 @@ ui:
|
||||
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"
|
||||
@@ -125,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"
|
||||
@@ -138,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"
|
||||
|
||||
@@ -116,6 +116,7 @@ ui:
|
||||
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"
|
||||
@@ -125,6 +126,8 @@ scoreboard:
|
||||
items: "ITEMS COLLECTED "
|
||||
time: " TIME "
|
||||
rooms: "ROOMS"
|
||||
cheat_infinite_lives: "inf lives"
|
||||
cheat_invincibility: "inv"
|
||||
|
||||
game:
|
||||
music_enabled: "MUSIC ENABLED"
|
||||
@@ -138,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,58 +1,568 @@
|
||||
# VOID MAIN
|
||||
enemies:
|
||||
-
|
||||
animation: code.yaml
|
||||
boundaries:
|
||||
position1:
|
||||
x: 3
|
||||
y: 2
|
||||
position2:
|
||||
x: 20
|
||||
y: 2
|
||||
color: yellow
|
||||
position:
|
||||
x: 8
|
||||
y: 2
|
||||
velocity:
|
||||
x: 24.0
|
||||
y: 0
|
||||
items:
|
||||
-
|
||||
counter: 1
|
||||
position:
|
||||
x: 21
|
||||
y: 13
|
||||
tile: 42
|
||||
tileSetFile: items.gif
|
||||
room:
|
||||
name_en: "VOID MAIN"
|
||||
name_ca: "VOID MAIN"
|
||||
bgColor: black
|
||||
border: magenta
|
||||
tileSetFile: standard.gif
|
||||
|
||||
# Conexiones de la habitación (null = sin conexión)
|
||||
connections:
|
||||
up: null
|
||||
down: null
|
||||
left: 02.yaml
|
||||
right: null
|
||||
|
||||
# Colores de los objetos
|
||||
up: null
|
||||
conveyorBelt: left
|
||||
itemColor1: bright_cyan
|
||||
itemColor2: yellow
|
||||
|
||||
# Dirección de la cinta transportadora: left, none, right
|
||||
conveyorBelt: left
|
||||
|
||||
# Tilemap: 16 filas × 32 columnas (256×192 píxeles @ 8px/tile)
|
||||
# Índices de tiles (-1 = vacío)
|
||||
name_ca: VOID MAIN
|
||||
name_en: VOID MAIN
|
||||
tileSetFile: standard.gif
|
||||
tilemap:
|
||||
- [24, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 24, 24]
|
||||
- [24, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 24, 24]
|
||||
- [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 24, 24]
|
||||
- [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 24, 24]
|
||||
- [24, 24, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, -1, -1, 504, 24, 24, 24, 24, 24, 24, 24, 24]
|
||||
- [24, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 504, -1, 24, -1, -1, -1, -1, -1, 24, 24]
|
||||
- [24, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 504, -1, -1, 24, -1, -1, -1, -1, -1, 24, 24]
|
||||
- [24, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 504, -1, -1, -1, 24, -1, -1, -1, -1, -1, 24, 24]
|
||||
- [24, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 504, -1, -1, -1, -1, 24, -1, -1, -1, -1, -1, 24, 24]
|
||||
- [24, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 504, -1, -1, -1, -1, -1, 24, -1, -1, -1, -1, -1, 24, 24]
|
||||
- [24, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 504, -1, -1, -1, -1, -1, -1, 24, -1, -1, -1, -1, -1, 24, 24]
|
||||
- [24, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 504, -1, -1, -1, -1, -1, -1, -1, 24, 24, 24, -1, -1, -1, 24, 24]
|
||||
- [24, 24, -1, -1, -1, -1, -1, -1, -1, 252, 252, 252, 252, 252, 252, 252, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 24, 24]
|
||||
- [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 134, 24]
|
||||
- [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 440, 440, 440, 440, -1, -1, -1, -1, -1, -1, -1, -1, 134, 24]
|
||||
- [24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24]
|
||||
|
||||
# Enemigos en esta habitación
|
||||
enemies:
|
||||
- animation: code.yaml
|
||||
position: {x: 3, y: 2}
|
||||
velocity: {x: 24.0, y: 0}
|
||||
boundaries:
|
||||
position1: {x: 3, y: 2}
|
||||
position2: {x: 27, y: 2}
|
||||
color: yellow
|
||||
|
||||
# Objetos en esta habitación
|
||||
items:
|
||||
- tileSetFile: items.gif
|
||||
tile: 42
|
||||
position: {x: 21, y: 13}
|
||||
counter: 1
|
||||
-
|
||||
- 24
|
||||
- 24
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 24
|
||||
- 24
|
||||
-
|
||||
- 24
|
||||
- 24
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 24
|
||||
- 24
|
||||
-
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 24
|
||||
- 24
|
||||
-
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 24
|
||||
- 24
|
||||
-
|
||||
- 24
|
||||
- 24
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- -1
|
||||
- -1
|
||||
- 504
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
-
|
||||
- 24
|
||||
- 24
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 504
|
||||
- -1
|
||||
- 24
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 24
|
||||
- 24
|
||||
-
|
||||
- 24
|
||||
- 24
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 504
|
||||
- -1
|
||||
- -1
|
||||
- 24
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 24
|
||||
- 24
|
||||
-
|
||||
- 24
|
||||
- 24
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 504
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 24
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 24
|
||||
- 24
|
||||
-
|
||||
- 24
|
||||
- 24
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 504
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 24
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 24
|
||||
- 24
|
||||
-
|
||||
- 24
|
||||
- 24
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 504
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 24
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 24
|
||||
- 24
|
||||
-
|
||||
- 24
|
||||
- 24
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 504
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 24
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 24
|
||||
- 24
|
||||
-
|
||||
- 24
|
||||
- 24
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 504
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 24
|
||||
- 24
|
||||
-
|
||||
- 24
|
||||
- 24
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- 252
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 24
|
||||
- 24
|
||||
-
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 134
|
||||
- 24
|
||||
-
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 440
|
||||
- 440
|
||||
- 440
|
||||
- 440
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- -1
|
||||
- 134
|
||||
- 24
|
||||
-
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
- 24
|
||||
|
||||
@@ -92,21 +92,21 @@ namespace GlobalInputs {
|
||||
|
||||
void handleToggleShaders() {
|
||||
Screen::get()->toggleShaders();
|
||||
Notifier::get()->show({Locale::get()->get(Options::video.postfx ? "ui.shaders_enabled" : "ui.shaders_disabled")}); // NOLINT(readability-static-accessed-through-instance)
|
||||
Notifier::get()->show({Locale::get()->get(Options::video.shader.enabled ? "ui.shaders_enabled" : "ui.shaders_disabled")}); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
|
||||
void handleNextShaderPreset() {
|
||||
if (Options::current_shader == Rendering::ShaderType::CRTPI) {
|
||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
if (!Options::crtpi_presets.empty()) {
|
||||
Options::current_crtpi_preset = (Options::current_crtpi_preset + 1) % static_cast<int>(Options::crtpi_presets.size());
|
||||
Options::video.shader.current_crtpi_preset = (Options::video.shader.current_crtpi_preset + 1) % static_cast<int>(Options::crtpi_presets.size());
|
||||
Screen::get()->reloadCrtPi();
|
||||
Notifier::get()->show({Locale::get()->get("ui.crtpi") + " " + Options::crtpi_presets[static_cast<size_t>(Options::current_crtpi_preset)].name}); // NOLINT(readability-static-accessed-through-instance)
|
||||
Notifier::get()->show({Locale::get()->get("ui.crtpi") + " " + prettyName(Options::crtpi_presets[static_cast<size_t>(Options::video.shader.current_crtpi_preset)].name)}); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
} else {
|
||||
if (!Options::postfx_presets.empty()) {
|
||||
Options::current_postfx_preset = (Options::current_postfx_preset + 1) % static_cast<int>(Options::postfx_presets.size());
|
||||
Options::video.shader.current_postfx_preset = (Options::video.shader.current_postfx_preset + 1) % static_cast<int>(Options::postfx_presets.size());
|
||||
Screen::get()->reloadPostFX();
|
||||
Notifier::get()->show({Locale::get()->get("ui.postfx") + " " + Options::postfx_presets[static_cast<size_t>(Options::current_postfx_preset)].name}); // NOLINT(readability-static-accessed-through-instance)
|
||||
Notifier::get()->show({Locale::get()->get("ui.postfx") + " " + prettyName(Options::postfx_presets[static_cast<size_t>(Options::video.shader.current_postfx_preset)].name)}); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,17 +114,22 @@ namespace GlobalInputs {
|
||||
void handleNextShader() {
|
||||
Screen::get()->nextShader();
|
||||
Notifier::get()->show({Locale::get()->get("ui.shader") + " " + // NOLINT(readability-static-accessed-through-instance)
|
||||
(Options::current_shader == Rendering::ShaderType::CRTPI ? "CRTPI" : "POSTFX")});
|
||||
(Options::video.shader.current_shader == Rendering::ShaderType::CRTPI ? "CRTPI" : "POSTFX")});
|
||||
}
|
||||
|
||||
void handleNextPalette() {
|
||||
Screen::get()->nextPalette();
|
||||
Notifier::get()->show({Locale::get()->get("ui.palette") + " " + Options::video.palette}); // NOLINT(readability-static-accessed-through-instance)
|
||||
Notifier::get()->show({Locale::get()->get("ui.palette") + " " + toUpper(Screen::get()->getPalettePrettyName())}); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
|
||||
void handlePreviousPalette() {
|
||||
Screen::get()->previousPalette();
|
||||
Notifier::get()->show({Locale::get()->get("ui.palette") + " " + Options::video.palette}); // NOLINT(readability-static-accessed-through-instance)
|
||||
Notifier::get()->show({Locale::get()->get("ui.palette") + " " + toUpper(Screen::get()->getPalettePrettyName())}); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
|
||||
void 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() {
|
||||
@@ -160,20 +165,25 @@ namespace GlobalInputs {
|
||||
return InputAction::WINDOW_INC_ZOOM;
|
||||
}
|
||||
}
|
||||
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)) {
|
||||
if (Options::video.shader.enabled && ((SDL_GetModState() & SDL_KMOD_SHIFT) != 0U)) {
|
||||
return InputAction::NEXT_SHADER_PRESET; // Shift+F4
|
||||
}
|
||||
return InputAction::TOGGLE_SHADER; // F4
|
||||
}
|
||||
if (Input::get()->checkAction(InputAction::NEXT_PALETTE, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
return InputAction::NEXT_PALETTE;
|
||||
}
|
||||
if (Input::get()->checkAction(InputAction::PREVIOUS_PALETTE, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
return InputAction::PREVIOUS_PALETTE;
|
||||
if (Input::get()->checkAction(InputAction::NEXT_PALETTE, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
if ((SDL_GetModState() & SDL_KMOD_CTRL) != 0U) {
|
||||
return InputAction::PREVIOUS_PALETTE; // Ctrl+F5
|
||||
}
|
||||
return InputAction::NEXT_PALETTE; // F5
|
||||
}
|
||||
if (Input::get()->checkAction(InputAction::NEXT_PALETTE_SORT, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
return InputAction::NEXT_PALETTE_SORT; // F6
|
||||
}
|
||||
if (Input::get()->checkAction(InputAction::TOGGLE_INTEGER_SCALE, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
return InputAction::TOGGLE_INTEGER_SCALE;
|
||||
@@ -271,6 +281,10 @@ namespace GlobalInputs {
|
||||
handlePreviousPalette();
|
||||
break;
|
||||
|
||||
case InputAction::NEXT_PALETTE_SORT:
|
||||
handleNextPaletteSortMode();
|
||||
break;
|
||||
|
||||
case InputAction::TOGGLE_INTEGER_SCALE:
|
||||
handleToggleIntegerScale();
|
||||
break;
|
||||
|
||||
@@ -45,7 +45,7 @@ Input::Input(std::string game_controller_db_path)
|
||||
{Action::TOGGLE_FULLSCREEN, KeyState{.scancode = SDL_SCANCODE_F3}},
|
||||
{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_IN_GAME_MUSIC, KeyState{.scancode = SDL_SCANCODE_F8}},
|
||||
{Action::TOGGLE_BORDER, KeyState{.scancode = SDL_SCANCODE_F9}},
|
||||
|
||||
@@ -20,6 +20,7 @@ const std::unordered_map<InputAction, std::string> ACTION_TO_STRING = {
|
||||
{InputAction::TOGGLE_IN_GAME_MUSIC, "TOGGLE_MUSIC"},
|
||||
{InputAction::NEXT_PALETTE, "NEXT_PALETTE"},
|
||||
{InputAction::PREVIOUS_PALETTE, "PREVIOUS_PALETTE"},
|
||||
{InputAction::NEXT_PALETTE_SORT, "NEXT_PALETTE_SORT"},
|
||||
{InputAction::TOGGLE_SHADER, "TOGGLE_POSTFX"},
|
||||
{InputAction::NEXT_SHADER_PRESET, "NEXT_POSTFX_PRESET"},
|
||||
{InputAction::TOGGLE_INFO, "TOGGLE_DEBUG"},
|
||||
@@ -42,6 +43,7 @@ const std::unordered_map<std::string, InputAction> STRING_TO_ACTION = {
|
||||
{"TOGGLE_MUSIC", InputAction::TOGGLE_IN_GAME_MUSIC},
|
||||
{"NEXT_PALETTE", InputAction::NEXT_PALETTE},
|
||||
{"PREVIOUS_PALETTE", InputAction::PREVIOUS_PALETTE},
|
||||
{"NEXT_PALETTE_SORT", InputAction::NEXT_PALETTE_SORT},
|
||||
{"TOGGLE_POSTFX", InputAction::TOGGLE_SHADER},
|
||||
{"NEXT_POSTFX_PRESET", InputAction::NEXT_SHADER_PRESET},
|
||||
{"TOGGLE_DEBUG", InputAction::TOGGLE_INFO},
|
||||
|
||||
@@ -31,6 +31,7 @@ enum class InputAction : int { // Acciones de entrada posibles en el juego
|
||||
TOGGLE_IN_GAME_MUSIC,
|
||||
NEXT_PALETTE,
|
||||
PREVIOUS_PALETTE,
|
||||
NEXT_PALETTE_SORT,
|
||||
TOGGLE_INFO,
|
||||
TOGGLE_CONSOLE,
|
||||
|
||||
|
||||
@@ -2,20 +2,146 @@
|
||||
|
||||
#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)) {
|
||||
@@ -23,7 +149,7 @@ PaletteManager::PaletteManager(
|
||||
|
||||
// Leer y aplicar paleta inicial directamente desde el archivo
|
||||
// (Resource::Cache aún no está disponible en este punto del ciclo de vida)
|
||||
const auto initial_palette = readPalFile(palettes_.at(current_));
|
||||
const auto initial_palette = sortPalette(readPalFile(palettes_.at(current_)), sort_mode_);
|
||||
game_surface_->setPalette(initial_palette);
|
||||
border_surface_->setPalette(initial_palette);
|
||||
|
||||
@@ -44,9 +170,9 @@ void PaletteManager::previous() {
|
||||
}
|
||||
|
||||
auto PaletteManager::setByName(const std::string& name) -> bool {
|
||||
const std::string upper_name = toUpper(name + ".pal");
|
||||
const std::string lower_name = toLower(name + ".pal");
|
||||
for (size_t i = 0; i < palettes_.size(); ++i) {
|
||||
if (toUpper(palettes_[i]) == upper_name) {
|
||||
if (toLower(palettes_[i]) == lower_name) {
|
||||
current_ = i;
|
||||
apply();
|
||||
return true;
|
||||
@@ -62,7 +188,7 @@ auto PaletteManager::getNames() const -> std::vector<std::string> {
|
||||
std::string name = p;
|
||||
const size_t pos = name.find(".pal");
|
||||
if (pos != std::string::npos) { name.erase(pos, 4); }
|
||||
std::ranges::transform(name, name.begin(), ::toupper);
|
||||
std::ranges::transform(name, name.begin(), ::tolower);
|
||||
names.push_back(std::move(name));
|
||||
}
|
||||
return names;
|
||||
@@ -72,13 +198,41 @@ auto PaletteManager::getCurrentName() const -> std::string {
|
||||
std::string name = palettes_.at(current_);
|
||||
const size_t pos = name.find(".pal");
|
||||
if (pos != std::string::npos) { name.erase(pos, 4); }
|
||||
std::ranges::transform(name, name.begin(), ::toupper);
|
||||
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() {
|
||||
game_surface_->loadPalette(Resource::Cache::get()->getPalette(palettes_.at(current_)));
|
||||
border_surface_->loadPalette(Resource::Cache::get()->getPalette(palettes_.at(current_)));
|
||||
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();
|
||||
|
||||
@@ -88,9 +242,16 @@ void PaletteManager::apply() {
|
||||
}
|
||||
|
||||
auto PaletteManager::findIndex(const std::string& name) const -> size_t {
|
||||
const std::string upper_name = toUpper(name + ".pal");
|
||||
const std::string lower_name = toLower(name + ".pal");
|
||||
for (size_t i = 0; i < palettes_.size(); ++i) {
|
||||
if (toUpper(getFileName(palettes_[i])) == upper_name) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -102,3 +263,14 @@ void PaletteManager::processPathList() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,30 @@
|
||||
#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()>;
|
||||
@@ -14,6 +32,7 @@ class 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 = nullptr);
|
||||
@@ -21,16 +40,24 @@ class PaletteManager {
|
||||
void next(); // Avanza a la siguiente paleta
|
||||
void previous(); // Retrocede a la paleta anterior
|
||||
auto setByName(const std::string& name) -> bool; // Cambia a paleta por nombre; false si no existe
|
||||
[[nodiscard]] auto getNames() const -> std::vector<std::string>; // Nombres disponibles (mayúsculas, sin .pal)
|
||||
[[nodiscard]] auto getCurrentName() const -> std::string; // Nombre de la paleta actual (mayúsculas, sin .pal)
|
||||
[[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_;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "game/options.hpp" // Para Options
|
||||
#include "game/ui/console.hpp" // Para Console
|
||||
#include "game/ui/notifier.hpp" // Para Notifier
|
||||
#include "utils/utils.hpp" // Para prettyName
|
||||
|
||||
// [SINGLETON]
|
||||
RenderInfo* RenderInfo::render_info = nullptr;
|
||||
@@ -89,20 +90,20 @@ void RenderInfo::render() const {
|
||||
line += " | " + zoom_str + "x";
|
||||
|
||||
// PostFX: muestra shader + preset y supersampling, o nada si está desactivado
|
||||
if (Options::video.postfx) {
|
||||
const bool IS_CRTPI = (Options::current_shader == Rendering::ShaderType::CRTPI);
|
||||
if (Options::video.shader.enabled) {
|
||||
const bool IS_CRTPI = (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI);
|
||||
const std::string SHADER_NAME = IS_CRTPI ? "crtpi" : "postfx";
|
||||
std::string preset_name = "-";
|
||||
if (IS_CRTPI) {
|
||||
if (!Options::crtpi_presets.empty()) {
|
||||
preset_name = Options::crtpi_presets[static_cast<size_t>(Options::current_crtpi_preset)].name;
|
||||
preset_name = prettyName(Options::crtpi_presets[static_cast<size_t>(Options::video.shader.current_crtpi_preset)].name);
|
||||
}
|
||||
} else {
|
||||
if (!Options::postfx_presets.empty()) {
|
||||
preset_name = Options::postfx_presets[static_cast<size_t>(Options::current_postfx_preset)].name;
|
||||
preset_name = prettyName(Options::postfx_presets[static_cast<size_t>(Options::video.shader.current_postfx_preset)].name);
|
||||
}
|
||||
}
|
||||
const bool SHOW_SS = Options::video.supersampling && !IS_CRTPI;
|
||||
const bool SHOW_SS = Options::video.supersampling.enabled && !IS_CRTPI;
|
||||
line += " | " + SHADER_NAME + " " + preset_name + (SHOW_SS ? " (ss)" : "");
|
||||
}
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ Screen::Screen() {
|
||||
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]() {
|
||||
@@ -239,11 +240,11 @@ void Screen::renderNotifications() const {
|
||||
|
||||
// Activa/desactiva todos los shaders respetando el shader actualmente seleccionado
|
||||
void Screen::toggleShaders() {
|
||||
Options::video.postfx = !Options::video.postfx;
|
||||
Options::video.shader.enabled = !Options::video.shader.enabled;
|
||||
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||
if (Options::video.postfx) {
|
||||
if (Options::video.shader.enabled) {
|
||||
// Activar: usar el shader actualmente seleccionado
|
||||
if (Options::current_shader == Rendering::ShaderType::CRTPI) {
|
||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
shader_backend_->setActiveShader(Rendering::ShaderType::CRTPI);
|
||||
applyCurrentCrtPiPreset();
|
||||
} else {
|
||||
@@ -262,10 +263,10 @@ void Screen::toggleShaders() {
|
||||
|
||||
// Recarga el shader del preset actual sin toggle
|
||||
void Screen::reloadPostFX() {
|
||||
if (Options::video.postfx && shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||
if (Options::video.shader.enabled && shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||
// El backend ya está activo: solo actualizar uniforms, sin recrear el pipeline
|
||||
applyCurrentPostFXPreset();
|
||||
} else if (Options::video.postfx) {
|
||||
} else if (Options::video.shader.enabled) {
|
||||
initShaders();
|
||||
}
|
||||
}
|
||||
@@ -446,8 +447,12 @@ void Screen::renderOverlays() {
|
||||
// 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); }
|
||||
|
||||
// Devuelve los nombres de paletas disponibles (mayúsculas, sin extensión .pal)
|
||||
// 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); }
|
||||
@@ -504,14 +509,14 @@ auto loadData(const std::string& filepath) -> std::vector<uint8_t> {
|
||||
}
|
||||
|
||||
void Screen::setLinearUpscale(bool linear) {
|
||||
Options::video.linear_upscale = linear;
|
||||
Options::video.supersampling.linear_upscale = linear;
|
||||
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||
shader_backend_->setLinearUpscale(linear);
|
||||
}
|
||||
}
|
||||
|
||||
void Screen::setDownscaleAlgo(int algo) {
|
||||
Options::video.downscale_algo = algo;
|
||||
Options::video.supersampling.downscale_algo = algo;
|
||||
if (shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||
shader_backend_->setDownscaleAlgo(algo);
|
||||
}
|
||||
@@ -524,8 +529,8 @@ auto Screen::getSsTextureSize() const -> std::pair<int, int> {
|
||||
|
||||
// Activa/desactiva el supersampling global (Ctrl+F4)
|
||||
void Screen::toggleSupersampling() {
|
||||
Options::video.supersampling = !Options::video.supersampling;
|
||||
if (Options::video.postfx && shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||
Options::video.supersampling.enabled = !Options::video.supersampling.enabled;
|
||||
if (Options::video.shader.enabled && shader_backend_ && shader_backend_->isHardwareAccelerated()) {
|
||||
applyCurrentPostFXPreset();
|
||||
}
|
||||
}
|
||||
@@ -533,11 +538,11 @@ void Screen::toggleSupersampling() {
|
||||
// Aplica los parámetros del preset actual al backend de shaders
|
||||
void Screen::applyCurrentPostFXPreset() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
if (shader_backend_ && !Options::postfx_presets.empty()) {
|
||||
const auto& p = Options::postfx_presets[static_cast<size_t>(Options::current_postfx_preset)];
|
||||
// Supersampling es un toggle global (Options::video.supersampling), no por preset.
|
||||
const auto& p = Options::postfx_presets[static_cast<size_t>(Options::video.shader.current_postfx_preset)];
|
||||
// Supersampling es un toggle global (Options::video.supersampling.enabled), no por preset.
|
||||
// setOversample primero: puede recrear texturas antes de que setPostFXParams
|
||||
// decida si hornear scanlines en CPU o aplicarlas en GPU.
|
||||
shader_backend_->setOversample(Options::video.supersampling ? 3 : 1);
|
||||
shader_backend_->setOversample(Options::video.supersampling.enabled ? 3 : 1);
|
||||
Rendering::PostFXParams params{.vignette = p.vignette, .scanlines = p.scanlines, .chroma = p.chroma, .mask = p.mask, .gamma = p.gamma, .curvature = p.curvature, .bleeding = p.bleeding, .flicker = p.flicker};
|
||||
shader_backend_->setPostFXParams(params);
|
||||
}
|
||||
@@ -546,7 +551,7 @@ void Screen::applyCurrentPostFXPreset() { // NOLINT(readability-convert-member-
|
||||
// Aplica los parámetros del preset CrtPi actual al backend de shaders
|
||||
void Screen::applyCurrentCrtPiPreset() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
if (shader_backend_ && !Options::crtpi_presets.empty()) {
|
||||
const auto& p = Options::crtpi_presets[static_cast<size_t>(Options::current_crtpi_preset)];
|
||||
const auto& p = Options::crtpi_presets[static_cast<size_t>(Options::video.shader.current_crtpi_preset)];
|
||||
Rendering::CrtPiParams params{
|
||||
.scanline_weight = p.scanline_weight,
|
||||
.scanline_gap_brightness = p.scanline_gap_brightness,
|
||||
@@ -569,9 +574,9 @@ void Screen::applyCurrentCrtPiPreset() { // NOLINT(readability-convert-member-f
|
||||
|
||||
// Cambia el shader de post-procesado activo y aplica el preset correspondiente
|
||||
void Screen::setActiveShader(Rendering::ShaderType type) {
|
||||
Options::current_shader = type;
|
||||
Options::video.shader.current_shader = type;
|
||||
if (!shader_backend_) { return; }
|
||||
if (!Options::video.postfx) {
|
||||
if (!Options::video.shader.enabled) {
|
||||
// Shaders desactivados: guardar preferencia pero mantener pass-through
|
||||
shader_backend_->setActiveShader(Rendering::ShaderType::POSTFX);
|
||||
shader_backend_->setPostFXParams(Rendering::PostFXParams{});
|
||||
@@ -587,7 +592,7 @@ void Screen::setActiveShader(Rendering::ShaderType type) {
|
||||
|
||||
// Cicla al siguiente shader disponible (preparado para futura UI)
|
||||
void Screen::nextShader() {
|
||||
const Rendering::ShaderType NEXT = (Options::current_shader == Rendering::ShaderType::POSTFX)
|
||||
const Rendering::ShaderType NEXT = (Options::video.shader.current_shader == Rendering::ShaderType::POSTFX)
|
||||
? Rendering::ShaderType::CRTPI
|
||||
: Rendering::ShaderType::POSTFX;
|
||||
setActiveShader(NEXT);
|
||||
@@ -601,7 +606,8 @@ void Screen::initShaders() {
|
||||
|
||||
if (!shader_backend_) {
|
||||
shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
|
||||
shader_backend_->setPreferredDriver(Options::video.gpu_preferred_driver);
|
||||
const std::string fallback_driver = "none";
|
||||
shader_backend_->setPreferredDriver(Options::video.gpu.acceleration ? Options::video.gpu.preferred_driver : fallback_driver);
|
||||
}
|
||||
shader_backend_->init(window_, tex, "", "");
|
||||
gpu_driver_ = shader_backend_->getDriverName();
|
||||
@@ -609,10 +615,10 @@ void Screen::initShaders() {
|
||||
// Propagar flags de vsync, integer scale, upscale y downscale al backend GPU
|
||||
shader_backend_->setVSync(Options::video.vertical_sync);
|
||||
shader_backend_->setScaleMode(Options::video.integer_scale);
|
||||
shader_backend_->setLinearUpscale(Options::video.linear_upscale);
|
||||
shader_backend_->setDownscaleAlgo(Options::video.downscale_algo);
|
||||
shader_backend_->setLinearUpscale(Options::video.supersampling.linear_upscale);
|
||||
shader_backend_->setDownscaleAlgo(Options::video.supersampling.downscale_algo);
|
||||
|
||||
if (Options::video.postfx) {
|
||||
if (Options::video.shader.enabled) {
|
||||
applyCurrentPostFXPreset();
|
||||
} else {
|
||||
// Pass-through: todos los efectos a 0, el shader solo copia la textura
|
||||
@@ -620,8 +626,8 @@ void Screen::initShaders() {
|
||||
}
|
||||
|
||||
// Restaurar el shader activo guardado en config (y sus parámetros CrtPi si aplica)
|
||||
shader_backend_->setActiveShader(Options::current_shader);
|
||||
if (Options::current_shader == Rendering::ShaderType::CRTPI) {
|
||||
shader_backend_->setActiveShader(Options::video.shader.current_shader);
|
||||
if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
|
||||
applyCurrentCrtPiPreset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,11 @@ class Screen {
|
||||
void nextPalette(); // Cambia a la siguiente paleta
|
||||
void previousPalette(); // Cambia a la paleta anterior
|
||||
auto setPaletteByName(const std::string& name) -> bool; // Cambia a paleta por nombre; false si no existe
|
||||
[[nodiscard]] auto getPaletteNames() const -> std::vector<std::string>; // Nombres disponibles (mayúsculas, sin .pal)
|
||||
[[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
|
||||
@@ -80,6 +84,7 @@ class Screen {
|
||||
[[nodiscard]] auto getText() const -> std::shared_ptr<Text> { return text_; }
|
||||
[[nodiscard]] auto getGameSurfaceDstRect() const -> SDL_FRect { return game_surface_dstrect_; }
|
||||
[[nodiscard]] auto getGPUDriver() const -> const std::string& { return gpu_driver_; }
|
||||
[[nodiscard]] auto isHardwareAccelerated() const -> bool { return shader_backend_ && shader_backend_->isHardwareAccelerated(); }
|
||||
[[nodiscard]] auto getLastFPS() const -> int { return fps_.last_value; }
|
||||
[[nodiscard]] auto getZoomFactor() const -> float { return zoom_factor_; }
|
||||
[[nodiscard]] auto getMaxZoom() const -> int;
|
||||
|
||||
@@ -403,7 +403,7 @@ namespace Rendering {
|
||||
// ----------------------------------------------------------------
|
||||
if (preferred_driver_ == "none") {
|
||||
SDL_Log("SDL3GPUShader: GPU disabled by config, using SDL_Renderer fallback");
|
||||
driver_name_ = "none";
|
||||
driver_name_ = ""; // vacío → RenderInfo mostrará "sdl"
|
||||
return false;
|
||||
}
|
||||
if (device_ == nullptr) {
|
||||
|
||||
@@ -47,14 +47,12 @@ class AnimatedSprite : public MovingSprite {
|
||||
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)
|
||||
AnimatedSprite(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)
|
||||
|
||||
private:
|
||||
// Variables miembro
|
||||
std::vector<AnimationData> animations_; // Vector con las diferentes animaciones
|
||||
|
||||
@@ -175,12 +175,12 @@ void Surface::fillRect(const SDL_FRect* rect, Uint8 color) { // NOLINT(readabil
|
||||
float x_end = std::min(rect->x + rect->w, surface_data_->width);
|
||||
float y_end = std::min(rect->y + rect->h, surface_data_->height);
|
||||
|
||||
// Recorrer cada píxel dentro del rectángulo directamente
|
||||
for (int y = y_start; y < y_end; ++y) {
|
||||
for (int x = x_start; x < x_end; ++x) {
|
||||
const int INDEX = x + (y * surface_data_->width);
|
||||
surface_data_->data.get()[INDEX] = color;
|
||||
}
|
||||
// Rellenar fila a fila con memset (memoria contigua por fila)
|
||||
Uint8* data_ptr = surface_data_->data.get();
|
||||
const int surf_width = static_cast<int>(surface_data_->width);
|
||||
const int row_width = static_cast<int>(x_end) - static_cast<int>(x_start);
|
||||
for (int y = static_cast<int>(y_start); y < static_cast<int>(y_end); ++y) {
|
||||
std::memset(data_ptr + (y * surf_width) + static_cast<int>(x_start), color, row_width);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,16 +192,12 @@ void Surface::drawRectBorder(const SDL_FRect* rect, Uint8 color) { // NOLINT(re
|
||||
float x_end = std::min(rect->x + rect->w, surface_data_->width);
|
||||
float y_end = std::min(rect->y + rect->h, surface_data_->height);
|
||||
|
||||
// Dibujar bordes horizontales
|
||||
for (int x = x_start; x < x_end; ++x) {
|
||||
// Borde superior
|
||||
const int TOP_INDEX = x + (y_start * surface_data_->width);
|
||||
surface_data_->data.get()[TOP_INDEX] = color;
|
||||
|
||||
// Borde inferior
|
||||
const int BOTTOM_INDEX = x + ((y_end - 1) * surface_data_->width);
|
||||
surface_data_->data.get()[BOTTOM_INDEX] = color;
|
||||
}
|
||||
// Dibujar bordes horizontales con memset (líneas contiguas en memoria)
|
||||
Uint8* data_ptr = surface_data_->data.get();
|
||||
const int surf_width = static_cast<int>(surface_data_->width);
|
||||
const int row_width = static_cast<int>(x_end) - static_cast<int>(x_start);
|
||||
std::memset(data_ptr + (static_cast<int>(y_start) * surf_width) + static_cast<int>(x_start), color, row_width);
|
||||
std::memset(data_ptr + ((static_cast<int>(y_end) - 1) * surf_width) + static_cast<int>(x_start), color, row_width);
|
||||
|
||||
// Dibujar bordes verticales
|
||||
for (int y = y_start; y < y_end; ++y) {
|
||||
@@ -261,6 +257,8 @@ void Surface::render(float dx, float dy, float sx, float sy, float w, float h) {
|
||||
w = std::min(w, surface_data->width - dx);
|
||||
h = std::min(h, surface_data->height - dy);
|
||||
|
||||
const Uint8* src_ptr = surface_data_->data.get();
|
||||
Uint8* dst_ptr = surface_data->data.get();
|
||||
for (int iy = 0; iy < h; ++iy) {
|
||||
for (int ix = 0; ix < w; ++ix) {
|
||||
// Verificar que las coordenadas de destino están dentro de los límites
|
||||
@@ -269,9 +267,9 @@ void Surface::render(float dx, float dy, float sx, float sy, float w, float h) {
|
||||
int src_x = sx + ix;
|
||||
int src_y = sy + iy;
|
||||
|
||||
Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + (src_y * surface_data_->width))];
|
||||
Uint8 color = src_ptr[static_cast<size_t>(src_x + (src_y * surface_data_->width))];
|
||||
if (color != static_cast<Uint8>(transparent_color_)) {
|
||||
surface_data->data.get()[static_cast<size_t>(dest_x + (dest_y * surface_data->width))] = sub_palette_[color];
|
||||
dst_ptr[static_cast<size_t>(dest_x + (dest_y * surface_data->width))] = sub_palette_[color];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,6 +297,8 @@ void Surface::render(int x, int y, SDL_FRect* src_rect, SDL_FlipMode flip) { //
|
||||
h = std::min(h, surface_data_dest->height - y);
|
||||
|
||||
// Renderiza píxel por píxel aplicando el flip si es necesario
|
||||
const Uint8* src_ptr = surface_data_->data.get();
|
||||
Uint8* dst_ptr = surface_data_dest->data.get();
|
||||
for (int iy = 0; iy < h; ++iy) {
|
||||
for (int ix = 0; ix < w; ++ix) {
|
||||
// Coordenadas de origen
|
||||
@@ -312,9 +312,9 @@ void Surface::render(int x, int y, SDL_FRect* src_rect, SDL_FlipMode flip) { //
|
||||
// Verificar que las coordenadas de destino están dentro de los límites
|
||||
if (dest_x >= 0 && dest_x < surface_data_dest->width && dest_y >= 0 && dest_y < surface_data_dest->height) {
|
||||
// Copia el píxel si no es transparente
|
||||
Uint8 color = surface_data_->data.get()[static_cast<size_t>(src_x + (src_y * surface_data_->width))];
|
||||
Uint8 color = src_ptr[static_cast<size_t>(src_x + (src_y * surface_data_->width))];
|
||||
if (color != static_cast<Uint8>(transparent_color_)) {
|
||||
surface_data_dest->data[dest_x + (dest_y * surface_data_dest->width)] = sub_palette_[color];
|
||||
dst_ptr[static_cast<size_t>(dest_x + (dest_y * surface_data_dest->width))] = sub_palette_[color];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -567,13 +567,16 @@ void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture) { //
|
||||
// Convertir `pitch` de bytes a Uint32 (asegurando alineación correcta en hardware)
|
||||
int row_stride = pitch / sizeof(Uint32);
|
||||
|
||||
for (int y = 0; y < surface_data_->height; ++y) {
|
||||
for (int x = 0; x < surface_data_->width; ++x) {
|
||||
// Calcular la posición correcta en la textura teniendo en cuenta el stride
|
||||
int texture_index = (y * row_stride) + x;
|
||||
int surface_index = (y * surface_data_->width) + x;
|
||||
|
||||
pixels[texture_index] = palette_[surface_data_->data.get()[surface_index]];
|
||||
// Cachear punteros fuera del bucle para permitir autovectorización SIMD
|
||||
const Uint8* src = surface_data_->data.get();
|
||||
const Uint32* pal = palette_.data();
|
||||
const int width = surface_data_->width;
|
||||
const int height = surface_data_->height;
|
||||
for (int y = 0; y < height; ++y) {
|
||||
const Uint8* src_row = src + (y * width);
|
||||
Uint32* dst_row = pixels + (y * row_stride);
|
||||
for (int x = 0; x < width; ++x) {
|
||||
dst_row[x] = pal[src_row[x]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,12 +616,16 @@ void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FR
|
||||
|
||||
int row_stride = pitch / sizeof(Uint32);
|
||||
|
||||
for (int y = 0; y < surface_data_->height; ++y) {
|
||||
for (int x = 0; x < surface_data_->width; ++x) {
|
||||
int texture_index = (y * row_stride) + x;
|
||||
int surface_index = (y * surface_data_->width) + x;
|
||||
|
||||
pixels[texture_index] = palette_[surface_data_->data.get()[surface_index]];
|
||||
// Cachear punteros fuera del bucle para permitir autovectorización SIMD
|
||||
const Uint8* src = surface_data_->data.get();
|
||||
const Uint32* pal = palette_.data();
|
||||
const int width = surface_data_->width;
|
||||
const int height = surface_data_->height;
|
||||
for (int y = 0; y < height; ++y) {
|
||||
const Uint8* src_row = src + (y * width);
|
||||
Uint32* dst_row = pixels + (y * row_stride);
|
||||
for (int x = 0; x < width; ++x) {
|
||||
dst_row[x] = pal[src_row[x]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -173,6 +175,34 @@ 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_;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
|
||||
#ifdef _DEBUG
|
||||
#include "core/system/debug.hpp" // Para Debug
|
||||
#include "game/editor/map_editor.hpp" // Para MapEditor
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
@@ -183,6 +184,7 @@ Director::Director() {
|
||||
Debug::get()->setDebugFile(Resource::List::get()->get("debug.yaml"));
|
||||
Debug::get()->loadFromFile();
|
||||
SceneManager::current = Debug::get()->getInitialScene();
|
||||
MapEditor::init();
|
||||
#endif
|
||||
|
||||
std::cout << "\n"; // Fin de inicialización de sistemas
|
||||
@@ -217,6 +219,7 @@ Director::~Director() {
|
||||
Cheevos::destroy();
|
||||
Locale::destroy();
|
||||
#ifdef _DEBUG
|
||||
MapEditor::destroy();
|
||||
Debug::destroy();
|
||||
#endif
|
||||
Input::destroy();
|
||||
|
||||
@@ -25,13 +25,15 @@ namespace Defaults::Video {
|
||||
constexpr bool FULLSCREEN = false; // Modo de pantalla completa por defecto (false = ventana)
|
||||
constexpr Screen::Filter FILTER = Screen::Filter::NEAREST; // Filtro por defecto
|
||||
constexpr bool VERTICAL_SYNC = true; // Vsync activado por defecto
|
||||
constexpr bool POSTFX = false; // PostFX desactivado por defecto
|
||||
constexpr bool SHADER_ENABLED = false; // Shaders de post-procesado desactivados por defecto
|
||||
constexpr bool SUPERSAMPLING = false; // Supersampling desactivado por defecto
|
||||
constexpr bool INTEGER_SCALE = true; // Escalado entero activado por defecto
|
||||
constexpr bool KEEP_ASPECT = true; // Mantener aspecto activado por defecto
|
||||
constexpr const char* PALETTE_NAME = "zx-spectrum"; // Paleta por defecto
|
||||
constexpr const char* PALETTE_SORT = "original"; // Modo de ordenación de paleta por defecto
|
||||
constexpr bool LINEAR_UPSCALE = false; // Upscale NEAREST por defecto
|
||||
constexpr int DOWNSCALE_ALGO = 1; // Downscale Lanczos2 por defecto
|
||||
constexpr bool GPU_ACCELERATION = true; // Aceleración GPU activada por defecto
|
||||
} // namespace Defaults::Video
|
||||
|
||||
namespace Defaults::Border {
|
||||
@@ -100,5 +102,6 @@ namespace Defaults::Game::Player {
|
||||
constexpr int SPAWN_X = 25 * Tile::SIZE; // Posición X inicial
|
||||
constexpr int SPAWN_Y = 13 * Tile::SIZE; // Posición Y inicial
|
||||
constexpr SDL_FlipMode SPAWN_FLIP = Flip::LEFT; // Orientación inicial
|
||||
constexpr int SKIN = 1; // Skin del jugador por defecto (1=normal, 2=alternativa)
|
||||
constexpr const char* SKIN = "default"; // Skin del jugador por defecto
|
||||
constexpr int COLOR = -1; // Color del jugador (-1 = automático según cheats)
|
||||
} // namespace Defaults::Game::Player
|
||||
|
||||
78
source/game/editor/editor_statusbar.cpp
Normal file
78
source/game/editor/editor_statusbar.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include "game/editor/editor_statusbar.hpp"
|
||||
|
||||
#include <string> // Para to_string
|
||||
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
#include "core/rendering/text.hpp" // Para Text
|
||||
#include "core/resources/resource_cache.hpp" // Para Resource::Cache
|
||||
#include "game/options.hpp" // Para Options::game
|
||||
#include "utils/defines.hpp" // Para Tile::SIZE
|
||||
#include "utils/utils.hpp" // Para stringToColor
|
||||
|
||||
// Constructor
|
||||
EditorStatusBar::EditorStatusBar(const std::string& room_number, const std::string& room_name)
|
||||
: room_number_(room_number),
|
||||
room_name_(room_name) {
|
||||
const float SURFACE_WIDTH = Options::game.width;
|
||||
constexpr float SURFACE_HEIGHT = 6.0F * Tile::SIZE; // 48 pixels, igual que el scoreboard
|
||||
|
||||
surface_ = std::make_shared<Surface>(SURFACE_WIDTH, SURFACE_HEIGHT);
|
||||
surface_dest_ = {.x = 0, .y = Options::game.height - SURFACE_HEIGHT, .w = SURFACE_WIDTH, .h = SURFACE_HEIGHT};
|
||||
}
|
||||
|
||||
// Pinta la barra de estado en pantalla
|
||||
void EditorStatusBar::render() {
|
||||
surface_->render(nullptr, &surface_dest_);
|
||||
}
|
||||
|
||||
// Actualiza la barra de estado
|
||||
void EditorStatusBar::update([[maybe_unused]] float delta_time) {
|
||||
fillTexture();
|
||||
}
|
||||
|
||||
// Establece las coordenadas del ratón en tiles
|
||||
void EditorStatusBar::setMouseTile(int tile_x, int tile_y) {
|
||||
mouse_tile_x_ = tile_x;
|
||||
mouse_tile_y_ = tile_y;
|
||||
}
|
||||
|
||||
// Establece la información de la entidad seleccionada
|
||||
void EditorStatusBar::setSelectionInfo(const std::string& info) {
|
||||
selection_info_ = info;
|
||||
}
|
||||
|
||||
// Dibuja los elementos en la surface
|
||||
void EditorStatusBar::fillTexture() {
|
||||
auto previous_renderer = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(surface_);
|
||||
|
||||
surface_->clear(stringToColor("black"));
|
||||
|
||||
auto text = Resource::Cache::get()->getText("8bithud");
|
||||
const Uint8 LABEL_COLOR = stringToColor("bright_cyan");
|
||||
const Uint8 VALUE_COLOR = stringToColor("white");
|
||||
|
||||
// Línea 1: Número y nombre de la habitación
|
||||
const std::string ROOM_TEXT = toLower(room_number_ + " " + room_name_);
|
||||
text->writeColored(LEFT_X, LINE1_Y, ROOM_TEXT, LABEL_COLOR);
|
||||
|
||||
// Línea 2: Coordenadas del ratón en tiles
|
||||
const std::string TILE_X_STR = (mouse_tile_x_ < 10 ? "0" : "") + std::to_string(mouse_tile_x_);
|
||||
const std::string TILE_Y_STR = (mouse_tile_y_ < 10 ? "0" : "") + std::to_string(mouse_tile_y_);
|
||||
text->writeColored(LEFT_X, LINE2_Y, toLower("tile:"), LABEL_COLOR);
|
||||
text->writeColored(LEFT_X + 30, LINE2_Y, TILE_X_STR + "," + TILE_Y_STR, VALUE_COLOR);
|
||||
|
||||
// Info de selección o indicador de modo editor
|
||||
if (!selection_info_.empty()) {
|
||||
text->writeColored(LEFT_X + 80, LINE2_Y, toLower(selection_info_), stringToColor("bright_yellow"));
|
||||
} else {
|
||||
text->writeColored(176, LINE2_Y, toLower("editor"), stringToColor("bright_green"));
|
||||
}
|
||||
|
||||
Screen::get()->setRendererSurface(previous_renderer);
|
||||
}
|
||||
|
||||
#endif // _DEBUG
|
||||
43
source/game/editor/editor_statusbar.hpp
Normal file
43
source/game/editor/editor_statusbar.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
|
||||
class Surface;
|
||||
|
||||
class EditorStatusBar {
|
||||
public:
|
||||
EditorStatusBar(const std::string& room_number, const std::string& room_name);
|
||||
~EditorStatusBar() = default;
|
||||
|
||||
void render();
|
||||
void update(float delta_time);
|
||||
void setMouseTile(int tile_x, int tile_y);
|
||||
void setSelectionInfo(const std::string& info);
|
||||
|
||||
private:
|
||||
void fillTexture(); // Dibuja los elementos en la surface
|
||||
|
||||
// Constantes de posición (en pixels dentro de la surface de 256x48)
|
||||
static constexpr int LINE1_Y = 4; // Nombre de la habitación
|
||||
static constexpr int LINE2_Y = 14; // Coordenadas del ratón + selección
|
||||
static constexpr int LINE3_Y = 24; // Línea extra disponible
|
||||
static constexpr int LEFT_X = 4; // Margen izquierdo
|
||||
|
||||
// Objetos
|
||||
std::shared_ptr<Surface> surface_; // Surface donde dibujar la barra
|
||||
SDL_FRect surface_dest_{}; // Rectángulo destino en pantalla
|
||||
|
||||
// Variables
|
||||
std::string room_number_; // Número de la habitación
|
||||
std::string room_name_; // Nombre de la habitación
|
||||
int mouse_tile_x_{0}; // Coordenada X del ratón en tiles
|
||||
int mouse_tile_y_{0}; // Coordenada Y del ratón en tiles
|
||||
std::string selection_info_; // Información de la entidad seleccionada
|
||||
};
|
||||
|
||||
#endif // _DEBUG
|
||||
523
source/game/editor/map_editor.cpp
Normal file
523
source/game/editor/map_editor.cpp
Normal file
@@ -0,0 +1,523 @@
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include "game/editor/map_editor.hpp"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <cmath> // Para std::round
|
||||
#include <iostream> // Para cout
|
||||
|
||||
#include "core/input/mouse.hpp" // Para Mouse
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
#include "core/resources/resource_cache.hpp" // Para Resource::Cache
|
||||
#include "core/resources/resource_list.hpp" // Para Resource::List
|
||||
#include "game/editor/editor_statusbar.hpp" // Para EditorStatusBar
|
||||
#include "game/editor/room_saver.hpp" // Para RoomSaver
|
||||
#include "game/entities/player.hpp" // Para Player
|
||||
#include "game/gameplay/enemy_manager.hpp" // Para EnemyManager
|
||||
#include "game/gameplay/item_manager.hpp" // Para ItemManager
|
||||
#include "game/gameplay/room.hpp" // Para Room
|
||||
#include "game/options.hpp" // Para Options
|
||||
#include "utils/defines.hpp" // Para Tile::SIZE, PlayArea
|
||||
#include "utils/utils.hpp" // Para stringToColor
|
||||
|
||||
// Singleton
|
||||
MapEditor* MapEditor::instance_ = nullptr;
|
||||
|
||||
void MapEditor::init() {
|
||||
instance_ = new MapEditor();
|
||||
}
|
||||
|
||||
void MapEditor::destroy() {
|
||||
delete instance_;
|
||||
instance_ = nullptr;
|
||||
}
|
||||
|
||||
auto MapEditor::get() -> MapEditor* {
|
||||
return instance_;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
MapEditor::MapEditor() = default;
|
||||
|
||||
// Destructor
|
||||
MapEditor::~MapEditor() = default;
|
||||
|
||||
// Entra en modo editor
|
||||
void MapEditor::enter(std::shared_ptr<Room> room, std::shared_ptr<Player> player, const std::string& room_path, std::shared_ptr<Scoreboard::Data> scoreboard_data) {
|
||||
if (active_) { return; }
|
||||
|
||||
room_ = std::move(room);
|
||||
player_ = std::move(player);
|
||||
room_path_ = room_path;
|
||||
scoreboard_data_ = std::move(scoreboard_data);
|
||||
|
||||
// Cargar una copia de los datos de la habitación (para boundaries y edición)
|
||||
auto room_data_ptr = Resource::Cache::get()->getRoom(room_path);
|
||||
if (room_data_ptr) {
|
||||
room_data_ = *room_data_ptr;
|
||||
}
|
||||
|
||||
// Obtener la ruta completa y cargar el YAML original (para edición parcial y backup)
|
||||
file_path_ = Resource::List::get()->get(room_path_);
|
||||
if (!file_path_.empty()) {
|
||||
yaml_ = RoomSaver::loadYAML(file_path_);
|
||||
yaml_backup_ = yaml_; // Copia profunda para revert
|
||||
}
|
||||
|
||||
// Guardar estado de invencibilidad y forzarla
|
||||
invincible_before_editor_ = Options::cheats.invincible;
|
||||
Options::cheats.invincible = Options::Cheat::State::ENABLED;
|
||||
player_->setColor();
|
||||
|
||||
// Resetear enemigos a su posición inicial (pueden haberse movido durante el gameplay)
|
||||
room_->resetEnemyPositions(room_data_.enemies);
|
||||
|
||||
// Crear la barra de estado
|
||||
statusbar_ = std::make_unique<EditorStatusBar>(room_->getNumber(), room_->getName());
|
||||
|
||||
// Resetear estado de drag
|
||||
drag_ = {};
|
||||
|
||||
active_ = true;
|
||||
std::cout << "MapEditor: ON (room " << room_path_ << ")\n";
|
||||
}
|
||||
|
||||
// Sale del modo editor
|
||||
void MapEditor::exit() {
|
||||
if (!active_) { return; }
|
||||
|
||||
active_ = false;
|
||||
|
||||
// Restaurar invencibilidad
|
||||
Options::cheats.invincible = invincible_before_editor_;
|
||||
player_->setColor();
|
||||
|
||||
// Liberar recursos
|
||||
drag_ = {};
|
||||
statusbar_.reset();
|
||||
room_.reset();
|
||||
player_.reset();
|
||||
scoreboard_data_.reset();
|
||||
|
||||
std::cout << "MapEditor: OFF\n";
|
||||
}
|
||||
|
||||
// Revierte todos los cambios al estado original
|
||||
auto MapEditor::revert() -> std::string {
|
||||
if (!active_) { return "Editor not active"; }
|
||||
if (file_path_.empty()) { return "Error: No file path"; }
|
||||
|
||||
// Restaurar el YAML al backup original
|
||||
yaml_ = yaml_backup_;
|
||||
RoomSaver::saveYAML(file_path_, yaml_);
|
||||
|
||||
// Recargar room_data_ desde el backup
|
||||
auto room_data_ptr = Resource::Cache::get()->getRoom(room_path_);
|
||||
if (room_data_ptr) {
|
||||
room_data_ = *room_data_ptr;
|
||||
}
|
||||
|
||||
// Resetear los sprites vivos a las posiciones originales
|
||||
room_->resetEnemyPositions(room_data_.enemies);
|
||||
|
||||
// Resetear items (recargar posiciones)
|
||||
auto* item_mgr = room_->getItemManager();
|
||||
for (int i = 0; i < item_mgr->getCount() && i < static_cast<int>(room_data_.items.size()); ++i) {
|
||||
item_mgr->getItem(i)->setPosition(room_data_.items[i].x, room_data_.items[i].y);
|
||||
}
|
||||
|
||||
return "Reverted to original";
|
||||
}
|
||||
|
||||
// Auto-guarda los cambios puntuales al YAML tras soltar una entidad
|
||||
void MapEditor::autosave() {
|
||||
if (file_path_.empty()) { return; }
|
||||
RoomSaver::saveYAML(file_path_, yaml_);
|
||||
}
|
||||
|
||||
// Actualiza el editor
|
||||
void MapEditor::update(float delta_time) {
|
||||
// Mantener el ratón siempre visible
|
||||
SDL_ShowCursor();
|
||||
Mouse::last_mouse_move_time = SDL_GetTicks();
|
||||
|
||||
// Actualizar animaciones de enemigos e items (sin mover enemigos)
|
||||
room_->updateEditorMode(delta_time);
|
||||
|
||||
// Actualizar posición del ratón
|
||||
updateMousePosition();
|
||||
|
||||
// Si estamos arrastrando, actualizar la posición snapped
|
||||
if (drag_.target != DragTarget::NONE) {
|
||||
updateDrag();
|
||||
}
|
||||
|
||||
// Actualizar la barra de estado con las coordenadas del ratón y la selección
|
||||
if (statusbar_) {
|
||||
statusbar_->setMouseTile(mouse_tile_x_, mouse_tile_y_);
|
||||
|
||||
// Construir info de selección
|
||||
std::string sel_info;
|
||||
if (drag_.target != DragTarget::NONE) {
|
||||
switch (drag_.target) {
|
||||
case DragTarget::PLAYER:
|
||||
sel_info = "PLAYER";
|
||||
break;
|
||||
case DragTarget::ENEMY_INITIAL:
|
||||
sel_info = "ENEMY " + std::to_string(drag_.index);
|
||||
break;
|
||||
case DragTarget::ENEMY_BOUND1:
|
||||
sel_info = "E" + std::to_string(drag_.index) + " BOUND1";
|
||||
break;
|
||||
case DragTarget::ENEMY_BOUND2:
|
||||
sel_info = "E" + std::to_string(drag_.index) + " BOUND2";
|
||||
break;
|
||||
case DragTarget::ITEM:
|
||||
sel_info = "ITEM " + std::to_string(drag_.index);
|
||||
break;
|
||||
case DragTarget::NONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
statusbar_->setSelectionInfo(sel_info);
|
||||
statusbar_->update(delta_time);
|
||||
}
|
||||
}
|
||||
|
||||
// Renderiza el editor
|
||||
void MapEditor::render() {
|
||||
// El tilemap ya ha sido renderizado por Game::renderPlaying() antes de llamar aquí
|
||||
|
||||
// Renderizar los marcadores de boundaries y líneas de ruta (debajo de los sprites)
|
||||
renderEnemyBoundaries();
|
||||
|
||||
// Renderizar entidades normales: enemigos (animados en posición inicial), items, jugador
|
||||
room_->renderEnemies();
|
||||
room_->renderItems();
|
||||
player_->render();
|
||||
|
||||
// Renderizar highlight de selección (encima de los sprites)
|
||||
renderSelectionHighlight();
|
||||
|
||||
// Renderizar barra de estado del editor (reemplaza al scoreboard)
|
||||
if (statusbar_) {
|
||||
statusbar_->render();
|
||||
}
|
||||
}
|
||||
|
||||
// Maneja eventos del editor
|
||||
void MapEditor::handleEvent(const SDL_Event& event) {
|
||||
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && event.button.button == SDL_BUTTON_LEFT) {
|
||||
handleMouseDown(mouse_game_x_, mouse_game_y_);
|
||||
} else if (event.type == SDL_EVENT_MOUSE_BUTTON_UP && event.button.button == SDL_BUTTON_LEFT) {
|
||||
handleMouseUp();
|
||||
}
|
||||
}
|
||||
|
||||
// Procesa click del ratón: hit test + inicio de drag
|
||||
void MapEditor::handleMouseDown(float game_x, float game_y) {
|
||||
// Prioridad de hit test: jugador → enemigos (initial) → enemigos (boundaries) → items
|
||||
|
||||
// Helper para iniciar drag
|
||||
auto startDrag = [&](DragTarget target, int index, float entity_x, float entity_y) {
|
||||
drag_.target = target;
|
||||
drag_.index = index;
|
||||
drag_.offset_x = game_x - entity_x;
|
||||
drag_.offset_y = game_y - entity_y;
|
||||
drag_.snap_x = entity_x;
|
||||
drag_.snap_y = entity_y;
|
||||
};
|
||||
|
||||
// 1. Hit test sobre el jugador (8x16)
|
||||
SDL_FRect player_rect = player_->getRect();
|
||||
if (pointInRect(game_x, game_y, player_rect)) {
|
||||
startDrag(DragTarget::PLAYER, -1, player_rect.x, player_rect.y);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Hit test sobre enemigos: posición inicial (usan el rect del sprite vivo)
|
||||
auto* enemy_mgr = room_->getEnemyManager();
|
||||
for (int i = 0; i < enemy_mgr->getCount(); ++i) {
|
||||
SDL_FRect enemy_rect = enemy_mgr->getEnemy(i)->getRect();
|
||||
if (pointInRect(game_x, game_y, enemy_rect)) {
|
||||
startDrag(DragTarget::ENEMY_INITIAL, i, enemy_rect.x, enemy_rect.y);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Hit test sobre boundaries de enemigos (rectángulos 8x8 en las posiciones de room_data_)
|
||||
for (int i = 0; i < static_cast<int>(room_data_.enemies.size()); ++i) {
|
||||
const auto& ed = room_data_.enemies[i];
|
||||
constexpr float SZ = static_cast<float>(Tile::SIZE);
|
||||
|
||||
SDL_FRect b1_rect = {.x = static_cast<float>(ed.x1), .y = static_cast<float>(ed.y1), .w = SZ, .h = SZ};
|
||||
if (pointInRect(game_x, game_y, b1_rect)) {
|
||||
startDrag(DragTarget::ENEMY_BOUND1, i, b1_rect.x, b1_rect.y);
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_FRect b2_rect = {.x = static_cast<float>(ed.x2), .y = static_cast<float>(ed.y2), .w = SZ, .h = SZ};
|
||||
if (pointInRect(game_x, game_y, b2_rect)) {
|
||||
startDrag(DragTarget::ENEMY_BOUND2, i, b2_rect.x, b2_rect.y);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Hit test sobre items (8x8)
|
||||
auto* item_mgr = room_->getItemManager();
|
||||
for (int i = 0; i < item_mgr->getCount(); ++i) {
|
||||
SDL_FRect item_rect = item_mgr->getItem(i)->getCollider();
|
||||
if (pointInRect(game_x, game_y, item_rect)) {
|
||||
startDrag(DragTarget::ITEM, i, item_rect.x, item_rect.y);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Procesa soltar el ratón: commit del drag
|
||||
void MapEditor::handleMouseUp() {
|
||||
if (drag_.target == DragTarget::NONE) { return; }
|
||||
|
||||
const int IDX = drag_.index;
|
||||
const int SNAP_X = static_cast<int>(drag_.snap_x);
|
||||
const int SNAP_Y = static_cast<int>(drag_.snap_y);
|
||||
|
||||
bool changed = false;
|
||||
|
||||
switch (drag_.target) {
|
||||
case DragTarget::PLAYER:
|
||||
player_->setDebugPosition(drag_.snap_x, drag_.snap_y);
|
||||
player_->finalizeDebugTeleport();
|
||||
// El jugador no se guarda en el YAML de la habitación (es dato de spawn global)
|
||||
break;
|
||||
|
||||
case DragTarget::ENEMY_INITIAL:
|
||||
if (IDX >= 0 && IDX < static_cast<int>(room_data_.enemies.size())) {
|
||||
room_data_.enemies[IDX].x = drag_.snap_x;
|
||||
room_data_.enemies[IDX].y = drag_.snap_y;
|
||||
room_->getEnemyManager()->getEnemy(IDX)->resetToInitialPosition(room_data_.enemies[IDX]);
|
||||
RoomSaver::updateEnemyPosition(yaml_, IDX, drag_.snap_x, drag_.snap_y);
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case DragTarget::ENEMY_BOUND1:
|
||||
if (IDX >= 0 && IDX < static_cast<int>(room_data_.enemies.size())) {
|
||||
room_data_.enemies[IDX].x1 = SNAP_X;
|
||||
room_data_.enemies[IDX].y1 = SNAP_Y;
|
||||
RoomSaver::updateEnemyBound1(yaml_, IDX, SNAP_X, SNAP_Y);
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case DragTarget::ENEMY_BOUND2:
|
||||
if (IDX >= 0 && IDX < static_cast<int>(room_data_.enemies.size())) {
|
||||
room_data_.enemies[IDX].x2 = SNAP_X;
|
||||
room_data_.enemies[IDX].y2 = SNAP_Y;
|
||||
RoomSaver::updateEnemyBound2(yaml_, IDX, SNAP_X, SNAP_Y);
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case DragTarget::ITEM:
|
||||
if (IDX >= 0 && IDX < room_->getItemManager()->getCount()) {
|
||||
room_->getItemManager()->getItem(IDX)->setPosition(drag_.snap_x, drag_.snap_y);
|
||||
RoomSaver::updateItemPosition(yaml_, IDX, drag_.snap_x, drag_.snap_y);
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case DragTarget::NONE:
|
||||
break;
|
||||
}
|
||||
|
||||
// Auto-guardar si hubo cambio
|
||||
if (changed) {
|
||||
autosave();
|
||||
}
|
||||
|
||||
// Resetear estado de drag
|
||||
drag_ = {};
|
||||
}
|
||||
|
||||
// Actualiza la posición snapped durante el drag
|
||||
void MapEditor::updateDrag() {
|
||||
float raw_x = mouse_game_x_ - drag_.offset_x;
|
||||
float raw_y = mouse_game_y_ - drag_.offset_y;
|
||||
|
||||
drag_.snap_x = snapToGrid(raw_x);
|
||||
drag_.snap_y = snapToGrid(raw_y);
|
||||
|
||||
// Mientras arrastramos, mover la entidad visualmente a la posición snapped
|
||||
switch (drag_.target) {
|
||||
case DragTarget::PLAYER:
|
||||
player_->setDebugPosition(drag_.snap_x, drag_.snap_y);
|
||||
break;
|
||||
|
||||
case DragTarget::ENEMY_INITIAL:
|
||||
if (drag_.index >= 0 && drag_.index < room_->getEnemyManager()->getCount()) {
|
||||
// Mover el sprite vivo del enemigo durante el arrastre
|
||||
auto& enemy = room_->getEnemyManager()->getEnemy(drag_.index);
|
||||
Enemy::Data temp_data = room_data_.enemies[drag_.index];
|
||||
temp_data.x = drag_.snap_x;
|
||||
temp_data.y = drag_.snap_y;
|
||||
enemy->resetToInitialPosition(temp_data);
|
||||
}
|
||||
break;
|
||||
|
||||
case DragTarget::ENEMY_BOUND1:
|
||||
// Los boundaries se actualizan visualmente en renderEnemyBoundaries() via drag_.snap
|
||||
break;
|
||||
|
||||
case DragTarget::ENEMY_BOUND2:
|
||||
break;
|
||||
|
||||
case DragTarget::ITEM:
|
||||
if (drag_.index >= 0 && drag_.index < room_->getItemManager()->getCount()) {
|
||||
room_->getItemManager()->getItem(drag_.index)->setPosition(drag_.snap_x, drag_.snap_y);
|
||||
}
|
||||
break;
|
||||
|
||||
case DragTarget::NONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja highlight del elemento seleccionado/arrastrado
|
||||
void MapEditor::renderSelectionHighlight() {
|
||||
if (drag_.target == DragTarget::NONE) { return; }
|
||||
|
||||
auto game_surface = Screen::get()->getRendererSurface();
|
||||
if (!game_surface) { return; }
|
||||
|
||||
const Uint8 HIGHLIGHT_COLOR = stringToColor("bright_white");
|
||||
constexpr float SZ = static_cast<float>(Tile::SIZE);
|
||||
|
||||
SDL_FRect highlight_rect{};
|
||||
switch (drag_.target) {
|
||||
case DragTarget::PLAYER:
|
||||
highlight_rect = player_->getRect();
|
||||
break;
|
||||
|
||||
case DragTarget::ENEMY_INITIAL:
|
||||
if (drag_.index >= 0 && drag_.index < room_->getEnemyManager()->getCount()) {
|
||||
highlight_rect = room_->getEnemyManager()->getEnemy(drag_.index)->getRect();
|
||||
}
|
||||
break;
|
||||
|
||||
case DragTarget::ENEMY_BOUND1:
|
||||
case DragTarget::ENEMY_BOUND2:
|
||||
highlight_rect = {.x = drag_.snap_x, .y = drag_.snap_y, .w = SZ, .h = SZ};
|
||||
break;
|
||||
|
||||
case DragTarget::ITEM:
|
||||
if (drag_.index >= 0 && drag_.index < room_->getItemManager()->getCount()) {
|
||||
highlight_rect = room_->getItemManager()->getItem(drag_.index)->getCollider();
|
||||
}
|
||||
break;
|
||||
|
||||
case DragTarget::NONE:
|
||||
return;
|
||||
}
|
||||
|
||||
// Dibujar rectángulo de highlight alrededor de la entidad
|
||||
SDL_FRect border = {
|
||||
.x = highlight_rect.x - 1,
|
||||
.y = highlight_rect.y - 1,
|
||||
.w = highlight_rect.w + 2,
|
||||
.h = highlight_rect.h + 2};
|
||||
game_surface->drawRectBorder(&border, HIGHLIGHT_COLOR);
|
||||
}
|
||||
|
||||
// Alinea un valor a la cuadrícula de 8x8
|
||||
auto MapEditor::snapToGrid(float value) -> float {
|
||||
return std::round(value / static_cast<float>(Tile::SIZE)) * static_cast<float>(Tile::SIZE);
|
||||
}
|
||||
|
||||
// Hit test: punto dentro de rectángulo
|
||||
auto MapEditor::pointInRect(float px, float py, const SDL_FRect& rect) -> bool {
|
||||
return px >= rect.x && px < rect.x + rect.w && py >= rect.y && py < rect.y + rect.h;
|
||||
}
|
||||
|
||||
// Dibuja marcadores de boundaries y líneas de ruta para los enemigos
|
||||
void MapEditor::renderEnemyBoundaries() {
|
||||
auto game_surface = Screen::get()->getRendererSurface();
|
||||
if (!game_surface) { return; }
|
||||
|
||||
const Uint8 COLOR_BOUND1 = stringToColor("bright_cyan");
|
||||
const Uint8 COLOR_BOUND2 = stringToColor("bright_yellow");
|
||||
const Uint8 COLOR_ROUTE = stringToColor("bright_white");
|
||||
|
||||
for (int i = 0; i < static_cast<int>(room_data_.enemies.size()); ++i) {
|
||||
const auto& enemy = room_data_.enemies[i];
|
||||
constexpr float HALF = Tile::SIZE / 2.0F;
|
||||
|
||||
// Posiciones base (pueden estar siendo arrastradas)
|
||||
float init_x = enemy.x;
|
||||
float init_y = enemy.y;
|
||||
float b1_x = static_cast<float>(enemy.x1);
|
||||
float b1_y = static_cast<float>(enemy.y1);
|
||||
float b2_x = static_cast<float>(enemy.x2);
|
||||
float b2_y = static_cast<float>(enemy.y2);
|
||||
|
||||
// Si estamos arrastrando una boundary de este enemigo, usar la posición snapped
|
||||
if (drag_.index == i) {
|
||||
if (drag_.target == DragTarget::ENEMY_BOUND1) {
|
||||
b1_x = drag_.snap_x;
|
||||
b1_y = drag_.snap_y;
|
||||
} else if (drag_.target == DragTarget::ENEMY_BOUND2) {
|
||||
b2_x = drag_.snap_x;
|
||||
b2_y = drag_.snap_y;
|
||||
} else if (drag_.target == DragTarget::ENEMY_INITIAL) {
|
||||
init_x = drag_.snap_x;
|
||||
init_y = drag_.snap_y;
|
||||
}
|
||||
}
|
||||
|
||||
// Dibujar líneas de ruta
|
||||
game_surface->drawLine(b1_x + HALF, b1_y + HALF, init_x + HALF, init_y + HALF, COLOR_ROUTE);
|
||||
game_surface->drawLine(init_x + HALF, init_y + HALF, b2_x + HALF, b2_y + HALF, COLOR_ROUTE);
|
||||
|
||||
// Marcadores en las boundaries
|
||||
renderBoundaryMarker(b1_x, b1_y, COLOR_BOUND1);
|
||||
renderBoundaryMarker(b2_x, b2_y, COLOR_BOUND2);
|
||||
}
|
||||
}
|
||||
|
||||
// Dibuja un marcador de boundary (rectángulo pequeño) en una posición
|
||||
void MapEditor::renderBoundaryMarker(float x, float y, Uint8 color) {
|
||||
auto game_surface = Screen::get()->getRendererSurface();
|
||||
if (!game_surface) { return; }
|
||||
|
||||
SDL_FRect marker = {.x = x, .y = y, .w = static_cast<float>(Tile::SIZE), .h = static_cast<float>(Tile::SIZE)};
|
||||
game_surface->drawRectBorder(&marker, color);
|
||||
}
|
||||
|
||||
// Convierte coordenadas de ventana a coordenadas de juego y tile
|
||||
void MapEditor::updateMousePosition() {
|
||||
float mouse_x = 0.0F;
|
||||
float mouse_y = 0.0F;
|
||||
SDL_GetMouseState(&mouse_x, &mouse_y);
|
||||
|
||||
float render_x = 0.0F;
|
||||
float render_y = 0.0F;
|
||||
SDL_RenderCoordinatesFromWindow(Screen::get()->getRenderer(), mouse_x, mouse_y, &render_x, &render_y);
|
||||
|
||||
SDL_FRect dst_rect = Screen::get()->getGameSurfaceDstRect();
|
||||
mouse_game_x_ = render_x - dst_rect.x;
|
||||
mouse_game_y_ = render_y - dst_rect.y;
|
||||
|
||||
// Convertir a coordenadas de tile (clampeadas al área de juego)
|
||||
mouse_tile_x_ = static_cast<int>(mouse_game_x_) / Tile::SIZE;
|
||||
mouse_tile_y_ = static_cast<int>(mouse_game_y_) / Tile::SIZE;
|
||||
|
||||
// Clampear a los límites del mapa
|
||||
if (mouse_tile_x_ < 0) { mouse_tile_x_ = 0; }
|
||||
if (mouse_tile_x_ >= PlayArea::WIDTH / Tile::SIZE) { mouse_tile_x_ = PlayArea::WIDTH / Tile::SIZE - 1; }
|
||||
if (mouse_tile_y_ < 0) { mouse_tile_y_ = 0; }
|
||||
if (mouse_tile_y_ >= PlayArea::HEIGHT / Tile::SIZE) { mouse_tile_y_ = PlayArea::HEIGHT / Tile::SIZE - 1; }
|
||||
}
|
||||
|
||||
#endif // _DEBUG
|
||||
101
source/game/editor/map_editor.hpp
Normal file
101
source/game/editor/map_editor.hpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr, unique_ptr
|
||||
#include <string> // Para string
|
||||
|
||||
#include "external/fkyaml_node.hpp" // Para fkyaml::node
|
||||
#include "game/entities/enemy.hpp" // Para Enemy::Data
|
||||
#include "game/entities/item.hpp" // Para Item::Data
|
||||
#include "game/entities/player.hpp" // Para Player::SpawnData
|
||||
#include "game/gameplay/room.hpp" // Para Room::Data
|
||||
#include "game/gameplay/scoreboard.hpp" // Para Scoreboard::Data
|
||||
#include "game/options.hpp" // Para Options::Cheat
|
||||
|
||||
class EditorStatusBar;
|
||||
|
||||
class MapEditor {
|
||||
public:
|
||||
static void init(); // [SINGLETON] Crea el objeto
|
||||
static void destroy(); // [SINGLETON] Destruye el objeto
|
||||
static auto get() -> MapEditor*; // [SINGLETON] Obtiene el objeto
|
||||
|
||||
void enter(std::shared_ptr<Room> room, std::shared_ptr<Player> player, const std::string& room_path, std::shared_ptr<Scoreboard::Data> scoreboard_data);
|
||||
void exit();
|
||||
[[nodiscard]] auto isActive() const -> bool { return active_; }
|
||||
|
||||
void update(float delta_time);
|
||||
void render();
|
||||
void handleEvent(const SDL_Event& event);
|
||||
auto revert() -> std::string; // Revierte todos los cambios al estado original
|
||||
|
||||
private:
|
||||
static MapEditor* instance_; // [SINGLETON] Objeto privado
|
||||
|
||||
MapEditor(); // Constructor
|
||||
~MapEditor(); // Destructor
|
||||
|
||||
// Tipos para drag & drop
|
||||
enum class DragTarget { NONE,
|
||||
PLAYER,
|
||||
ENEMY_INITIAL,
|
||||
ENEMY_BOUND1,
|
||||
ENEMY_BOUND2,
|
||||
ITEM };
|
||||
|
||||
struct DragState {
|
||||
DragTarget target{DragTarget::NONE};
|
||||
int index{-1}; // Índice del enemigo o item en room_data_
|
||||
float offset_x{0.0F}; // Offset entre el cursor y el origen de la entidad al inicio del drag
|
||||
float offset_y{0.0F};
|
||||
float snap_x{0.0F}; // Posición snapped actual (para preview)
|
||||
float snap_y{0.0F};
|
||||
};
|
||||
|
||||
// Métodos internos
|
||||
void updateMousePosition(); // Convierte coordenadas de ventana a coordenadas de juego y tile
|
||||
void renderEnemyBoundaries(); // Dibuja marcadores de boundaries y líneas de ruta
|
||||
void renderBoundaryMarker(float x, float y, Uint8 color); // Dibuja un marcador de boundary en una posición
|
||||
void renderSelectionHighlight(); // Dibuja highlight del elemento seleccionado/arrastrado
|
||||
void handleMouseDown(float game_x, float game_y); // Procesa click del ratón (hit test + inicio de drag)
|
||||
void handleMouseUp(); // Procesa soltar el ratón (commit del drag + autosave)
|
||||
void updateDrag(); // Actualiza la posición snapped durante el drag
|
||||
void autosave(); // Guarda los cambios puntuales al YAML
|
||||
static auto snapToGrid(float value) -> float; // Alinea un valor a la cuadrícula de 8x8
|
||||
static auto pointInRect(float px, float py, const SDL_FRect& rect) -> bool; // Hit test punto en rectángulo
|
||||
|
||||
// Estado del editor
|
||||
bool active_{false};
|
||||
DragState drag_;
|
||||
|
||||
// Datos de la habitación
|
||||
Room::Data room_data_; // Copia mutable (para boundaries y edición)
|
||||
std::string room_path_; // Nombre del fichero (ej: "06.yaml")
|
||||
std::string file_path_; // Ruta completa del fichero en disco
|
||||
|
||||
// YAML: nodo vivo (se edita parcialmente) y backup para revert
|
||||
fkyaml::node yaml_; // Nodo YAML actual (editado parcialmente)
|
||||
fkyaml::node yaml_backup_; // Backup del YAML original al entrar
|
||||
|
||||
// Referencias a objetos vivos (para rendering)
|
||||
std::shared_ptr<Room> room_;
|
||||
std::shared_ptr<Player> player_;
|
||||
std::shared_ptr<Scoreboard::Data> scoreboard_data_;
|
||||
|
||||
// Barra de estado del editor
|
||||
std::unique_ptr<EditorStatusBar> statusbar_;
|
||||
|
||||
// Estado del ratón en coordenadas de juego
|
||||
float mouse_game_x_{0.0F};
|
||||
float mouse_game_y_{0.0F};
|
||||
int mouse_tile_x_{0};
|
||||
int mouse_tile_y_{0};
|
||||
|
||||
// Estado previo de invencibilidad (para restaurar al salir)
|
||||
Options::Cheat::State invincible_before_editor_{Options::Cheat::State::DISABLED};
|
||||
};
|
||||
|
||||
#endif // _DEBUG
|
||||
118
source/game/editor/room_saver.cpp
Normal file
118
source/game/editor/room_saver.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include "game/editor/room_saver.hpp"
|
||||
|
||||
#include <cmath> // Para std::round
|
||||
#include <fstream> // Para ifstream, ofstream, istreambuf_iterator
|
||||
#include <iostream> // Para cout, cerr
|
||||
|
||||
#include "utils/defines.hpp" // Para Tile::SIZE
|
||||
|
||||
// Carga el YAML original directamente del filesystem (no del resource pack)
|
||||
auto RoomSaver::loadYAML(const std::string& file_path) -> fkyaml::node {
|
||||
std::ifstream file(file_path);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "RoomSaver: Cannot open " << file_path << "\n";
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
file.close();
|
||||
return fkyaml::node::deserialize(content);
|
||||
}
|
||||
|
||||
// Guarda el nodo YAML completo a disco
|
||||
auto RoomSaver::saveYAML(const std::string& file_path, const fkyaml::node& yaml) -> std::string {
|
||||
std::string content = fkyaml::node::serialize(yaml);
|
||||
|
||||
std::ofstream file(file_path);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "RoomSaver: Cannot write to " << file_path << "\n";
|
||||
return "Error: Cannot write to " + file_path;
|
||||
}
|
||||
|
||||
file << content;
|
||||
file.close();
|
||||
|
||||
const std::string FILE_NAME = file_path.substr(file_path.find_last_of("\\/") + 1);
|
||||
std::cout << "RoomSaver: Saved " << FILE_NAME << "\n";
|
||||
return "Saved " + FILE_NAME;
|
||||
}
|
||||
|
||||
// Actualiza la posición inicial de un enemigo (pixels → tiles)
|
||||
void RoomSaver::updateEnemyPosition(fkyaml::node& yaml, int index, float x, float y) {
|
||||
if (!yaml.contains("enemies")) { return; }
|
||||
auto& enemies = yaml["enemies"];
|
||||
if (index < 0 || index >= static_cast<int>(enemies.size())) { return; }
|
||||
|
||||
auto& enemy = enemies[index];
|
||||
int tile_x = static_cast<int>(std::round(x / Tile::SIZE));
|
||||
int tile_y = static_cast<int>(std::round(y / Tile::SIZE));
|
||||
|
||||
if (!enemy.contains("position")) {
|
||||
enemy["position"] = fkyaml::node::mapping();
|
||||
}
|
||||
enemy["position"]["x"] = tile_x;
|
||||
enemy["position"]["y"] = tile_y;
|
||||
}
|
||||
|
||||
// Actualiza boundary1 de un enemigo (pixels → tiles)
|
||||
void RoomSaver::updateEnemyBound1(fkyaml::node& yaml, int index, int x, int y) {
|
||||
if (!yaml.contains("enemies")) { return; }
|
||||
auto& enemies = yaml["enemies"];
|
||||
if (index < 0 || index >= static_cast<int>(enemies.size())) { return; }
|
||||
|
||||
auto& enemy = enemies[index];
|
||||
int tile_x = x / Tile::SIZE;
|
||||
int tile_y = y / Tile::SIZE;
|
||||
|
||||
if (!enemy.contains("boundaries")) {
|
||||
enemy["boundaries"] = fkyaml::node::mapping();
|
||||
}
|
||||
auto& bounds = enemy["boundaries"];
|
||||
if (!bounds.contains("position1")) {
|
||||
bounds["position1"] = fkyaml::node::mapping();
|
||||
}
|
||||
bounds["position1"]["x"] = tile_x;
|
||||
bounds["position1"]["y"] = tile_y;
|
||||
}
|
||||
|
||||
// Actualiza boundary2 de un enemigo (pixels → tiles)
|
||||
void RoomSaver::updateEnemyBound2(fkyaml::node& yaml, int index, int x, int y) {
|
||||
if (!yaml.contains("enemies")) { return; }
|
||||
auto& enemies = yaml["enemies"];
|
||||
if (index < 0 || index >= static_cast<int>(enemies.size())) { return; }
|
||||
|
||||
auto& enemy = enemies[index];
|
||||
int tile_x = x / Tile::SIZE;
|
||||
int tile_y = y / Tile::SIZE;
|
||||
|
||||
if (!enemy.contains("boundaries")) {
|
||||
enemy["boundaries"] = fkyaml::node::mapping();
|
||||
}
|
||||
auto& bounds = enemy["boundaries"];
|
||||
if (!bounds.contains("position2")) {
|
||||
bounds["position2"] = fkyaml::node::mapping();
|
||||
}
|
||||
bounds["position2"]["x"] = tile_x;
|
||||
bounds["position2"]["y"] = tile_y;
|
||||
}
|
||||
|
||||
// Actualiza la posición de un item (pixels → tiles)
|
||||
void RoomSaver::updateItemPosition(fkyaml::node& yaml, int index, float x, float y) {
|
||||
if (!yaml.contains("items")) { return; }
|
||||
auto& items = yaml["items"];
|
||||
if (index < 0 || index >= static_cast<int>(items.size())) { return; }
|
||||
|
||||
auto& item = items[index];
|
||||
int tile_x = static_cast<int>(std::round(x / Tile::SIZE));
|
||||
int tile_y = static_cast<int>(std::round(y / Tile::SIZE));
|
||||
|
||||
if (!item.contains("position")) {
|
||||
item["position"] = fkyaml::node::mapping();
|
||||
}
|
||||
item["position"]["x"] = tile_x;
|
||||
item["position"]["y"] = tile_y;
|
||||
}
|
||||
|
||||
#endif // _DEBUG
|
||||
36
source/game/editor/room_saver.hpp
Normal file
36
source/game/editor/room_saver.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include <string> // Para string
|
||||
|
||||
#include "external/fkyaml_node.hpp" // Para fkyaml::node
|
||||
#include "game/entities/enemy.hpp" // Para Enemy::Data
|
||||
#include "game/entities/item.hpp" // Para Item::Data
|
||||
#include "game/entities/player.hpp" // Para Player::SpawnData
|
||||
|
||||
/**
|
||||
* @brief Edición parcial de archivos YAML de habitaciones
|
||||
*
|
||||
* Lee el YAML original, modifica solo los campos editados y serializa.
|
||||
* Preserva todos los campos que no se editan (name_ca, comentarios del formato, etc.)
|
||||
* Solo se usa en builds de debug (editor de mapas).
|
||||
*/
|
||||
class RoomSaver {
|
||||
public:
|
||||
RoomSaver() = delete;
|
||||
|
||||
// Carga el YAML original desde disco (llamar al entrar al editor)
|
||||
static auto loadYAML(const std::string& file_path) -> fkyaml::node;
|
||||
|
||||
// Guarda el nodo YAML completo a disco
|
||||
static auto saveYAML(const std::string& file_path, const fkyaml::node& yaml) -> std::string;
|
||||
|
||||
// Modificaciones puntuales sobre el nodo YAML (posiciones en pixels, se convierten a tiles)
|
||||
static void updateEnemyPosition(fkyaml::node& yaml, int index, float x, float y);
|
||||
static void updateEnemyBound1(fkyaml::node& yaml, int index, int x, int y);
|
||||
static void updateEnemyBound2(fkyaml::node& yaml, int index, int x, int y);
|
||||
static void updateItemPosition(fkyaml::node& yaml, int index, float x, float y);
|
||||
};
|
||||
|
||||
#endif // _DEBUG
|
||||
@@ -48,6 +48,27 @@ void Enemy::update(float delta_time) {
|
||||
collider_ = getRect();
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Solo actualiza la animación sin mover al enemigo
|
||||
void Enemy::updateAnimation(float delta_time) {
|
||||
sprite_->animate(delta_time);
|
||||
}
|
||||
|
||||
// Resetea el enemigo a su posición inicial (para editor)
|
||||
void Enemy::resetToInitialPosition(const Data& data) {
|
||||
sprite_->setPosX(data.x);
|
||||
sprite_->setPosY(data.y);
|
||||
sprite_->setVelX(data.vx);
|
||||
sprite_->setVelY(data.vy);
|
||||
|
||||
const int FLIP = (should_flip_ && data.vx < 0.0F) ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
|
||||
const int MIRROR = should_mirror_ ? SDL_FLIP_VERTICAL : SDL_FLIP_NONE;
|
||||
sprite_->setFlip(static_cast<SDL_FlipMode>(FLIP | MIRROR));
|
||||
|
||||
collider_ = getRect();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Comprueba si ha llegado al limite del recorrido para darse media vuelta
|
||||
void Enemy::checkPath() { // NOLINT(readability-make-member-function-const)
|
||||
if (sprite_->getPosX() > x2_ || sprite_->getPosX() < x1_) {
|
||||
|
||||
@@ -29,6 +29,10 @@ class Enemy {
|
||||
|
||||
void render(); // Pinta el enemigo en pantalla
|
||||
void update(float delta_time); // Actualiza las variables del objeto
|
||||
#ifdef _DEBUG
|
||||
void updateAnimation(float delta_time); // Solo actualiza la animación sin mover al enemigo
|
||||
void resetToInitialPosition(const Data& data); // Resetea el enemigo a su posición inicial (para editor)
|
||||
#endif
|
||||
|
||||
auto getRect() -> SDL_FRect; // Devuelve el rectangulo que contiene al enemigo
|
||||
auto getCollider() -> SDL_FRect&; // Obtiene el rectangulo de colision del enemigo
|
||||
|
||||
@@ -41,6 +41,14 @@ auto Item::getPos() -> SDL_FPoint { // NOLINT(readability-convert-member-functi
|
||||
return P;
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Establece la posición del item (para editor)
|
||||
void Item::setPosition(float x, float y) {
|
||||
sprite_->setPosition(x, y);
|
||||
collider_ = sprite_->getRect();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Asigna los colores del objeto
|
||||
void Item::setColors(Uint8 col1, Uint8 col2) {
|
||||
// Reinicializa el vector de colores
|
||||
|
||||
@@ -29,6 +29,9 @@ class Item {
|
||||
auto getCollider() -> SDL_FRect& { return collider_; } // Obtiene el rectangulo de colision del objeto
|
||||
auto getPos() -> SDL_FPoint; // Obtiene su ubicación
|
||||
void setColors(Uint8 col1, Uint8 col2); // Asigna los colores del objeto
|
||||
#ifdef _DEBUG
|
||||
void setPosition(float x, float y); // Establece la posición del item (para editor)
|
||||
#endif
|
||||
|
||||
private:
|
||||
static constexpr float ITEM_SIZE = 8.0F; // Tamaño del item en pixels
|
||||
|
||||
@@ -620,20 +620,26 @@ auto Player::handleKillingTiles() -> bool {
|
||||
return false; // No se encontró ninguna colisión
|
||||
}
|
||||
|
||||
// Establece el color del jugador (0 = automático según cheats)
|
||||
// Establece el color del jugador (0 = automático según options)
|
||||
void Player::setColor(Uint8 color) {
|
||||
if (color != 0) {
|
||||
color_ = color;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Options::cheats.invincible == Options::Cheat::State::ENABLED) {
|
||||
color_ = static_cast<Uint8>(PaletteColor::CYAN);
|
||||
} else if (Options::cheats.infinite_lives == Options::Cheat::State::ENABLED) {
|
||||
color_ = static_cast<Uint8>(PaletteColor::YELLOW);
|
||||
// Color personalizado desde opciones
|
||||
if (Options::game.player_color >= 0) {
|
||||
color_ = static_cast<Uint8>(Options::game.player_color);
|
||||
} else {
|
||||
color_ = static_cast<Uint8>(PaletteColor::WHITE);
|
||||
}
|
||||
|
||||
// Si el color coincide con el fondo de la habitación, usar fallback
|
||||
if (room_ != nullptr && color_ == room_->getBGColor()) {
|
||||
color_ = (room_->getBGColor() != static_cast<Uint8>(PaletteColor::WHITE))
|
||||
? static_cast<Uint8>(PaletteColor::WHITE)
|
||||
: static_cast<Uint8>(PaletteColor::BRIGHT_BLACK);
|
||||
}
|
||||
}
|
||||
|
||||
// Actualiza los puntos de colisión
|
||||
@@ -765,11 +771,18 @@ void Player::applySpawnValues(const SpawnData& spawn) {
|
||||
sprite_->setFlip(spawn.flip);
|
||||
}
|
||||
|
||||
// Resuelve nombre de skin a fichero de animación
|
||||
auto Player::skinToAnimationPath(const std::string& skin_name) -> std::string {
|
||||
if (skin_name == "default") {
|
||||
return "player.yaml";
|
||||
}
|
||||
return skin_name + ".yaml";
|
||||
}
|
||||
|
||||
// Cambia la skin del jugador en caliente preservando la orientación actual
|
||||
void Player::setSkin(int skin_num) {
|
||||
void Player::setSkin(const std::string& skin_name) {
|
||||
const auto FLIP = sprite_->getFlip();
|
||||
const std::string PATH = (skin_num == 2) ? "player2.yaml" : "player.yaml";
|
||||
initSprite(PATH);
|
||||
initSprite(skinToAnimationPath(skin_name));
|
||||
sprite_->setFlip(FLIP);
|
||||
}
|
||||
|
||||
@@ -779,7 +792,7 @@ void Player::initSprite(const std::string& animations_path) { // NOLINT(readabi
|
||||
sprite_ = std::make_unique<AnimatedSprite>(animation_data);
|
||||
sprite_->setWidth(WIDTH);
|
||||
sprite_->setHeight(HEIGHT);
|
||||
sprite_->setCurrentAnimation("walk");
|
||||
sprite_->setCurrentAnimation("default");
|
||||
}
|
||||
|
||||
// Actualiza la posición del sprite y las colisiones
|
||||
|
||||
@@ -100,7 +100,8 @@ class Player {
|
||||
auto getCollider() -> SDL_FRect& { return collider_box_; } // Obtiene el rectangulo de colision del jugador
|
||||
auto getSpawnParams() -> SpawnData { return {.x = x_, .y = y_, .vx = vx_, .vy = vy_, .last_grounded_position = last_grounded_position_, .state = state_, .flip = sprite_->getFlip()}; } // Obtiene el estado de reaparición del jugador
|
||||
void setColor(Uint8 color = 0); // Establece el color del jugador (0 = automático según cheats)
|
||||
void setSkin(int skin_num); // Cambia la skin del jugador en caliente (1=normal, 2=alternativa)
|
||||
void setSkin(const std::string& skin_name); // Cambia la skin del jugador en caliente ("default" o nombre de enemigo)
|
||||
static auto skinToAnimationPath(const std::string& skin_name) -> std::string; // Resuelve nombre de skin a fichero de animación
|
||||
void setRoom(std::shared_ptr<Room> room) { room_ = std::move(room); } // Establece la habitación en la que se encuentra el jugador
|
||||
//[[nodiscard]] auto isAlive() const -> bool { return is_alive_ || (Options::cheats.invincible == Options::Cheat::State::ENABLED); } // Comprueba si el jugador esta vivo
|
||||
[[nodiscard]] auto isAlive() const -> bool { return is_alive_; } // Comprueba si el jugador esta vivo
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
#include <string>
|
||||
|
||||
namespace GameControl {
|
||||
// Disponible en todos los builds — refresca el color del jugador según cheats
|
||||
inline std::function<void()> refresh_player_color;
|
||||
// Disponible en todos los builds — cambia la skin del jugador (1=normal, 2=alternativa)
|
||||
inline std::function<void(int)> change_player_skin;
|
||||
// Disponible en todos los builds — cambia la skin del jugador ("default" o nombre de enemigo)
|
||||
inline std::function<void(const std::string&)> change_player_skin;
|
||||
// Disponible en todos los builds — cambia el color del jugador (-1 = automático, 0-15 = color fijo)
|
||||
inline std::function<void(int)> change_player_color;
|
||||
} // namespace GameControl
|
||||
|
||||
#ifdef _DEBUG
|
||||
@@ -23,5 +23,11 @@ namespace GameControl {
|
||||
inline std::function<std::string()> set_initial_room;
|
||||
// Registrada por Game::Game() — guarda la posición/flip actuales del jugador como posición de inicio en debug.yaml
|
||||
inline std::function<std::string()> set_initial_pos;
|
||||
// Registradas por Game::Game() — control del editor de mapas
|
||||
inline std::function<void()> enter_editor;
|
||||
inline std::function<void()> exit_editor;
|
||||
inline std::function<std::string()> revert_editor;
|
||||
inline std::function<void()> reload_current_room; // Recarga la habitación actual desde disco
|
||||
inline std::function<std::string(const std::string&)> get_adjacent_room; // Obtiene la room adyacente (UP/DOWN/LEFT/RIGHT)
|
||||
} // namespace GameControl
|
||||
#endif
|
||||
|
||||
@@ -41,6 +41,33 @@ void EnemyManager::render() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Solo actualiza animaciones sin mover enemigos
|
||||
void EnemyManager::updateAnimations(float delta_time) {
|
||||
for (const auto& enemy : enemies_) {
|
||||
enemy->updateAnimation(delta_time);
|
||||
}
|
||||
}
|
||||
|
||||
// Resetea todos los enemigos a su posición inicial
|
||||
void EnemyManager::resetPositions(const std::vector<Enemy::Data>& enemy_data) {
|
||||
const int COUNT = std::min(static_cast<int>(enemies_.size()), static_cast<int>(enemy_data.size()));
|
||||
for (int i = 0; i < COUNT; ++i) {
|
||||
enemies_[i]->resetToInitialPosition(enemy_data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Número de enemigos
|
||||
auto EnemyManager::getCount() const -> int {
|
||||
return static_cast<int>(enemies_.size());
|
||||
}
|
||||
|
||||
// Acceso a un enemigo por índice
|
||||
auto EnemyManager::getEnemy(int index) -> std::shared_ptr<Enemy>& {
|
||||
return enemies_.at(index);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Comprueba si hay colisión con algún enemigo
|
||||
auto EnemyManager::checkCollision(SDL_FRect& rect) -> bool {
|
||||
return std::ranges::any_of(enemies_, [&rect](const auto& enemy) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <vector> // Para vector
|
||||
|
||||
class Enemy;
|
||||
#include "game/entities/enemy.hpp" // Para Enemy, Enemy::Data
|
||||
|
||||
/**
|
||||
* @brief Gestor de enemigos de una habitación
|
||||
@@ -40,6 +40,13 @@ class EnemyManager {
|
||||
// Detección de colisiones
|
||||
auto checkCollision(SDL_FRect& rect) -> bool; // Comprueba si hay colisión con algún enemigo
|
||||
|
||||
#ifdef _DEBUG
|
||||
void updateAnimations(float delta_time); // Solo actualiza animaciones sin mover enemigos
|
||||
void resetPositions(const std::vector<Enemy::Data>& enemy_data); // Resetea todos los enemigos a su posición inicial
|
||||
[[nodiscard]] auto getCount() const -> int; // Número de enemigos
|
||||
auto getEnemy(int index) -> std::shared_ptr<Enemy>&; // Acceso a un enemigo por índice
|
||||
#endif
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<Enemy>> enemies_; // Colección de enemigos
|
||||
};
|
||||
|
||||
@@ -6,10 +6,9 @@
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "game/entities/item.hpp" // Para Item, Item::Data
|
||||
#include "scoreboard.hpp" // Para Scoreboard::Data
|
||||
|
||||
class Item;
|
||||
|
||||
/**
|
||||
* @brief Gestor de items de una habitación
|
||||
*
|
||||
@@ -47,6 +46,11 @@ class ItemManager {
|
||||
// Estado
|
||||
void setPaused(bool paused); // Pausa/despausa todos los items
|
||||
|
||||
#ifdef _DEBUG
|
||||
[[nodiscard]] auto getCount() const -> int { return static_cast<int>(items_.size()); } // Número de items
|
||||
auto getItem(int index) -> std::shared_ptr<Item>& { return items_.at(index); } // Acceso a un item por índice
|
||||
#endif
|
||||
|
||||
// Detección de colisiones
|
||||
/**
|
||||
* @brief Comprueba si hay colisión con algún item
|
||||
|
||||
@@ -121,6 +121,18 @@ void Room::renderItems() {
|
||||
void Room::redrawMap() {
|
||||
tilemap_renderer_->redrawMap(collision_map_.get());
|
||||
}
|
||||
|
||||
// Actualiza animaciones sin mover enemigos (para editor de mapas)
|
||||
void Room::updateEditorMode(float delta_time) {
|
||||
tilemap_renderer_->update(delta_time);
|
||||
enemy_manager_->updateAnimations(delta_time);
|
||||
item_manager_->update(delta_time);
|
||||
}
|
||||
|
||||
// Resetea enemigos a posiciones iniciales (para editor de mapas)
|
||||
void Room::resetEnemyPositions(const std::vector<Enemy::Data>& enemy_data) {
|
||||
enemy_manager_->resetPositions(enemy_data);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Actualiza las variables y objetos de la habitación
|
||||
|
||||
@@ -70,6 +70,10 @@ class Room {
|
||||
void renderItems(); // Dibuja los objetos en pantalla
|
||||
#ifdef _DEBUG
|
||||
void redrawMap(); // Redibuja el mapa (para actualizar modo debug)
|
||||
void updateEditorMode(float delta_time); // Actualiza animaciones sin mover enemigos (para editor)
|
||||
void resetEnemyPositions(const std::vector<Enemy::Data>& enemy_data); // Resetea enemigos a posiciones iniciales
|
||||
auto getEnemyManager() -> EnemyManager* { return enemy_manager_.get(); } // Acceso al gestor de enemigos (para editor)
|
||||
auto getItemManager() -> ItemManager* { return item_manager_.get(); } // Acceso al gestor de items (para editor)
|
||||
#endif
|
||||
void update(float delta_time); // Actualiza las variables y objetos de la habitación
|
||||
auto getRoom(Border border) -> std::string; // Devuelve la cadena del fichero de la habitación contigua segun el borde
|
||||
|
||||
@@ -316,6 +316,25 @@ void RoomLoader::parseItems(const fkyaml::node& yaml, Room::Data& room, bool ver
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Carga una habitación desde un string YAML (para el editor de mapas, evita el resource pack)
|
||||
auto RoomLoader::loadFromString(const std::string& yaml_content, const std::string& file_name) -> Room::Data {
|
||||
Room::Data room;
|
||||
try {
|
||||
auto yaml = fkyaml::node::deserialize(yaml_content);
|
||||
parseRoomConfig(yaml, room, file_name);
|
||||
parseTilemap(yaml, room, file_name, false);
|
||||
parseEnemies(yaml, room, false);
|
||||
parseItems(yaml, room, false);
|
||||
} catch (const fkyaml::exception& e) {
|
||||
std::cerr << "YAML parsing error in " << file_name << ": " << e.what() << '\n';
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error loading room " << file_name << ": " << e.what() << '\n';
|
||||
}
|
||||
return room;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Carga un archivo de room en formato YAML
|
||||
auto RoomLoader::loadYAML(const std::string& file_path, bool verbose) -> Room::Data { // NOLINT(readability-convert-member-functions-to-static)
|
||||
Room::Data room;
|
||||
|
||||
@@ -46,6 +46,9 @@ class RoomLoader {
|
||||
* - items: lista de items (opcional)
|
||||
*/
|
||||
static auto loadYAML(const std::string& file_path, bool verbose = false) -> Room::Data;
|
||||
#ifdef _DEBUG
|
||||
static auto loadFromString(const std::string& yaml_content, const std::string& file_name) -> Room::Data;
|
||||
#endif
|
||||
|
||||
private:
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
#include "core/rendering/text.hpp" // Para Text
|
||||
#include "core/resources/resource_cache.hpp" // Para Resource
|
||||
#include "game/entities/player.hpp" // Para Player::skinToAnimationPath
|
||||
#include "game/options.hpp" // Para Options, options, Cheat, OptionsGame
|
||||
#include "utils/defines.hpp" // Para BLOCK
|
||||
#include "utils/utils.hpp" // Para stringToColor
|
||||
@@ -22,9 +23,10 @@ Scoreboard::Scoreboard(std::shared_ptr<Data> data)
|
||||
constexpr float SURFACE_HEIGHT = 6.0F * Tile::SIZE;
|
||||
|
||||
// Reserva memoria para los objetos
|
||||
const auto& player_animation_data = Resource::Cache::get()->getAnimationData((Options::game.player_skin == 2) ? "player2.yaml" : "player.yaml");
|
||||
const std::string player_anim_path = Player::skinToAnimationPath(Options::game.player_skin);
|
||||
const auto& player_animation_data = Resource::Cache::get()->getAnimationData(player_anim_path);
|
||||
player_sprite_ = std::make_shared<AnimatedSprite>(player_animation_data);
|
||||
player_sprite_->setCurrentAnimation("walk_menu");
|
||||
player_sprite_->setCurrentAnimation("default");
|
||||
|
||||
surface_ = std::make_shared<Surface>(SURFACE_WIDTH, SURFACE_HEIGHT);
|
||||
surface_dest_ = {.x = 0, .y = Options::game.height - SURFACE_HEIGHT, .w = SURFACE_WIDTH, .h = SURFACE_HEIGHT};
|
||||
@@ -49,9 +51,6 @@ void Scoreboard::update(float delta_time) {
|
||||
// Acumular tiempo para animaciones
|
||||
time_accumulator_ += delta_time;
|
||||
|
||||
// Actualizar sprite con delta time
|
||||
player_sprite_->update(delta_time);
|
||||
|
||||
// Actualiza el color de la cantidad de items recogidos
|
||||
updateItemsColor(delta_time);
|
||||
|
||||
@@ -77,6 +76,14 @@ auto Scoreboard::getTime() -> Scoreboard::ClockData { // NOLINT(readability-con
|
||||
return time;
|
||||
}
|
||||
|
||||
// Actualiza el sprite del jugador con la skin actual
|
||||
void Scoreboard::refreshPlayerSkin() {
|
||||
const std::string player_anim_path = Player::skinToAnimationPath(Options::game.player_skin);
|
||||
const auto& player_animation_data = Resource::Cache::get()->getAnimationData(player_anim_path);
|
||||
player_sprite_ = std::make_shared<AnimatedSprite>(player_animation_data);
|
||||
player_sprite_->setCurrentAnimation("default");
|
||||
}
|
||||
|
||||
// Pone el marcador en modo pausa
|
||||
void Scoreboard::setPaused(bool value) {
|
||||
if (is_paused_ == value) {
|
||||
@@ -130,18 +137,14 @@ void Scoreboard::fillTexture() {
|
||||
// Limpia la textura
|
||||
surface_->clear(stringToColor("black"));
|
||||
|
||||
// Anclas
|
||||
constexpr int LINE1 = Tile::SIZE;
|
||||
constexpr int LINE2 = 3 * Tile::SIZE;
|
||||
|
||||
// Dibuja las vidas
|
||||
// Calcular desplazamiento basado en tiempo
|
||||
const int DESP = static_cast<int>(time_accumulator_ / SPRITE_WALK_CYCLE_DURATION) % 8;
|
||||
const int FRAME = DESP % SPRITE_WALK_FRAMES;
|
||||
const int WALK_FRAMES = player_sprite_->getCurrentAnimationSize();
|
||||
const int DESP = static_cast<int>(time_accumulator_ / SPRITE_WALK_CYCLE_DURATION) % (WALK_FRAMES * 2);
|
||||
const int FRAME = DESP % WALK_FRAMES;
|
||||
player_sprite_->setCurrentAnimationFrame(FRAME);
|
||||
player_sprite_->setPosY(LINE2);
|
||||
player_sprite_->setPosY(LINE2_Y);
|
||||
for (int i = 0; i < data_->lives; ++i) {
|
||||
player_sprite_->setPosX(8 + (16 * i) + DESP);
|
||||
player_sprite_->setPosX(LIVES_START_X + (LIVES_SPACING * i) + DESP);
|
||||
const int INDEX = i % color_.size();
|
||||
player_sprite_->render(1, color_.at(INDEX));
|
||||
}
|
||||
@@ -150,21 +153,30 @@ void Scoreboard::fillTexture() {
|
||||
if (data_->music) {
|
||||
const Uint8 C = data_->color;
|
||||
SDL_FRect clip = {.x = 0, .y = 8, .w = 8, .h = 8};
|
||||
item_surface_->renderWithColorReplace(20 * Tile::SIZE, LINE2, 1, C, &clip);
|
||||
item_surface_->renderWithColorReplace(MUSIC_ICON_X, LINE2_Y, 1, C, &clip);
|
||||
}
|
||||
|
||||
// Escribe los textos
|
||||
auto text = Resource::Cache::get()->getText("smb2");
|
||||
const std::string TIME_TEXT = std::to_string((clock_.minutes % 100) / 10) + std::to_string(clock_.minutes % 10) + clock_.separator + std::to_string((clock_.seconds % 60) / 10) + std::to_string(clock_.seconds % 10);
|
||||
const std::string ITEMS_TEXT = std::to_string(data_->items / 100) + std::to_string((data_->items % 100) / 10) + std::to_string(data_->items % 10);
|
||||
text->writeColored(Tile::SIZE, LINE1, Locale::get()->get("scoreboard.items"), data_->color); // NOLINT(readability-static-accessed-through-instance)
|
||||
text->writeColored(17 * Tile::SIZE, LINE1, ITEMS_TEXT, items_color_);
|
||||
text->writeColored(20 * Tile::SIZE, LINE1, Locale::get()->get("scoreboard.time"), data_->color); // NOLINT(readability-static-accessed-through-instance)
|
||||
text->writeColored(26 * Tile::SIZE, LINE1, TIME_TEXT, stringToColor("white"));
|
||||
text->writeColored(ITEMS_LABEL_X, LINE1_Y, Locale::get()->get("scoreboard.items"), data_->color); // NOLINT(readability-static-accessed-through-instance)
|
||||
text->writeColored(ITEMS_VALUE_X, LINE1_Y, ITEMS_TEXT, items_color_);
|
||||
text->writeColored(TIME_LABEL_X, LINE1_Y, Locale::get()->get("scoreboard.time"), data_->color); // NOLINT(readability-static-accessed-through-instance)
|
||||
text->writeColored(TIME_VALUE_X, LINE1_Y, TIME_TEXT, stringToColor("white"));
|
||||
|
||||
const std::string ROOMS_TEXT = std::to_string(data_->rooms / 100) + std::to_string((data_->rooms % 100) / 10) + std::to_string(data_->rooms % 10);
|
||||
text->writeColored(22 * Tile::SIZE, LINE2, Locale::get()->get("scoreboard.rooms"), stringToColor("white")); // NOLINT(readability-static-accessed-through-instance)
|
||||
text->writeColored(28 * Tile::SIZE, LINE2, ROOMS_TEXT, stringToColor("white"));
|
||||
text->writeColored(ROOMS_LABEL_X, LINE2_Y, Locale::get()->get("scoreboard.rooms"), stringToColor("white")); // NOLINT(readability-static-accessed-through-instance)
|
||||
text->writeColored(ROOMS_VALUE_X, LINE2_Y, ROOMS_TEXT, stringToColor("white"));
|
||||
|
||||
// Indicadores de trucos activos (fuente 8bithud)
|
||||
auto cheat_text = Resource::Cache::get()->getText("8bithud");
|
||||
if (Options::cheats.infinite_lives == Options::Cheat::State::ENABLED) {
|
||||
cheat_text->writeColored(CHEAT_INF_LIVES_X, CHEAT_INF_LIVES_Y, Locale::get()->get("scoreboard.cheat_infinite_lives"), data_->color); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
if (Options::cheats.invincible == Options::Cheat::State::ENABLED) {
|
||||
cheat_text->writeColored(CHEAT_INVINCIBLE_X, CHEAT_INVINCIBLE_Y, Locale::get()->get("scoreboard.cheat_invincibility"), data_->color); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
|
||||
// Deja el renderizador como estaba
|
||||
Screen::get()->setRendererSurface(previuos_renderer);
|
||||
|
||||
@@ -28,6 +28,7 @@ class Scoreboard {
|
||||
void render(); // Pinta el objeto en pantalla
|
||||
void update(float delta_time); // Actualiza las variables del objeto
|
||||
void setPaused(bool value); // Pone el marcador en modo pausa
|
||||
void refreshPlayerSkin(); // Actualiza el sprite del jugador con la skin actual
|
||||
auto getMinutes() -> int; // Devuelve la cantidad de minutos de juego transcurridos
|
||||
|
||||
private:
|
||||
@@ -42,7 +43,23 @@ class Scoreboard {
|
||||
// Constantes de tiempo
|
||||
static constexpr float ITEMS_COLOR_BLINK_DURATION = 0.333F; // Duración de cada estado del parpadeo (era 10 frames @ 60fps)
|
||||
static constexpr float SPRITE_WALK_CYCLE_DURATION = 0.667F; // Duración del ciclo de caminar (era 40 frames @ 60fps)
|
||||
static constexpr int SPRITE_WALK_FRAMES = 4; // Número de frames de animación
|
||||
|
||||
// Posición de los elementos del marcador (en pixels, Tile::SIZE = 8)
|
||||
static constexpr int LINE1_Y = 8; // Fila superior (1 * Tile::SIZE)
|
||||
static constexpr int LINE2_Y = 24; // Fila inferior (3 * Tile::SIZE)
|
||||
static constexpr int ITEMS_LABEL_X = 8; // "TRESORS PILLATS" / "ITEMS COLLECTED"
|
||||
static constexpr int ITEMS_VALUE_X = 136; // Valor numérico de items
|
||||
static constexpr int TIME_LABEL_X = 160; // "HORA" / "TIME"
|
||||
static constexpr int TIME_VALUE_X = 208; // Valor numérico del tiempo
|
||||
static constexpr int LIVES_START_X = 8; // Primera vida (sprite)
|
||||
static constexpr int LIVES_SPACING = 16; // Separación entre vidas
|
||||
static constexpr int MUSIC_ICON_X = 160; // Icono de música
|
||||
static constexpr int ROOMS_LABEL_X = 176; // "SALES" / "ROOMS"
|
||||
static constexpr int ROOMS_VALUE_X = 224; // Valor numérico de salas
|
||||
static constexpr int CHEAT_INF_LIVES_X = 176; // Indicador "vides inf" / "inf lives"
|
||||
static constexpr int CHEAT_INF_LIVES_Y = 34; // Posición Y del indicador de vidas infinitas
|
||||
static constexpr int CHEAT_INVINCIBLE_X = 231; // Indicador "inv"
|
||||
static constexpr int CHEAT_INVINCIBLE_Y = 34; // Posición Y del indicador de invencibilidad
|
||||
|
||||
// Métodos privados
|
||||
auto getTime() -> ClockData; // Obtiene el tiempo transcurrido de partida
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "core/input/input_types.hpp" // Para BUTTON_TO_STRING, STRING_TO_BUTTON
|
||||
#include "external/fkyaml_node.hpp" // Para fkyaml::node
|
||||
#include "game/defaults.hpp" // Para GameDefaults::VERSION
|
||||
#include "utils/utils.hpp" // Para toLower
|
||||
|
||||
namespace Options {
|
||||
|
||||
@@ -177,24 +178,6 @@ namespace Options {
|
||||
{"LEFT_STICK_LEFT", 200},
|
||||
{"LEFT_STICK_RIGHT", 201}};
|
||||
|
||||
// Lista de paletas válidas
|
||||
const std::vector<std::string> VALID_PALETTES = {
|
||||
"black-and-white",
|
||||
"green-phosphor",
|
||||
"island-joy-16",
|
||||
"lost-century",
|
||||
"na16",
|
||||
"orange-screen",
|
||||
"pico-8",
|
||||
"ruzx-spectrum",
|
||||
"ruzx-spectrum-revision-2",
|
||||
"steam-lords",
|
||||
"sweetie-16",
|
||||
"sweetie-16_bona",
|
||||
"zx-spectrum",
|
||||
"zx-spectrum-adjusted",
|
||||
"zxarne-5-2"};
|
||||
|
||||
// Funciones helper de conversión
|
||||
auto filterToString(Screen::Filter filter) -> std::string {
|
||||
auto it = FILTER_TO_STRING.find(filter);
|
||||
@@ -257,12 +240,6 @@ namespace Options {
|
||||
}
|
||||
}
|
||||
|
||||
auto isValidPalette(const std::string& palette) -> bool {
|
||||
std::string lower_palette = palette;
|
||||
std::ranges::transform(lower_palette, lower_palette.begin(), ::tolower);
|
||||
return std::ranges::any_of(VALID_PALETTES, [&lower_palette](const auto& valid) { return valid == lower_palette; });
|
||||
}
|
||||
|
||||
// --- Funciones helper para loadFromFile() ---
|
||||
|
||||
// Carga configuración de ventana desde YAML
|
||||
@@ -309,20 +286,99 @@ namespace Options {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: carga el campo palette con validación extra
|
||||
// Helper: carga el campo palette; PaletteManager hará el fallback si el nombre no existe
|
||||
void loadPaletteFromYaml(const fkyaml::node& vid) {
|
||||
if (!vid.contains("palette")) { return; }
|
||||
if (vid.contains("palette")) {
|
||||
try {
|
||||
auto palette_str = vid["palette"].get_value<std::string>();
|
||||
video.palette = isValidPalette(palette_str) ? palette_str : Defaults::Video::PALETTE_NAME;
|
||||
video.palette = toLower(palette_str);
|
||||
} catch (...) {
|
||||
video.palette = Defaults::Video::PALETTE_NAME;
|
||||
}
|
||||
}
|
||||
if (vid.contains("palette_sort")) {
|
||||
try {
|
||||
video.palette_sort = toLower(vid["palette_sort"].get_value<std::string>());
|
||||
} catch (...) {
|
||||
video.palette_sort = Defaults::Video::PALETTE_SORT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Carga los campos básicos de configuración de video
|
||||
// Helper: carga la sección gpu desde YAML
|
||||
void loadGPUConfigFromYaml(const fkyaml::node& gpu_node) {
|
||||
if (gpu_node.contains("acceleration")) {
|
||||
try {
|
||||
video.gpu.acceleration = gpu_node["acceleration"].get_value<bool>();
|
||||
} catch (...) {
|
||||
video.gpu.acceleration = Defaults::Video::GPU_ACCELERATION;
|
||||
}
|
||||
}
|
||||
if (gpu_node.contains("preferred_driver")) {
|
||||
try {
|
||||
video.gpu.preferred_driver = gpu_node["preferred_driver"].get_value<std::string>();
|
||||
} catch (...) {
|
||||
video.gpu.preferred_driver = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: carga la sección supersampling desde YAML
|
||||
void loadSupersamplingConfigFromYaml(const fkyaml::node& ss_node) {
|
||||
if (ss_node.contains("enabled")) {
|
||||
try {
|
||||
video.supersampling.enabled = ss_node["enabled"].get_value<bool>();
|
||||
} catch (...) {
|
||||
video.supersampling.enabled = Defaults::Video::SUPERSAMPLING;
|
||||
}
|
||||
}
|
||||
if (ss_node.contains("linear_upscale")) {
|
||||
try {
|
||||
video.supersampling.linear_upscale = ss_node["linear_upscale"].get_value<bool>();
|
||||
} catch (...) {
|
||||
video.supersampling.linear_upscale = Defaults::Video::LINEAR_UPSCALE;
|
||||
}
|
||||
}
|
||||
if (ss_node.contains("downscale_algo")) {
|
||||
try {
|
||||
video.supersampling.downscale_algo = ss_node["downscale_algo"].get_value<int>();
|
||||
} catch (...) {
|
||||
video.supersampling.downscale_algo = Defaults::Video::DOWNSCALE_ALGO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: carga la sección shader desde YAML
|
||||
void loadShaderConfigFromYaml(const fkyaml::node& sh_node) {
|
||||
if (sh_node.contains("enabled")) {
|
||||
try {
|
||||
video.shader.enabled = sh_node["enabled"].get_value<bool>();
|
||||
} catch (...) {
|
||||
video.shader.enabled = Defaults::Video::SHADER_ENABLED;
|
||||
}
|
||||
}
|
||||
if (sh_node.contains("current_shader")) {
|
||||
try {
|
||||
const std::string s = sh_node["current_shader"].get_value<std::string>();
|
||||
video.shader.current_shader = (s == "crtpi") ? Rendering::ShaderType::CRTPI : Rendering::ShaderType::POSTFX;
|
||||
} catch (...) {
|
||||
video.shader.current_shader = Rendering::ShaderType::POSTFX;
|
||||
}
|
||||
}
|
||||
if (sh_node.contains("current_postfx_preset")) {
|
||||
try {
|
||||
video.shader.current_postfx_preset_name = sh_node["current_postfx_preset"].get_value<std::string>();
|
||||
} catch (...) {}
|
||||
}
|
||||
if (sh_node.contains("current_crtpi_preset")) {
|
||||
try {
|
||||
video.shader.current_crtpi_preset_name = sh_node["current_crtpi_preset"].get_value<std::string>();
|
||||
} catch (...) {}
|
||||
}
|
||||
}
|
||||
|
||||
// Carga los campos básicos de configuración de video (nivel video:)
|
||||
void loadBasicVideoFieldsFromYaml(const fkyaml::node& vid) {
|
||||
// fullscreen (antes era "mode")
|
||||
if (vid.contains("fullscreen")) {
|
||||
try {
|
||||
video.fullscreen = vid["fullscreen"].get_value<bool>();
|
||||
@@ -331,7 +387,6 @@ namespace Options {
|
||||
}
|
||||
}
|
||||
|
||||
// filter (ahora es string)
|
||||
if (vid.contains("filter")) {
|
||||
try {
|
||||
auto filter_str = vid["filter"].get_value<std::string>();
|
||||
@@ -341,47 +396,6 @@ namespace Options {
|
||||
}
|
||||
}
|
||||
|
||||
if (vid.contains("postfx")) {
|
||||
try {
|
||||
video.postfx = vid["postfx"].get_value<bool>();
|
||||
} catch (...) {
|
||||
video.postfx = Defaults::Video::POSTFX;
|
||||
}
|
||||
}
|
||||
|
||||
if (vid.contains("supersampling")) {
|
||||
try {
|
||||
video.supersampling = vid["supersampling"].get_value<bool>();
|
||||
} catch (...) {
|
||||
video.supersampling = Defaults::Video::SUPERSAMPLING;
|
||||
}
|
||||
}
|
||||
|
||||
if (vid.contains("current_postfx_preset")) {
|
||||
try {
|
||||
current_postfx_preset = vid["current_postfx_preset"].get_value<int>();
|
||||
} catch (...) {
|
||||
current_postfx_preset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (vid.contains("current_crtpi_preset")) {
|
||||
try {
|
||||
current_crtpi_preset = vid["current_crtpi_preset"].get_value<int>();
|
||||
} catch (...) {
|
||||
current_crtpi_preset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (vid.contains("current_shader")) {
|
||||
try {
|
||||
const std::string s = vid["current_shader"].get_value<std::string>();
|
||||
current_shader = (s == "crtpi") ? Rendering::ShaderType::CRTPI : Rendering::ShaderType::POSTFX;
|
||||
} catch (...) {
|
||||
current_shader = Rendering::ShaderType::POSTFX;
|
||||
}
|
||||
}
|
||||
|
||||
if (vid.contains("vertical_sync")) {
|
||||
try {
|
||||
video.vertical_sync = vid["vertical_sync"].get_value<bool>();
|
||||
@@ -406,30 +420,6 @@ namespace Options {
|
||||
}
|
||||
}
|
||||
|
||||
if (vid.contains("linear_upscale")) {
|
||||
try {
|
||||
video.linear_upscale = vid["linear_upscale"].get_value<bool>();
|
||||
} catch (...) {
|
||||
video.linear_upscale = Defaults::Video::LINEAR_UPSCALE;
|
||||
}
|
||||
}
|
||||
|
||||
if (vid.contains("downscale_algo")) {
|
||||
try {
|
||||
video.downscale_algo = vid["downscale_algo"].get_value<int>();
|
||||
} catch (...) {
|
||||
video.downscale_algo = Defaults::Video::DOWNSCALE_ALGO;
|
||||
}
|
||||
}
|
||||
|
||||
if (vid.contains("gpu_preferred_driver")) {
|
||||
try {
|
||||
video.gpu_preferred_driver = vid["gpu_preferred_driver"].get_value<std::string>();
|
||||
} catch (...) {
|
||||
video.gpu_preferred_driver = "";
|
||||
}
|
||||
}
|
||||
|
||||
loadPaletteFromYaml(vid);
|
||||
}
|
||||
|
||||
@@ -439,10 +429,18 @@ namespace Options {
|
||||
const auto& vid = yaml["video"];
|
||||
loadBasicVideoFieldsFromYaml(vid);
|
||||
|
||||
// Lee border
|
||||
if (vid.contains("border")) {
|
||||
loadBorderConfigFromYaml(vid["border"]);
|
||||
}
|
||||
if (vid.contains("gpu")) {
|
||||
loadGPUConfigFromYaml(vid["gpu"]);
|
||||
}
|
||||
if (vid.contains("supersampling")) {
|
||||
loadSupersamplingConfigFromYaml(vid["supersampling"]);
|
||||
}
|
||||
if (vid.contains("shader")) {
|
||||
loadShaderConfigFromYaml(vid["shader"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,12 +518,19 @@ namespace Options {
|
||||
const auto& player_node = yaml["player"];
|
||||
if (player_node.contains("skin")) {
|
||||
try {
|
||||
int skin = player_node["skin"].get_value<int>();
|
||||
game.player_skin = (skin == 2) ? 2 : Defaults::Game::Player::SKIN;
|
||||
game.player_skin = player_node["skin"].get_value<std::string>();
|
||||
} catch (...) {
|
||||
game.player_skin = Defaults::Game::Player::SKIN;
|
||||
}
|
||||
}
|
||||
if (player_node.contains("color")) {
|
||||
try {
|
||||
int color = player_node["color"].get_value<int>();
|
||||
game.player_color = (color >= 0 && color <= 15) ? color : Defaults::Game::Player::COLOR;
|
||||
} catch (...) {
|
||||
game.player_color = Defaults::Game::Player::COLOR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -681,6 +686,25 @@ namespace Options {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: retorna el nombre del preset PostFX actual (para guardar en config)
|
||||
auto currentPostFXPresetName() -> std::string {
|
||||
const auto idx = static_cast<size_t>(video.shader.current_postfx_preset);
|
||||
if (idx < postfx_presets.size()) {
|
||||
return postfx_presets[idx].name;
|
||||
}
|
||||
// Presets no cargados aún: devolver el nombre almacenado del config
|
||||
return video.shader.current_postfx_preset_name;
|
||||
}
|
||||
|
||||
// Helper: retorna el nombre del preset CrtPi actual (para guardar en config)
|
||||
auto currentCrtPiPresetName() -> std::string {
|
||||
const auto idx = static_cast<size_t>(video.shader.current_crtpi_preset);
|
||||
if (idx < crtpi_presets.size()) {
|
||||
return crtpi_presets[idx].name;
|
||||
}
|
||||
return video.shader.current_crtpi_preset_name;
|
||||
}
|
||||
|
||||
// Guarda las opciones al fichero configurado
|
||||
auto saveToFile() -> bool {
|
||||
// Abre el fichero para escritura
|
||||
@@ -727,23 +751,28 @@ namespace Options {
|
||||
file << "# VIDEO \n";
|
||||
file << "video:\n";
|
||||
file << " fullscreen: " << (video.fullscreen ? "true" : "false") << "\n";
|
||||
file << " filter: " << filterToString(video.filter) << " # filter: nearest (pixel perfect) | linear (smooth)\n";
|
||||
file << " postfx: " << (video.postfx ? "true" : "false") << "\n";
|
||||
file << " supersampling: " << (video.supersampling ? "true" : "false") << "\n";
|
||||
file << " current_postfx_preset: " << current_postfx_preset << "\n";
|
||||
file << " current_crtpi_preset: " << current_crtpi_preset << "\n";
|
||||
file << " current_shader: " << (current_shader == Rendering::ShaderType::CRTPI ? "crtpi" : "postfx") << "\n";
|
||||
file << " vertical_sync: " << (video.vertical_sync ? "true" : "false") << "\n";
|
||||
file << " integer_scale: " << (video.integer_scale ? "true" : "false") << "\n";
|
||||
file << " keep_aspect: " << (video.keep_aspect ? "true" : "false") << "\n";
|
||||
file << " linear_upscale: " << (video.linear_upscale ? "true" : "false") << "\n";
|
||||
file << " downscale_algo: " << video.downscale_algo << " # 0=bilinear, 1=Lanczos2, 2=Lanczos3\n";
|
||||
file << " gpu_preferred_driver: \"" << video.gpu_preferred_driver << "\" # GPU driver (empty = auto)\n";
|
||||
file << " filter: " << filterToString(video.filter) << " # filter: nearest (pixel perfect) | linear (smooth)\n";
|
||||
file << " palette: " << video.palette << "\n";
|
||||
file << " palette_sort: " << video.palette_sort << "\n";
|
||||
file << " border:\n";
|
||||
file << " enabled: " << (video.border.enabled ? "true" : "false") << "\n";
|
||||
file << " width: " << video.border.width << "\n";
|
||||
file << " height: " << video.border.height << "\n";
|
||||
file << " gpu:\n";
|
||||
file << " acceleration: " << (video.gpu.acceleration ? "true" : "false") << " # Usar aceleración hardware GPU (false = SDL fallback)\n";
|
||||
file << " preferred_driver: \"" << video.gpu.preferred_driver << "\" # Driver GPU específico (empty = auto, aplica solo si gpu_acceleration: true)\n";
|
||||
file << " supersampling:\n";
|
||||
file << " enabled: " << (video.supersampling.enabled ? "true" : "false") << "\n";
|
||||
file << " linear_upscale: " << (video.supersampling.linear_upscale ? "true" : "false") << "\n";
|
||||
file << " downscale_algo: " << video.supersampling.downscale_algo << " # 0=bilinear, 1=Lanczos2, 2=Lanczos3\n";
|
||||
file << " shader:\n";
|
||||
file << " enabled: " << (video.shader.enabled ? "true" : "false") << "\n";
|
||||
file << " current_shader: " << (video.shader.current_shader == Rendering::ShaderType::CRTPI ? "crtpi" : "postfx") << "\n";
|
||||
file << " current_postfx_preset: " << currentPostFXPresetName() << "\n";
|
||||
file << " current_crtpi_preset: " << currentCrtPiPresetName() << "\n";
|
||||
file << "\n";
|
||||
|
||||
// KEYBOARD CONTROLS
|
||||
@@ -765,7 +794,8 @@ namespace Options {
|
||||
file << "\n";
|
||||
file << "# PLAYER\n";
|
||||
file << "player:\n";
|
||||
file << " skin: " << game.player_skin << "\n";
|
||||
file << " skin: \"" << game.player_skin << "\"\n";
|
||||
file << " color: " << game.player_color << "\n";
|
||||
file << "\n";
|
||||
|
||||
file << "# KIOSK MODE\n";
|
||||
@@ -787,6 +817,40 @@ namespace Options {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Resuelve el nombre del preset PostFX a índice dentro del vector de presets
|
||||
void resolvePostFXPresetName() {
|
||||
const auto& name = video.shader.current_postfx_preset_name;
|
||||
if (name.empty()) {
|
||||
video.shader.current_postfx_preset = 0;
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < static_cast<int>(postfx_presets.size()); ++i) {
|
||||
if (postfx_presets[static_cast<size_t>(i)].name == name) {
|
||||
video.shader.current_postfx_preset = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
std::cout << "PostFX preset '" << name << "' not found, defaulting to first preset\n";
|
||||
video.shader.current_postfx_preset = 0;
|
||||
}
|
||||
|
||||
// Resuelve el nombre del preset CrtPi a índice dentro del vector de presets
|
||||
void resolveCrtPiPresetName() {
|
||||
const auto& name = video.shader.current_crtpi_preset_name;
|
||||
if (name.empty()) {
|
||||
video.shader.current_crtpi_preset = 0;
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < static_cast<int>(crtpi_presets.size()); ++i) {
|
||||
if (crtpi_presets[static_cast<size_t>(i)].name == name) {
|
||||
video.shader.current_crtpi_preset = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
std::cout << "CrtPi preset '" << name << "' not found, defaulting to first preset\n";
|
||||
video.shader.current_crtpi_preset = 0;
|
||||
}
|
||||
|
||||
// Establece la ruta del fichero de PostFX
|
||||
void setPostFXFile(const std::string& path) {
|
||||
postfx_file_path = path;
|
||||
@@ -838,14 +902,11 @@ namespace Options {
|
||||
}
|
||||
}
|
||||
|
||||
// Preservar el índice cargado desde config.yaml; clampar al rango válido.
|
||||
// Resolver el nombre del preset a índice
|
||||
if (!postfx_presets.empty()) {
|
||||
current_postfx_preset = std::clamp(
|
||||
current_postfx_preset,
|
||||
0,
|
||||
static_cast<int>(postfx_presets.size()) - 1);
|
||||
resolvePostFXPresetName();
|
||||
} else {
|
||||
current_postfx_preset = 0;
|
||||
video.shader.current_postfx_preset = 0;
|
||||
}
|
||||
|
||||
std::cout << "PostFX file loaded: " << postfx_presets.size() << " preset(s)\n";
|
||||
@@ -950,7 +1011,7 @@ namespace Options {
|
||||
postfx_presets.push_back({"SCANLINES", 0.0F, 0.8F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F});
|
||||
postfx_presets.push_back({"SUBTLE", 0.3F, 0.4F, 0.05F, 0.0F, 0.3F, 0.0F, 0.0F, 0.0F});
|
||||
postfx_presets.push_back({"CRT LIVE", 0.5F, 0.6F, 0.3F, 0.3F, 0.4F, 0.3F, 0.4F, 0.8F});
|
||||
current_postfx_preset = 0;
|
||||
video.shader.current_postfx_preset = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -981,7 +1042,7 @@ namespace Options {
|
||||
crtpi_presets.push_back({"CURVED", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, true, false});
|
||||
crtpi_presets.push_back({"SHARP", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, false, true, false, true});
|
||||
crtpi_presets.push_back({"MINIMAL", 8.0F, 0.05F, 2.0F, 2.4F, 2.2F, 1.00F, 0.0F, 0.0F, 0, true, false, false, false, false});
|
||||
current_crtpi_preset = 0;
|
||||
video.shader.current_crtpi_preset = 0;
|
||||
return true;
|
||||
}
|
||||
out << "# JailDoctor's Dilemma - CrtPi Shader Presets\n";
|
||||
@@ -1062,7 +1123,7 @@ namespace Options {
|
||||
crtpi_presets.push_back({"CURVED", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, true, false});
|
||||
crtpi_presets.push_back({"SHARP", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, false, true, false, true});
|
||||
crtpi_presets.push_back({"MINIMAL", 8.0F, 0.05F, 2.0F, 2.4F, 2.2F, 1.00F, 0.0F, 0.0F, 0, true, false, false, false, false});
|
||||
current_crtpi_preset = 0;
|
||||
video.shader.current_crtpi_preset = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1121,13 +1182,11 @@ namespace Options {
|
||||
}
|
||||
}
|
||||
|
||||
// Resolver el nombre del preset a índice
|
||||
if (!crtpi_presets.empty()) {
|
||||
current_crtpi_preset = std::clamp(
|
||||
current_crtpi_preset,
|
||||
0,
|
||||
static_cast<int>(crtpi_presets.size()) - 1);
|
||||
resolveCrtPiPresetName();
|
||||
} else {
|
||||
current_crtpi_preset = 0;
|
||||
video.shader.current_crtpi_preset = 0;
|
||||
}
|
||||
|
||||
std::cout << "CrtPi file loaded: " << crtpi_presets.size() << " preset(s)\n";
|
||||
@@ -1138,7 +1197,7 @@ namespace Options {
|
||||
// Cargar defaults en memoria en caso de error
|
||||
crtpi_presets.clear();
|
||||
crtpi_presets.push_back({"DEFAULT", 6.0F, 0.12F, 3.5F, 2.4F, 2.2F, 0.80F, 0.05F, 0.10F, 2, true, true, true, false, false});
|
||||
current_crtpi_preset = 0;
|
||||
video.shader.current_crtpi_preset = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,21 +75,43 @@ namespace Options {
|
||||
float height{Defaults::Border::HEIGHT}; // Alto del borde
|
||||
};
|
||||
|
||||
// Estructura para las opciones de GPU
|
||||
struct GPU {
|
||||
bool acceleration{Defaults::Video::GPU_ACCELERATION}; // Usar aceleración GPU; false = path SDL fallback directo
|
||||
std::string preferred_driver; // Driver GPU preferido; vacío = auto. Aplica en el próximo arranque.
|
||||
};
|
||||
|
||||
// Estructura para las opciones de supersampling
|
||||
struct Supersampling {
|
||||
bool enabled{Defaults::Video::SUPERSAMPLING}; // Indica si el supersampling 3× está activo
|
||||
bool linear_upscale{Defaults::Video::LINEAR_UPSCALE}; // Upscale LINEAR (true) o NEAREST (false)
|
||||
int downscale_algo{Defaults::Video::DOWNSCALE_ALGO}; // 0=bilinear, 1=Lanczos2, 2=Lanczos3
|
||||
};
|
||||
|
||||
// Estructura para las opciones de shader (dentro de Video)
|
||||
struct ShaderConfig {
|
||||
bool enabled{Defaults::Video::SHADER_ENABLED}; // Indica si se usan shaders de post-procesado
|
||||
Rendering::ShaderType current_shader{Rendering::ShaderType::POSTFX}; // Shader de post-procesado activo
|
||||
std::string current_postfx_preset_name; // Nombre del preset PostFX leído del config
|
||||
std::string current_crtpi_preset_name; // Nombre del preset CrtPi leído del config
|
||||
int current_postfx_preset{0}; // Índice resuelto del preset PostFX
|
||||
int current_crtpi_preset{0}; // Índice resuelto del preset CrtPi
|
||||
};
|
||||
|
||||
// Estructura para las opciones de video
|
||||
struct Video {
|
||||
bool fullscreen{Defaults::Video::FULLSCREEN}; // Contiene el valor del modo de pantalla completa
|
||||
Screen::Filter filter{Defaults::Video::FILTER}; // Filtro usado para el escalado de la imagen
|
||||
bool vertical_sync{Defaults::Video::VERTICAL_SYNC}; // Indica si se quiere usar vsync o no
|
||||
bool postfx{Defaults::Video::POSTFX}; // Indica si se van a usar efectos PostFX o no
|
||||
bool supersampling{Defaults::Video::SUPERSAMPLING}; // Indica si el supersampling 3× está activo
|
||||
bool integer_scale{Defaults::Video::INTEGER_SCALE}; // Indica si el escalado de la imagen ha de ser entero en el modo a pantalla completa
|
||||
bool keep_aspect{Defaults::Video::KEEP_ASPECT}; // Indica si se ha de mantener la relación de aspecto al poner el modo a pantalla completa
|
||||
bool linear_upscale{Defaults::Video::LINEAR_UPSCALE}; // Upscale LINEAR (true) o NEAREST (false)
|
||||
int downscale_algo{Defaults::Video::DOWNSCALE_ALGO}; // 0=bilinear, 1=Lanczos2, 2=Lanczos3
|
||||
Border border{}; // Borde de la pantalla
|
||||
std::string palette{Defaults::Video::PALETTE_NAME}; // Paleta de colores a usar en el juego
|
||||
std::string palette_sort{Defaults::Video::PALETTE_SORT}; // Modo de ordenación de la paleta (original/luminance/spectrum)
|
||||
std::string info; // Información sobre el modo de vídeo
|
||||
std::string gpu_preferred_driver; // Driver GPU preferido; vacío = auto. Aplica en el próximo arranque.
|
||||
Border border{}; // Borde de la pantalla
|
||||
GPU gpu{}; // Opciones de aceleración GPU
|
||||
Supersampling supersampling{}; // Opciones de supersampling
|
||||
ShaderConfig shader{}; // Opciones de shader post-procesado
|
||||
};
|
||||
|
||||
// Estructura para las opciones de musica
|
||||
@@ -116,7 +138,8 @@ namespace Options {
|
||||
struct Game {
|
||||
float width{Defaults::Canvas::WIDTH}; // Ancho de la resolucion del juego
|
||||
float height{Defaults::Canvas::HEIGHT}; // Alto de la resolucion del juego
|
||||
int player_skin{Defaults::Game::Player::SKIN}; // Skin del jugador (1=normal, 2=alternativa)
|
||||
std::string player_skin{Defaults::Game::Player::SKIN}; // Skin del jugador ("default" o nombre de enemigo)
|
||||
int player_color{Defaults::Game::Player::COLOR}; // Color del jugador (-1 = automático, 0-15 = color fijo)
|
||||
};
|
||||
|
||||
// Estructura para un preset de PostFX
|
||||
@@ -171,17 +194,12 @@ namespace Options {
|
||||
|
||||
// --- Variables PostFX ---
|
||||
inline std::vector<PostFXPreset> postfx_presets{}; // Lista de presets de PostFX
|
||||
inline int current_postfx_preset{0}; // Índice del preset de PostFX actual
|
||||
inline std::string postfx_file_path{}; // Ruta del fichero postfx.yaml
|
||||
|
||||
// --- Variables CrtPi ---
|
||||
inline std::vector<CrtPiPreset> crtpi_presets{}; // Lista de presets del shader CRT-Pi
|
||||
inline int current_crtpi_preset{0}; // Índice del preset CRT-Pi actual
|
||||
inline std::string crtpi_file_path{}; // Ruta del fichero crtpi.yaml
|
||||
|
||||
// --- Shader activo ---
|
||||
inline Rendering::ShaderType current_shader{Rendering::ShaderType::POSTFX}; // Shader de post-procesado activo
|
||||
|
||||
// --- Funciones públicas ---
|
||||
void setConfigFile(const std::string& path); // Establece la ruta del fichero de configuración
|
||||
auto loadFromFile() -> bool; // Carga las opciones desde el fichero configurado
|
||||
@@ -192,4 +210,7 @@ namespace Options {
|
||||
void setCrtPiFile(const std::string& path); // Establece la ruta del fichero de CrtPi
|
||||
auto loadCrtPiFromFile() -> bool; // Carga los presets de CrtPi desde el fichero (crea defaults si no existe)
|
||||
|
||||
void resolvePostFXPresetName(); // Resuelve el nombre del preset PostFX a índice
|
||||
void resolveCrtPiPresetName(); // Resuelve el nombre del preset CrtPi a índice
|
||||
|
||||
} // namespace Options
|
||||
@@ -391,7 +391,7 @@ void Ending2::placeSprites() const {
|
||||
const float X = (Options::game.width - sprites_.back()->getWidth()) / 2;
|
||||
const float Y = sprites_.back()->getPosY() + (sprite_max_height_ * 2);
|
||||
sprites_.back()->setPos(X, Y);
|
||||
sprites_.back()->setCurrentAnimation("walk");
|
||||
sprites_.back()->setCurrentAnimation("default");
|
||||
}
|
||||
|
||||
// Crea los sprites con las texturas con los textos
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
|
||||
#ifdef _DEBUG
|
||||
#include "core/system/debug.hpp" // Para Debug
|
||||
#include "game/editor/map_editor.hpp" // Para MapEditor
|
||||
#endif
|
||||
|
||||
// Constructor
|
||||
@@ -64,12 +65,16 @@ Game::Game(Mode mode)
|
||||
Cheevos::get()->enable(!Options::cheats.enabled()); // Deshabilita los logros si hay trucos activados
|
||||
Cheevos::get()->clearUnobtainableState();
|
||||
|
||||
GameControl::refresh_player_color = [this]() -> void { player_->setColor(); };
|
||||
Console::get()->on_toggle = [this](bool open) { player_->setIgnoreInput(open); };
|
||||
if (Console::get()->isActive()) { player_->setIgnoreInput(true); }
|
||||
GameControl::change_player_skin = [this](int skin_num) -> void {
|
||||
Options::game.player_skin = skin_num;
|
||||
player_->setSkin(skin_num);
|
||||
GameControl::change_player_skin = [this](const std::string& skin_name) -> void {
|
||||
Options::game.player_skin = skin_name;
|
||||
player_->setSkin(skin_name);
|
||||
scoreboard_->refreshPlayerSkin();
|
||||
};
|
||||
GameControl::change_player_color = [this](int color) -> void {
|
||||
Options::game.player_color = color;
|
||||
player_->setColor();
|
||||
};
|
||||
|
||||
#ifdef _DEBUG
|
||||
@@ -80,9 +85,17 @@ Game::Game(Mode mode)
|
||||
Options::stats.items = count;
|
||||
};
|
||||
GameControl::toggle_debug_mode = [this]() -> void {
|
||||
const bool ENTERING_DEBUG = !Debug::get()->isEnabled();
|
||||
if (ENTERING_DEBUG) {
|
||||
invincible_before_debug_ = (Options::cheats.invincible == Options::Cheat::State::ENABLED);
|
||||
}
|
||||
Debug::get()->toggleEnabled();
|
||||
room_->redrawMap();
|
||||
Options::cheats.invincible = static_cast<Options::Cheat::State>(Debug::get()->isEnabled());
|
||||
if (Debug::get()->isEnabled()) {
|
||||
Options::cheats.invincible = Options::Cheat::State::ENABLED;
|
||||
} else {
|
||||
Options::cheats.invincible = invincible_before_debug_ ? Options::Cheat::State::ENABLED : Options::Cheat::State::DISABLED;
|
||||
}
|
||||
player_->setColor();
|
||||
scoreboard_data_->music = !Debug::get()->isEnabled();
|
||||
scoreboard_data_->music ? Audio::get()->resumeMusic() : Audio::get()->pauseMusic();
|
||||
@@ -107,6 +120,26 @@ Game::Game(Mode mode)
|
||||
Debug::get()->saveToFile();
|
||||
return "Pos:" + std::to_string(tile_x) + "," + std::to_string(tile_y);
|
||||
};
|
||||
GameControl::enter_editor = [this]() -> void {
|
||||
MapEditor::get()->enter(room_, player_, current_room_, scoreboard_data_);
|
||||
};
|
||||
GameControl::exit_editor = [this]() -> void {
|
||||
MapEditor::get()->exit();
|
||||
// Recargar la habitación desde disco (con los cambios del editor)
|
||||
Resource::Cache::get()->reloadRoom(current_room_);
|
||||
changeRoom(current_room_);
|
||||
player_->setRoom(room_);
|
||||
};
|
||||
GameControl::revert_editor = []() -> std::string {
|
||||
return MapEditor::get()->revert();
|
||||
};
|
||||
GameControl::get_adjacent_room = [this](const std::string& direction) -> std::string {
|
||||
if (direction == "UP") { return room_->getRoom(Room::Border::TOP); }
|
||||
if (direction == "DOWN") { return room_->getRoom(Room::Border::BOTTOM); }
|
||||
if (direction == "LEFT") { return room_->getRoom(Room::Border::LEFT); }
|
||||
if (direction == "RIGHT") { return room_->getRoom(Room::Border::RIGHT); }
|
||||
return "0";
|
||||
};
|
||||
#endif
|
||||
|
||||
SceneManager::current = (mode_ == Mode::GAME) ? SceneManager::Scene::GAME : SceneManager::Scene::DEMO;
|
||||
@@ -116,8 +149,8 @@ Game::Game(Mode mode)
|
||||
Game::~Game() {
|
||||
ItemTracker::destroy();
|
||||
|
||||
GameControl::refresh_player_color = nullptr;
|
||||
GameControl::change_player_skin = nullptr;
|
||||
GameControl::change_player_color = nullptr;
|
||||
Console::get()->on_toggle = nullptr;
|
||||
|
||||
#ifdef _DEBUG
|
||||
@@ -127,6 +160,12 @@ Game::~Game() {
|
||||
GameControl::toggle_debug_mode = nullptr;
|
||||
GameControl::set_initial_room = nullptr;
|
||||
GameControl::set_initial_pos = nullptr;
|
||||
if (MapEditor::get()->isActive()) { MapEditor::get()->exit(); }
|
||||
GameControl::enter_editor = nullptr;
|
||||
GameControl::exit_editor = nullptr;
|
||||
GameControl::revert_editor = nullptr;
|
||||
GameControl::reload_current_room = nullptr;
|
||||
GameControl::get_adjacent_room = nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -137,8 +176,21 @@ void Game::handleEvents() {
|
||||
GlobalEvents::handle(event);
|
||||
#ifdef _DEBUG
|
||||
if (!Console::get()->isActive()) {
|
||||
// Tecla 9: toggle editor (funciona tanto dentro como fuera del editor)
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_9 && static_cast<int>(event.key.repeat) == 0) {
|
||||
if (MapEditor::get()->isActive()) {
|
||||
GameControl::exit_editor();
|
||||
Notifier::get()->show({Locale::get()->get("game.editor_disabled")}); // NOLINT(readability-static-accessed-through-instance)
|
||||
} else {
|
||||
GameControl::enter_editor();
|
||||
Notifier::get()->show({Locale::get()->get("game.editor_enabled")}); // NOLINT(readability-static-accessed-through-instance)
|
||||
}
|
||||
} else if (MapEditor::get()->isActive()) {
|
||||
MapEditor::get()->handleEvent(event);
|
||||
} else {
|
||||
handleDebugEvents(event);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -160,6 +212,14 @@ void Game::handleInput() {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Si el editor de mapas está activo, no procesar inputs del juego
|
||||
if (MapEditor::get()->isActive()) {
|
||||
GlobalInputs::handle();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Durante fade/postfade, solo procesar inputs globales
|
||||
if (state_ != State::PLAYING) {
|
||||
GlobalInputs::handle();
|
||||
@@ -228,6 +288,14 @@ void Game::update() {
|
||||
|
||||
// Actualiza el juego en estado PLAYING
|
||||
void Game::updatePlaying(float delta_time) {
|
||||
#ifdef _DEBUG
|
||||
// Si el editor de mapas está activo, delegar en él y no ejecutar gameplay
|
||||
if (MapEditor::get()->isActive()) {
|
||||
MapEditor::get()->update(delta_time);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Actualiza los objetos
|
||||
room_->update(delta_time);
|
||||
switch (mode_) {
|
||||
@@ -367,8 +435,19 @@ void Game::renderPlaying() {
|
||||
// Prepara para dibujar el frame
|
||||
Screen::get()->start();
|
||||
|
||||
// Dibuja los elementos del juego en orden
|
||||
// Dibuja el mapa de tiles (siempre)
|
||||
room_->renderMap();
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Si el editor está activo, delegar el renderizado de entidades y statusbar
|
||||
if (MapEditor::get()->isActive()) {
|
||||
MapEditor::get()->render();
|
||||
Screen::get()->render();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Dibuja los elementos del juego en orden
|
||||
room_->renderEnemies();
|
||||
room_->renderItems();
|
||||
if (mode_ == Mode::GAME) {
|
||||
@@ -512,15 +591,24 @@ void Game::handleDebugEvents(const SDL_Event& event) { // NOLINT(readability-co
|
||||
Notifier::get()->show({Locale::get()->get("achievements.header"), Locale::get()->get("achievements.c11")}, Notifier::Style::CHEEVO, -1, false, "F7"); // NOLINT(readability-static-accessed-through-instance)
|
||||
break;
|
||||
|
||||
case SDLK_0:
|
||||
case SDLK_0: {
|
||||
const bool ENTERING_DEBUG = !Debug::get()->isEnabled();
|
||||
if (ENTERING_DEBUG) {
|
||||
invincible_before_debug_ = (Options::cheats.invincible == Options::Cheat::State::ENABLED);
|
||||
}
|
||||
Debug::get()->toggleEnabled();
|
||||
Notifier::get()->show({Debug::get()->isEnabled() ? Locale::get()->get("game.debug_enabled") : Locale::get()->get("game.debug_disabled")}); // NOLINT(readability-static-accessed-through-instance)
|
||||
room_->redrawMap();
|
||||
Options::cheats.invincible = static_cast<Options::Cheat::State>(Debug::get()->isEnabled());
|
||||
if (Debug::get()->isEnabled()) {
|
||||
Options::cheats.invincible = Options::Cheat::State::ENABLED;
|
||||
} else {
|
||||
Options::cheats.invincible = invincible_before_debug_ ? Options::Cheat::State::ENABLED : Options::Cheat::State::DISABLED;
|
||||
}
|
||||
player_->setColor();
|
||||
scoreboard_data_->music = !Debug::get()->isEnabled();
|
||||
scoreboard_data_->music ? Audio::get()->resumeMusic() : Audio::get()->pauseMusic();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
@@ -638,6 +726,9 @@ auto Game::changeRoom(const std::string& room_path) -> bool {
|
||||
// Pasa la nueva habitación al jugador
|
||||
player_->setRoom(room_);
|
||||
|
||||
// Recalcula el color del jugador (evita coincidir con el fondo)
|
||||
player_->setColor();
|
||||
|
||||
// Cambia la habitación actual
|
||||
current_room_ = room_path;
|
||||
|
||||
@@ -884,7 +975,7 @@ void Game::checkEndGameCheevos() { // NOLINT(readability-convert-member-functio
|
||||
// Inicializa al jugador
|
||||
void Game::initPlayer(const Player::SpawnData& spawn_point, std::shared_ptr<Room> room) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
const bool IGNORE_INPUT = player_ != nullptr && player_->getIgnoreInput();
|
||||
std::string player_animations = (Options::game.player_skin == 2) ? "player2.yaml" : "player.yaml";
|
||||
std::string player_animations = Player::skinToAnimationPath(Options::game.player_skin);
|
||||
const Player::Data PLAYER{.spawn_data = spawn_point, .animations_path = player_animations, .room = std::move(room)};
|
||||
player_ = std::make_shared<Player>(PLAYER);
|
||||
if (IGNORE_INPUT) { player_->setIgnoreInput(true); }
|
||||
|
||||
@@ -130,5 +130,7 @@ class Game {
|
||||
// Variables de debug para arrastre con ratón
|
||||
bool debug_dragging_player_{false}; // Indica si estamos arrastrando al jugador con el ratón
|
||||
float debug_drag_speed_{0.0F}; // Velocidad actual del arrastre (ease-in)
|
||||
// Estado previo de invencibilidad antes de entrar en modo debug
|
||||
bool invincible_before_debug_{false};
|
||||
#endif
|
||||
};
|
||||
@@ -2,43 +2,21 @@
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <algorithm> // Para ranges::transform
|
||||
#include <cctype> // Para toupper
|
||||
#include <functional> // Para function
|
||||
#include <iostream> // Para std::cout
|
||||
#include <sstream> // Para std::istringstream
|
||||
#include <string> // Para string
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "core/audio/audio.hpp" // Para Audio
|
||||
#include "core/locale/locale.hpp" // Para Locale
|
||||
#include "core/rendering/render_info.hpp" // Para RenderInfo
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/rendering/sprite/sprite.hpp" // Para Sprite
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
#include "core/rendering/text.hpp" // Para Text
|
||||
#include "core/resources/resource_cache.hpp" // Para Resource
|
||||
#include "game/game_control.hpp" // Para GameControl (refresh_player_color)
|
||||
#include "game/options.hpp" // Para Options
|
||||
#include "game/scene_manager.hpp" // Para SceneManager
|
||||
#include "game/ui/notifier.hpp" // Para Notifier
|
||||
|
||||
#ifdef _DEBUG
|
||||
#include "core/system/debug.hpp" // Para Debug
|
||||
#endif
|
||||
|
||||
// ── Sistema de comandos ────────────────────────────────────────────────────────
|
||||
|
||||
// Mapa de completions: {ruta_completa_en_mayúsculas, {opciones}}
|
||||
// Ej: {"CHEAT OPEN THE", {"JAIL"}}
|
||||
using CompletionMap = std::vector<std::pair<std::string_view, std::vector<std::string_view>>>;
|
||||
|
||||
struct ConsoleCommand {
|
||||
std::string_view keyword;
|
||||
std::function<std::string(const std::vector<std::string>& args)> execute;
|
||||
bool instant{false}; // Si true, muestra la respuesta sin efecto typewriter
|
||||
bool hidden{false}; // Si true, no aparece en el autocompletado (TAB)
|
||||
CompletionMap completions{}; // Árbol de sub-argumentos para TAB; cargado en el constructor de Console
|
||||
};
|
||||
// ── Helpers de texto ──────────────────────────────────────────────────────────
|
||||
|
||||
// Convierte la entrada a uppercase y la divide en tokens por espacios
|
||||
static auto parseTokens(const std::string& input) -> std::vector<std::string> {
|
||||
@@ -60,854 +38,6 @@ static auto parseTokens(const std::string& input) -> std::vector<std::string> {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
// Macro para comando de toggle booleano (evita repetición en ON/OFF)
|
||||
#define BOOL_TOGGLE_CMD(label, getter, toggle_fn) \
|
||||
[](const std::vector<std::string>& args) -> std::string { \
|
||||
if (args.empty()) { \
|
||||
(toggle_fn); \
|
||||
return label " " + std::string((getter) ? "ON" : "OFF"); \
|
||||
} \
|
||||
if (args[0] == "ON") { \
|
||||
if (getter) { return label " already ON"; } \
|
||||
(toggle_fn); \
|
||||
return label " ON"; \
|
||||
} \
|
||||
if (args[0] == "OFF") { \
|
||||
if (!(getter)) { return label " already OFF"; } \
|
||||
(toggle_fn); \
|
||||
return label " OFF"; \
|
||||
} \
|
||||
return "usage: " label " [on|off]"; \
|
||||
}
|
||||
|
||||
// Texto de ayuda común para HELP y ?
|
||||
static void printHelp() {
|
||||
std::cout << "=== JDD CONSOLE COMMANDS ===" << '\n';
|
||||
|
||||
std::cout << '\n';
|
||||
std::cout << "[VIDEO]" << '\n';
|
||||
std::cout << " SS [ON|OFF|SIZE] Supersampling" << '\n';
|
||||
std::cout << " SS UPSCALE [NEAREST|LINEAR] SS upscale filter" << '\n';
|
||||
std::cout << " SS DOWNSCALE [BILINEAR|LANCZOS2|LANCZOS3] SS downscale algorithm" << '\n';
|
||||
std::cout << " SHADER [ON|OFF|NEXT [PRESET]|POSTFX|CRTPI] Toggle/select shader (F4)" << '\n';
|
||||
std::cout << " BORDER [ON|OFF] Decorative border (B)" << '\n';
|
||||
std::cout << " FULLSCREEN [ON|OFF] Fullscreen mode (F3)" << '\n';
|
||||
std::cout << " ZOOM [UP|DOWN] Window zoom (F1/F2)" << '\n';
|
||||
std::cout << " INTSCALE [ON|OFF] Integer scaling (F7)" << '\n';
|
||||
std::cout << " VSYNC [ON|OFF] Vertical sync" << '\n';
|
||||
std::cout << " DRIVER [LIST|AUTO|NONE|<name>] GPU driver (restart to apply)" << '\n';
|
||||
std::cout << " PALETTE [NEXT|PREV] Color palette (F5/F6)" << '\n';
|
||||
|
||||
std::cout << '\n';
|
||||
std::cout << "[AUDIO]" << '\n';
|
||||
std::cout << " AUDIO [ON|OFF|VOL <0-100>] Audio master" << '\n';
|
||||
std::cout << " MUSIC [ON|OFF|VOL <0-100>] Music volume" << '\n';
|
||||
std::cout << " SOUND [ON|OFF|VOL <0-100>] Sound volume" << '\n';
|
||||
|
||||
std::cout << '\n';
|
||||
std::cout << "[GAME]" << '\n';
|
||||
std::cout << " SET PLAYER SKIN <1|2> Change player skin (GAME only)" << '\n';
|
||||
std::cout << " RESTART Restart from the beginning" << '\n';
|
||||
std::cout << " KIOSK [ON] Enable kiosk mode" << '\n';
|
||||
std::cout << " EXIT / QUIT Quit application" << '\n';
|
||||
|
||||
std::cout << '\n';
|
||||
std::cout << "[INFO]" << '\n';
|
||||
std::cout << " SHOW [INFO] Show info overlay" << '\n';
|
||||
std::cout << " HIDE [INFO] Hide info overlay" << '\n';
|
||||
std::cout << " SIZE Window size in pixels" << '\n';
|
||||
std::cout << " HELP / ? Show this help in terminal" << '\n';
|
||||
|
||||
#ifdef _DEBUG
|
||||
std::cout << '\n';
|
||||
std::cout << "[DEBUG]" << '\n';
|
||||
std::cout << " DEBUG Toggle debug overlay (F12)" << '\n';
|
||||
std::cout << " ROOM <1-60>|NEXT|PREV Change to room number (GAME only)" << '\n';
|
||||
std::cout << " SCENE [LOGO|LOADING|TITLE|CREDITS|GAME|ENDING|ENDING2|RESTART]" << '\n';
|
||||
std::cout << " SET INITIAL [ROOM|POS] Set initial room/position from current state (GAME only)" << '\n';
|
||||
std::cout << " SET INITIAL SCENE [<name>] Set initial debug scene (GAME|LOGO|TITLE|LOADING|CREDITS|ENDING|ENDING2)" << '\n';
|
||||
std::cout << " SET ITEMS <0-200> Set collected items count (GAME only)" << '\n';
|
||||
std::cout << " CHEAT INFINITE LIVES [ON|OFF] Infinite lives (GAME only)" << '\n';
|
||||
std::cout << " CHEAT INVINCIBILITY [ON|OFF] Invincibility (GAME only)" << '\n';
|
||||
std::cout << " CHEAT OPEN THE JAIL Open the jail (GAME only)" << '\n';
|
||||
std::cout << " CHEAT CLOSE THE JAIL Close the jail (GAME only)" << '\n';
|
||||
std::cout << " SHOW NOTIFICATION Test notification popup" << '\n';
|
||||
std::cout << " SHOW CHEEVO Test achievement notification" << '\n';
|
||||
#endif
|
||||
}
|
||||
|
||||
// En Release, los comandos de truco (CHEAT) son ocultos en el autocompletado
|
||||
#ifdef _DEBUG
|
||||
static constexpr bool CHEAT_HIDDEN = false;
|
||||
#else
|
||||
static constexpr bool CHEAT_HIDDEN = true;
|
||||
#endif
|
||||
|
||||
// Tabla de comandos disponibles
|
||||
static const std::vector<ConsoleCommand> COMMANDS = {
|
||||
// SS [ON|OFF|SIZE|UPSCALE [NEAREST|LINEAR]|DOWNSCALE [BILINEAR|LANCZOS2|LANCZOS3]] — Supersampling
|
||||
{.keyword = "SS", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
static const std::array<std::string_view, 3> DOWNSCALE_NAMES = {"Bilinear", "Lanczos2", "Lanczos3"};
|
||||
if (!args.empty() && args[0] == "SIZE") {
|
||||
if (!Options::video.supersampling) { return "Supersampling is OFF: no texture"; }
|
||||
const auto [w, h] = Screen::get()->getSsTextureSize();
|
||||
if (w == 0) { return "SS texture: not active"; }
|
||||
return "SS texture: " + std::to_string(w) + "x" + std::to_string(h);
|
||||
}
|
||||
if (!args.empty() && args[0] == "UPSCALE") {
|
||||
if (args.size() == 1) {
|
||||
Screen::get()->setLinearUpscale(!Options::video.linear_upscale);
|
||||
return std::string("Upscale: ") + (Options::video.linear_upscale ? "Linear" : "Nearest");
|
||||
}
|
||||
if (args[1] == "NEAREST") {
|
||||
if (!Options::video.linear_upscale) { return "Upscale already Nearest"; }
|
||||
Screen::get()->setLinearUpscale(false);
|
||||
return "Upscale: Nearest";
|
||||
}
|
||||
if (args[1] == "LINEAR") {
|
||||
if (Options::video.linear_upscale) { return "Upscale already Linear"; }
|
||||
Screen::get()->setLinearUpscale(true);
|
||||
return "Upscale: Linear";
|
||||
}
|
||||
return "usage: ss upscale [nearest|linear]";
|
||||
}
|
||||
if (!args.empty() && args[0] == "DOWNSCALE") {
|
||||
if (args.size() == 1) {
|
||||
return std::string("Downscale: ") + std::string(DOWNSCALE_NAMES[static_cast<size_t>(Options::video.downscale_algo)]);
|
||||
}
|
||||
int algo = -1;
|
||||
if (args[1] == "BILINEAR") { algo = 0; }
|
||||
if (args[1] == "LANCZOS2") { algo = 1; }
|
||||
if (args[1] == "LANCZOS3") { algo = 2; }
|
||||
if (algo == -1) { return "usage: ss downscale [bilinear|lanczos2|lanczos3]"; }
|
||||
if (Options::video.downscale_algo == algo) {
|
||||
return std::string("Downscale already ") + std::string(DOWNSCALE_NAMES[static_cast<size_t>(algo)]);
|
||||
}
|
||||
Screen::get()->setDownscaleAlgo(algo);
|
||||
return std::string("Downscale: ") + std::string(DOWNSCALE_NAMES[static_cast<size_t>(algo)]);
|
||||
}
|
||||
if (args.empty()) {
|
||||
Screen::get()->toggleSupersampling();
|
||||
return std::string("PostFX Supersampling ") + (Options::video.supersampling ? "ON" : "OFF");
|
||||
}
|
||||
if (args[0] == "ON") {
|
||||
if (Options::video.supersampling) { return "Supersampling already ON"; }
|
||||
Screen::get()->toggleSupersampling();
|
||||
return "PostFX Supersampling ON";
|
||||
}
|
||||
if (args[0] == "OFF") {
|
||||
if (!Options::video.supersampling) { return "Supersampling already OFF"; }
|
||||
Screen::get()->toggleSupersampling();
|
||||
return "PostFX Supersampling OFF";
|
||||
}
|
||||
return "usage: ss [on|off|size|upscale [nearest|linear]|downscale [bilinear|lanczos2|lanczos3]]";
|
||||
},
|
||||
.completions = {
|
||||
{"SS", {"ON", "OFF", "SIZE", "UPSCALE", "DOWNSCALE"}},
|
||||
{"SS UPSCALE", {"NEAREST", "LINEAR"}},
|
||||
{"SS DOWNSCALE", {"BILINEAR", "LANCZOS2", "LANCZOS3"}},
|
||||
}},
|
||||
|
||||
// SHADER [ON|OFF|NEXT [PRESET]|POSTFX|CRTPI] — Toggle/cicla/selecciona shader (F4 / Shift+F4)
|
||||
{.keyword = "SHADER", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
if (args.empty()) {
|
||||
Screen::get()->toggleShaders();
|
||||
return std::string("Shader ") + (Options::video.postfx ? "ON" : "OFF");
|
||||
}
|
||||
if (args[0] == "ON") {
|
||||
if (Options::video.postfx) { return "Shader already ON"; }
|
||||
Screen::get()->toggleShaders();
|
||||
return "Shader ON";
|
||||
}
|
||||
if (args[0] == "OFF") {
|
||||
if (!Options::video.postfx) { return "Shader already OFF"; }
|
||||
Screen::get()->toggleShaders();
|
||||
return "Shader OFF";
|
||||
}
|
||||
if (args[0] == "POSTFX") {
|
||||
Screen::get()->setActiveShader(Rendering::ShaderType::POSTFX);
|
||||
return "Shader: PostFX";
|
||||
}
|
||||
if (args[0] == "CRTPI") {
|
||||
Screen::get()->setActiveShader(Rendering::ShaderType::CRTPI);
|
||||
return "Shader: CrtPi";
|
||||
}
|
||||
if (args[0] == "NEXT") {
|
||||
// SHADER NEXT PRESET → cicla presets del shader activo
|
||||
if (args.size() >= 2 && args[1] == "PRESET") {
|
||||
if (Options::current_shader == Rendering::ShaderType::CRTPI) {
|
||||
if (Options::crtpi_presets.empty()) { return "No CrtPi presets available"; }
|
||||
Options::current_crtpi_preset =
|
||||
(Options::current_crtpi_preset + 1) %
|
||||
static_cast<int>(Options::crtpi_presets.size());
|
||||
Screen::get()->reloadCrtPi();
|
||||
return "CrtPi preset: " +
|
||||
Options::crtpi_presets[static_cast<size_t>(Options::current_crtpi_preset)].name;
|
||||
}
|
||||
if (Options::postfx_presets.empty()) { return "No PostFX presets available"; }
|
||||
Options::current_postfx_preset =
|
||||
(Options::current_postfx_preset + 1) %
|
||||
static_cast<int>(Options::postfx_presets.size());
|
||||
Screen::get()->reloadPostFX();
|
||||
return "PostFX preset: " +
|
||||
Options::postfx_presets[static_cast<size_t>(Options::current_postfx_preset)].name;
|
||||
}
|
||||
// SHADER NEXT → cicla entre tipos de shader (PostFX ↔ CrtPi)
|
||||
Screen::get()->nextShader();
|
||||
return std::string("Shader: ") +
|
||||
(Options::current_shader == Rendering::ShaderType::CRTPI ? "CrtPi" : "PostFX");
|
||||
}
|
||||
return "usage: shader [on|off|next [preset]|postfx|crtpi]";
|
||||
},
|
||||
.completions = {
|
||||
{"SHADER", {"ON", "OFF", "NEXT", "POSTFX", "CRTPI"}},
|
||||
{"SHADER NEXT", {"PRESET"}},
|
||||
}},
|
||||
|
||||
// BORDER [ON|OFF] — Borde decorativo (B)
|
||||
{.keyword = "BORDER", .execute = BOOL_TOGGLE_CMD("Border", Options::video.border.enabled, Screen::get()->toggleBorder()), .completions = {{"BORDER", {"ON", "OFF"}}}},
|
||||
|
||||
// FULLSCREEN [ON|OFF [PLEASE]] — Pantalla completa (F3); OFF bloqueado en kiosk sin PLEASE
|
||||
{.keyword = "FULLSCREEN", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
const bool EXPLICIT_ON = !args.empty() && args[0] == "ON";
|
||||
const bool EXPLICIT_OFF = !args.empty() && args[0] == "OFF";
|
||||
const bool WITH_PLEASE = !args.empty() && args.back() == "PLEASE";
|
||||
const bool IS_TOGGLE = args.empty();
|
||||
const bool TURNING_OFF = EXPLICIT_OFF || (IS_TOGGLE && Options::video.fullscreen);
|
||||
|
||||
if (TURNING_OFF && Options::kiosk.enabled && !WITH_PLEASE) {
|
||||
return "Not allowed in kiosk mode";
|
||||
}
|
||||
if (EXPLICIT_ON) {
|
||||
if (Options::video.fullscreen) { return "Fullscreen already ON"; }
|
||||
Screen::get()->toggleVideoMode();
|
||||
return "Fullscreen ON";
|
||||
}
|
||||
if (EXPLICIT_OFF) {
|
||||
if (!Options::video.fullscreen) { return "Fullscreen already OFF"; }
|
||||
Screen::get()->toggleVideoMode();
|
||||
return "Fullscreen OFF";
|
||||
}
|
||||
if (IS_TOGGLE) {
|
||||
Screen::get()->toggleVideoMode();
|
||||
return std::string("Fullscreen ") + (Options::video.fullscreen ? "ON" : "OFF");
|
||||
}
|
||||
return "usage: fullscreen [on|off]";
|
||||
},
|
||||
.completions = {{"FULLSCREEN", {"ON", "OFF"}}}},
|
||||
|
||||
// ZOOM UP/DOWN — Zoom de ventana (F1/F2)
|
||||
{.keyword = "ZOOM", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
if (args.empty()) { return "usage: zoom [up|down|<1-" + std::to_string(Screen::get()->getMaxZoom()) + ">]"; }
|
||||
if (args[0] == "UP") {
|
||||
if (!Screen::get()->incWindowZoom()) { return "Max zoom reached"; }
|
||||
return "Zoom " + std::to_string(Options::window.zoom);
|
||||
}
|
||||
if (args[0] == "DOWN") {
|
||||
if (!Screen::get()->decWindowZoom()) { return "Min zoom reached"; }
|
||||
return "Zoom " + std::to_string(Options::window.zoom);
|
||||
}
|
||||
// Zoom numérico directo
|
||||
try {
|
||||
const int N = std::stoi(args[0]);
|
||||
const int MAX = Screen::get()->getMaxZoom();
|
||||
if (N < 1 || N > MAX) {
|
||||
return "Zoom must be between 1 and " + std::to_string(MAX);
|
||||
}
|
||||
if (N == Options::window.zoom) { return "Zoom already " + std::to_string(N); }
|
||||
Screen::get()->setWindowZoom(N);
|
||||
return "Zoom " + std::to_string(Options::window.zoom);
|
||||
} catch (...) {}
|
||||
return "usage: zoom [up|down|<1-" + std::to_string(Screen::get()->getMaxZoom()) + ">]";
|
||||
},
|
||||
.completions = {{"ZOOM", {"UP", "DOWN"}}}},
|
||||
|
||||
// INTSCALE [ON|OFF] — Escalado entero (F7)
|
||||
{.keyword = "INTSCALE", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
const bool ON = args.empty() ? !Options::video.integer_scale
|
||||
: (args[0] == "ON");
|
||||
if (!args.empty() && args[0] != "ON" && args[0] != "OFF") {
|
||||
return "usage: intscale [on|off]";
|
||||
}
|
||||
if (ON == Options::video.integer_scale) {
|
||||
return std::string("IntScale already ") + (ON ? "ON" : "OFF");
|
||||
}
|
||||
Screen::get()->toggleIntegerScale();
|
||||
Screen::get()->setVideoMode(Options::video.fullscreen);
|
||||
return std::string("IntScale ") + (Options::video.integer_scale ? "ON" : "OFF");
|
||||
},
|
||||
.completions = {{"INTSCALE", {"ON", "OFF"}}}},
|
||||
|
||||
// VSYNC [ON|OFF] — Sincronización vertical
|
||||
{.keyword = "VSYNC", .execute = BOOL_TOGGLE_CMD("VSync", Options::video.vertical_sync, Screen::get()->toggleVSync()), .completions = {{"VSYNC", {"ON", "OFF"}}}},
|
||||
|
||||
// DRIVER [LIST|AUTO|<nombre>] — Driver GPU (aplica en el próximo arranque)
|
||||
{.keyword = "DRIVER", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
// Sin argumentos: muestra el driver activo (permitido en kiosk)
|
||||
if (args.empty()) {
|
||||
const auto& driver = Screen::get()->getGPUDriver();
|
||||
return "GPU: " + (driver.empty() ? std::string("sdl") : driver);
|
||||
}
|
||||
// LIST: lista drivers disponibles marcando el activo con * (permitido en kiosk)
|
||||
if (args[0] == "LIST") {
|
||||
const int COUNT = SDL_GetNumGPUDrivers();
|
||||
if (COUNT <= 0) { return "No GPU drivers found"; }
|
||||
const std::string& active = Screen::get()->getGPUDriver();
|
||||
std::string result = "Drivers:";
|
||||
for (int i = 0; i < COUNT; ++i) {
|
||||
const char* name = SDL_GetGPUDriver(i);
|
||||
if (name != nullptr) {
|
||||
result += ' ';
|
||||
result += name;
|
||||
if (active == name) { result += '*'; }
|
||||
}
|
||||
}
|
||||
SDL_Log("SDL GPU drivers: %s", result.c_str());
|
||||
return result;
|
||||
}
|
||||
// Cambiar driver: bloqueado en kiosk salvo PLEASE
|
||||
const bool HAS_PLEASE = std::ranges::find(args, std::string("PLEASE")) != args.end();
|
||||
if (Options::kiosk.enabled && !HAS_PLEASE) {
|
||||
return "Not allowed in kiosk mode";
|
||||
}
|
||||
if (args[0] == "AUTO") {
|
||||
Options::video.gpu_preferred_driver.clear();
|
||||
Options::saveToFile();
|
||||
return "Driver: auto (restart)";
|
||||
}
|
||||
if (args[0] == "NONE") {
|
||||
Options::video.gpu_preferred_driver = "none";
|
||||
Options::saveToFile();
|
||||
return "Driver: none (SDL fallback, restart)";
|
||||
}
|
||||
std::string driver_lower = args[0];
|
||||
std::ranges::transform(driver_lower, driver_lower.begin(), ::tolower);
|
||||
// Validar que el nombre existe en la lista de drivers SDL
|
||||
const int COUNT = SDL_GetNumGPUDrivers();
|
||||
bool found = false;
|
||||
for (int i = 0; i < COUNT && !found; ++i) {
|
||||
const char* name = SDL_GetGPUDriver(i);
|
||||
if (name != nullptr && driver_lower == name) { found = true; }
|
||||
}
|
||||
if (!found) {
|
||||
return "Unknown driver: " + driver_lower + ". Use DRIVER LIST or NONE";
|
||||
}
|
||||
Options::video.gpu_preferred_driver = driver_lower;
|
||||
Options::saveToFile();
|
||||
return "Driver: " + driver_lower + " (restart)";
|
||||
},
|
||||
.completions = {{"DRIVER", {"LIST", "AUTO", "NONE"}}}},
|
||||
|
||||
// PALETTE NEXT/PREV/<nombre> — Paleta de colores (F5/F6 o por nombre)
|
||||
{.keyword = "PALETTE", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
const auto palName = []() -> std::string {
|
||||
std::string name = Options::video.palette;
|
||||
std::ranges::transform(name, name.begin(), ::tolower);
|
||||
return name;
|
||||
};
|
||||
if (args.empty()) { return "usage: palette [next|prev|<name>]"; }
|
||||
if (args[0] == "NEXT") {
|
||||
Screen::get()->nextPalette();
|
||||
return "Palette: " + palName();
|
||||
}
|
||||
if (args[0] == "PREV") {
|
||||
Screen::get()->previousPalette();
|
||||
return "Palette: " + palName();
|
||||
}
|
||||
if (!Screen::get()->setPaletteByName(args[0])) {
|
||||
std::string arg_lower = args[0];
|
||||
std::ranges::transform(arg_lower, arg_lower.begin(), ::tolower);
|
||||
return "Unknown palette: " + arg_lower;
|
||||
}
|
||||
return "Palette: " + palName();
|
||||
}},
|
||||
|
||||
#ifdef _DEBUG
|
||||
// DEBUG [ON|OFF] — Activa/desactiva el modo debug del juego (tecla 0); solo en escena GAME
|
||||
{.keyword = "DEBUG", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; }
|
||||
if (!GameControl::toggle_debug_mode) { return "Game not initialized"; }
|
||||
const bool ENABLED = Debug::get()->isEnabled();
|
||||
if (!args.empty() && args[0] == "ON") {
|
||||
if (ENABLED) { return "Debug mode already ON"; }
|
||||
GameControl::toggle_debug_mode();
|
||||
return "Debug mode ON";
|
||||
}
|
||||
if (!args.empty() && args[0] == "OFF") {
|
||||
if (!ENABLED) { return "Debug mode already OFF"; }
|
||||
GameControl::toggle_debug_mode();
|
||||
return "Debug mode OFF";
|
||||
}
|
||||
if (!args.empty()) { return "usage: debug [on|off]"; }
|
||||
GameControl::toggle_debug_mode();
|
||||
return std::string("Debug mode ") + (Debug::get()->isEnabled() ? "ON" : "OFF");
|
||||
},
|
||||
.completions = {{"DEBUG", {"ON", "OFF"}}}},
|
||||
|
||||
// ROOM <num>|NEXT|PREV — Cambia a la habitación indicada (1-60); solo en escena GAME
|
||||
{.keyword = "ROOM", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; }
|
||||
if (args.empty()) { return "usage: room <1-60>|next|prev"; }
|
||||
int num = 0;
|
||||
if (args[0] == "NEXT" || args[0] == "PREV") {
|
||||
if (!GameControl::get_current_room) { return "Game not initialized"; }
|
||||
const std::string current = GameControl::get_current_room();
|
||||
try {
|
||||
num = std::stoi(current.substr(0, current.find('.')));
|
||||
} catch (...) { return "Cannot determine current room"; }
|
||||
num += (args[0] == "NEXT") ? 1 : -1;
|
||||
} else {
|
||||
try {
|
||||
num = std::stoi(args[0]);
|
||||
} catch (...) { return "usage: room <1-60>|next|prev"; }
|
||||
}
|
||||
if (num < 1 || num > 60) { return "Room must be between 1 and 60"; }
|
||||
char buf[16];
|
||||
std::snprintf(buf, sizeof(buf), "%02d.yaml", num);
|
||||
if (GameControl::change_room && GameControl::change_room(buf)) {
|
||||
return std::string("Room: ") + buf;
|
||||
}
|
||||
return std::string("Room not found: ") + buf;
|
||||
},
|
||||
.completions = {{"ROOM", {"NEXT", "PREV"}}}},
|
||||
|
||||
#endif
|
||||
|
||||
// SHOW INFO — disponible en Release; SHOW NOTIFICATION / SHOW CHEEVO — solo en Debug
|
||||
{.keyword = "SHOW", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
#ifdef _DEBUG
|
||||
if (!args.empty() && args[0] == "NOTIFICATION") {
|
||||
Notifier::get()->show({"NOTIFICATION"});
|
||||
return "Notification shown";
|
||||
}
|
||||
if (!args.empty() && args[0] == "CHEEVO") {
|
||||
Notifier::get()->show({Locale::get()->get("achievements.header"), Locale::get()->get("achievements.c1")}, Notifier::Style::CHEEVO, -1, false); // NOLINT(readability-static-accessed-through-instance)
|
||||
return "Cheevo notification shown";
|
||||
}
|
||||
if (args.empty() || args[0] != "INFO") { return "usage: show [info|notification|cheevo]"; }
|
||||
#else
|
||||
if (args.empty() || args[0] != "INFO") { return "usage: show [info]"; }
|
||||
#endif
|
||||
if (RenderInfo::get()->isActive()) { return "Info overlay already ON"; }
|
||||
RenderInfo::get()->toggle();
|
||||
return "Info overlay ON";
|
||||
},
|
||||
.completions = {
|
||||
#ifdef _DEBUG
|
||||
{"SHOW", {"INFO", "NOTIFICATION", "CHEEVO"}},
|
||||
#else
|
||||
{"SHOW", {"INFO"}},
|
||||
#endif
|
||||
}},
|
||||
|
||||
// HIDE INFO — disponible en Release
|
||||
{.keyword = "HIDE", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
if (args.empty() || args[0] != "INFO") { return "usage: hide [info]"; }
|
||||
if (!RenderInfo::get()->isActive()) { return "Info overlay already OFF"; }
|
||||
RenderInfo::get()->toggle();
|
||||
return "Info overlay OFF";
|
||||
},
|
||||
.completions = {{"HIDE", {"INFO"}}}},
|
||||
|
||||
// CHEAT <subcomando> — Trucos de juego; solo en escena GAME; no aparece en ayuda en builds Release
|
||||
{.keyword = "CHEAT", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; }
|
||||
if (args.empty()) { return "usage: cheat [infinite lives|invincibility|open the jail|close the jail]"; }
|
||||
|
||||
// CHEAT INFINITE LIVES [ON|OFF]
|
||||
if (args[0] == "INFINITE") {
|
||||
if (args.size() < 2 || args[1] != "LIVES") { return "usage: cheat infinite lives [on|off]"; }
|
||||
auto& cheat = Options::cheats.infinite_lives;
|
||||
using State = Options::Cheat::State;
|
||||
const std::vector<std::string> REST(args.begin() + 2, args.end());
|
||||
if (REST.empty()) {
|
||||
cheat = (cheat == State::ENABLED) ? State::DISABLED : State::ENABLED;
|
||||
} else if (REST[0] == "ON") {
|
||||
if (cheat == State::ENABLED) { return "Infinite lives already ON"; }
|
||||
cheat = State::ENABLED;
|
||||
} else if (REST[0] == "OFF") {
|
||||
if (cheat == State::DISABLED) { return "Infinite lives already OFF"; }
|
||||
cheat = State::DISABLED;
|
||||
} else {
|
||||
return "usage: cheat infinite lives [on|off]";
|
||||
}
|
||||
if (GameControl::refresh_player_color) { GameControl::refresh_player_color(); }
|
||||
return std::string("Infinite lives ") + (cheat == State::ENABLED ? "ON" : "OFF");
|
||||
}
|
||||
|
||||
// CHEAT INVINCIBILITY [ON|OFF]
|
||||
if (args[0] == "INVINCIBILITY" || args[0] == "INVENCIBILITY") {
|
||||
auto& cheat = Options::cheats.invincible;
|
||||
using State = Options::Cheat::State;
|
||||
if (args.size() == 1) {
|
||||
cheat = (cheat == State::ENABLED) ? State::DISABLED : State::ENABLED;
|
||||
} else if (args[1] == "ON") {
|
||||
if (cheat == State::ENABLED) { return "Invincibility already ON"; }
|
||||
cheat = State::ENABLED;
|
||||
} else if (args[1] == "OFF") {
|
||||
if (cheat == State::DISABLED) { return "Invincibility already OFF"; }
|
||||
cheat = State::DISABLED;
|
||||
} else {
|
||||
return "usage: cheat invincibility [on|off]";
|
||||
}
|
||||
if (GameControl::refresh_player_color) { GameControl::refresh_player_color(); }
|
||||
return std::string("Invincibility ") + (cheat == State::ENABLED ? "ON" : "OFF");
|
||||
}
|
||||
|
||||
// CHEAT OPEN THE JAIL
|
||||
if (args[0] == "OPEN") {
|
||||
if (args.size() < 3 || args[1] != "THE" || args[2] != "JAIL") { return "usage: cheat open the jail"; }
|
||||
if (Options::cheats.jail_is_open == Options::Cheat::State::ENABLED) { return "Jail already open"; }
|
||||
Options::cheats.jail_is_open = Options::Cheat::State::ENABLED;
|
||||
return "Jail opened";
|
||||
}
|
||||
|
||||
// CHEAT CLOSE THE JAIL
|
||||
if (args[0] == "CLOSE") {
|
||||
if (args.size() < 3 || args[1] != "THE" || args[2] != "JAIL") { return "usage: cheat close the jail"; }
|
||||
if (Options::cheats.jail_is_open == Options::Cheat::State::DISABLED) { return "Jail already closed"; }
|
||||
Options::cheats.jail_is_open = Options::Cheat::State::DISABLED;
|
||||
return "Jail closed";
|
||||
}
|
||||
|
||||
return "usage: cheat [infinite lives|invincibility|open the jail|close the jail]";
|
||||
},
|
||||
.hidden = CHEAT_HIDDEN,
|
||||
.completions = {
|
||||
{"CHEAT", {"INFINITE", "INVINCIBILITY", "OPEN", "CLOSE"}},
|
||||
{"CHEAT INFINITE", {"LIVES"}},
|
||||
{"CHEAT INFINITE LIVES", {"ON", "OFF"}},
|
||||
{"CHEAT INVINCIBILITY", {"ON", "OFF"}},
|
||||
{"CHEAT OPEN", {"THE"}},
|
||||
{"CHEAT OPEN THE", {"JAIL"}},
|
||||
{"CHEAT CLOSE", {"THE"}},
|
||||
{"CHEAT CLOSE THE", {"JAIL"}},
|
||||
}},
|
||||
|
||||
// SET PLAYER SKIN <1|2> — Cambia la skin del jugador (disponible en todos los builds, GAME)
|
||||
// SET INITIAL [ROOM|POS] — Guarda habitación/posición actual como inicio (solo _DEBUG, GAME)
|
||||
// SET INITIAL SCENE [<name>] — Guarda escena como escena inicial de debug (solo _DEBUG)
|
||||
// SET ITEMS <0-200> — Fija el contador de items recogidos (solo _DEBUG, GAME)
|
||||
{.keyword = "SET", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
if (args.size() >= 3 && args[0] == "PLAYER" && args[1] == "SKIN") {
|
||||
if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; }
|
||||
int num = 0;
|
||||
try {
|
||||
num = std::stoi(args[2]);
|
||||
} catch (...) {}
|
||||
if (num < 1 || num > 2) { return "usage: set player skin <1|2>"; }
|
||||
if (!GameControl::change_player_skin) { return "Game not initialized"; }
|
||||
GameControl::change_player_skin(num);
|
||||
return "Player skin: " + std::to_string(num);
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
// SET INITIAL SCENE [<nombre>] — disponible desde cualquier escena
|
||||
if (args.size() >= 2 && args[0] == "INITIAL" && args[1] == "SCENE") {
|
||||
SceneManager::Scene target = SceneManager::current;
|
||||
std::string name = "current";
|
||||
if (args.size() >= 3) {
|
||||
if (args[2] == "GAME") {
|
||||
target = SceneManager::Scene::GAME;
|
||||
name = "game";
|
||||
} else if (args[2] == "LOGO") {
|
||||
target = SceneManager::Scene::LOGO;
|
||||
name = "logo";
|
||||
} else if (args[2] == "LOADING") {
|
||||
target = SceneManager::Scene::LOADING_SCREEN;
|
||||
name = "loading";
|
||||
} else if (args[2] == "TITLE") {
|
||||
target = SceneManager::Scene::TITLE;
|
||||
name = "title";
|
||||
} else if (args[2] == "CREDITS") {
|
||||
target = SceneManager::Scene::CREDITS;
|
||||
name = "credits";
|
||||
} else if (args[2] == "ENDING") {
|
||||
target = SceneManager::Scene::ENDING;
|
||||
name = "ending";
|
||||
} else if (args[2] == "ENDING2") {
|
||||
target = SceneManager::Scene::ENDING2;
|
||||
name = "ending2";
|
||||
} else {
|
||||
std::string scene_lower = args[2];
|
||||
std::ranges::transform(scene_lower, scene_lower.begin(), ::tolower);
|
||||
return "Unknown scene: " + scene_lower;
|
||||
}
|
||||
}
|
||||
Debug::get()->setInitialScene(target);
|
||||
Debug::get()->saveToFile();
|
||||
return "Initial scene set to: " + name;
|
||||
}
|
||||
|
||||
if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; }
|
||||
|
||||
// SET ITEMS <0-200> — Fija el contador de items recogidos
|
||||
if (args[0] == "ITEMS") {
|
||||
if (args.size() < 2) { return "usage: set items <0-200>"; }
|
||||
int count = 0;
|
||||
try {
|
||||
count = std::stoi(args[1]);
|
||||
} catch (...) { return "usage: set items <0-200>"; }
|
||||
if (count < 0 || count > 200) { return "Items must be between 0 and 200"; }
|
||||
if (!GameControl::set_items) { return "Game not initialized"; }
|
||||
GameControl::set_items(count);
|
||||
return "Items: " + std::to_string(count);
|
||||
}
|
||||
|
||||
if (args.empty() || args[0] != "INITIAL") { return "usage: set initial [room|pos|scene] | set items <0-200> | set player skin <1|2>"; }
|
||||
|
||||
const bool DO_ROOM = args.size() == 1 || (args.size() >= 2 && args[1] == "ROOM");
|
||||
const bool DO_POS = args.size() == 1 || (args.size() >= 2 && args[1] == "POS");
|
||||
|
||||
if (!DO_ROOM && !DO_POS) { return "usage: set initial [room|pos|scene]"; }
|
||||
if (!GameControl::set_initial_room || !GameControl::set_initial_pos) { return "Game not initialized"; }
|
||||
|
||||
std::string result;
|
||||
if (DO_ROOM) { result = GameControl::set_initial_room(); }
|
||||
if (DO_POS) {
|
||||
if (!result.empty()) { result += ", "; }
|
||||
result += GameControl::set_initial_pos();
|
||||
}
|
||||
return result;
|
||||
#else
|
||||
return "usage: set player skin <1|2>";
|
||||
#endif
|
||||
},
|
||||
.completions = {
|
||||
#ifdef _DEBUG
|
||||
{"SET", {"PLAYER", "INITIAL", "ITEMS"}},
|
||||
{"SET PLAYER", {"SKIN"}},
|
||||
{"SET INITIAL", {"ROOM", "POS", "SCENE"}},
|
||||
{"SET INITIAL SCENE", {"LOGO", "LOADING", "TITLE", "CREDITS", "GAME", "ENDING", "ENDING2"}},
|
||||
#else
|
||||
{"SET", {"PLAYER"}},
|
||||
{"SET PLAYER", {"SKIN"}},
|
||||
#endif
|
||||
}},
|
||||
|
||||
#ifdef _DEBUG
|
||||
// SCENE [LOGO|LOADING|TITLE|CREDITS|GAME|ENDING|ENDING2|RESTART] — Cambiar o reiniciar escena; solo en Debug
|
||||
{.keyword = "SCENE", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
if (Options::kiosk.enabled) { return "Not allowed in kiosk mode"; }
|
||||
if (args.empty()) { return "usage: scene [logo|loading|title|credits|game|ending|ending2|restart]"; }
|
||||
|
||||
// RESTART: reinicia la escena actual (funciona desde cualquier escena)
|
||||
if (args[0] == "RESTART") {
|
||||
SceneManager::scene_before_restart = SceneManager::current;
|
||||
SceneManager::current = SceneManager::Scene::RESTART_CURRENT;
|
||||
return "Restarting...";
|
||||
}
|
||||
|
||||
// Para el resto: si pedimos la escena que ya está activa → también reiniciar
|
||||
const auto GO_TO = [](SceneManager::Scene target, const std::string& label) -> std::string {
|
||||
if (SceneManager::current == target) {
|
||||
SceneManager::scene_before_restart = target;
|
||||
SceneManager::current = SceneManager::Scene::RESTART_CURRENT;
|
||||
} else {
|
||||
SceneManager::current = target;
|
||||
}
|
||||
return "Scene: " + label;
|
||||
};
|
||||
|
||||
if (args[0] == "LOGO") { return GO_TO(SceneManager::Scene::LOGO, "Logo"); }
|
||||
if (args[0] == "LOADING") { return GO_TO(SceneManager::Scene::LOADING_SCREEN, "Loading"); }
|
||||
if (args[0] == "TITLE") { return GO_TO(SceneManager::Scene::TITLE, "Title"); }
|
||||
if (args[0] == "CREDITS") { return GO_TO(SceneManager::Scene::CREDITS, "Credits"); }
|
||||
if (args[0] == "GAME") { return GO_TO(SceneManager::Scene::GAME, "Game"); }
|
||||
if (args[0] == "ENDING") { return GO_TO(SceneManager::Scene::ENDING, "Ending"); }
|
||||
if (args[0] == "ENDING2") { return GO_TO(SceneManager::Scene::ENDING2, "Ending 2"); }
|
||||
return "Unknown scene: " + args[0];
|
||||
},
|
||||
.completions = {{"SCENE", {"LOGO", "LOADING", "TITLE", "CREDITS", "GAME", "ENDING", "ENDING2", "RESTART"}}}},
|
||||
#endif
|
||||
|
||||
// RESTART — Reiniciar desde el principio (equivale a SCENE LOGO)
|
||||
{.keyword = "RESTART", .execute = [](const std::vector<std::string>&) -> std::string {
|
||||
SceneManager::current = SceneManager::Scene::LOGO;
|
||||
Audio::get()->stopMusic();
|
||||
return "Restarting...";
|
||||
},
|
||||
.instant = true},
|
||||
|
||||
// KIOSK [ON|OFF PLEASE|PLEASE] — Modo kiosko
|
||||
{.keyword = "KIOSK", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
const bool DISABLE = (!args.empty() && args[0] == "PLEASE") ||
|
||||
(args.size() >= 2 && args[0] == "OFF" && args[1] == "PLEASE");
|
||||
if (DISABLE) {
|
||||
Options::kiosk.enabled = false;
|
||||
return "Kiosk mode OFF";
|
||||
}
|
||||
if (!args.empty() && args[0] == "OFF") {
|
||||
return "Not allowed in kiosk mode";
|
||||
}
|
||||
if (args.empty() || args[0] == "ON") {
|
||||
if (Options::kiosk.enabled) { return "Kiosk mode already ON"; }
|
||||
Options::kiosk.enabled = true;
|
||||
if (!Options::video.fullscreen) { Screen::get()->toggleVideoMode(); }
|
||||
return "Kiosk mode ON";
|
||||
}
|
||||
return "usage: kiosk [on]";
|
||||
},
|
||||
.completions = {{"KIOSK", {"ON"}}}},
|
||||
|
||||
// AUDIO [ON|OFF|VOL <0-100>] — Audio maestro (estado + volumen)
|
||||
{.keyword = "AUDIO", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
if (args.empty()) {
|
||||
const int VOL = static_cast<int>(Options::audio.volume * 100.0F);
|
||||
return std::string("Audio ") + (Options::audio.enabled ? "ON" : "OFF") +
|
||||
" vol:" + std::to_string(VOL);
|
||||
}
|
||||
if (args[0] == "ON") {
|
||||
if (Options::audio.enabled) { return "Audio already ON"; }
|
||||
Options::audio.enabled = true;
|
||||
Audio::get()->enable(true);
|
||||
return "Audio ON";
|
||||
}
|
||||
if (args[0] == "OFF") {
|
||||
if (!Options::audio.enabled) { return "Audio already OFF"; }
|
||||
Options::audio.enabled = false;
|
||||
Audio::get()->enable(false);
|
||||
return "Audio OFF";
|
||||
}
|
||||
if (args[0] == "VOL" && args.size() >= 2) {
|
||||
try {
|
||||
const int VAL = std::stoi(args[1]);
|
||||
if (VAL < 0 || VAL > 100) { return "Vol must be 0-100"; }
|
||||
Options::audio.volume = static_cast<float>(VAL) / 100.0F;
|
||||
Audio::get()->enable(Options::audio.enabled);
|
||||
return "Audio vol:" + std::to_string(VAL);
|
||||
} catch (...) { return "usage: audio vol <0-100>"; }
|
||||
}
|
||||
return "usage: audio [on|off|vol n]";
|
||||
},
|
||||
.completions = {{"AUDIO", {"ON", "OFF", "VOL"}}}},
|
||||
|
||||
// MUSIC [ON|OFF|VOL <0-100>] — Volumen e interruptor de música
|
||||
{.keyword = "MUSIC", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
if (args.empty()) {
|
||||
const int VOL = static_cast<int>(Options::audio.music.volume * 100.0F);
|
||||
return std::string("Music ") + (Options::audio.music.enabled ? "ON" : "OFF") +
|
||||
" vol:" + std::to_string(VOL);
|
||||
}
|
||||
if (args[0] == "ON") {
|
||||
if (Options::audio.music.enabled) { return "Music already ON"; }
|
||||
Options::audio.music.enabled = true;
|
||||
Audio::get()->enableMusic(true);
|
||||
Audio::get()->setMusicVolume(Options::audio.music.volume);
|
||||
return "Music ON";
|
||||
}
|
||||
if (args[0] == "OFF") {
|
||||
if (!Options::audio.music.enabled) { return "Music already OFF"; }
|
||||
Audio::get()->setMusicVolume(0.0F);
|
||||
Audio::get()->enableMusic(false);
|
||||
Options::audio.music.enabled = false;
|
||||
return "Music OFF";
|
||||
}
|
||||
if (args[0] == "VOL" && args.size() >= 2) {
|
||||
try {
|
||||
const int VAL = std::stoi(args[1]);
|
||||
if (VAL < 0 || VAL > 100) { return "Vol must be 0-100"; }
|
||||
Options::audio.music.volume = static_cast<float>(VAL) / 100.0F;
|
||||
if (Options::audio.music.enabled) {
|
||||
Audio::get()->setMusicVolume(Options::audio.music.volume);
|
||||
}
|
||||
return "Music vol:" + std::to_string(VAL);
|
||||
} catch (...) { return "usage: music vol <0-100>"; }
|
||||
}
|
||||
return "usage: music [on|off|vol n]";
|
||||
},
|
||||
.completions = {{"MUSIC", {"ON", "OFF", "VOL"}}}},
|
||||
|
||||
// SOUND [ON|OFF|VOL <0-100>] — Volumen e interruptor de efectos de sonido
|
||||
{.keyword = "SOUND", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
if (args.empty()) {
|
||||
const int VOL = static_cast<int>(Options::audio.sound.volume * 100.0F);
|
||||
return std::string("Sound ") + (Options::audio.sound.enabled ? "ON" : "OFF") +
|
||||
" vol:" + std::to_string(VOL);
|
||||
}
|
||||
if (args[0] == "ON") {
|
||||
if (Options::audio.sound.enabled) { return "Sound already ON"; }
|
||||
Options::audio.sound.enabled = true;
|
||||
Audio::get()->enableSound(true);
|
||||
Audio::get()->setSoundVolume(Options::audio.sound.volume);
|
||||
return "Sound ON";
|
||||
}
|
||||
if (args[0] == "OFF") {
|
||||
if (!Options::audio.sound.enabled) { return "Sound already OFF"; }
|
||||
Audio::get()->setSoundVolume(0.0F);
|
||||
Audio::get()->enableSound(false);
|
||||
Options::audio.sound.enabled = false;
|
||||
return "Sound OFF";
|
||||
}
|
||||
if (args[0] == "VOL" && args.size() >= 2) {
|
||||
try {
|
||||
const int VAL = std::stoi(args[1]);
|
||||
if (VAL < 0 || VAL > 100) { return "Vol must be 0-100"; }
|
||||
Options::audio.sound.volume = static_cast<float>(VAL) / 100.0F;
|
||||
if (Options::audio.sound.enabled) {
|
||||
Audio::get()->setSoundVolume(Options::audio.sound.volume);
|
||||
}
|
||||
return "Sound vol:" + std::to_string(VAL);
|
||||
} catch (...) { return "usage: sound vol <0-100>"; }
|
||||
}
|
||||
return "usage: sound [on|off|vol n]";
|
||||
},
|
||||
.completions = {{"SOUND", {"ON", "OFF", "VOL"}}}},
|
||||
|
||||
// EXIT / QUIT — Cerrar la aplicacion (bloqueado en kiosk)
|
||||
{.keyword = "EXIT", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
if (Options::kiosk.enabled && (args.empty() || args[0] != "PLEASE")) {
|
||||
return "Not allowed in kiosk mode";
|
||||
}
|
||||
SceneManager::current = SceneManager::Scene::QUIT;
|
||||
return "Quitting...";
|
||||
},
|
||||
.instant = true},
|
||||
{.keyword = "QUIT", .execute = [](const std::vector<std::string>& args) -> std::string {
|
||||
if (Options::kiosk.enabled && (args.empty() || args[0] != "PLEASE")) {
|
||||
return "Not allowed in kiosk mode";
|
||||
}
|
||||
SceneManager::current = SceneManager::Scene::QUIT;
|
||||
return "Quitting...";
|
||||
},
|
||||
.instant = true},
|
||||
|
||||
// SIZE — Devuelve el tamaño actual de la ventana en píxeles
|
||||
{.keyword = "SIZE", .execute = [](const std::vector<std::string>&) -> std::string {
|
||||
int w = 0;
|
||||
int h = 0;
|
||||
SDL_GetWindowSize(SDL_GetRenderWindow(Screen::get()->getRenderer()), &w, &h);
|
||||
return std::to_string(w) + "x" + std::to_string(h);
|
||||
}},
|
||||
|
||||
// HELP / ? — Muestra ayuda en la terminal del sistema y lista de comandos en consola
|
||||
{.keyword = "HELP", .execute = [](const std::vector<std::string>&) -> std::string {
|
||||
printHelp();
|
||||
std::string result =
|
||||
"Commands:\n"
|
||||
"fullscreen, zoom, intscale, vsync, driver, palette, audio, music, sound, set, restart, kiosk, exit, quit, show, hide, size, help\n";
|
||||
#ifdef _DEBUG
|
||||
result +=
|
||||
"\nDebug commands:\n"
|
||||
"debug, room, scene, cheat\n";
|
||||
#endif
|
||||
result += "-- more info on the terminal";
|
||||
return result;
|
||||
}},
|
||||
{.keyword = "?", .execute = [](const std::vector<std::string>&) -> std::string {
|
||||
printHelp();
|
||||
std::string result =
|
||||
"Commands:\n"
|
||||
"fullscreen, zoom, intscale, vsync, driver, palette, audio, music, sound, set, restart, kiosk, exit, quit, show, hide, size, help\n";
|
||||
#ifdef _DEBUG
|
||||
result +=
|
||||
"\nDebug commands:\n"
|
||||
"debug, room, scene, cheat\n";
|
||||
#endif
|
||||
result += "-- more info on the terminal";
|
||||
return result;
|
||||
}},
|
||||
};
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
// Calcula la altura total de la consola para N líneas de mensaje (+ 1 línea de input)
|
||||
@@ -979,13 +109,8 @@ Console::Console(const std::string& font_name)
|
||||
target_height_ = height_;
|
||||
y_ = -height_;
|
||||
|
||||
// Construir mapa de autocompletado a partir de COMMANDS
|
||||
for (const auto& cmd : COMMANDS) {
|
||||
for (const auto& [path, opts] : cmd.completions) {
|
||||
auto& vec = tab_completions_[std::string(path)];
|
||||
for (const auto& opt : opts) { vec.emplace_back(opt); }
|
||||
}
|
||||
}
|
||||
// Cargar comandos desde YAML
|
||||
registry_.load("data/console/commands.yaml");
|
||||
|
||||
buildSurface();
|
||||
}
|
||||
@@ -1225,39 +350,22 @@ void Console::handleEvent(const SDL_Event& event) {
|
||||
|
||||
const size_t space_pos = upper.rfind(' ');
|
||||
if (space_pos == std::string::npos) {
|
||||
// Modo comando: ciclar keywords que empiecen por el prefijo
|
||||
for (const auto& cmd : COMMANDS) {
|
||||
if (cmd.hidden) { continue; }
|
||||
if (upper.empty() || cmd.keyword.starts_with(upper)) {
|
||||
tab_matches_.emplace_back(cmd.keyword);
|
||||
// Modo comando: ciclar keywords visibles que empiecen por el prefijo
|
||||
for (const auto& kw : registry_.getVisibleKeywords()) {
|
||||
if (upper.empty() || kw.starts_with(upper)) {
|
||||
tab_matches_.emplace_back(kw);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const std::string base_cmd = upper.substr(0, space_pos);
|
||||
const std::string sub_prefix = upper.substr(space_pos + 1);
|
||||
if (base_cmd == "PALETTE" && Screen::get() != nullptr) {
|
||||
// NEXT/PREV primero, luego todos los nombres de paleta disponibles
|
||||
for (const auto* sv : {"NEXT", "PREV"}) {
|
||||
if (sub_prefix.empty() || std::string_view{sv}.starts_with(sub_prefix)) {
|
||||
tab_matches_.emplace_back("PALETTE " + std::string(sv));
|
||||
}
|
||||
}
|
||||
for (const auto& name : Screen::get()->getPaletteNames()) {
|
||||
if (sub_prefix.empty() || std::string_view{name}.starts_with(sub_prefix)) {
|
||||
tab_matches_.emplace_back("PALETTE " + name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const auto it = tab_completions_.find(base_cmd);
|
||||
if (it != tab_completions_.end()) {
|
||||
for (const auto& arg : it->second) {
|
||||
const auto opts = registry_.getCompletions(base_cmd);
|
||||
for (const auto& arg : opts) {
|
||||
if (sub_prefix.empty() || std::string_view{arg}.starts_with(sub_prefix)) {
|
||||
tab_matches_.emplace_back(base_cmd + " " + arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tab_index_ = -1;
|
||||
}
|
||||
if (tab_matches_.empty()) { break; }
|
||||
@@ -1289,17 +397,13 @@ void Console::processCommand() {
|
||||
const std::string& cmd = TOKENS[0];
|
||||
const std::vector<std::string> ARGS(TOKENS.begin() + 1, TOKENS.end());
|
||||
std::string result;
|
||||
bool found = false;
|
||||
bool instant = false;
|
||||
for (const auto& command : COMMANDS) {
|
||||
if (command.keyword == cmd) {
|
||||
result = command.execute(ARGS);
|
||||
instant = command.instant;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
|
||||
const auto* def = registry_.findCommand(cmd);
|
||||
if (def != nullptr) {
|
||||
result = registry_.execute(cmd, ARGS);
|
||||
instant = def->instant;
|
||||
} else {
|
||||
std::string cmd_lower = cmd;
|
||||
std::ranges::transform(cmd_lower, cmd_lower.begin(), ::tolower);
|
||||
result = "Unknown: " + cmd_lower;
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
#include <functional> // Para function
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <unordered_map> // Para unordered_map (tab_completions_)
|
||||
#include <vector> // Para vector
|
||||
|
||||
#include "game/ui/console_commands.hpp" // Para CommandRegistry
|
||||
|
||||
class Surface;
|
||||
class Sprite;
|
||||
class Text;
|
||||
@@ -102,5 +103,7 @@ class Console {
|
||||
// Estado de autocompletado (TAB)
|
||||
std::vector<std::string> tab_matches_; // Comandos que coinciden con el prefijo actual
|
||||
int tab_index_{-1}; // Índice actual en tab_matches_
|
||||
std::unordered_map<std::string, std::vector<std::string>> tab_completions_; // Mapa pre-calculado en constructor
|
||||
|
||||
// Registro de comandos (metadatos YAML + handlers C++)
|
||||
CommandRegistry registry_;
|
||||
};
|
||||
|
||||
1124
source/game/ui/console_commands.cpp
Normal file
1124
source/game/ui/console_commands.cpp
Normal file
File diff suppressed because it is too large
Load Diff
56
source/game/ui/console_commands.hpp
Normal file
56
source/game/ui/console_commands.hpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional> // Para function
|
||||
#include <string> // Para string
|
||||
#include <unordered_map> // Para unordered_map
|
||||
#include <vector> // Para vector
|
||||
|
||||
// Definición de un comando de consola (metadatos cargados desde YAML)
|
||||
struct CommandDef {
|
||||
std::string keyword;
|
||||
std::string handler_id;
|
||||
std::string category;
|
||||
std::string description;
|
||||
std::string usage;
|
||||
bool instant{false};
|
||||
bool hidden{false};
|
||||
bool debug_only{false};
|
||||
bool help_hidden{false};
|
||||
bool dynamic_completions{false};
|
||||
std::unordered_map<std::string, std::vector<std::string>> completions;
|
||||
};
|
||||
|
||||
// Tipo de función handler para comandos
|
||||
using CommandHandler = std::function<std::string(const std::vector<std::string>& args)>;
|
||||
|
||||
// Proveedor de completions dinámicas: devuelve las opciones para TAB en UPPERCASE
|
||||
using DynamicCompletionProvider = std::function<std::vector<std::string>()>;
|
||||
|
||||
// Registro de comandos: une metadatos YAML con handlers C++
|
||||
class CommandRegistry {
|
||||
public:
|
||||
// Carga los metadatos de comandos desde un archivo YAML y registra los handlers
|
||||
void load(const std::string& yaml_path);
|
||||
|
||||
// Búsqueda y ejecución
|
||||
[[nodiscard]] auto findCommand(const std::string& keyword) const -> const CommandDef*;
|
||||
auto execute(const std::string& keyword, const std::vector<std::string>& args) const -> std::string;
|
||||
|
||||
// Generación de ayuda (auto-generada desde los metadatos)
|
||||
[[nodiscard]] auto generateTerminalHelp() const -> std::string;
|
||||
[[nodiscard]] auto generateConsoleHelp() const -> std::string;
|
||||
|
||||
// TAB completion
|
||||
// Devuelve las opciones de completado para un path dado (ej: "SHADER", "SHADER PRESET")
|
||||
// Combina completions estáticas del YAML con dinámicas registradas en C++
|
||||
[[nodiscard]] auto getCompletions(const std::string& path) const -> std::vector<std::string>;
|
||||
[[nodiscard]] auto getVisibleKeywords() const -> std::vector<std::string>;
|
||||
|
||||
private:
|
||||
std::vector<CommandDef> commands_;
|
||||
std::unordered_map<std::string, CommandHandler> handlers_;
|
||||
std::unordered_map<std::string, std::vector<std::string>> completions_map_;
|
||||
std::unordered_map<std::string, DynamicCompletionProvider> dynamic_providers_;
|
||||
|
||||
void registerHandlers();
|
||||
};
|
||||
@@ -6,7 +6,7 @@
|
||||
namespace Texts {
|
||||
constexpr const char* WINDOW_CAPTION = "© 2022 JailDoctor's Dilemma — JailDesigner";
|
||||
constexpr const char* COPYRIGHT = "@2022 JailDesigner";
|
||||
constexpr const char* VERSION = "1.11"; // Versión por defecto
|
||||
constexpr const char* VERSION = "1.13"; // Versión por defecto
|
||||
} // namespace Texts
|
||||
|
||||
// Tamaño de bloque
|
||||
|
||||
@@ -414,6 +414,13 @@ auto toUpper(const std::string& str) -> std::string {
|
||||
return upper_str;
|
||||
}
|
||||
|
||||
// Convierte guiones a espacios ("crt-live" → "crt live")
|
||||
auto prettyName(const std::string& str) -> std::string {
|
||||
std::string result = str;
|
||||
std::ranges::replace(result, '-', ' ');
|
||||
return result;
|
||||
}
|
||||
|
||||
// Obtiene el nombre de un fichero a partir de una ruta completa
|
||||
auto getFileName(const std::string& path) -> std::string {
|
||||
return std::filesystem::path(path).filename().string();
|
||||
|
||||
@@ -98,6 +98,7 @@ auto stringToBool(const std::string& str) -> bool; // Strin
|
||||
auto boolToString(bool value) -> std::string; // Bool a string (1/0)
|
||||
auto toLower(const std::string& str) -> std::string; // String a minúsculas
|
||||
auto toUpper(const std::string& str) -> std::string; // String a mayúsculas
|
||||
auto prettyName(const std::string& str) -> std::string; // Guiones a espacios ("crt-live" → "crt live")
|
||||
|
||||
// OPERACIONES CON STRINGS
|
||||
auto stringInVector(const std::vector<std::string>& vec, const std::string& str) -> bool; // Busca string en vector
|
||||
|
||||
125
tools/sort_palette/sort_palette.py
Normal file
125
tools/sort_palette/sort_palette.py
Normal file
@@ -0,0 +1,125 @@
|
||||
import sys
|
||||
import math
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
# ------------------------------
|
||||
# Utilidades de color
|
||||
# ------------------------------
|
||||
|
||||
def luminosidad(rgb):
|
||||
r, g, b = rgb
|
||||
return 0.2126*r + 0.7152*g + 0.0722*b
|
||||
|
||||
def distancia_rgb(c1, c2):
|
||||
return math.sqrt(
|
||||
(c1[0] - c2[0])**2 +
|
||||
(c1[1] - c2[1])**2 +
|
||||
(c1[2] - c2[2])**2
|
||||
)
|
||||
|
||||
# Paleta ZX Spectrum
|
||||
PALETA_SPECTRUM = [
|
||||
(0, 0, 0),
|
||||
(0, 0, 0),
|
||||
(0, 0, 216),
|
||||
(0, 0, 255),
|
||||
(216, 0, 0),
|
||||
(255, 0, 0),
|
||||
(216, 0, 216),
|
||||
(255, 0, 255),
|
||||
(0, 216, 0),
|
||||
(0, 255, 0),
|
||||
(0, 216, 216),
|
||||
(0, 255, 255),
|
||||
(216, 216, 0),
|
||||
(255, 255, 0),
|
||||
(216, 216, 216),
|
||||
(255, 255, 255),
|
||||
]
|
||||
|
||||
# ------------------------------
|
||||
# Lectura / escritura JASC-PAL
|
||||
# ------------------------------
|
||||
|
||||
def leer_paleta_jasc(ruta):
|
||||
with open(ruta, "r") as f:
|
||||
lineas = [l.strip() for l in f.readlines()]
|
||||
|
||||
if lineas[0] != "JASC-PAL":
|
||||
raise ValueError("El fichero no es un JASC-PAL válido")
|
||||
|
||||
num_colores = int(lineas[2])
|
||||
colores = []
|
||||
|
||||
for i in range(num_colores):
|
||||
r, g, b = map(int, lineas[3 + i].split())
|
||||
colores.append((r, g, b))
|
||||
|
||||
return colores
|
||||
|
||||
def guardar_paleta_jasc(ruta, colores):
|
||||
with open(ruta, "w") as f:
|
||||
f.write("JASC-PAL\n")
|
||||
f.write("0100\n")
|
||||
f.write(f"{len(colores)}\n")
|
||||
for r, g, b in colores:
|
||||
f.write(f"{r} {g} {b}\n")
|
||||
|
||||
# ------------------------------
|
||||
# Métodos de ordenación
|
||||
# ------------------------------
|
||||
|
||||
def ordenar_por_luminosidad(colores):
|
||||
return sorted(colores, key=luminosidad)
|
||||
|
||||
def ordenar_por_similitud_spectrum(colores):
|
||||
colores_disponibles = colores.copy()
|
||||
resultado = []
|
||||
|
||||
for ref in PALETA_SPECTRUM:
|
||||
mejor = min(colores_disponibles, key=lambda c: distancia_rgb(c, ref))
|
||||
resultado.append(mejor)
|
||||
colores_disponibles.remove(mejor)
|
||||
|
||||
return resultado
|
||||
|
||||
# ------------------------------
|
||||
# Main
|
||||
# ------------------------------
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 4:
|
||||
print("Uso: python ordenar_paleta.py entrada.pal salida.pal [luminosidad|spectrum]")
|
||||
return
|
||||
|
||||
entrada = sys.argv[1]
|
||||
salida = sys.argv[2]
|
||||
modo = sys.argv[3].lower()
|
||||
|
||||
colores = leer_paleta_jasc(entrada)
|
||||
|
||||
if modo == "luminosidad":
|
||||
colores_ordenados = ordenar_por_luminosidad(colores)
|
||||
elif modo == "spectrum":
|
||||
colores_ordenados = ordenar_por_similitud_spectrum(colores)
|
||||
else:
|
||||
print(f"Modo desconocido: {modo}")
|
||||
print("Modos disponibles: luminosidad, spectrum")
|
||||
return
|
||||
|
||||
# Si salida == entrada, sobrescribimos de forma segura
|
||||
if entrada == salida:
|
||||
# Guardamos primero en un temporal para evitar corrupción si algo falla
|
||||
with tempfile.NamedTemporaryFile(delete=False) as tmp:
|
||||
temp_path = tmp.name
|
||||
guardar_paleta_jasc(temp_path, colores_ordenados)
|
||||
os.replace(temp_path, entrada)
|
||||
print(f"Paleta sobrescrita ({modo}) en {entrada}")
|
||||
else:
|
||||
guardar_paleta_jasc(salida, colores_ordenados)
|
||||
print(f"Paleta ordenada ({modo}) guardada en {salida}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user