Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 46dc81124f | |||
| 8dcc1d282a | |||
| dc1470ec0e | |||
| 052607873b | |||
| 6faa80eef4 | |||
| 9ffd29bea8 | |||
| 7287d65ca3 | |||
| cea5492abc | |||
| cccb2359cf | |||
| b5e822c65f | |||
| c14774478c | |||
| 3c3e012386 | |||
| 44b6f6830d | |||
| 0d12591925 | |||
| 22d6ac2fbf | |||
| acaf434e5c | |||
| 5f25562d52 | |||
| fc28586940 | |||
| 273d9304dc | |||
| 3a5b16346b | |||
| 0e61b94848 | |||
| eca5a55d3c | |||
| dccd0d41e4 | |||
| 20bac58814 | |||
| a6fae7b001 | |||
| b31346830f | |||
| b6fec3eba7 | |||
| 606388227c | |||
| a2caf95005 | |||
| 0bfb535d4d |
@@ -21,7 +21,7 @@ Checks:
|
||||
WarningsAsErrors: '*'
|
||||
# Solo incluir archivos de tu código fuente (external tiene su propio .clang-tidy)
|
||||
# Excluye jail_audio.hpp del análisis
|
||||
HeaderFilterRegex: '^source/(?!core/audio/jail_audio\.hpp).*'
|
||||
HeaderFilterRegex: 'source/(?!core/audio/jail_audio\.hpp|core/rendering/sdl3gpu/.*_spv\.h).*'
|
||||
FormatStyle: file
|
||||
|
||||
CheckOptions:
|
||||
|
||||
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,6 +96,13 @@ 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
|
||||
source/game/editor/tile_picker.cpp
|
||||
source/game/editor/mini_map.cpp
|
||||
|
||||
# Game - UI
|
||||
source/game/ui/console.cpp
|
||||
source/game/ui/console_commands.cpp
|
||||
|
||||
@@ -4,620 +4,369 @@
|
||||
assets:
|
||||
# FONTS
|
||||
fonts:
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/font/smb2.gif
|
||||
- type: FONT
|
||||
path: ${PREFIX}/data/font/smb2.fnt
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/font/aseprite.gif
|
||||
- type: FONT
|
||||
path: ${PREFIX}/data/font/aseprite.fnt
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/font/gauntlet.gif
|
||||
- type: FONT
|
||||
path: ${PREFIX}/data/font/gauntlet.fnt
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/font/subatomic.gif
|
||||
- type: FONT
|
||||
path: ${PREFIX}/data/font/subatomic.fnt
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/font/8bithud.gif
|
||||
- type: FONT
|
||||
path: ${PREFIX}/data/font/8bithud.fnt
|
||||
BITMAP:
|
||||
- ${PREFIX}/data/font/smb2.gif
|
||||
- ${PREFIX}/data/font/aseprite.gif
|
||||
- ${PREFIX}/data/font/gauntlet.gif
|
||||
- ${PREFIX}/data/font/subatomic.gif
|
||||
- ${PREFIX}/data/font/8bithud.gif
|
||||
FONT:
|
||||
- ${PREFIX}/data/font/smb2.fnt
|
||||
- ${PREFIX}/data/font/aseprite.fnt
|
||||
- ${PREFIX}/data/font/gauntlet.fnt
|
||||
- ${PREFIX}/data/font/subatomic.fnt
|
||||
- ${PREFIX}/data/font/8bithud.fnt
|
||||
|
||||
# PALETTES
|
||||
palettes:
|
||||
- type: PALETTE
|
||||
path: ${PREFIX}/data/palette/zx-spectrum.pal
|
||||
- type: PALETTE
|
||||
path: ${PREFIX}/data/palette/zx-spectrum-adjusted.pal
|
||||
- type: PALETTE
|
||||
path: ${PREFIX}/data/palette/zxarne-5.2.pal
|
||||
- type: PALETTE
|
||||
path: ${PREFIX}/data/palette/black-and-white.pal
|
||||
- type: PALETTE
|
||||
path: ${PREFIX}/data/palette/green-phosphor.pal
|
||||
- type: PALETTE
|
||||
path: ${PREFIX}/data/palette/orange-screen.pal
|
||||
- type: PALETTE
|
||||
path: ${PREFIX}/data/palette/ruzx-spectrum.pal
|
||||
- type: PALETTE
|
||||
path: ${PREFIX}/data/palette/ruzx-spectrum-revision-2.pal
|
||||
- type: PALETTE
|
||||
path: ${PREFIX}/data/palette/pico-8.pal
|
||||
- type: PALETTE
|
||||
path: ${PREFIX}/data/palette/sweetie.pal
|
||||
- type: PALETTE
|
||||
path: ${PREFIX}/data/palette/island-joy.pal
|
||||
- type: PALETTE
|
||||
path: ${PREFIX}/data/palette/lost-century.pal
|
||||
- type: PALETTE
|
||||
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
|
||||
PALETTE:
|
||||
- ${PREFIX}/data/palette/zx-spectrum.pal
|
||||
- ${PREFIX}/data/palette/zx-spectrum-adjusted.pal
|
||||
- ${PREFIX}/data/palette/zxarne-5.2.pal
|
||||
- ${PREFIX}/data/palette/black-and-white.pal
|
||||
- ${PREFIX}/data/palette/green-phosphor.pal
|
||||
- ${PREFIX}/data/palette/orange-screen.pal
|
||||
- ${PREFIX}/data/palette/ruzx-spectrum.pal
|
||||
- ${PREFIX}/data/palette/ruzx-spectrum-revision-2.pal
|
||||
- ${PREFIX}/data/palette/pico-8.pal
|
||||
- ${PREFIX}/data/palette/sweetie.pal
|
||||
- ${PREFIX}/data/palette/island-joy.pal
|
||||
- ${PREFIX}/data/palette/lost-century.pal
|
||||
- ${PREFIX}/data/palette/na.pal
|
||||
- ${PREFIX}/data/palette/steam-lords.pal
|
||||
- ${PREFIX}/data/palette/winds-seed-pc98.pal
|
||||
- ${PREFIX}/data/palette/psychic-fibre.pal
|
||||
- ${PREFIX}/data/palette/shido-cyberneon.pal
|
||||
- ${PREFIX}/data/palette/darkseed.pal
|
||||
- ${PREFIX}/data/palette/antiquity.pal
|
||||
- ${PREFIX}/data/palette/bubblegum.pal
|
||||
- ${PREFIX}/data/palette/vanilla-milkshake.pal
|
||||
- ${PREFIX}/data/palette/aged-terracotta.pal
|
||||
- ${PREFIX}/data/palette/h16da.pal
|
||||
|
||||
# LOCALE
|
||||
locale:
|
||||
- type: DATA
|
||||
path: ${PREFIX}/data/locale/en.yaml
|
||||
- type: DATA
|
||||
path: ${PREFIX}/data/locale/ca.yaml
|
||||
DATA:
|
||||
- ${PREFIX}/data/locale/en.yaml
|
||||
- ${PREFIX}/data/locale/ca.yaml
|
||||
|
||||
# INPUT
|
||||
input:
|
||||
- type: DATA
|
||||
path: ${PREFIX}/gamecontrollerdb.txt
|
||||
DATA:
|
||||
- ${PREFIX}/gamecontrollerdb.txt
|
||||
|
||||
# SYSTEM
|
||||
system:
|
||||
- type: DATA
|
||||
path: ${SYSTEM_FOLDER}/config.yaml
|
||||
DATA:
|
||||
- path: ${SYSTEM_FOLDER}/config.yaml
|
||||
required: false
|
||||
absolute: true
|
||||
- type: DATA
|
||||
path: ${SYSTEM_FOLDER}/debug.yaml
|
||||
- path: ${SYSTEM_FOLDER}/debug.yaml
|
||||
required: false
|
||||
absolute: true
|
||||
- type: DATA
|
||||
path: ${SYSTEM_FOLDER}/stats_buffer.csv
|
||||
- path: ${SYSTEM_FOLDER}/editor.yaml
|
||||
required: false
|
||||
absolute: true
|
||||
- type: DATA
|
||||
path: ${SYSTEM_FOLDER}/stats.csv
|
||||
- path: ${SYSTEM_FOLDER}/stats_buffer.csv
|
||||
required: false
|
||||
absolute: true
|
||||
- type: DATA
|
||||
path: ${SYSTEM_FOLDER}/cheevos.bin
|
||||
- path: ${SYSTEM_FOLDER}/stats.csv
|
||||
required: false
|
||||
absolute: true
|
||||
- type: DATA
|
||||
path: ${SYSTEM_FOLDER}/shaders/postfx.yaml
|
||||
- path: ${SYSTEM_FOLDER}/cheevos.bin
|
||||
required: false
|
||||
absolute: true
|
||||
- type: DATA
|
||||
path: ${SYSTEM_FOLDER}/shaders/crtpi.yaml
|
||||
- path: ${SYSTEM_FOLDER}/shaders/postfx.yaml
|
||||
required: false
|
||||
absolute: true
|
||||
- path: ${SYSTEM_FOLDER}/shaders/crtpi.yaml
|
||||
required: false
|
||||
absolute: true
|
||||
|
||||
# CONSOLE
|
||||
console:
|
||||
- type: DATA
|
||||
path: ${PREFIX}/data/console/commands.yaml
|
||||
DATA:
|
||||
- ${PREFIX}/data/console/commands.yaml
|
||||
|
||||
# ROOMS
|
||||
rooms:
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/01.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/02.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/03.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/04.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/05.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/06.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/07.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/08.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/09.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/10.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/11.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/12.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/13.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/14.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/15.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/16.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/17.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/18.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/19.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/20.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/21.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/22.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/23.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/24.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/25.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/26.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/27.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/28.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/29.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/30.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/31.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/32.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/33.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/34.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/35.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/36.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/37.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/38.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/39.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/40.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/41.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/42.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/43.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/44.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/45.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/46.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/47.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/48.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/49.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/50.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/51.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/52.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/53.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/54.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/55.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/56.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/57.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/58.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/59.yaml
|
||||
- type: ROOM
|
||||
path: ${PREFIX}/data/room/60.yaml
|
||||
ROOM:
|
||||
- ${PREFIX}/data/room/01.yaml
|
||||
- ${PREFIX}/data/room/02.yaml
|
||||
- ${PREFIX}/data/room/03.yaml
|
||||
- ${PREFIX}/data/room/04.yaml
|
||||
- ${PREFIX}/data/room/05.yaml
|
||||
- ${PREFIX}/data/room/06.yaml
|
||||
- ${PREFIX}/data/room/07.yaml
|
||||
- ${PREFIX}/data/room/08.yaml
|
||||
- ${PREFIX}/data/room/09.yaml
|
||||
- ${PREFIX}/data/room/10.yaml
|
||||
- ${PREFIX}/data/room/11.yaml
|
||||
- ${PREFIX}/data/room/12.yaml
|
||||
- ${PREFIX}/data/room/13.yaml
|
||||
- ${PREFIX}/data/room/14.yaml
|
||||
- ${PREFIX}/data/room/15.yaml
|
||||
- ${PREFIX}/data/room/16.yaml
|
||||
- ${PREFIX}/data/room/17.yaml
|
||||
- ${PREFIX}/data/room/18.yaml
|
||||
- ${PREFIX}/data/room/19.yaml
|
||||
- ${PREFIX}/data/room/20.yaml
|
||||
- ${PREFIX}/data/room/21.yaml
|
||||
- ${PREFIX}/data/room/22.yaml
|
||||
- ${PREFIX}/data/room/23.yaml
|
||||
- ${PREFIX}/data/room/24.yaml
|
||||
- ${PREFIX}/data/room/25.yaml
|
||||
- ${PREFIX}/data/room/26.yaml
|
||||
- ${PREFIX}/data/room/27.yaml
|
||||
- ${PREFIX}/data/room/28.yaml
|
||||
- ${PREFIX}/data/room/29.yaml
|
||||
- ${PREFIX}/data/room/30.yaml
|
||||
- ${PREFIX}/data/room/31.yaml
|
||||
- ${PREFIX}/data/room/32.yaml
|
||||
- ${PREFIX}/data/room/33.yaml
|
||||
- ${PREFIX}/data/room/34.yaml
|
||||
- ${PREFIX}/data/room/35.yaml
|
||||
- ${PREFIX}/data/room/36.yaml
|
||||
- ${PREFIX}/data/room/37.yaml
|
||||
- ${PREFIX}/data/room/38.yaml
|
||||
- ${PREFIX}/data/room/39.yaml
|
||||
- ${PREFIX}/data/room/40.yaml
|
||||
- ${PREFIX}/data/room/41.yaml
|
||||
- ${PREFIX}/data/room/42.yaml
|
||||
- ${PREFIX}/data/room/43.yaml
|
||||
- ${PREFIX}/data/room/44.yaml
|
||||
- ${PREFIX}/data/room/45.yaml
|
||||
- ${PREFIX}/data/room/46.yaml
|
||||
- ${PREFIX}/data/room/47.yaml
|
||||
- ${PREFIX}/data/room/48.yaml
|
||||
- ${PREFIX}/data/room/49.yaml
|
||||
- ${PREFIX}/data/room/50.yaml
|
||||
- ${PREFIX}/data/room/51.yaml
|
||||
- ${PREFIX}/data/room/52.yaml
|
||||
- ${PREFIX}/data/room/53.yaml
|
||||
- ${PREFIX}/data/room/54.yaml
|
||||
- ${PREFIX}/data/room/55.yaml
|
||||
- ${PREFIX}/data/room/56.yaml
|
||||
- ${PREFIX}/data/room/57.yaml
|
||||
- ${PREFIX}/data/room/58.yaml
|
||||
- ${PREFIX}/data/room/59.yaml
|
||||
- ${PREFIX}/data/room/60.yaml
|
||||
|
||||
# TILESETS
|
||||
tilesets:
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/tilesets/standard.gif
|
||||
BITMAP:
|
||||
- ${PREFIX}/data/tilesets/standard.gif
|
||||
|
||||
# ENEMIES
|
||||
enemies:
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/abad_bell.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/abad_bell.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/abad.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/abad.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/amstrad_cs.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/amstrad_cs.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/flying_arounder.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/flying_arounder.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/stopped_arounder.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/stopped_arounder.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/walking_arounder.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/walking_arounder.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/arounders_door.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/arounders_door.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/arounders_machine.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/arounders_machine.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/bat.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/bat.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/batman_bell.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/batman_bell.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/batman_fire.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/batman_fire.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/batman.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/batman.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/bell.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/bell.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/bin.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/bin.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/bird.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/bird.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/breakout.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/breakout.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/bry.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/bry.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/chip.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/chip.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/code.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/code.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/congo.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/congo.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/crosshair.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/crosshair.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/demon.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/demon.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/dimallas.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/dimallas.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/floppy.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/floppy.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/dong.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/dong.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/guitar.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/guitar.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/heavy.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/heavy.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/jailer1.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/jailer1.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/jailer2.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/jailer2.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/jailer3.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/jailer3.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/jailbattle_alien.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/jailbattle_alien.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/jailbattle_human.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/jailbattle_human.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/jeannine.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/jeannine.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/lamp.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/lamp.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/lord_abad.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/lord_abad.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/matatunos.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/matatunos.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/mummy.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/mummy.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/paco.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/paco.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/elsa.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/elsa.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/qvoid.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/qvoid.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/robot.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/robot.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/sam.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/sam.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/shock.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/shock.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/sigmasua.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/sigmasua.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/spark.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/spark.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/special/aerojailer.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/special/aerojailer.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/special/arounder.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/special/arounder.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/special/pepe_rosita_job.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/special/pepe_rosita_job.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/special/shooting_star.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/special/shooting_star.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/spider.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/spider.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/tree_thing.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/tree_thing.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/tuno.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/tuno.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/tv_panel.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/tv_panel.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/tv.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/tv.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/upv_student.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/upv_student.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/wave.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/wave.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/enemies/z80.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/enemies/z80.gif
|
||||
ANIMATION:
|
||||
- ${PREFIX}/data/enemies/abad_bell.yaml
|
||||
- ${PREFIX}/data/enemies/abad.yaml
|
||||
- ${PREFIX}/data/enemies/amstrad_cs.yaml
|
||||
- ${PREFIX}/data/enemies/flying_arounder.yaml
|
||||
- ${PREFIX}/data/enemies/stopped_arounder.yaml
|
||||
- ${PREFIX}/data/enemies/walking_arounder.yaml
|
||||
- ${PREFIX}/data/enemies/arounders_door.yaml
|
||||
- ${PREFIX}/data/enemies/arounders_machine.yaml
|
||||
- ${PREFIX}/data/enemies/bat.yaml
|
||||
- ${PREFIX}/data/enemies/batman_bell.yaml
|
||||
- ${PREFIX}/data/enemies/batman_fire.yaml
|
||||
- ${PREFIX}/data/enemies/batman.yaml
|
||||
- ${PREFIX}/data/enemies/bell.yaml
|
||||
- ${PREFIX}/data/enemies/bin.yaml
|
||||
- ${PREFIX}/data/enemies/bird.yaml
|
||||
- ${PREFIX}/data/enemies/breakout.yaml
|
||||
- ${PREFIX}/data/enemies/bry.yaml
|
||||
- ${PREFIX}/data/enemies/chip.yaml
|
||||
- ${PREFIX}/data/enemies/code.yaml
|
||||
- ${PREFIX}/data/enemies/congo.yaml
|
||||
- ${PREFIX}/data/enemies/crosshair.yaml
|
||||
- ${PREFIX}/data/enemies/demon.yaml
|
||||
- ${PREFIX}/data/enemies/dimallas.yaml
|
||||
- ${PREFIX}/data/enemies/floppy.yaml
|
||||
- ${PREFIX}/data/enemies/dong.yaml
|
||||
- ${PREFIX}/data/enemies/guitar.yaml
|
||||
- ${PREFIX}/data/enemies/heavy.yaml
|
||||
- ${PREFIX}/data/enemies/jailer1.yaml
|
||||
- ${PREFIX}/data/enemies/jailer2.yaml
|
||||
- ${PREFIX}/data/enemies/jailer3.yaml
|
||||
- ${PREFIX}/data/enemies/jailbattle_alien.yaml
|
||||
- ${PREFIX}/data/enemies/jailbattle_human.yaml
|
||||
- ${PREFIX}/data/enemies/jeannine.yaml
|
||||
- ${PREFIX}/data/enemies/lamp.yaml
|
||||
- ${PREFIX}/data/enemies/lord_abad.yaml
|
||||
- ${PREFIX}/data/enemies/matatunos.yaml
|
||||
- ${PREFIX}/data/enemies/mummy.yaml
|
||||
- ${PREFIX}/data/enemies/paco.yaml
|
||||
- ${PREFIX}/data/enemies/elsa.yaml
|
||||
- ${PREFIX}/data/enemies/qvoid.yaml
|
||||
- ${PREFIX}/data/enemies/robot.yaml
|
||||
- ${PREFIX}/data/enemies/sam.yaml
|
||||
- ${PREFIX}/data/enemies/shock.yaml
|
||||
- ${PREFIX}/data/enemies/sigmasua.yaml
|
||||
- ${PREFIX}/data/enemies/spark.yaml
|
||||
- ${PREFIX}/data/enemies/special/aerojailer.yaml
|
||||
- ${PREFIX}/data/enemies/special/arounder.yaml
|
||||
- ${PREFIX}/data/enemies/special/pepe_rosita_job.yaml
|
||||
- ${PREFIX}/data/enemies/special/shooting_star.yaml
|
||||
- ${PREFIX}/data/enemies/spider.yaml
|
||||
- ${PREFIX}/data/enemies/tree_thing.yaml
|
||||
- ${PREFIX}/data/enemies/tuno.yaml
|
||||
- ${PREFIX}/data/enemies/tv_panel.yaml
|
||||
- ${PREFIX}/data/enemies/tv.yaml
|
||||
- ${PREFIX}/data/enemies/upv_student.yaml
|
||||
- ${PREFIX}/data/enemies/wave.yaml
|
||||
- ${PREFIX}/data/enemies/z80.yaml
|
||||
BITMAP:
|
||||
- ${PREFIX}/data/enemies/abad_bell.gif
|
||||
- ${PREFIX}/data/enemies/abad.gif
|
||||
- ${PREFIX}/data/enemies/amstrad_cs.gif
|
||||
- ${PREFIX}/data/enemies/flying_arounder.gif
|
||||
- ${PREFIX}/data/enemies/stopped_arounder.gif
|
||||
- ${PREFIX}/data/enemies/walking_arounder.gif
|
||||
- ${PREFIX}/data/enemies/arounders_door.gif
|
||||
- ${PREFIX}/data/enemies/arounders_machine.gif
|
||||
- ${PREFIX}/data/enemies/bat.gif
|
||||
- ${PREFIX}/data/enemies/batman_bell.gif
|
||||
- ${PREFIX}/data/enemies/batman_fire.gif
|
||||
- ${PREFIX}/data/enemies/batman.gif
|
||||
- ${PREFIX}/data/enemies/bell.gif
|
||||
- ${PREFIX}/data/enemies/bin.gif
|
||||
- ${PREFIX}/data/enemies/bird.gif
|
||||
- ${PREFIX}/data/enemies/breakout.gif
|
||||
- ${PREFIX}/data/enemies/bry.gif
|
||||
- ${PREFIX}/data/enemies/chip.gif
|
||||
- ${PREFIX}/data/enemies/code.gif
|
||||
- ${PREFIX}/data/enemies/congo.gif
|
||||
- ${PREFIX}/data/enemies/crosshair.gif
|
||||
- ${PREFIX}/data/enemies/demon.gif
|
||||
- ${PREFIX}/data/enemies/dimallas.gif
|
||||
- ${PREFIX}/data/enemies/floppy.gif
|
||||
- ${PREFIX}/data/enemies/dong.gif
|
||||
- ${PREFIX}/data/enemies/guitar.gif
|
||||
- ${PREFIX}/data/enemies/heavy.gif
|
||||
- ${PREFIX}/data/enemies/jailer1.gif
|
||||
- ${PREFIX}/data/enemies/jailer2.gif
|
||||
- ${PREFIX}/data/enemies/jailer3.gif
|
||||
- ${PREFIX}/data/enemies/jailbattle_alien.gif
|
||||
- ${PREFIX}/data/enemies/jailbattle_human.gif
|
||||
- ${PREFIX}/data/enemies/jeannine.gif
|
||||
- ${PREFIX}/data/enemies/lamp.gif
|
||||
- ${PREFIX}/data/enemies/lord_abad.gif
|
||||
- ${PREFIX}/data/enemies/matatunos.gif
|
||||
- ${PREFIX}/data/enemies/mummy.gif
|
||||
- ${PREFIX}/data/enemies/paco.gif
|
||||
- ${PREFIX}/data/enemies/elsa.gif
|
||||
- ${PREFIX}/data/enemies/qvoid.gif
|
||||
- ${PREFIX}/data/enemies/robot.gif
|
||||
- ${PREFIX}/data/enemies/sam.gif
|
||||
- ${PREFIX}/data/enemies/shock.gif
|
||||
- ${PREFIX}/data/enemies/sigmasua.gif
|
||||
- ${PREFIX}/data/enemies/spark.gif
|
||||
- ${PREFIX}/data/enemies/special/aerojailer.gif
|
||||
- ${PREFIX}/data/enemies/special/arounder.gif
|
||||
- ${PREFIX}/data/enemies/special/pepe_rosita_job.gif
|
||||
- ${PREFIX}/data/enemies/special/shooting_star.gif
|
||||
- ${PREFIX}/data/enemies/spider.gif
|
||||
- ${PREFIX}/data/enemies/tree_thing.gif
|
||||
- ${PREFIX}/data/enemies/tuno.gif
|
||||
- ${PREFIX}/data/enemies/tv_panel.gif
|
||||
- ${PREFIX}/data/enemies/tv.gif
|
||||
- ${PREFIX}/data/enemies/upv_student.gif
|
||||
- ${PREFIX}/data/enemies/wave.gif
|
||||
- ${PREFIX}/data/enemies/z80.gif
|
||||
|
||||
# PLAYER
|
||||
player:
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/player/player.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/player/player.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/player/player2.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/player/player2.yaml
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/player/player_game_over.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/player/player_game_over.yaml
|
||||
BITMAP:
|
||||
- ${PREFIX}/data/player/player.gif
|
||||
- ${PREFIX}/data/player/player2.gif
|
||||
- ${PREFIX}/data/player/player_game_over.gif
|
||||
ANIMATION:
|
||||
- ${PREFIX}/data/player/player.yaml
|
||||
- ${PREFIX}/data/player/player2.yaml
|
||||
- ${PREFIX}/data/player/player_game_over.yaml
|
||||
|
||||
# ITEMS
|
||||
items:
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/items/items.gif
|
||||
BITMAP:
|
||||
- ${PREFIX}/data/items/items.gif
|
||||
|
||||
# MUSIC
|
||||
music:
|
||||
- type: MUSIC
|
||||
path: ${PREFIX}/data/music/title.ogg
|
||||
- type: MUSIC
|
||||
path: ${PREFIX}/data/music/game.ogg
|
||||
- type: MUSIC
|
||||
path: ${PREFIX}/data/music/loading_data1.ogg
|
||||
- type: MUSIC
|
||||
path: ${PREFIX}/data/music/loading_data2.ogg
|
||||
- type: MUSIC
|
||||
path: ${PREFIX}/data/music/loading_header.ogg
|
||||
- type: MUSIC
|
||||
path: ${PREFIX}/data/music/loading_screen_color.ogg
|
||||
- type: MUSIC
|
||||
path: ${PREFIX}/data/music/loading_screen_data.ogg
|
||||
- type: MUSIC
|
||||
path: ${PREFIX}/data/music/ending1.ogg
|
||||
- type: MUSIC
|
||||
path: ${PREFIX}/data/music/ending2.ogg
|
||||
- type: MUSIC
|
||||
path: ${PREFIX}/data/music/game_over.ogg
|
||||
MUSIC:
|
||||
- ${PREFIX}/data/music/title.ogg
|
||||
- ${PREFIX}/data/music/game.ogg
|
||||
- ${PREFIX}/data/music/loading_data1.ogg
|
||||
- ${PREFIX}/data/music/loading_data2.ogg
|
||||
- ${PREFIX}/data/music/loading_header.ogg
|
||||
- ${PREFIX}/data/music/loading_screen_color.ogg
|
||||
- ${PREFIX}/data/music/loading_screen_data.ogg
|
||||
- ${PREFIX}/data/music/ending1.ogg
|
||||
- ${PREFIX}/data/music/ending2.ogg
|
||||
- ${PREFIX}/data/music/game_over.ogg
|
||||
|
||||
# SOUNDS
|
||||
sounds:
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/item.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/death.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/notify.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump1.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump2.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump3.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump4.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump5.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump6.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump7.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump8.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump9.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump10.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump11.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump12.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump13.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump14.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump15.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump16.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump17.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump18.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump19.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump20.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump21.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump22.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump23.wav
|
||||
- type: SOUND
|
||||
path: ${PREFIX}/data/sound/jump24.wav
|
||||
SOUND:
|
||||
- ${PREFIX}/data/sound/item.wav
|
||||
- ${PREFIX}/data/sound/death.wav
|
||||
- ${PREFIX}/data/sound/notify.wav
|
||||
- ${PREFIX}/data/sound/jump1.wav
|
||||
- ${PREFIX}/data/sound/jump2.wav
|
||||
- ${PREFIX}/data/sound/jump3.wav
|
||||
- ${PREFIX}/data/sound/jump4.wav
|
||||
- ${PREFIX}/data/sound/jump5.wav
|
||||
- ${PREFIX}/data/sound/jump6.wav
|
||||
- ${PREFIX}/data/sound/jump7.wav
|
||||
- ${PREFIX}/data/sound/jump8.wav
|
||||
- ${PREFIX}/data/sound/jump9.wav
|
||||
- ${PREFIX}/data/sound/jump10.wav
|
||||
- ${PREFIX}/data/sound/jump11.wav
|
||||
- ${PREFIX}/data/sound/jump12.wav
|
||||
- ${PREFIX}/data/sound/jump13.wav
|
||||
- ${PREFIX}/data/sound/jump14.wav
|
||||
- ${PREFIX}/data/sound/jump15.wav
|
||||
- ${PREFIX}/data/sound/jump16.wav
|
||||
- ${PREFIX}/data/sound/jump17.wav
|
||||
- ${PREFIX}/data/sound/jump18.wav
|
||||
- ${PREFIX}/data/sound/jump19.wav
|
||||
- ${PREFIX}/data/sound/jump20.wav
|
||||
- ${PREFIX}/data/sound/jump21.wav
|
||||
- ${PREFIX}/data/sound/jump22.wav
|
||||
- ${PREFIX}/data/sound/jump23.wav
|
||||
- ${PREFIX}/data/sound/jump24.wav
|
||||
|
||||
# LOGO
|
||||
logo:
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/logo/jailgames.gif
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/logo/since_1998.gif
|
||||
BITMAP:
|
||||
- ${PREFIX}/data/logo/jailgames.gif
|
||||
- ${PREFIX}/data/logo/since_1998.gif
|
||||
|
||||
# LOADING
|
||||
loading:
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/loading/loading_screen_bn.gif
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/loading/loading_screen_color.gif
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/loading/program_jaildoc.gif
|
||||
BITMAP:
|
||||
- ${PREFIX}/data/loading/loading_screen_bn.gif
|
||||
- ${PREFIX}/data/loading/loading_screen_color.gif
|
||||
- ${PREFIX}/data/loading/program_jaildoc.gif
|
||||
|
||||
# TITLE
|
||||
title:
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/title/title_logo.gif
|
||||
BITMAP:
|
||||
- ${PREFIX}/data/title/title_logo.gif
|
||||
|
||||
# ENDING
|
||||
ending:
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/ending/ending1.gif
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/ending/ending2.gif
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/ending/ending3.gif
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/ending/ending4.gif
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/ending/ending5.gif
|
||||
BITMAP:
|
||||
- ${PREFIX}/data/ending/ending1.gif
|
||||
- ${PREFIX}/data/ending/ending2.gif
|
||||
- ${PREFIX}/data/ending/ending3.gif
|
||||
- ${PREFIX}/data/ending/ending4.gif
|
||||
- ${PREFIX}/data/ending/ending5.gif
|
||||
|
||||
# CREDITS
|
||||
credits:
|
||||
- type: BITMAP
|
||||
path: ${PREFIX}/data/credits/shine.gif
|
||||
- type: ANIMATION
|
||||
path: ${PREFIX}/data/credits/shine.yaml
|
||||
BITMAP:
|
||||
- ${PREFIX}/data/credits/shine.gif
|
||||
ANIMATION:
|
||||
- ${PREFIX}/data/credits/shine.yaml
|
||||
|
||||
@@ -14,9 +14,12 @@
|
||||
# dynamic_completions - (optional) Completions generated at runtime (default: false)
|
||||
# completions - (optional) Static TAB completion tree
|
||||
# debug_extras - (optional) Overrides applied in debug builds
|
||||
# scope - (optional) Visibility scope: global, game, editor, debug (default: global)
|
||||
# Can be a string or a list. Categories can set a default scope for all commands.
|
||||
|
||||
categories:
|
||||
- name: VIDEO
|
||||
scope: game
|
||||
commands:
|
||||
- keyword: SS
|
||||
handler: cmd_ss
|
||||
@@ -86,6 +89,7 @@ categories:
|
||||
dynamic_completions: true
|
||||
|
||||
- name: AUDIO
|
||||
scope: game
|
||||
commands:
|
||||
- keyword: AUDIO
|
||||
handler: cmd_audio
|
||||
@@ -109,6 +113,7 @@ categories:
|
||||
SOUND: [ON, OFF, VOL]
|
||||
|
||||
- name: GAME
|
||||
scope: game
|
||||
commands:
|
||||
- keyword: PLAYER
|
||||
handler: cmd_player
|
||||
@@ -137,6 +142,7 @@ categories:
|
||||
description: Quit application
|
||||
usage: EXIT
|
||||
instant: true
|
||||
scope: global
|
||||
|
||||
- keyword: QUIT
|
||||
handler: cmd_quit
|
||||
@@ -144,8 +150,10 @@ categories:
|
||||
usage: QUIT
|
||||
instant: true
|
||||
help_hidden: true
|
||||
scope: global
|
||||
|
||||
- name: INFO
|
||||
scope: global
|
||||
commands:
|
||||
- keyword: SHOW
|
||||
handler: cmd_show
|
||||
@@ -174,7 +182,8 @@ categories:
|
||||
- keyword: HELP
|
||||
handler: cmd_help
|
||||
description: "Show this help"
|
||||
usage: "HELP / ?"
|
||||
usage: "HELP [<command>]"
|
||||
dynamic_completions: true
|
||||
|
||||
- keyword: "?"
|
||||
handler: cmd_help
|
||||
@@ -182,6 +191,7 @@ categories:
|
||||
|
||||
- name: DEBUG
|
||||
debug_only: true
|
||||
scope: debug
|
||||
commands:
|
||||
- keyword: DEBUG
|
||||
handler: cmd_debug
|
||||
@@ -201,9 +211,11 @@ categories:
|
||||
- keyword: ROOM
|
||||
handler: cmd_room
|
||||
description: "Change to room number (GAME only)"
|
||||
usage: "ROOM <1-60>|NEXT|PREV"
|
||||
usage: "ROOM <1-60>|NEXT|PREV|LEFT|RIGHT|UP|DOWN"
|
||||
scope: [debug, editor]
|
||||
completions:
|
||||
ROOM: [NEXT, PREV]
|
||||
ROOM: [NEXT, PREV, LEFT, RIGHT, UP, DOWN, NEW, DELETE]
|
||||
ROOM NEW: [LEFT, RIGHT, UP, DOWN]
|
||||
|
||||
- keyword: SCENE
|
||||
handler: cmd_scene
|
||||
@@ -212,13 +224,54 @@ categories:
|
||||
completions:
|
||||
SCENE: [LOGO, LOADING, TITLE, CREDITS, GAME, ENDING, ENDING2, RESTART]
|
||||
|
||||
- keyword: EDIT
|
||||
handler: cmd_edit
|
||||
description: "Map editor mode (GAME only)"
|
||||
usage: "EDIT [ON|OFF|REVERT|SHOW|HIDE|MAPBG|MAPCONN] [...]"
|
||||
scope: [debug, editor]
|
||||
dynamic_completions: true
|
||||
completions:
|
||||
EDIT: [ON, OFF, REVERT, SHOW, HIDE, MAPBG, MAPCONN]
|
||||
EDIT SHOW: [INFO, GRID]
|
||||
EDIT HIDE: [INFO, GRID]
|
||||
|
||||
- name: EDITOR
|
||||
debug_only: true
|
||||
scope: editor
|
||||
commands:
|
||||
- keyword: ENEMY
|
||||
handler: cmd_enemy
|
||||
description: "Add, delete or duplicate enemy"
|
||||
usage: "ENEMY <ADD|DELETE|DUPLICATE>"
|
||||
completions:
|
||||
ENEMY: [ADD, DELETE, DUPLICATE]
|
||||
|
||||
- keyword: ITEM
|
||||
handler: cmd_item
|
||||
description: "Add, delete or duplicate item"
|
||||
usage: "ITEM <ADD|DELETE|DUPLICATE>"
|
||||
completions:
|
||||
ITEM: [ADD, DELETE, DUPLICATE]
|
||||
|
||||
- keyword: SET
|
||||
handler: cmd_set
|
||||
description: "Set property (enemy, item or room)"
|
||||
usage: "SET <property> <value>"
|
||||
dynamic_completions: true
|
||||
completions:
|
||||
SET FLIP: [ON, OFF]
|
||||
SET MIRROR: [ON, OFF]
|
||||
SET CONVEYOR: [LEFT, NONE, RIGHT]
|
||||
|
||||
- name: CHEATS
|
||||
scope: [game, editor]
|
||||
commands:
|
||||
- keyword: CHEAT
|
||||
handler: cmd_cheat
|
||||
description: "Game cheats (GAME only)"
|
||||
usage: "CHEAT [INFINITE LIVES|INVINCIBILITY|OPEN THE JAIL|CLOSE THE JAIL]"
|
||||
hidden: true
|
||||
help_hidden: true
|
||||
completions:
|
||||
CHEAT: [INFINITE, INVINCIBILITY, OPEN, CLOSE]
|
||||
CHEAT INFINITE: [LIVES]
|
||||
@@ -230,3 +283,4 @@ categories:
|
||||
CHEAT CLOSE THE: [JAIL]
|
||||
debug_extras:
|
||||
hidden: false
|
||||
help_hidden: false
|
||||
|
||||
@@ -141,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"
|
||||
|
||||
@@ -141,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"
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace GlobalInputs {
|
||||
}
|
||||
|
||||
// Detecta qué acción global ha sido presionada (si alguna)
|
||||
auto getPressedAction() -> InputAction {
|
||||
auto getPressedAction() -> InputAction { // NOLINT(readability-function-cognitive-complexity)
|
||||
if (Input::get()->checkAction(InputAction::EXIT, Input::DO_NOT_ALLOW_REPEAT)) {
|
||||
return InputAction::EXIT;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
// ── 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; }
|
||||
const std::string LOWER = toLower(str);
|
||||
if (LOWER == "luminance") { return PaletteSortMode::LUMINANCE; }
|
||||
if (LOWER == "spectrum") { return PaletteSortMode::SPECTRUM; }
|
||||
return PaletteSortMode::ORIGINAL;
|
||||
}
|
||||
|
||||
@@ -66,15 +66,15 @@ namespace {
|
||||
|
||||
// Luminancia percibida (ITU-R BT.709)
|
||||
auto luminance(Uint32 color) -> double {
|
||||
return 0.2126 * redOf(color) + 0.7152 * greenOf(color) + 0.0722 * blueOf(color);
|
||||
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;
|
||||
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)
|
||||
@@ -89,31 +89,31 @@ namespace {
|
||||
|
||||
// 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) {
|
||||
const size_t N = countActiveColors(palette);
|
||||
std::vector<Uint32> colors(palette.begin(), palette.begin() + static_cast<ptrdiff_t>(N));
|
||||
std::ranges::sort(colors, [](Uint32 a, Uint32 b) {
|
||||
return luminance(a) < luminance(b);
|
||||
});
|
||||
|
||||
Palette result{};
|
||||
result.fill(0);
|
||||
std::copy(colors.begin(), colors.end(), result.begin());
|
||||
std::ranges::copy(colors, 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));
|
||||
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);
|
||||
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);
|
||||
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::ranges::min_element(available, [REF](Uint32 a, Uint32 b) {
|
||||
return rgbDistanceSq(a, REF) < rgbDistanceSq(b, REF);
|
||||
});
|
||||
result.push_back(*best);
|
||||
available.erase(best);
|
||||
@@ -126,7 +126,7 @@ namespace {
|
||||
|
||||
Palette out{};
|
||||
out.fill(0);
|
||||
std::copy(result.begin(), result.end(), out.begin());
|
||||
std::ranges::copy(result, out.begin());
|
||||
return out;
|
||||
}
|
||||
} // namespace
|
||||
@@ -149,9 +149,9 @@ 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 = sortPalette(readPalFile(palettes_.at(current_)), sort_mode_);
|
||||
game_surface_->setPalette(initial_palette);
|
||||
border_surface_->setPalette(initial_palette);
|
||||
const auto INITIAL_PALETTE = sortPalette(readPalFile(palettes_.at(current_)), sort_mode_);
|
||||
game_surface_->setPalette(INITIAL_PALETTE);
|
||||
border_surface_->setPalette(INITIAL_PALETTE);
|
||||
|
||||
// Procesar la lista: conservar solo los nombres de archivo (sin ruta)
|
||||
processPathList();
|
||||
@@ -170,9 +170,9 @@ void PaletteManager::previous() {
|
||||
}
|
||||
|
||||
auto PaletteManager::setByName(const std::string& name) -> bool {
|
||||
const std::string lower_name = toLower(name + ".pal");
|
||||
const std::string LOWER_NAME = toLower(name + ".pal");
|
||||
for (size_t i = 0; i < palettes_.size(); ++i) {
|
||||
if (toLower(palettes_[i]) == lower_name) {
|
||||
if (toLower(palettes_[i]) == LOWER_NAME) {
|
||||
current_ = i;
|
||||
apply();
|
||||
return true;
|
||||
@@ -186,8 +186,8 @@ auto PaletteManager::getNames() const -> std::vector<std::string> {
|
||||
names.reserve(palettes_.size());
|
||||
for (const auto& p : palettes_) {
|
||||
std::string name = p;
|
||||
const size_t pos = name.find(".pal");
|
||||
if (pos != std::string::npos) { name.erase(pos, 4); }
|
||||
const size_t POS = name.find(".pal");
|
||||
if (POS != std::string::npos) { name.erase(POS, 4); }
|
||||
std::ranges::transform(name, name.begin(), ::tolower);
|
||||
names.push_back(std::move(name));
|
||||
}
|
||||
@@ -196,8 +196,8 @@ auto PaletteManager::getNames() const -> std::vector<std::string> {
|
||||
|
||||
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); }
|
||||
const size_t POS = name.find(".pal");
|
||||
if (POS != std::string::npos) { name.erase(POS, 4); }
|
||||
std::ranges::transform(name, name.begin(), ::tolower);
|
||||
return name;
|
||||
}
|
||||
@@ -242,16 +242,16 @@ void PaletteManager::apply() {
|
||||
}
|
||||
|
||||
auto PaletteManager::findIndex(const std::string& name) const -> size_t {
|
||||
const std::string lower_name = toLower(name + ".pal");
|
||||
const std::string LOWER_NAME = toLower(name + ".pal");
|
||||
for (size_t i = 0; i < palettes_.size(); ++i) {
|
||||
if (toLower(getFileName(palettes_[i])) == lower_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");
|
||||
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) {
|
||||
if (toLower(getFileName(palettes_[i])) == DEFAULT_NAME) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ void RenderInfo::render() const {
|
||||
oss << std::fixed << std::setprecision(2) << ROUNDED;
|
||||
zoom_str = oss.str();
|
||||
if (zoom_str.back() == '0') { zoom_str.pop_back(); }
|
||||
std::replace(zoom_str.begin(), zoom_str.end(), '.', ',');
|
||||
std::ranges::replace(zoom_str, '.', ',');
|
||||
}
|
||||
line += " | " + zoom_str + "x";
|
||||
|
||||
@@ -108,13 +108,13 @@ void RenderInfo::render() const {
|
||||
}
|
||||
|
||||
// Todo en lowercase
|
||||
std::transform(line.begin(), line.end(), line.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
std::ranges::transform(line, line.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
|
||||
// Constantes visuales (igual que Console)
|
||||
static constexpr Uint8 BG_COLOR = 0; // PaletteColor::BLACK
|
||||
static constexpr Uint8 MSG_COLOR = 9; // PaletteColor::BRIGHT_GREEN
|
||||
static constexpr int TEXT_SIZE = 6;
|
||||
static constexpr int PADDING_V = TEXT_SIZE / 2 - 1;
|
||||
static constexpr int PADDING_V = (TEXT_SIZE / 2) - 1;
|
||||
|
||||
// Fuente: preferir la de la consola si está disponible
|
||||
auto text_obj = (Console::get() != nullptr) ? Console::get()->getText() : Screen::get()->getText();
|
||||
|
||||
@@ -209,7 +209,7 @@ auto Screen::setWindowZoom(int zoom) -> bool {
|
||||
}
|
||||
|
||||
// Devuelve el zoom máximo permitido según la pantalla actual
|
||||
auto Screen::getMaxZoom() const -> int {
|
||||
auto Screen::getMaxZoom() -> int {
|
||||
return Options::window.max_zoom;
|
||||
}
|
||||
|
||||
@@ -305,17 +305,19 @@ void Screen::adjustWindowSize() {
|
||||
|
||||
// Lógica de centrado y redimensionado de ventana SDL
|
||||
if (static_cast<int>(Options::video.fullscreen) == 0) {
|
||||
int old_w, old_h;
|
||||
int old_w;
|
||||
int old_h;
|
||||
SDL_GetWindowSize(window_, &old_w, &old_h);
|
||||
int old_x, old_y;
|
||||
int old_x;
|
||||
int old_y;
|
||||
SDL_GetWindowPosition(window_, &old_x, &old_y);
|
||||
|
||||
const int new_w = window_width_ * Options::window.zoom;
|
||||
const int new_h = window_height_ * Options::window.zoom;
|
||||
const int NEW_X = old_x + ((old_w - new_w) / 2);
|
||||
const int NEW_Y = old_y + ((old_h - new_h) / 2);
|
||||
const int NEW_W = window_width_ * Options::window.zoom;
|
||||
const int NEW_H = window_height_ * Options::window.zoom;
|
||||
const int NEW_X = old_x + ((old_w - NEW_W) / 2);
|
||||
const int NEW_Y = old_y + ((old_h - NEW_H) / 2);
|
||||
|
||||
SDL_SetWindowSize(window_, new_w, new_h);
|
||||
SDL_SetWindowSize(window_, NEW_W, NEW_H);
|
||||
|
||||
// En Wayland, SDL_SetWindowPosition es ignorado por el compositor (limitación de
|
||||
// protocolo: el compositor controla la posición de ventanas toplevel). Solo se
|
||||
@@ -324,8 +326,8 @@ void Screen::adjustWindowSize() {
|
||||
// (evita el race condition en X11).
|
||||
SDL_SyncWindow(window_);
|
||||
const char* driver = SDL_GetCurrentVideoDriver();
|
||||
const bool is_wayland = (driver != nullptr && SDL_strcmp(driver, "wayland") == 0);
|
||||
if (!is_wayland) {
|
||||
const bool IS_WAYLAND = (driver != nullptr && SDL_strcmp(driver, "wayland") == 0);
|
||||
if (!IS_WAYLAND) {
|
||||
SDL_SetWindowPosition(window_, std::max(NEW_X, WINDOWS_DECORATIONS), std::max(NEW_Y, 0));
|
||||
}
|
||||
}
|
||||
@@ -348,7 +350,8 @@ void Screen::updateZoomFactor() {
|
||||
zoom_factor_ = 1.0F;
|
||||
return;
|
||||
}
|
||||
int pw{0}, ph{0};
|
||||
int pw{0};
|
||||
int ph{0};
|
||||
SDL_GetRenderOutputSize(renderer_, &pw, &ph);
|
||||
const float SCALE = std::min(static_cast<float>(pw) / static_cast<float>(window_width_),
|
||||
static_cast<float>(ph) / static_cast<float>(window_height_));
|
||||
@@ -401,7 +404,7 @@ void Screen::textureToRenderer() {
|
||||
// Columnas laterales en las filas del área de juego
|
||||
for (int y = OFF_Y; y < OFF_Y + GAME_H; ++y) {
|
||||
std::fill_n(&border_pixel_buffer_[y * BORDER_W], OFF_X, border_argb_color_);
|
||||
std::fill_n(&border_pixel_buffer_[y * BORDER_W + OFF_X + GAME_W],
|
||||
std::fill_n(&border_pixel_buffer_[(y * BORDER_W) + OFF_X + GAME_W],
|
||||
BORDER_W - OFF_X - GAME_W,
|
||||
border_argb_color_);
|
||||
}
|
||||
@@ -415,7 +418,7 @@ void Screen::textureToRenderer() {
|
||||
game_surface_->toARGBBuffer(game_pixel_buffer_.data());
|
||||
for (int y = 0; y < GAME_H; ++y) {
|
||||
const Uint32* src = &game_pixel_buffer_[y * GAME_W];
|
||||
Uint32* dst = &border_pixel_buffer_[(OFF_Y + y) * BORDER_W + OFF_X];
|
||||
Uint32* dst = &border_pixel_buffer_[((OFF_Y + y) * BORDER_W) + OFF_X];
|
||||
std::memcpy(dst, src, GAME_W * sizeof(Uint32));
|
||||
}
|
||||
|
||||
@@ -606,8 +609,8 @@ void Screen::initShaders() {
|
||||
|
||||
if (!shader_backend_) {
|
||||
shader_backend_ = std::make_unique<Rendering::SDL3GPUShader>();
|
||||
const std::string fallback_driver = "none";
|
||||
shader_backend_->setPreferredDriver(Options::video.gpu.acceleration ? Options::video.gpu.preferred_driver : fallback_driver);
|
||||
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();
|
||||
|
||||
@@ -87,7 +87,7 @@ class Screen {
|
||||
[[nodiscard]] auto isHardwareAccelerated() const -> bool { return shader_backend_ && shader_backend_->isHardwareAccelerated(); }
|
||||
[[nodiscard]] auto getLastFPS() const -> int { return fps_.last_value; }
|
||||
[[nodiscard]] auto getZoomFactor() const -> float { return zoom_factor_; }
|
||||
[[nodiscard]] auto getMaxZoom() const -> int;
|
||||
[[nodiscard]] static auto getMaxZoom() -> int;
|
||||
[[nodiscard]] auto getSsTextureSize() const -> std::pair<int, int>;
|
||||
|
||||
private:
|
||||
@@ -166,7 +166,7 @@ class Screen {
|
||||
// Configuración de ventana y pantalla
|
||||
int window_width_{0}; // Ancho de la pantalla o ventana
|
||||
int window_height_{0}; // Alto de la pantalla o ventana
|
||||
float zoom_factor_{1.0f}; // Factor de zoom calculado (alto físico / alto lógico)
|
||||
float zoom_factor_{1.0F}; // Factor de zoom calculado (alto físico / alto lógico)
|
||||
SDL_FRect game_surface_dstrect_; // Coordenadas donde se dibuja la textura del juego
|
||||
|
||||
// Paletas y colores
|
||||
|
||||
@@ -527,7 +527,7 @@ namespace Rendering {
|
||||
// ---------------------------------------------------------------------------
|
||||
// createPipeline
|
||||
// ---------------------------------------------------------------------------
|
||||
auto SDL3GPUShader::createPipeline() -> bool {
|
||||
auto SDL3GPUShader::createPipeline() -> bool { // NOLINT(readability-function-cognitive-complexity)
|
||||
const SDL_GPUTextureFormat SWAPCHAIN_FMT = SDL_GetGPUSwapchainTextureFormat(device_, window_);
|
||||
|
||||
// ---- PostFX pipeline (scene/scaled → swapchain) ----
|
||||
@@ -770,7 +770,7 @@ namespace Rendering {
|
||||
// ---------------------------------------------------------------------------
|
||||
// render — upload scene texture + PostFX pass → swapchain
|
||||
// ---------------------------------------------------------------------------
|
||||
void SDL3GPUShader::render() {
|
||||
void SDL3GPUShader::render() { // NOLINT(readability-function-cognitive-complexity)
|
||||
if (!is_initialized_) { return; }
|
||||
|
||||
// Paso 0: si SS activo, calcular el factor necesario según el zoom actual y recrear si cambió.
|
||||
@@ -868,9 +868,11 @@ namespace Rendering {
|
||||
// pixel_scale: subpíxeles por pixel lógico.
|
||||
// Sin SS: vh/game_height (zoom de ventana).
|
||||
// Con SS: ss_factor_ exacto (3, 6, 9...).
|
||||
uniforms_.pixel_scale = (oversample_ > 1 && ss_factor_ > 0)
|
||||
? static_cast<float>(ss_factor_)
|
||||
: ((game_height_ > 0) ? (vh / static_cast<float>(game_height_)) : 1.0F);
|
||||
if (oversample_ > 1 && ss_factor_ > 0) {
|
||||
uniforms_.pixel_scale = static_cast<float>(ss_factor_);
|
||||
} else {
|
||||
uniforms_.pixel_scale = (game_height_ > 0) ? (vh / static_cast<float>(game_height_)) : 1.0F;
|
||||
}
|
||||
uniforms_.time = static_cast<float>(SDL_GetTicks()) / 1000.0F;
|
||||
uniforms_.oversample = (oversample_ > 1 && ss_factor_ > 0)
|
||||
? static_cast<float>(ss_factor_)
|
||||
@@ -1266,8 +1268,8 @@ namespace Rendering {
|
||||
// ---------------------------------------------------------------------------
|
||||
auto SDL3GPUShader::calcSsFactor(float zoom) -> int {
|
||||
const int MULTIPLE = 3;
|
||||
const int n = static_cast<int>(std::ceil(zoom / static_cast<float>(MULTIPLE)));
|
||||
return std::max(1, n) * MULTIPLE;
|
||||
const int N = static_cast<int>(std::ceil(zoom / static_cast<float>(MULTIPLE)));
|
||||
return std::max(1, N) * MULTIPLE;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -177,10 +177,10 @@ void Surface::fillRect(const SDL_FRect* rect, Uint8 color) { // NOLINT(readabil
|
||||
|
||||
// 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);
|
||||
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);
|
||||
std::memset(data_ptr + (y * SURF_WIDTH) + static_cast<int>(x_start), color, ROW_WIDTH);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,10 +194,10 @@ void Surface::drawRectBorder(const SDL_FRect* rect, Uint8 color) { // NOLINT(re
|
||||
|
||||
// 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);
|
||||
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) {
|
||||
@@ -525,7 +525,7 @@ void Surface::renderWithVerticalFade(int x, int y, int fade_h, int canvas_height
|
||||
|
||||
// Vuelca los píxeles como ARGB8888 a un buffer externo (sin SDL_Texture ni SDL_Renderer)
|
||||
void Surface::toARGBBuffer(Uint32* buffer) const {
|
||||
if (!surface_data_ || !surface_data_->data || !buffer) { return; }
|
||||
if (!surface_data_ || !surface_data_->data || (buffer == nullptr)) { return; }
|
||||
|
||||
const int WIDTH = static_cast<int>(surface_data_->width);
|
||||
const int HEIGHT = static_cast<int>(surface_data_->height);
|
||||
@@ -570,12 +570,12 @@ void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture) { //
|
||||
// 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);
|
||||
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) {
|
||||
for (int x = 0; x < WIDTH; ++x) {
|
||||
dst_row[x] = pal[src_row[x]];
|
||||
}
|
||||
}
|
||||
@@ -619,12 +619,12 @@ void Surface::copyToTexture(SDL_Renderer* renderer, SDL_Texture* texture, SDL_FR
|
||||
// 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);
|
||||
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) {
|
||||
for (int x = 0; x < WIDTH; ++x) {
|
||||
dst_row[x] = pal[src_row[x]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +139,7 @@ class Surface {
|
||||
|
||||
// Paleta
|
||||
void setPalette(const std::array<Uint32, 256>& palette) { palette_ = palette; }
|
||||
[[nodiscard]] auto getPaletteColor(Uint8 index) const -> Uint32 { return palette_[index]; }
|
||||
|
||||
// Inicializa la sub paleta
|
||||
static void initializeSubPalette(SubPalette& palette) { std::iota(palette.begin(), palette.end(), 0); }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -51,8 +51,97 @@ namespace Resource {
|
||||
addToMap(file_path, type, required, absolute);
|
||||
}
|
||||
|
||||
// Añade un asset al mapa y lo persiste en assets.yaml
|
||||
void List::addAsset(const std::string& path, Type type) {
|
||||
// Añadir al mapa en memoria
|
||||
addToMap(path, type, true, true);
|
||||
|
||||
// Persistir en assets.yaml
|
||||
if (config_file_path_.empty()) { return; }
|
||||
|
||||
std::ifstream in(config_file_path_);
|
||||
if (!in.is_open()) { return; }
|
||||
std::string content((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
|
||||
in.close();
|
||||
|
||||
// Construir la ruta con variable ${PREFIX} (invertir la sustitución)
|
||||
std::string var_path = path;
|
||||
if (!prefix_.empty() && !executable_path_.empty()) {
|
||||
std::string full_prefix = executable_path_ + prefix_;
|
||||
auto pos = var_path.find(full_prefix);
|
||||
if (pos != std::string::npos) {
|
||||
var_path.replace(pos, full_prefix.length(), "${PREFIX}");
|
||||
}
|
||||
}
|
||||
|
||||
// Buscar la última entrada con el mismo prefijo de ruta e insertar después
|
||||
std::string entry = " - " + var_path + "\n";
|
||||
auto last_pos = content.rfind(var_path.substr(0, var_path.rfind('/')));
|
||||
if (last_pos != std::string::npos) {
|
||||
auto end_of_line = content.find('\n', last_pos);
|
||||
if (end_of_line != std::string::npos) {
|
||||
content.insert(end_of_line + 1, entry);
|
||||
}
|
||||
}
|
||||
|
||||
std::ofstream out(config_file_path_);
|
||||
if (out.is_open()) {
|
||||
out << content;
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Quita un asset del mapa y lo elimina de assets.yaml
|
||||
void List::removeAsset(const std::string& filename) {
|
||||
// Obtener la ruta antes de borrar del mapa
|
||||
auto it = file_list_.find(filename);
|
||||
std::string file_path;
|
||||
if (it != file_list_.end()) {
|
||||
file_path = it->second.file;
|
||||
file_list_.erase(it);
|
||||
}
|
||||
|
||||
// Persistir en assets.yaml
|
||||
if (config_file_path_.empty() || file_path.empty()) { return; }
|
||||
|
||||
std::ifstream in(config_file_path_);
|
||||
if (!in.is_open()) { return; }
|
||||
std::string content((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
|
||||
in.close();
|
||||
|
||||
// Construir la ruta con variable ${PREFIX}
|
||||
std::string var_path = file_path;
|
||||
if (!prefix_.empty() && !executable_path_.empty()) {
|
||||
std::string full_prefix = executable_path_ + prefix_;
|
||||
auto pos = var_path.find(full_prefix);
|
||||
if (pos != std::string::npos) {
|
||||
var_path.replace(pos, full_prefix.length(), "${PREFIX}");
|
||||
}
|
||||
}
|
||||
|
||||
// Buscar la línea con el path y eliminarla
|
||||
auto pos = content.find(var_path);
|
||||
if (pos != std::string::npos) {
|
||||
auto line_start = content.rfind('\n', pos);
|
||||
line_start = (line_start == std::string::npos) ? 0 : line_start;
|
||||
auto line_end = content.find('\n', pos);
|
||||
if (line_end != std::string::npos) {
|
||||
content.erase(line_start, line_end - line_start);
|
||||
}
|
||||
}
|
||||
|
||||
std::ofstream out(config_file_path_);
|
||||
if (out.is_open()) {
|
||||
out << content;
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Carga recursos desde un archivo de configuración con soporte para variables
|
||||
void List::loadFromFile(const std::string& config_file_path, const std::string& prefix, const std::string& system_folder) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
config_file_path_ = config_file_path;
|
||||
prefix_ = prefix;
|
||||
|
||||
std::ifstream file(config_file_path);
|
||||
if (!file.is_open()) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
@@ -71,7 +160,7 @@ namespace Resource {
|
||||
}
|
||||
|
||||
// Carga recursos desde un string de configuración (para release con pack)
|
||||
void List::loadFromString(const std::string& config_content, const std::string& prefix, const std::string& system_folder) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
void List::loadFromString(const std::string& config_content, const std::string& prefix, const std::string& system_folder) { // NOLINT(readability-convert-member-functions-to-static,readability-function-cognitive-complexity)
|
||||
try {
|
||||
// Parsear YAML
|
||||
auto yaml = fkyaml::node::deserialize(config_content);
|
||||
@@ -89,18 +178,59 @@ namespace Resource {
|
||||
const std::string& category = it.key().get_value<std::string>();
|
||||
const auto& category_assets = it.value();
|
||||
|
||||
// Verificar que es un array
|
||||
if (!category_assets.is_sequence()) {
|
||||
if (category_assets.is_mapping()) {
|
||||
// Nuevo formato: categoría → { TIPO: [paths...], TIPO2: [paths...] }
|
||||
for (auto type_it = category_assets.begin(); type_it != category_assets.end(); ++type_it) {
|
||||
try {
|
||||
auto type_str = type_it.key().get_value<std::string>();
|
||||
Type type = parseAssetType(type_str);
|
||||
const auto& items = type_it.value();
|
||||
|
||||
if (!items.is_sequence()) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Warning: Category '%s' is not a sequence, skipping",
|
||||
"Warning: Type '%s' in category '%s' is not a sequence, skipping",
|
||||
type_str.c_str(),
|
||||
category.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Procesar cada asset en la categoría
|
||||
for (const auto& item : items) {
|
||||
try {
|
||||
if (item.is_string()) {
|
||||
// Formato simple: solo el path
|
||||
auto path = replaceVariables(item.get_value<std::string>(), prefix, system_folder);
|
||||
addToMap(path, type, true, false);
|
||||
} else if (item.is_mapping() && item.contains("path")) {
|
||||
// Formato expandido: { path, required?, absolute? }
|
||||
auto path = replaceVariables(item["path"].get_value<std::string>(), prefix, system_folder);
|
||||
bool required = !item.contains("required") || item["required"].get_value<bool>();
|
||||
bool absolute = item.contains("absolute") && item["absolute"].get_value<bool>();
|
||||
addToMap(path, type, required, absolute);
|
||||
} else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Warning: Invalid item in type '%s', category '%s', skipping",
|
||||
type_str.c_str(),
|
||||
category.c_str());
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error parsing asset in category '%s', type '%s': %s",
|
||||
category.c_str(),
|
||||
type_str.c_str(),
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Error parsing type in category '%s': %s",
|
||||
category.c_str(),
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
} else if (category_assets.is_sequence()) {
|
||||
// Formato antiguo (retrocompatibilidad): categoría → [{type, path}, ...]
|
||||
for (const auto& asset : category_assets) {
|
||||
try {
|
||||
// Verificar campos obligatorios
|
||||
if (!asset.contains("type") || !asset.contains("path")) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Warning: Asset in category '%s' missing 'type' or 'path', skipping",
|
||||
@@ -108,15 +238,11 @@ namespace Resource {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extraer campos
|
||||
auto type_str = asset["type"].get_value<std::string>();
|
||||
auto path = asset["path"].get_value<std::string>();
|
||||
|
||||
// Valores por defecto
|
||||
bool required = true;
|
||||
bool absolute = false;
|
||||
|
||||
// Campos opcionales
|
||||
if (asset.contains("required")) {
|
||||
required = asset["required"].get_value<bool>();
|
||||
}
|
||||
@@ -124,13 +250,8 @@ namespace Resource {
|
||||
absolute = asset["absolute"].get_value<bool>();
|
||||
}
|
||||
|
||||
// Reemplazar variables en la ruta
|
||||
path = replaceVariables(path, prefix, system_folder);
|
||||
|
||||
// Parsear el tipo de asset
|
||||
Type type = parseAssetType(type_str);
|
||||
|
||||
// Añadir al mapa
|
||||
addToMap(path, type, required, absolute);
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
@@ -140,6 +261,11 @@ namespace Resource {
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||
"Warning: Category '%s' has invalid format, skipping",
|
||||
category.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Loaded " << file_list_.size() << " assets from YAML config" << '\n';
|
||||
|
||||
@@ -33,6 +33,8 @@ namespace Resource {
|
||||
|
||||
// --- Métodos para la gestión de recursos ---
|
||||
void add(const std::string& file_path, Type type, bool required = true, bool absolute = false);
|
||||
void addAsset(const std::string& path, Type type); // Añade al mapa y persiste en assets.yaml
|
||||
void removeAsset(const std::string& filename); // Quita del mapa y persiste en assets.yaml
|
||||
void loadFromFile(const std::string& config_file_path, const std::string& prefix = "", const std::string& system_folder = ""); // Con soporte para variables
|
||||
void loadFromString(const std::string& config_content, const std::string& prefix = "", const std::string& system_folder = ""); // Para cargar desde pack (release)
|
||||
[[nodiscard]] auto get(const std::string& filename) const -> std::string; // Obtiene la ruta completa
|
||||
@@ -56,6 +58,8 @@ namespace Resource {
|
||||
// --- Variables internas ---
|
||||
std::unordered_map<std::string, Item> file_list_; // Mapa para búsqueda O(1)
|
||||
std::string executable_path_; // Ruta del ejecutable
|
||||
std::string config_file_path_; // Ruta del fichero assets.yaml
|
||||
std::string prefix_; // Prefijo para rutas (${PREFIX})
|
||||
|
||||
// --- Métodos internos ---
|
||||
[[nodiscard]] static auto getTypeName(Type type) -> std::string; // Obtiene el nombre del tipo
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
|
||||
#ifdef _DEBUG
|
||||
#include "core/system/debug.hpp" // Para Debug
|
||||
#include "game/editor/map_editor.hpp" // Para MapEditor
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
@@ -49,7 +50,9 @@ Director::Director() {
|
||||
|
||||
// Obtiene la ruta del ejecutable
|
||||
std::string base = SDL_GetBasePath();
|
||||
if (!base.empty() && base.back() == '/') base.pop_back();
|
||||
if (!base.empty() && base.back() == '/') {
|
||||
base.pop_back();
|
||||
}
|
||||
executable_path_ = base;
|
||||
|
||||
// Crea la carpeta del sistema donde guardar datos
|
||||
@@ -183,6 +186,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 +221,7 @@ Director::~Director() {
|
||||
Cheevos::destroy();
|
||||
Locale::destroy();
|
||||
#ifdef _DEBUG
|
||||
MapEditor::destroy();
|
||||
Debug::destroy();
|
||||
#endif
|
||||
Input::destroy();
|
||||
|
||||
89
source/game/editor/editor_statusbar.cpp
Normal file
89
source/game/editor/editor_statusbar.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include "game/editor/editor_statusbar.hpp"
|
||||
|
||||
#include <string> // Para to_string
|
||||
#include <utility>
|
||||
|
||||
#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, toLower
|
||||
|
||||
// Constructor
|
||||
EditorStatusBar::EditorStatusBar(std::string room_number, std::string room_name)
|
||||
: room_number_(std::move(room_number)),
|
||||
room_name_(std::move(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();
|
||||
}
|
||||
|
||||
void EditorStatusBar::setMouseTile(int tile_x, int tile_y) {
|
||||
mouse_tile_x_ = tile_x;
|
||||
mouse_tile_y_ = tile_y;
|
||||
}
|
||||
|
||||
void EditorStatusBar::setLine2(const std::string& text) { line2_ = text; }
|
||||
void EditorStatusBar::setLine3(const std::string& text) { line3_ = text; }
|
||||
void EditorStatusBar::setLine4(const std::string& text) { line4_ = text; }
|
||||
void EditorStatusBar::setLine5(const std::string& text) { line5_ = text; }
|
||||
|
||||
// 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");
|
||||
const Uint8 DETAIL_COLOR = stringToColor("bright_yellow");
|
||||
|
||||
// Línea 1: Nombre de la habitación
|
||||
text->writeColored(LEFT_X, LINE1_Y, toLower(room_number_ + " " + room_name_), LABEL_COLOR);
|
||||
|
||||
// Línea 2: Propiedades de room o info de enemigo
|
||||
if (!line2_.empty()) {
|
||||
text->writeColored(LEFT_X, LINE2_Y, toLower(line2_), DETAIL_COLOR);
|
||||
}
|
||||
|
||||
// Línea 3: Conexiones+items o propiedades del enemigo
|
||||
if (!line3_.empty()) {
|
||||
text->writeColored(LEFT_X, LINE3_Y, toLower(line3_), VALUE_COLOR);
|
||||
}
|
||||
|
||||
// Línea 4: Extra
|
||||
if (!line4_.empty()) {
|
||||
text->writeColored(LEFT_X, LINE4_Y, toLower(line4_), DETAIL_COLOR);
|
||||
}
|
||||
|
||||
// Línea 5: Tile coords + drag info
|
||||
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_);
|
||||
std::string line5 = "tile:" + TILE_X_STR + "," + TILE_Y_STR;
|
||||
if (!line5_.empty()) {
|
||||
line5 += " " + line5_;
|
||||
}
|
||||
text->writeColored(LEFT_X, LINE5_Y, toLower(line5), stringToColor("bright_green"));
|
||||
|
||||
Screen::get()->setRendererSurface(previous_renderer);
|
||||
}
|
||||
|
||||
#endif // _DEBUG
|
||||
52
source/game/editor/editor_statusbar.hpp
Normal file
52
source/game/editor/editor_statusbar.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
|
||||
class Surface;
|
||||
|
||||
class EditorStatusBar {
|
||||
public:
|
||||
EditorStatusBar(std::string room_number, std::string room_name);
|
||||
~EditorStatusBar() = default;
|
||||
|
||||
void render();
|
||||
void update(float delta_time);
|
||||
void setMouseTile(int tile_x, int tile_y);
|
||||
void setLine2(const std::string& text);
|
||||
void setLine3(const std::string& text);
|
||||
void setLine4(const std::string& text);
|
||||
void setLine5(const std::string& text);
|
||||
|
||||
private:
|
||||
void fillTexture(); // Dibuja los elementos en la surface
|
||||
|
||||
// Constantes de posición (en pixels dentro de la surface de 256x48)
|
||||
// Font 8bithud lowercase = 6px alto → 5 líneas con 8px de separación
|
||||
static constexpr int LINE1_Y = 2; // Nombre de la habitación
|
||||
static constexpr int LINE2_Y = 10; // Propiedades de room / enemy info
|
||||
static constexpr int LINE3_Y = 18; // Conexiones+items / enemy detail
|
||||
static constexpr int LINE4_Y = 26; // Extra
|
||||
static constexpr int LINE5_Y = 34; // Tile coords + drag info
|
||||
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 line2_; // Contenido de la línea 2
|
||||
std::string line3_; // Contenido de la línea 3
|
||||
std::string line4_; // Contenido de la línea 4
|
||||
std::string line5_; // Contenido de la línea 5
|
||||
};
|
||||
|
||||
#endif // _DEBUG
|
||||
1633
source/game/editor/map_editor.cpp
Normal file
1633
source/game/editor/map_editor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
160
source/game/editor/map_editor.hpp
Normal file
160
source/game/editor/map_editor.hpp
Normal file
@@ -0,0 +1,160 @@
|
||||
#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/editor/mini_map.hpp" // Para MiniMap
|
||||
#include "game/editor/tile_picker.hpp" // Para TilePicker
|
||||
#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;
|
||||
|
||||
// Comandos para enemigos (llamados desde console_commands)
|
||||
auto setEnemyProperty(const std::string& property, const std::string& value) -> std::string;
|
||||
auto addEnemy() -> std::string;
|
||||
auto deleteEnemy() -> std::string;
|
||||
auto duplicateEnemy() -> std::string;
|
||||
[[nodiscard]] auto hasSelectedEnemy() const -> bool;
|
||||
[[nodiscard]] auto getSetCompletions() const -> std::vector<std::string>;
|
||||
|
||||
// Comandos para propiedades de la habitación
|
||||
auto setRoomProperty(const std::string& property, const std::string& value) -> std::string;
|
||||
auto createNewRoom(const std::string& direction = "") -> std::string;
|
||||
auto deleteRoom() -> std::string;
|
||||
|
||||
// Opciones del editor (llamados desde console_commands / teclas)
|
||||
auto showInfo(bool show) -> std::string;
|
||||
auto showGrid(bool show) -> std::string;
|
||||
[[nodiscard]] auto isGridEnabled() const -> bool { return settings_.grid; }
|
||||
void toggleMiniMap();
|
||||
void setReenter(bool value) { reenter_ = value; }
|
||||
auto setMiniMapBg(const std::string& color) -> std::string;
|
||||
auto setMiniMapConn(const std::string& color) -> std::string;
|
||||
|
||||
// Comandos para items
|
||||
auto setItemProperty(const std::string& property, const std::string& value) -> std::string;
|
||||
auto addItem() -> std::string;
|
||||
auto deleteItem() -> std::string;
|
||||
auto duplicateItem() -> std::string;
|
||||
[[nodiscard]] auto hasSelectedItem() const -> bool;
|
||||
void openTilePicker(const std::string& tileset_name, int current_tile);
|
||||
|
||||
private:
|
||||
static MapEditor* instance_; // NOLINT(readability-identifier-naming) [SINGLETON] Objeto privado
|
||||
|
||||
MapEditor(); // Constructor
|
||||
~MapEditor(); // Destructor
|
||||
|
||||
// Opciones persistentes del editor
|
||||
struct Settings {
|
||||
bool grid{false};
|
||||
bool show_render_info{false};
|
||||
std::string minimap_bg{"blue"};
|
||||
std::string minimap_conn{"white"};
|
||||
};
|
||||
Settings settings_;
|
||||
void loadSettings();
|
||||
void saveSettings() const;
|
||||
|
||||
// Tipos para drag & drop y selección
|
||||
enum class DragTarget { NONE,
|
||||
PLAYER,
|
||||
ENEMY_INITIAL,
|
||||
ENEMY_BOUND1,
|
||||
ENEMY_BOUND2,
|
||||
ITEM };
|
||||
|
||||
struct DragState {
|
||||
DragTarget target{DragTarget::NONE};
|
||||
int index{-1};
|
||||
float offset_x{0.0F};
|
||||
float offset_y{0.0F};
|
||||
float snap_x{0.0F};
|
||||
float snap_y{0.0F};
|
||||
bool moved{false}; // true si el ratón se movió durante el drag
|
||||
};
|
||||
|
||||
// Métodos internos
|
||||
void updateMousePosition();
|
||||
void renderEnemyBoundaries();
|
||||
static void renderBoundaryMarker(float x, float y, Uint8 color);
|
||||
void renderSelectionHighlight();
|
||||
void renderGrid() const;
|
||||
void handleMouseDown(float game_x, float game_y);
|
||||
void handleMouseUp();
|
||||
void updateDrag();
|
||||
void autosave();
|
||||
void updateStatusBarInfo();
|
||||
static auto snapToGrid(float value) -> float;
|
||||
static auto pointInRect(float px, float py, const SDL_FRect& rect) -> bool;
|
||||
|
||||
// Estado del editor
|
||||
bool active_{false};
|
||||
DragState drag_;
|
||||
int selected_enemy_{-1}; // Índice del enemigo seleccionado (-1 = ninguno)
|
||||
int selected_item_{-1}; // Índice del item seleccionado (-1 = ninguno)
|
||||
static constexpr int NO_BRUSH = -2; // Sin brush activo
|
||||
static constexpr int ERASER_BRUSH = -1; // Brush borrador (pinta tile vacío = -1)
|
||||
int brush_tile_{NO_BRUSH}; // Tile activo para pintar
|
||||
bool painting_{false}; // true mientras se está pintando con click izquierdo mantenido
|
||||
|
||||
// Datos de la habitación
|
||||
Room::Data room_data_;
|
||||
std::string room_path_;
|
||||
std::string file_path_;
|
||||
|
||||
// YAML: nodo original (para campos que no se editan: name_ca, etc.)
|
||||
fkyaml::node yaml_;
|
||||
fkyaml::node yaml_backup_;
|
||||
|
||||
// Referencias a objetos vivos
|
||||
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_;
|
||||
|
||||
// Tile picker y mini mapa
|
||||
TilePicker tile_picker_;
|
||||
std::unique_ptr<MiniMap> mini_map_;
|
||||
bool mini_map_visible_{false};
|
||||
|
||||
// Estado del ratón
|
||||
float mouse_game_x_{0.0F};
|
||||
float mouse_game_y_{0.0F};
|
||||
int mouse_tile_x_{0};
|
||||
int mouse_tile_y_{0};
|
||||
|
||||
// Estado previo (para restaurar al salir)
|
||||
Options::Cheat::State invincible_before_editor_{Options::Cheat::State::DISABLED};
|
||||
bool render_info_before_editor_{false};
|
||||
bool reenter_{false}; // true cuando es un re-enter tras cambio de room (no tocar render_info)
|
||||
};
|
||||
|
||||
#endif // _DEBUG
|
||||
391
source/game/editor/mini_map.cpp
Normal file
391
source/game/editor/mini_map.cpp
Normal file
@@ -0,0 +1,391 @@
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include "game/editor/mini_map.hpp"
|
||||
|
||||
#include <algorithm> // Para std::max, std::min
|
||||
#include <array> // Para std::array
|
||||
#include <cmath> // Para std::floor
|
||||
#include <iostream> // Para cout
|
||||
#include <map> // Para std::map
|
||||
#include <queue> // Para queue (BFS)
|
||||
#include <set> // Para set
|
||||
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
#include "core/resources/resource_cache.hpp" // Para Resource::Cache
|
||||
#include "game/gameplay/room.hpp" // Para Room::Data
|
||||
#include "utils/defines.hpp" // Para Tile::SIZE, PlayArea
|
||||
#include "utils/utils.hpp" // Para stringToColor
|
||||
|
||||
// Constructor: construye todo el minimapa
|
||||
MiniMap::MiniMap(Uint8 bg_color, Uint8 conn_color)
|
||||
: bg_color_(bg_color),
|
||||
conn_color_(conn_color) {
|
||||
buildTileColorTable("standard.gif");
|
||||
layoutRooms();
|
||||
buildRoomSurfaces();
|
||||
composeFinalSurface();
|
||||
}
|
||||
|
||||
// Regenera la surface final con nuevo color de fondo
|
||||
void MiniMap::rebuild(Uint8 bg_color, Uint8 conn_color) {
|
||||
bg_color_ = bg_color;
|
||||
conn_color_ = conn_color;
|
||||
composeFinalSurface();
|
||||
}
|
||||
|
||||
// Analiza el tileset y crea tabla: tile_index → color predominante
|
||||
void MiniMap::buildTileColorTable(const std::string& tileset_name) {
|
||||
auto tileset = Resource::Cache::get()->getSurface(tileset_name);
|
||||
if (!tileset) { return; }
|
||||
|
||||
tileset_width_ = static_cast<int>(tileset->getWidth()) / Tile::SIZE;
|
||||
tileset_transparent_ = tileset->getTransparentColor();
|
||||
int tileset_height = static_cast<int>(tileset->getHeight()) / Tile::SIZE;
|
||||
int total_tiles = tileset_width_ * tileset_height;
|
||||
|
||||
tile_colors_.resize(total_tiles, 0);
|
||||
|
||||
for (int tile = 0; tile < total_tiles; ++tile) {
|
||||
int tile_x = (tile % tileset_width_) * Tile::SIZE;
|
||||
int tile_y = (tile / tileset_width_) * Tile::SIZE;
|
||||
|
||||
// Contar frecuencia de cada color en el tile (ignorar el color transparente del tileset)
|
||||
Uint8 transparent = tileset->getTransparentColor();
|
||||
std::array<int, 256> freq{};
|
||||
for (int y = 0; y < Tile::SIZE; ++y) {
|
||||
for (int x = 0; x < Tile::SIZE; ++x) {
|
||||
Uint8 pixel = tileset->getPixel(tile_x + x, tile_y + y);
|
||||
if (pixel != transparent) {
|
||||
freq[pixel]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Encontrar el color más frecuente (transparent = tile vacío, no se pinta)
|
||||
Uint8 best_color = transparent;
|
||||
int best_count = 0;
|
||||
for (int c = 0; c < 256; ++c) {
|
||||
if (c == transparent) { continue; }
|
||||
if (freq[c] > best_count) {
|
||||
best_count = freq[c];
|
||||
best_color = static_cast<Uint8>(c);
|
||||
}
|
||||
}
|
||||
|
||||
tile_colors_[tile] = best_color;
|
||||
}
|
||||
}
|
||||
|
||||
// Posiciona las rooms en un grid usando BFS desde las conexiones
|
||||
void MiniMap::layoutRooms() {
|
||||
auto& rooms = Resource::Cache::get()->getRooms();
|
||||
if (rooms.empty()) { return; }
|
||||
|
||||
// Mapa de nombre → Room::Data
|
||||
std::unordered_map<std::string, std::shared_ptr<Room::Data>> room_map;
|
||||
for (const auto& r : rooms) {
|
||||
room_map[r.name] = r.room;
|
||||
}
|
||||
|
||||
// BFS para posicionar rooms
|
||||
std::set<std::string> visited;
|
||||
std::queue<std::pair<std::string, GridPos>> bfs;
|
||||
|
||||
// Empezar por la primera room
|
||||
const std::string& start = rooms[0].name;
|
||||
bfs.push({start, {.x = 0, .y = 0}});
|
||||
visited.insert(start);
|
||||
|
||||
// Grid ocupado: posición → nombre de room
|
||||
std::map<std::pair<int, int>, std::string> grid_occupied;
|
||||
|
||||
while (!bfs.empty()) {
|
||||
auto [name, pos] = bfs.front();
|
||||
bfs.pop();
|
||||
|
||||
auto key = std::make_pair(pos.x, pos.y);
|
||||
if (grid_occupied.contains(key)) { continue; }
|
||||
|
||||
grid_occupied[key] = name;
|
||||
room_positions_[name] = RoomMini{.surface = nullptr, .pos = pos};
|
||||
|
||||
auto it = room_map.find(name);
|
||||
if (it == room_map.end()) { continue; }
|
||||
const auto& data = it->second;
|
||||
|
||||
// Vecinos: up, down, left, right
|
||||
struct Neighbor {
|
||||
std::string room;
|
||||
int dx, dy;
|
||||
};
|
||||
std::array<Neighbor, 4> neighbors = {{
|
||||
{.room = data->upper_room, .dx = 0, .dy = -1},
|
||||
{.room = data->lower_room, .dx = 0, .dy = 1},
|
||||
{.room = data->left_room, .dx = -1, .dy = 0},
|
||||
{.room = data->right_room, .dx = 1, .dy = 0},
|
||||
}};
|
||||
|
||||
for (const auto& [neighbor_name, dx, dy] : neighbors) {
|
||||
if (neighbor_name == "0" || neighbor_name.empty()) { continue; }
|
||||
if (visited.contains(neighbor_name)) { continue; }
|
||||
|
||||
GridPos neighbor_pos = {.x = pos.x + dx, .y = pos.y + dy};
|
||||
auto nkey = std::make_pair(neighbor_pos.x, neighbor_pos.y);
|
||||
if (!grid_occupied.contains(nkey)) {
|
||||
visited.insert(neighbor_name);
|
||||
bfs.emplace(neighbor_name, neighbor_pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calcular bounds del grid
|
||||
min_grid_x_ = 0;
|
||||
min_grid_y_ = 0;
|
||||
int max_grid_x = 0;
|
||||
int max_grid_y = 0;
|
||||
for (const auto& [name, mini] : room_positions_) {
|
||||
min_grid_x_ = std::min(min_grid_x_, mini.pos.x);
|
||||
min_grid_y_ = std::min(min_grid_y_, mini.pos.y);
|
||||
max_grid_x = std::max(max_grid_x, mini.pos.x);
|
||||
max_grid_y = std::max(max_grid_y, mini.pos.y);
|
||||
}
|
||||
|
||||
int cols = max_grid_x - min_grid_x_ + 1;
|
||||
int rows = max_grid_y - min_grid_y_ + 1;
|
||||
map_width_ = cols * (CELL_W + GAP) - GAP + PADDING * 2;
|
||||
map_height_ = rows * (CELL_H + GAP) - GAP + PADDING * 2;
|
||||
|
||||
std::cout << "MiniMap: " << room_positions_.size() << " rooms, grid " << cols << "x" << rows
|
||||
<< " → " << map_width_ << "x" << map_height_ << " px\n";
|
||||
}
|
||||
|
||||
// Genera una mini-surface de 32x16 por room
|
||||
void MiniMap::buildRoomSurfaces() {
|
||||
for (auto& [name, mini] : room_positions_) {
|
||||
mini.surface = getRoomMiniSurface(name);
|
||||
}
|
||||
}
|
||||
|
||||
// Genera la mini-surface de una room: 1 pixel por tile, color predominante
|
||||
auto MiniMap::getRoomMiniSurface(const std::string& room_name) -> std::shared_ptr<Surface> {
|
||||
auto room_data = Resource::Cache::get()->getRoom(room_name);
|
||||
if (!room_data) { return nullptr; }
|
||||
|
||||
auto surface = std::make_shared<Surface>(ROOM_W, ROOM_H);
|
||||
|
||||
auto prev = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(surface);
|
||||
surface->clear(stringToColor(room_data->bg_color));
|
||||
|
||||
const auto& tile_map = room_data->tile_map;
|
||||
for (int y = 0; y < ROOM_H; ++y) {
|
||||
for (int x = 0; x < ROOM_W; ++x) {
|
||||
int index = (y * ROOM_W) + x;
|
||||
if (index >= static_cast<int>(tile_map.size())) { continue; }
|
||||
|
||||
int tile = tile_map[index];
|
||||
if (tile < 0 || tile >= static_cast<int>(tile_colors_.size())) { continue; }
|
||||
|
||||
Uint8 color = tile_colors_[tile];
|
||||
if (color != tileset_transparent_) {
|
||||
surface->putPixel(x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Screen::get()->setRendererSurface(prev);
|
||||
return surface;
|
||||
}
|
||||
|
||||
// Compone la surface final con todas las rooms posicionadas
|
||||
void MiniMap::composeFinalSurface() {
|
||||
if (map_width_ <= 0 || map_height_ <= 0) { return; }
|
||||
|
||||
// Surface un poco más grande para la sombra del borde inferior/derecho
|
||||
map_surface_ = std::make_shared<Surface>(map_width_ + SHADOW_OFFSET, map_height_ + SHADOW_OFFSET);
|
||||
|
||||
auto prev = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(map_surface_);
|
||||
|
||||
// 1. Fondo general
|
||||
map_surface_->clear(bg_color_);
|
||||
|
||||
// 2. Líneas de conexión entre rooms (debajo de todo)
|
||||
drawConnections();
|
||||
|
||||
// 3. Sombras de las rooms (desplazadas 1px abajo-derecha)
|
||||
for (const auto& [name, mini] : room_positions_) {
|
||||
if (!mini.surface) { continue; }
|
||||
int px = cellPixelX(mini.pos.x) + SHADOW_OFFSET;
|
||||
int py = cellPixelY(mini.pos.y) + SHADOW_OFFSET;
|
||||
SDL_FRect shadow = {.x = static_cast<float>(px), .y = static_cast<float>(py), .w = static_cast<float>(CELL_W), .h = static_cast<float>(CELL_H)};
|
||||
map_surface_->fillRect(&shadow, COLOR_SHADOW);
|
||||
}
|
||||
|
||||
// 4. Borde negro de cada room + contenido de la room
|
||||
for (const auto& [name, mini] : room_positions_) {
|
||||
if (!mini.surface) { continue; }
|
||||
int px = cellPixelX(mini.pos.x);
|
||||
int py = cellPixelY(mini.pos.y);
|
||||
|
||||
// Borde negro (la celda entera)
|
||||
SDL_FRect cell = {.x = static_cast<float>(px), .y = static_cast<float>(py), .w = static_cast<float>(CELL_W), .h = static_cast<float>(CELL_H)};
|
||||
map_surface_->fillRect(&cell, COLOR_ROOM_BORDER);
|
||||
|
||||
// Miniroom dentro del borde
|
||||
SDL_FRect dst = {.x = static_cast<float>(px + BORDER), .y = static_cast<float>(py + BORDER), .w = static_cast<float>(ROOM_W), .h = static_cast<float>(ROOM_H)};
|
||||
mini.surface->render(nullptr, &dst);
|
||||
}
|
||||
|
||||
Screen::get()->setRendererSurface(prev);
|
||||
}
|
||||
|
||||
// Dibuja las líneas de conexión entre rooms vecinas
|
||||
void MiniMap::drawConnections() {
|
||||
for (const auto& [name, mini] : room_positions_) {
|
||||
auto room_data = Resource::Cache::get()->getRoom(name);
|
||||
if (!room_data) { continue; }
|
||||
|
||||
int px = cellPixelX(mini.pos.x);
|
||||
int py = cellPixelY(mini.pos.y);
|
||||
|
||||
// Conexión derecha
|
||||
if (room_data->right_room != "0" && !room_data->right_room.empty() && room_positions_.contains(room_data->right_room)) {
|
||||
int x1 = px + CELL_W;
|
||||
int y_mid = py + (CELL_H / 2) - 1;
|
||||
SDL_FRect line = {.x = static_cast<float>(x1), .y = static_cast<float>(y_mid), .w = static_cast<float>(GAP), .h = 3.0F};
|
||||
map_surface_->fillRect(&line, conn_color_);
|
||||
}
|
||||
|
||||
// Conexión abajo
|
||||
if (room_data->lower_room != "0" && !room_data->lower_room.empty() && room_positions_.contains(room_data->lower_room)) {
|
||||
int x_mid = px + (CELL_W / 2) - 1;
|
||||
int y1 = py + CELL_H;
|
||||
SDL_FRect line = {.x = static_cast<float>(x_mid), .y = static_cast<float>(y1), .w = 3.0F, .h = static_cast<float>(GAP)};
|
||||
map_surface_->fillRect(&line, conn_color_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Centra el viewport en una room
|
||||
void MiniMap::centerOnRoom(const std::string& room_name) {
|
||||
auto it = room_positions_.find(room_name);
|
||||
if (it == room_positions_.end()) { return; }
|
||||
const auto& pos = it->second.pos;
|
||||
|
||||
auto room_cx = static_cast<float>(cellPixelX(pos.x) + (CELL_W / 2));
|
||||
auto room_cy = static_cast<float>(cellPixelY(pos.y) + (CELL_H / 2));
|
||||
view_x_ = static_cast<float>(PlayArea::WIDTH) / 2.0F - room_cx;
|
||||
view_y_ = static_cast<float>(PlayArea::HEIGHT) / 2.0F - room_cy;
|
||||
}
|
||||
|
||||
// Devuelve el nombre de la room en una posición de pantalla, o vacío si no hay ninguna
|
||||
auto MiniMap::roomAtScreen(float screen_x, float screen_y) -> std::string {
|
||||
// Convertir coordenada de pantalla a coordenada dentro del minimapa
|
||||
float map_x = screen_x - view_x_;
|
||||
float map_y = screen_y - view_y_;
|
||||
|
||||
for (const auto& [name, mini] : room_positions_) {
|
||||
auto rx = static_cast<float>(cellPixelX(mini.pos.x));
|
||||
auto ry = static_cast<float>(cellPixelY(mini.pos.y));
|
||||
if (map_x >= rx && map_x < rx + CELL_W && map_y >= ry && map_y < ry + CELL_H) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// Renderiza el minimapa
|
||||
void MiniMap::render(const std::string& current_room) {
|
||||
if (!map_surface_) { return; }
|
||||
|
||||
auto game_surface = Screen::get()->getRendererSurface();
|
||||
if (!game_surface) { return; }
|
||||
|
||||
// Renderizar la surface del minimapa con el viewport actual (alineado a pixel)
|
||||
float vx = std::floor(view_x_);
|
||||
float vy = std::floor(view_y_);
|
||||
SDL_FRect dst = {.x = vx, .y = vy, .w = static_cast<float>(map_width_ + SHADOW_OFFSET), .h = static_cast<float>(map_height_ + SHADOW_OFFSET)};
|
||||
map_surface_->render(nullptr, &dst);
|
||||
|
||||
// Highlight de la room actual (solo si está completamente visible en el play area)
|
||||
auto it = room_positions_.find(current_room);
|
||||
if (it != room_positions_.end()) {
|
||||
float cur_x = vx + static_cast<float>(cellPixelX(it->second.pos.x)) - 1;
|
||||
float cur_y = vy + static_cast<float>(cellPixelY(it->second.pos.y)) - 1;
|
||||
auto cur_w = static_cast<float>(CELL_W + 2);
|
||||
auto cur_h = static_cast<float>(CELL_H + 2);
|
||||
if (cur_x >= 0 && cur_y >= 0 && cur_x + cur_w <= PlayArea::WIDTH && cur_y + cur_h <= PlayArea::HEIGHT) {
|
||||
SDL_FRect highlight = {.x = cur_x, .y = cur_y, .w = cur_w, .h = cur_h};
|
||||
game_surface->drawRectBorder(&highlight, stringToColor("bright_white"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Maneja eventos del minimapa (drag para explorar, click para navegar)
|
||||
void MiniMap::handleEvent(const SDL_Event& event, const std::string& current_room) {
|
||||
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && event.button.button == SDL_BUTTON_LEFT) {
|
||||
// Guardar posición inicial para detectar si es click o drag
|
||||
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();
|
||||
|
||||
dragging_ = true;
|
||||
drag_start_x_ = render_x - dst_rect.x;
|
||||
drag_start_y_ = render_y - dst_rect.y;
|
||||
view_start_x_ = view_x_;
|
||||
view_start_y_ = view_y_;
|
||||
}
|
||||
|
||||
if (event.type == SDL_EVENT_MOUSE_MOTION && dragging_) {
|
||||
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();
|
||||
|
||||
float game_x = render_x - dst_rect.x;
|
||||
float game_y = render_y - dst_rect.y;
|
||||
view_x_ = view_start_x_ + (game_x - drag_start_x_);
|
||||
view_y_ = view_start_y_ + (game_y - drag_start_y_);
|
||||
}
|
||||
|
||||
if (event.type == SDL_EVENT_MOUSE_BUTTON_UP && event.button.button == SDL_BUTTON_LEFT) {
|
||||
if (dragging_) {
|
||||
// Comprobar si fue click (sin mover) o drag
|
||||
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();
|
||||
|
||||
float game_x = render_x - dst_rect.x;
|
||||
float game_y = render_y - dst_rect.y;
|
||||
|
||||
float dx = game_x - drag_start_x_;
|
||||
float dy = game_y - drag_start_y_;
|
||||
bool was_click = (dx * dx + dy * dy) < 4.0F; // Menos de 2px de movimiento = click
|
||||
|
||||
if (was_click) {
|
||||
// Click: navegar a la room bajo el cursor
|
||||
std::string room = roomAtScreen(game_x, game_y);
|
||||
if (!room.empty() && room != current_room && on_navigate) {
|
||||
on_navigate(room);
|
||||
}
|
||||
}
|
||||
|
||||
dragging_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // _DEBUG
|
||||
103
source/game/editor/mini_map.hpp
Normal file
103
source/game/editor/mini_map.hpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <functional> // Para function
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <unordered_map> // Para unordered_map
|
||||
#include <vector> // Para vector
|
||||
|
||||
class Surface;
|
||||
|
||||
/**
|
||||
* @brief Minimapa global del juego para el editor
|
||||
*
|
||||
* Genera una vista en miniatura de todas las habitaciones del juego,
|
||||
* posicionadas según sus conexiones.
|
||||
* Cada tile del mapa se representa como 1 pixel del color predominante de ese tile.
|
||||
* Resultado: cada room = 32x16 pixels.
|
||||
*/
|
||||
class MiniMap {
|
||||
public:
|
||||
explicit MiniMap(Uint8 bg_color = 2, Uint8 conn_color = 14);
|
||||
~MiniMap() = default;
|
||||
|
||||
void render(const std::string& current_room);
|
||||
void handleEvent(const SDL_Event& event, const std::string& current_room);
|
||||
void rebuild(Uint8 bg_color, Uint8 conn_color);
|
||||
void centerOnRoom(const std::string& room_name);
|
||||
[[nodiscard]] auto isReady() const -> bool { return !room_positions_.empty(); }
|
||||
|
||||
// Callback al hacer click en una minihabitación (nombre del room)
|
||||
std::function<void(const std::string&)> on_navigate;
|
||||
|
||||
private:
|
||||
// Posición de una room en el grid del minimapa
|
||||
struct GridPos {
|
||||
int x{0};
|
||||
int y{0};
|
||||
};
|
||||
|
||||
// Una room renderizada
|
||||
struct RoomMini {
|
||||
std::shared_ptr<Surface> surface; // 32x16 pixels
|
||||
GridPos pos; // Posición en el grid
|
||||
};
|
||||
|
||||
void buildTileColorTable(const std::string& tileset_name);
|
||||
void buildRoomSurfaces();
|
||||
void layoutRooms();
|
||||
void composeFinalSurface();
|
||||
auto getRoomMiniSurface(const std::string& room_name) -> std::shared_ptr<Surface>;
|
||||
void drawConnections();
|
||||
auto roomAtScreen(float screen_x, float screen_y) -> std::string;
|
||||
auto cellPixelX(int grid_x) const -> int { return PADDING + ((grid_x - min_grid_x_) * (CELL_W + GAP)); }
|
||||
auto cellPixelY(int grid_y) const -> int { return PADDING + ((grid_y - min_grid_y_) * (CELL_H + GAP)); }
|
||||
|
||||
// Tabla de color predominante por tile index
|
||||
std::vector<Uint8> tile_colors_; // tile_index → palette color index
|
||||
int tileset_width_{0}; // Ancho del tileset en tiles
|
||||
Uint8 tileset_transparent_{16}; // Color transparente del tileset
|
||||
|
||||
// Rooms renderizadas y posicionadas
|
||||
std::unordered_map<std::string, RoomMini> room_positions_;
|
||||
|
||||
// Surface final compuesta
|
||||
std::shared_ptr<Surface> map_surface_;
|
||||
int map_width_{0}; // Ancho en pixels
|
||||
int map_height_{0}; // Alto en pixels
|
||||
|
||||
// Offset para normalizar coordenadas
|
||||
int min_grid_x_{0};
|
||||
int min_grid_y_{0};
|
||||
|
||||
// Viewport: offset de la surface del minimapa respecto al play area
|
||||
float view_x_{0.0F}; // Offset X actual
|
||||
float view_y_{0.0F}; // Offset Y actual
|
||||
bool dragging_{false};
|
||||
float drag_start_x_{0.0F}; // Posición del ratón al inicio del drag
|
||||
float drag_start_y_{0.0F};
|
||||
float view_start_x_{0.0F}; // Viewport al inicio del drag
|
||||
float view_start_y_{0.0F};
|
||||
|
||||
// Constantes
|
||||
static constexpr int ROOM_W = 32; // Ancho de una room en pixels del minimapa
|
||||
static constexpr int ROOM_H = 16; // Alto de una room en pixels del minimapa
|
||||
static constexpr int BORDER = 1; // Borde alrededor de cada room
|
||||
static constexpr int CELL_W = ROOM_W + (BORDER * 2); // Room + borde
|
||||
static constexpr int CELL_H = ROOM_H + (BORDER * 2);
|
||||
static constexpr int GAP = 4; // Separación entre celdas
|
||||
static constexpr int SHADOW_OFFSET = 1; // Desplazamiento de la sombra
|
||||
static constexpr int PADDING = 4; // Padding alrededor del minimapa
|
||||
|
||||
// Colores del minimapa (índices de paleta)
|
||||
Uint8 bg_color_{2}; // Fondo general (configurable)
|
||||
Uint8 conn_color_{14}; // Líneas de conexión (configurable)
|
||||
static constexpr Uint8 COLOR_ROOM_BORDER = 0; // Borde de cada miniroom
|
||||
static constexpr Uint8 COLOR_SHADOW = 1; // Sombra de cada miniroom
|
||||
};
|
||||
|
||||
#endif // _DEBUG
|
||||
178
source/game/editor/room_saver.cpp
Normal file
178
source/game/editor/room_saver.cpp
Normal file
@@ -0,0 +1,178 @@
|
||||
#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 <sstream> // Para ostringstream
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
// Convierte una room connection al formato YAML
|
||||
auto RoomSaver::roomConnectionToYAML(const std::string& connection) -> std::string {
|
||||
if (connection == "0" || connection.empty()) { return "null"; }
|
||||
return connection;
|
||||
}
|
||||
|
||||
// Convierte la dirección del conveyor belt a string
|
||||
auto RoomSaver::conveyorBeltToString(int direction) -> std::string {
|
||||
if (direction < 0) { return "left"; }
|
||||
if (direction > 0) { return "right"; }
|
||||
return "none";
|
||||
}
|
||||
|
||||
// Genera el YAML completo como texto con formato compacto
|
||||
auto RoomSaver::buildYAML(const fkyaml::node& original_yaml, const Room::Data& room_data) -> std::string { // NOLINT(readability-function-cognitive-complexity)
|
||||
std::ostringstream out;
|
||||
|
||||
// --- Cabecera: nombre como comentario ---
|
||||
out << "# " << room_data.name << "\n";
|
||||
|
||||
// --- Sección room ---
|
||||
out << "room:\n";
|
||||
|
||||
// Escribir todos los campos name_* del YAML original (preserva name_ca, name_en, etc.)
|
||||
if (original_yaml.contains("room")) {
|
||||
const auto& room_node = original_yaml["room"];
|
||||
for (auto it = room_node.begin(); it != room_node.end(); ++it) {
|
||||
const auto KEY = it.key().get_value<std::string>();
|
||||
if (KEY.substr(0, 5) == "name_") {
|
||||
out << " " << KEY << ": \"" << it.value().get_value<std::string>() << "\"\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out << " bgColor: " << room_data.bg_color << "\n";
|
||||
out << " border: " << room_data.border_color << "\n";
|
||||
out << " tileSetFile: " << room_data.tile_set_file << "\n";
|
||||
|
||||
// Conexiones
|
||||
out << "\n";
|
||||
out << " # Conexiones de la habitación (null = sin conexión)\n";
|
||||
out << " connections:\n";
|
||||
out << " up: " << roomConnectionToYAML(room_data.upper_room) << "\n";
|
||||
out << " down: " << roomConnectionToYAML(room_data.lower_room) << "\n";
|
||||
out << " left: " << roomConnectionToYAML(room_data.left_room) << "\n";
|
||||
out << " right: " << roomConnectionToYAML(room_data.right_room) << "\n";
|
||||
|
||||
// Colores de items
|
||||
out << "\n";
|
||||
out << " # Colores de los objetos\n";
|
||||
out << " itemColor1: " << (room_data.item_color1.empty() ? "yellow" : room_data.item_color1) << "\n";
|
||||
out << " itemColor2: " << (room_data.item_color2.empty() ? "magenta" : room_data.item_color2) << "\n";
|
||||
|
||||
// Conveyor belt
|
||||
out << "\n";
|
||||
out << " # Dirección de la cinta transportadora: left, none, right\n";
|
||||
out << " conveyorBelt: " << conveyorBeltToString(room_data.conveyor_belt_direction) << "\n";
|
||||
|
||||
// --- Tilemap (16 filas × 32 columnas, formato flow) ---
|
||||
out << "\n";
|
||||
out << "# Tilemap: 16 filas × 32 columnas (256×192 píxeles @ 8px/tile)\n";
|
||||
out << "# Índices de tiles (-1 = vacío)\n";
|
||||
out << "tilemap:\n";
|
||||
constexpr int MAP_WIDTH = 32;
|
||||
constexpr int MAP_HEIGHT = 16;
|
||||
for (int row = 0; row < MAP_HEIGHT; ++row) {
|
||||
out << " - [";
|
||||
for (int col = 0; col < MAP_WIDTH; ++col) {
|
||||
int index = (row * MAP_WIDTH) + col;
|
||||
if (index < static_cast<int>(room_data.tile_map.size())) {
|
||||
out << room_data.tile_map[index];
|
||||
} else {
|
||||
out << -1;
|
||||
}
|
||||
if (col < MAP_WIDTH - 1) { out << ", "; }
|
||||
}
|
||||
out << "]\n";
|
||||
}
|
||||
|
||||
// --- Enemigos ---
|
||||
if (!room_data.enemies.empty()) {
|
||||
out << "\n";
|
||||
out << "# Enemigos en esta habitación\n";
|
||||
out << "enemies:\n";
|
||||
for (const auto& enemy : room_data.enemies) {
|
||||
out << " - animation: " << enemy.animation_path << "\n";
|
||||
|
||||
int pos_x = static_cast<int>(std::round(enemy.x / Tile::SIZE));
|
||||
int pos_y = static_cast<int>(std::round(enemy.y / Tile::SIZE));
|
||||
out << " position: {x: " << pos_x << ", y: " << pos_y << "}\n";
|
||||
|
||||
out << " velocity: {x: " << enemy.vx << ", y: " << enemy.vy << "}\n";
|
||||
|
||||
int b1_x = enemy.x1 / Tile::SIZE;
|
||||
int b1_y = enemy.y1 / Tile::SIZE;
|
||||
int b2_x = enemy.x2 / Tile::SIZE;
|
||||
int b2_y = enemy.y2 / Tile::SIZE;
|
||||
out << " boundaries:\n";
|
||||
out << " position1: {x: " << b1_x << ", y: " << b1_y << "}\n";
|
||||
out << " position2: {x: " << b2_x << ", y: " << b2_y << "}\n";
|
||||
|
||||
if (!enemy.color.empty() && enemy.color != "white") {
|
||||
out << " color: " << enemy.color << "\n";
|
||||
}
|
||||
if (enemy.flip) { out << " flip: true\n"; }
|
||||
if (enemy.mirror) { out << " mirror: true\n"; }
|
||||
if (enemy.frame != -1) { out << " frame: " << enemy.frame << "\n"; }
|
||||
|
||||
out << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// --- Items ---
|
||||
if (!room_data.items.empty()) {
|
||||
out << "# Objetos en esta habitación\n";
|
||||
out << "items:\n";
|
||||
for (const auto& item : room_data.items) {
|
||||
out << " - tileSetFile: " << item.tile_set_file << "\n";
|
||||
out << " tile: " << item.tile << "\n";
|
||||
|
||||
int item_x = static_cast<int>(std::round(item.x / Tile::SIZE));
|
||||
int item_y = static_cast<int>(std::round(item.y / Tile::SIZE));
|
||||
out << " position: {x: " << item_x << ", y: " << item_y << "}\n";
|
||||
|
||||
if (item.counter != 0) {
|
||||
out << " counter: " << item.counter << "\n";
|
||||
}
|
||||
|
||||
out << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
// Guarda el YAML a disco
|
||||
auto RoomSaver::saveYAML(const std::string& file_path, const fkyaml::node& original_yaml, const Room::Data& room_data) -> std::string {
|
||||
std::string content = buildYAML(original_yaml, room_data);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#endif // _DEBUG
|
||||
35
source/game/editor/room_saver.hpp
Normal file
35
source/game/editor/room_saver.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include <string> // Para string
|
||||
|
||||
#include "external/fkyaml_node.hpp" // Para fkyaml::node
|
||||
#include "game/gameplay/room.hpp" // Para Room::Data
|
||||
|
||||
/**
|
||||
* @brief Guardado de archivos YAML de habitaciones para el editor de mapas
|
||||
*
|
||||
* Lee el YAML original con fkyaml (para acceder a todos los campos: name_ca, name_en, etc.)
|
||||
* Genera el YAML como texto formateado compacto (idéntico al formato original de los ficheros).
|
||||
* Solo se usa en builds de debug.
|
||||
*/
|
||||
class RoomSaver {
|
||||
public:
|
||||
RoomSaver() = delete;
|
||||
|
||||
// Carga el YAML original desde disco como nodo fkyaml (lee del filesystem, no del pack)
|
||||
static auto loadYAML(const std::string& file_path) -> fkyaml::node;
|
||||
|
||||
// Genera y guarda el YAML completo a disco
|
||||
// original_yaml: nodo fkyaml con los datos originales (para campos que no se editan: name_ca, etc.)
|
||||
// room_data: datos editados (posiciones de enemigos, items, etc.)
|
||||
static auto saveYAML(const std::string& file_path, const fkyaml::node& original_yaml, const Room::Data& room_data) -> std::string;
|
||||
|
||||
private:
|
||||
static auto buildYAML(const fkyaml::node& original_yaml, const Room::Data& room_data) -> std::string;
|
||||
static auto roomConnectionToYAML(const std::string& connection) -> std::string;
|
||||
static auto conveyorBeltToString(int direction) -> std::string;
|
||||
};
|
||||
|
||||
#endif // _DEBUG
|
||||
240
source/game/editor/tile_picker.cpp
Normal file
240
source/game/editor/tile_picker.cpp
Normal file
@@ -0,0 +1,240 @@
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include "game/editor/tile_picker.hpp"
|
||||
|
||||
#include <algorithm> // Para std::clamp, std::min
|
||||
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
#include "core/resources/resource_cache.hpp" // Para Resource::Cache
|
||||
#include "utils/defines.hpp" // Para Tile::SIZE, PlayArea
|
||||
#include "utils/utils.hpp" // Para stringToColor
|
||||
|
||||
// Margen del borde alrededor del tileset (en pixels)
|
||||
static constexpr int BORDER_PAD = 3;
|
||||
|
||||
// Abre el picker con un tileset
|
||||
void TilePicker::open(const std::string& tileset_name, int current_tile, int bg_color, int source_color, int target_color, int tile_spacing_in, int tile_spacing_out) {
|
||||
tileset_ = Resource::Cache::get()->getSurface(tileset_name);
|
||||
if (!tileset_) {
|
||||
open_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
spacing_in_ = tile_spacing_in;
|
||||
spacing_out_ = tile_spacing_out;
|
||||
|
||||
// Calcular dimensiones del tileset en tiles (teniendo en cuenta spacing de entrada)
|
||||
int src_cell = Tile::SIZE + spacing_in_;
|
||||
tileset_width_ = static_cast<int>(tileset_->getWidth()) / src_cell;
|
||||
tileset_height_ = static_cast<int>(tileset_->getHeight()) / src_cell;
|
||||
// Corregir si el último tile cabe sin spacing
|
||||
if (tileset_width_ == 0 && tileset_->getWidth() >= Tile::SIZE) { tileset_width_ = 1; }
|
||||
if (tileset_height_ == 0 && tileset_->getHeight() >= Tile::SIZE) { tileset_height_ = 1; }
|
||||
|
||||
current_tile_ = current_tile;
|
||||
hover_tile_ = -1;
|
||||
scroll_y_ = 0;
|
||||
|
||||
// Dimensiones de salida (con spacing visual entre tiles)
|
||||
int out_cell = Tile::SIZE + spacing_out_;
|
||||
int display_w = (tileset_width_ * out_cell) - spacing_out_; // Sin trailing
|
||||
int display_h = (tileset_height_ * out_cell) - spacing_out_;
|
||||
|
||||
// Frame: display + borde
|
||||
int frame_w = display_w + (BORDER_PAD * 2);
|
||||
int frame_h = display_h + (BORDER_PAD * 2);
|
||||
frame_surface_ = std::make_shared<Surface>(frame_w, frame_h);
|
||||
|
||||
// Componer: fondo + borde + tiles uno a uno
|
||||
{
|
||||
auto prev = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(frame_surface_);
|
||||
|
||||
Uint8 fill_color = (bg_color >= 0) ? static_cast<Uint8>(bg_color) : stringToColor("black");
|
||||
frame_surface_->clear(fill_color);
|
||||
|
||||
// Borde doble
|
||||
SDL_FRect outer = {.x = 0, .y = 0, .w = static_cast<float>(frame_w), .h = static_cast<float>(frame_h)};
|
||||
frame_surface_->drawRectBorder(&outer, stringToColor("bright_white"));
|
||||
SDL_FRect inner = {.x = 1, .y = 1, .w = static_cast<float>(frame_w - 2), .h = static_cast<float>(frame_h - 2)};
|
||||
frame_surface_->drawRectBorder(&inner, stringToColor("white"));
|
||||
|
||||
// Renderizar cada tile individualmente
|
||||
constexpr auto TS = static_cast<float>(Tile::SIZE);
|
||||
for (int row = 0; row < tileset_height_; ++row) {
|
||||
for (int col = 0; col < tileset_width_; ++col) {
|
||||
// Fuente: posición en el tileset original
|
||||
SDL_FRect src = {
|
||||
.x = static_cast<float>(col * src_cell),
|
||||
.y = static_cast<float>(row * src_cell),
|
||||
.w = TS,
|
||||
.h = TS};
|
||||
|
||||
// Destino: posición en el frame con spacing de salida
|
||||
int dst_x = BORDER_PAD + (col * out_cell);
|
||||
int dst_y = BORDER_PAD + (row * out_cell);
|
||||
|
||||
if (source_color >= 0 && target_color >= 0) {
|
||||
tileset_->renderWithColorReplace(dst_x, dst_y, static_cast<Uint8>(source_color), static_cast<Uint8>(target_color), &src);
|
||||
} else {
|
||||
SDL_FRect dst = {.x = static_cast<float>(dst_x), .y = static_cast<float>(dst_y), .w = TS, .h = TS};
|
||||
tileset_->render(&src, &dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Screen::get()->setRendererSurface(prev);
|
||||
}
|
||||
|
||||
// Centrar en el play area
|
||||
offset_x_ = (PlayArea::WIDTH - frame_w) / 2;
|
||||
int offset_y = (PlayArea::HEIGHT - frame_h) / 2;
|
||||
offset_y = std::max(offset_y, 0);
|
||||
|
||||
visible_height_ = PlayArea::HEIGHT;
|
||||
|
||||
frame_dst_ = {.x = static_cast<float>(offset_x_), .y = static_cast<float>(offset_y), .w = static_cast<float>(frame_w), .h = static_cast<float>(frame_h)};
|
||||
|
||||
// Si el frame es más alto que el play area, necesitará scroll
|
||||
if (frame_h > visible_height_) {
|
||||
frame_dst_.y = 0;
|
||||
if (current_tile_ >= 0) {
|
||||
int tile_row = current_tile_ / tileset_width_;
|
||||
int tile_y_px = tile_row * out_cell;
|
||||
if (tile_y_px > visible_height_ / 2) {
|
||||
scroll_y_ = tile_y_px - visible_height_ / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open_ = true;
|
||||
updateMousePosition();
|
||||
}
|
||||
|
||||
// Cierra el picker
|
||||
void TilePicker::close() {
|
||||
open_ = false;
|
||||
tileset_.reset();
|
||||
frame_surface_.reset();
|
||||
}
|
||||
|
||||
// Renderiza el picker
|
||||
void TilePicker::render() {
|
||||
if (!open_ || !frame_surface_) { return; }
|
||||
|
||||
auto game_surface = Screen::get()->getRendererSurface();
|
||||
if (!game_surface) { return; }
|
||||
|
||||
int frame_h = static_cast<int>(frame_dst_.h);
|
||||
|
||||
if (frame_h <= visible_height_) {
|
||||
frame_surface_->render(nullptr, &frame_dst_);
|
||||
} else {
|
||||
int max_scroll = frame_h - visible_height_;
|
||||
scroll_y_ = std::clamp(scroll_y_, 0, max_scroll);
|
||||
|
||||
SDL_FRect src = {.x = 0, .y = static_cast<float>(scroll_y_), .w = frame_dst_.w, .h = static_cast<float>(visible_height_)};
|
||||
SDL_FRect dst = {.x = frame_dst_.x, .y = 0, .w = frame_dst_.w, .h = static_cast<float>(visible_height_)};
|
||||
frame_surface_->render(&src, &dst);
|
||||
}
|
||||
|
||||
// Highlights (en game_surface, encima del frame)
|
||||
int out_cell = Tile::SIZE + spacing_out_;
|
||||
float tileset_screen_x = frame_dst_.x + BORDER_PAD;
|
||||
float tileset_screen_y = frame_dst_.y + BORDER_PAD - static_cast<float>(scroll_y_);
|
||||
constexpr auto TS = static_cast<float>(Tile::SIZE);
|
||||
|
||||
// Highlight del tile bajo el cursor (blanco)
|
||||
if (hover_tile_ >= 0) {
|
||||
int col = hover_tile_ % tileset_width_;
|
||||
int row = hover_tile_ / tileset_width_;
|
||||
float hx = tileset_screen_x + static_cast<float>(col * out_cell);
|
||||
float hy = tileset_screen_y + static_cast<float>(row * out_cell);
|
||||
if (hy >= 0 && hy + TS <= visible_height_) {
|
||||
SDL_FRect highlight = {.x = hx, .y = hy, .w = TS, .h = TS};
|
||||
game_surface->drawRectBorder(&highlight, stringToColor("bright_white"));
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight del tile actual (verde)
|
||||
if (current_tile_ >= 0) {
|
||||
int col = current_tile_ % tileset_width_;
|
||||
int row = current_tile_ / tileset_width_;
|
||||
float cx = tileset_screen_x + static_cast<float>(col * out_cell);
|
||||
float cy = tileset_screen_y + static_cast<float>(row * out_cell);
|
||||
if (cy >= 0 && cy + TS <= visible_height_) {
|
||||
SDL_FRect cur_rect = {.x = cx, .y = cy, .w = TS, .h = TS};
|
||||
game_surface->drawRectBorder(&cur_rect, stringToColor("bright_green"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Maneja eventos del picker
|
||||
void TilePicker::handleEvent(const SDL_Event& event) {
|
||||
if (!open_) { return; }
|
||||
|
||||
if (event.type == SDL_EVENT_MOUSE_MOTION) {
|
||||
updateMousePosition();
|
||||
}
|
||||
|
||||
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
|
||||
if (event.button.button == SDL_BUTTON_LEFT && hover_tile_ >= 0) {
|
||||
if (on_select) { on_select(hover_tile_); }
|
||||
close();
|
||||
} else if (event.button.button == SDL_BUTTON_RIGHT) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
if (event.type == SDL_EVENT_MOUSE_WHEEL) {
|
||||
scroll_y_ -= static_cast<int>(event.wheel.y) * Tile::SIZE * 2;
|
||||
int max_scroll = static_cast<int>(frame_dst_.h) - visible_height_;
|
||||
max_scroll = std::max(max_scroll, 0);
|
||||
scroll_y_ = std::clamp(scroll_y_, 0, max_scroll);
|
||||
updateMousePosition();
|
||||
}
|
||||
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
// Calcula qué tile está bajo el cursor
|
||||
void TilePicker::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();
|
||||
float game_x = render_x - dst_rect.x;
|
||||
float game_y = render_y - dst_rect.y;
|
||||
|
||||
// Coordenada relativa al contenido del frame (con scroll)
|
||||
float rel_x = game_x - frame_dst_.x - BORDER_PAD;
|
||||
float rel_y = game_y - frame_dst_.y - BORDER_PAD + static_cast<float>(scroll_y_);
|
||||
|
||||
// Convertir a tile teniendo en cuenta el spacing de salida
|
||||
int out_cell = Tile::SIZE + spacing_out_;
|
||||
int tile_x = static_cast<int>(rel_x) / out_cell;
|
||||
int tile_y = static_cast<int>(rel_y) / out_cell;
|
||||
|
||||
// Verificar que estamos sobre un tile y no sobre el spacing
|
||||
int local_x = static_cast<int>(rel_x) % out_cell;
|
||||
int local_y = static_cast<int>(rel_y) % out_cell;
|
||||
bool on_tile = (local_x < Tile::SIZE && local_y < Tile::SIZE);
|
||||
|
||||
if (on_tile && rel_x >= 0 && rel_y >= 0 &&
|
||||
tile_x >= 0 && tile_x < tileset_width_ &&
|
||||
tile_y >= 0 && tile_y < tileset_height_) {
|
||||
hover_tile_ = tile_y * tileset_width_ + tile_x;
|
||||
} else {
|
||||
hover_tile_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // _DEBUG
|
||||
64
source/game/editor/tile_picker.hpp
Normal file
64
source/game/editor/tile_picker.hpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <functional> // Para function
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
|
||||
class Surface;
|
||||
|
||||
/**
|
||||
* @brief Selector visual de tiles de un tileset
|
||||
*
|
||||
* Muestra el tileset centrado en el play area.
|
||||
* Hover ilumina el tile bajo el cursor.
|
||||
* Click selecciona el tile y cierra el picker.
|
||||
* Mouse wheel para scroll si el tileset es más alto que el play area.
|
||||
* ESC o click derecho para cancelar.
|
||||
*/
|
||||
class TilePicker {
|
||||
public:
|
||||
TilePicker() = default;
|
||||
~TilePicker() = default;
|
||||
|
||||
// Abre el picker con un tileset
|
||||
// bg_color: color de fondo del panel (-1 = negro)
|
||||
// source_color/target_color: sustitución de color (-1 = sin sustitución)
|
||||
// tile_spacing_in: pixels de separación entre tiles en el fichero fuente
|
||||
// tile_spacing_out: pixels de separación visual entre tiles al mostrar
|
||||
void open(const std::string& tileset_name, int current_tile = -1, int bg_color = -1, int source_color = -1, int target_color = -1, int tile_spacing_in = 0, int tile_spacing_out = 1);
|
||||
void close();
|
||||
[[nodiscard]] auto isOpen() const -> bool { return open_; }
|
||||
|
||||
void render();
|
||||
void handleEvent(const SDL_Event& event);
|
||||
|
||||
// Callback al seleccionar un tile (índice del tile)
|
||||
std::function<void(int)> on_select;
|
||||
|
||||
private:
|
||||
void updateMousePosition();
|
||||
|
||||
bool open_{false};
|
||||
std::shared_ptr<Surface> tileset_; // Surface del tileset original
|
||||
std::shared_ptr<Surface> frame_surface_; // Surface compuesta: borde + tileset
|
||||
SDL_FRect frame_dst_{}; // Posición del frame en pantalla
|
||||
int tileset_width_{0}; // Ancho del tileset en tiles
|
||||
int tileset_height_{0}; // Alto del tileset en tiles
|
||||
int current_tile_{-1}; // Tile actualmente seleccionado (highlight)
|
||||
int hover_tile_{-1}; // Tile bajo el cursor
|
||||
|
||||
// Spacing
|
||||
int spacing_in_{0}; // Spacing en el fichero fuente
|
||||
int spacing_out_{1}; // Spacing visual al mostrar
|
||||
|
||||
// Scroll y posicionamiento
|
||||
int scroll_y_{0}; // Scroll vertical en pixels
|
||||
int offset_x_{0}; // Offset X para centrar en pantalla
|
||||
int visible_height_{0}; // Altura visible en pixels
|
||||
};
|
||||
|
||||
#endif // _DEBUG
|
||||
@@ -26,7 +26,7 @@ Enemy::Enemy(const Data& enemy)
|
||||
|
||||
const int FLIP = (should_flip_ && enemy.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));
|
||||
sprite_->setFlip(static_cast<SDL_FlipMode>(FLIP | MIRROR)); // NOLINT(clang-analyzer-optin.core.EnumCastOutOfRange) SDL flags are designed for bitwise OR
|
||||
|
||||
collider_ = getRect();
|
||||
|
||||
@@ -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)); // NOLINT(clang-analyzer-optin.core.EnumCastOutOfRange)
|
||||
|
||||
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,21 @@ 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
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Cambia el tile del item (para editor)
|
||||
void Item::setTile(int tile) {
|
||||
sprite_->setClip((tile % 10) * ITEM_SIZE, (tile / 10) * ITEM_SIZE, ITEM_SIZE, ITEM_SIZE);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Asigna los colores del objeto
|
||||
void Item::setColors(Uint8 col1, Uint8 col2) {
|
||||
// Reinicializa el vector de colores
|
||||
|
||||
@@ -29,6 +29,10 @@ 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)
|
||||
void setTile(int tile); // Cambia el tile del item (para editor)
|
||||
#endif
|
||||
|
||||
private:
|
||||
static constexpr float ITEM_SIZE = 8.0F; // Tamaño del item en pixels
|
||||
|
||||
@@ -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,45 @@ 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);
|
||||
}
|
||||
|
||||
// Cambia un tile y repinta la celda (para editor)
|
||||
void Room::setTile(int index, int tile_value) {
|
||||
if (index >= 0 && index < static_cast<int>(tile_map_.size())) {
|
||||
tile_map_[index] = tile_value;
|
||||
tilemap_renderer_->setTile(index, tile_value);
|
||||
}
|
||||
}
|
||||
|
||||
// Cambia color de fondo y redibuja el mapa (para editor)
|
||||
void Room::setBgColor(const std::string& color) {
|
||||
bg_color_ = color;
|
||||
tilemap_renderer_->setBgColor(color);
|
||||
tilemap_renderer_->redrawMap(collision_map_.get());
|
||||
}
|
||||
|
||||
// Cambia colores de items en vivo (para editor)
|
||||
void Room::setItemColors(const std::string& color1, const std::string& color2) {
|
||||
item_color1_ = color1;
|
||||
item_color2_ = color2;
|
||||
Uint8 c1 = stringToColor(color1);
|
||||
Uint8 c2 = stringToColor(color2);
|
||||
auto* item_mgr = item_manager_.get();
|
||||
for (int i = 0; i < item_mgr->getCount(); ++i) {
|
||||
item_mgr->getItem(i)->setColors(c1, c2);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Actualiza las variables y objetos de la habitación
|
||||
|
||||
@@ -70,6 +70,15 @@ 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)
|
||||
void setBgColor(const std::string& color); // Cambia color de fondo y redibuja (para editor)
|
||||
void setItemColors(const std::string& color1, const std::string& color2); // Cambia colores de items (para editor)
|
||||
void setTile(int index, int tile_value); // Cambia un tile y redibuja (para editor)
|
||||
[[nodiscard]] auto getTileSetFile() const -> const std::string& { return tile_set_file_; }
|
||||
[[nodiscard]] auto getTileSetWidth() const -> int { return tile_set_width_; }
|
||||
#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:
|
||||
/**
|
||||
|
||||
@@ -23,8 +23,8 @@ Scoreboard::Scoreboard(std::shared_ptr<Data> data)
|
||||
constexpr float SURFACE_HEIGHT = 6.0F * Tile::SIZE;
|
||||
|
||||
// Reserva memoria para los objetos
|
||||
const std::string player_anim_path = Player::skinToAnimationPath(Options::game.player_skin);
|
||||
const auto& player_animation_data = Resource::Cache::get()->getAnimationData(player_anim_path);
|
||||
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");
|
||||
|
||||
@@ -78,8 +78,8 @@ auto Scoreboard::getTime() -> Scoreboard::ClockData { // NOLINT(readability-con
|
||||
|
||||
// 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);
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -15,17 +15,6 @@ Stats::Stats(std::string file, std::string buffer)
|
||||
Stats::~Stats() {
|
||||
// Vuelca los datos del buffer en la lista de estadisticas
|
||||
updateListFromBuffer();
|
||||
|
||||
// Calcula cual es la habitación con más muertes
|
||||
checkWorstNightmare();
|
||||
|
||||
// Guarda las estadísticas
|
||||
saveToFile(buffer_path_, buffer_list_);
|
||||
saveToFile(file_path_, list_);
|
||||
|
||||
buffer_list_.clear();
|
||||
list_.clear();
|
||||
dictionary_.clear();
|
||||
}
|
||||
|
||||
// Inicializador
|
||||
@@ -90,12 +79,9 @@ auto Stats::findByName(const std::string& name, const std::vector<RoomData>& lis
|
||||
}
|
||||
|
||||
// Carga las estadisticas desde un fichero
|
||||
auto Stats::loadFromFile(const std::string& file_path, std::vector<RoomData>& list) -> bool { // NOLINT(readability-convert-member-functions-to-static)
|
||||
void Stats::loadFromFile(const std::string& file_path, std::vector<RoomData>& list) { // NOLINT(readability-convert-member-functions-to-static)
|
||||
list.clear();
|
||||
|
||||
// Indicador de éxito en la carga
|
||||
bool success = true;
|
||||
|
||||
// Variables para manejar el fichero
|
||||
std::ifstream file(file_path);
|
||||
|
||||
@@ -108,8 +94,11 @@ auto Stats::loadFromFile(const std::string& file_path, std::vector<RoomData>& li
|
||||
if (!line.empty() && line.back() == '\r') {
|
||||
line.pop_back();
|
||||
}
|
||||
// Comprueba que la linea no sea un comentario
|
||||
if (!line.starts_with("#")) {
|
||||
// Comprueba que la linea no sea un comentario ni esté vacía
|
||||
if (line.empty() || line.starts_with("#")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RoomData stat;
|
||||
std::stringstream ss(line);
|
||||
std::string tmp;
|
||||
@@ -118,17 +107,20 @@ auto Stats::loadFromFile(const std::string& file_path, std::vector<RoomData>& li
|
||||
getline(ss, tmp, ';');
|
||||
stat.name = tmp;
|
||||
|
||||
// Obtiene las visitas
|
||||
// Obtiene las visitas y muertes
|
||||
try {
|
||||
getline(ss, tmp, ';');
|
||||
stat.visited = std::stoi(tmp);
|
||||
|
||||
// Obtiene las muertes
|
||||
getline(ss, tmp, ';');
|
||||
stat.died = std::stoi(tmp);
|
||||
} catch (const std::exception&) {
|
||||
// Línea con formato incorrecto, la ignora
|
||||
continue;
|
||||
}
|
||||
|
||||
list.push_back(stat);
|
||||
}
|
||||
}
|
||||
|
||||
// Cierra el fichero
|
||||
file.close();
|
||||
@@ -139,8 +131,6 @@ auto Stats::loadFromFile(const std::string& file_path, std::vector<RoomData>& li
|
||||
// Crea el fichero con los valores por defecto
|
||||
saveToFile(file_path, list);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Guarda las estadisticas en un fichero
|
||||
@@ -169,11 +159,6 @@ void Stats::checkWorstNightmare() { // NOLINT(readability-convert-member-functi
|
||||
}
|
||||
}
|
||||
|
||||
// Añade una entrada al diccionario
|
||||
void Stats::addDictionary(const std::string& number, const std::string& name) {
|
||||
dictionary_.push_back({.number = number, .name = name});
|
||||
}
|
||||
|
||||
// Vuelca los datos del buffer en la lista de estadisticas
|
||||
void Stats::updateListFromBuffer() {
|
||||
// Actualiza list_ desde buffer_list_
|
||||
@@ -192,6 +177,13 @@ void Stats::updateListFromBuffer() {
|
||||
}
|
||||
}
|
||||
|
||||
// Limpia el buffer después de volcarlo
|
||||
buffer_list_.clear();
|
||||
|
||||
// Calcula cual es la habitación con más muertes
|
||||
checkWorstNightmare();
|
||||
|
||||
// Guarda las estadísticas
|
||||
saveToFile(buffer_path_, buffer_list_);
|
||||
saveToFile(file_path_, list_);
|
||||
}
|
||||
@@ -11,13 +11,7 @@ class Stats {
|
||||
int died; // Cuenta las veces que se ha muerto en una habitación
|
||||
};
|
||||
|
||||
struct Dictionary {
|
||||
std::string number; // Numero de la habitación
|
||||
std::string name; // Nombre de la habitación
|
||||
};
|
||||
|
||||
// Variables
|
||||
std::vector<Dictionary> dictionary_; // Lista con la equivalencia nombre-numero de habitacion
|
||||
std::vector<RoomData> buffer_list_; // Lista con las estadisticas temporales por habitación
|
||||
std::vector<RoomData> list_; // Lista con las estadisticas completas por habitación
|
||||
std::string buffer_path_; // Fichero con las estadísticas temporales
|
||||
@@ -27,7 +21,7 @@ class Stats {
|
||||
static auto findByName(const std::string& name, const std::vector<RoomData>& list) -> int;
|
||||
|
||||
// Carga las estadisticas desde un fichero
|
||||
static auto loadFromFile(const std::string& file_path, std::vector<RoomData>& list) -> bool;
|
||||
static void loadFromFile(const std::string& file_path, std::vector<RoomData>& list);
|
||||
|
||||
// Guarda las estadisticas en un fichero
|
||||
static void saveToFile(const std::string& file_path, const std::vector<RoomData>& list);
|
||||
@@ -39,14 +33,13 @@ class Stats {
|
||||
void updateListFromBuffer();
|
||||
|
||||
public:
|
||||
// Constructostd::string nst stdstd::string nst std::string& buffer);
|
||||
// Constructor
|
||||
Stats(std::string file, std::string buffer);
|
||||
|
||||
// Destructor
|
||||
~Stats();
|
||||
|
||||
// Inicializador
|
||||
// Se debe llamar a este procedimiento una vez se haya creado el diccionario numero-nombre
|
||||
void init();
|
||||
|
||||
// Añade una muerte a las estadisticas
|
||||
@@ -54,7 +47,4 @@ class Stats {
|
||||
|
||||
// Añade una visita a las estadisticas
|
||||
void addVisit(const std::string& name);
|
||||
|
||||
// Añade una entrada al diccionario
|
||||
void addDictionary(const std::string& number, const std::string& name);
|
||||
};
|
||||
@@ -100,6 +100,37 @@ static void renderDebugCollisionSurfaces(const CollisionMap* collision_map) {
|
||||
void TilemapRenderer::redrawMap(const CollisionMap* collision_map) {
|
||||
fillMapTexture(collision_map);
|
||||
}
|
||||
|
||||
// Cambia un tile y repinta solo esa celda en la map_surface
|
||||
void TilemapRenderer::setTile(int index, int tile_value) {
|
||||
if (index < 0 || index >= static_cast<int>(tile_map_.size())) { return; }
|
||||
|
||||
tile_map_[index] = tile_value;
|
||||
|
||||
int col = index % MAP_WIDTH;
|
||||
int row = index / MAP_WIDTH;
|
||||
|
||||
auto previous_renderer = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(map_surface_);
|
||||
|
||||
// Borrar la celda con el color de fondo
|
||||
SDL_FRect cell = {.x = static_cast<float>(col * TILE_SIZE), .y = static_cast<float>(row * TILE_SIZE), .w = static_cast<float>(TILE_SIZE), .h = static_cast<float>(TILE_SIZE)};
|
||||
map_surface_->fillRect(&cell, stringToColor(bg_color_));
|
||||
|
||||
// Dibujar el nuevo tile (si no es vacío ni animado)
|
||||
if (tile_value > -1) {
|
||||
const bool IS_ANIMATED = (tile_value >= 18 * tile_set_width_) && (tile_value < 19 * tile_set_width_);
|
||||
if (!IS_ANIMATED) {
|
||||
SDL_FRect clip = {.x = static_cast<float>((tile_value % tile_set_width_) * TILE_SIZE),
|
||||
.y = static_cast<float>((tile_value / tile_set_width_) * TILE_SIZE),
|
||||
.w = static_cast<float>(TILE_SIZE),
|
||||
.h = static_cast<float>(TILE_SIZE)};
|
||||
tileset_surface_->render(col * TILE_SIZE, row * TILE_SIZE, &clip);
|
||||
}
|
||||
}
|
||||
|
||||
Screen::get()->setRendererSurface(previous_renderer);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Pinta el mapa estático y debug lines
|
||||
|
||||
@@ -69,6 +69,8 @@ class TilemapRenderer {
|
||||
* Llamado cuando se activa/desactiva el modo debug para actualizar la visualización
|
||||
*/
|
||||
void redrawMap(const CollisionMap* collision_map);
|
||||
void setBgColor(const std::string& color) { bg_color_ = color; }
|
||||
void setTile(int index, int tile_value); // Cambia un tile y repinta esa celda
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
||||
@@ -359,8 +359,8 @@ namespace Options {
|
||||
}
|
||||
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;
|
||||
const auto 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;
|
||||
}
|
||||
@@ -566,7 +566,7 @@ namespace Options {
|
||||
}
|
||||
|
||||
// Carga configuración de audio desde YAML
|
||||
void loadAudioConfigFromYaml(const fkyaml::node& yaml) {
|
||||
void loadAudioConfigFromYaml(const fkyaml::node& yaml) { // NOLINT(readability-function-cognitive-complexity)
|
||||
if (!yaml.contains("audio")) { return; }
|
||||
const auto& a = yaml["audio"];
|
||||
|
||||
@@ -688,9 +688,9 @@ 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;
|
||||
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;
|
||||
@@ -698,9 +698,9 @@ namespace Options {
|
||||
|
||||
// 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;
|
||||
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;
|
||||
}
|
||||
@@ -1021,17 +1021,17 @@ namespace Options {
|
||||
}
|
||||
|
||||
// Carga los presets del shader CrtPi desde el fichero. Crea defaults si no existe.
|
||||
auto loadCrtPiFromFile() -> bool {
|
||||
auto loadCrtPiFromFile() -> bool { // NOLINT(readability-function-cognitive-complexity)
|
||||
crtpi_presets.clear();
|
||||
|
||||
std::ifstream file(crtpi_file_path);
|
||||
if (!file.good()) {
|
||||
std::cout << "CrtPi file not found, creating default: " << crtpi_file_path << '\n';
|
||||
// Crear directorio padre si no existe
|
||||
const std::filesystem::path p(crtpi_file_path);
|
||||
if (p.has_parent_path()) {
|
||||
const std::filesystem::path P(crtpi_file_path);
|
||||
if (P.has_parent_path()) {
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories(p.parent_path(), ec);
|
||||
std::filesystem::create_directories(P.parent_path(), ec);
|
||||
}
|
||||
// Escribir defaults
|
||||
std::ofstream out(crtpi_file_path);
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
|
||||
#ifdef _DEBUG
|
||||
#include "core/system/debug.hpp" // Para Debug
|
||||
#include "game/editor/map_editor.hpp" // Para MapEditor
|
||||
#endif
|
||||
|
||||
// Constructor
|
||||
@@ -49,6 +50,20 @@ Game::Game(Mode mode)
|
||||
current_room_(Defaults::Game::Room::INITIAL),
|
||||
spawn_data_(Player::SpawnData(Defaults::Game::Player::SPAWN_X, Defaults::Game::Player::SPAWN_Y, 0, 0, 0, Player::State::ON_GROUND, Defaults::Game::Player::SPAWN_FLIP)) {
|
||||
#endif
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Validar que la room de debug existe; si no, fallback a la default
|
||||
if (Resource::List::get()->get(current_room_).empty()) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Debug room %s not found, using default", current_room_.c_str());
|
||||
current_room_ = Defaults::Game::Room::INITIAL;
|
||||
spawn_data_ = Player::SpawnData(Defaults::Game::Player::SPAWN_X, Defaults::Game::Player::SPAWN_Y, 0, 0, 0, Player::State::ON_GROUND, Defaults::Game::Player::SPAWN_FLIP);
|
||||
auto ss = Debug::get()->getSpawnSettings();
|
||||
ss.room = current_room_;
|
||||
Debug::get()->setSpawnSettings(ss);
|
||||
Debug::get()->saveToFile();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Crea objetos e inicializa variables
|
||||
ItemTracker::init();
|
||||
demoInit();
|
||||
@@ -64,6 +79,11 @@ Game::Game(Mode mode)
|
||||
Cheevos::get()->enable(!Options::cheats.enabled()); // Deshabilita los logros si hay trucos activados
|
||||
Cheevos::get()->clearUnobtainableState();
|
||||
|
||||
#ifdef _DEBUG
|
||||
Console::get()->setScope("debug");
|
||||
#else
|
||||
Console::get()->setScope("game");
|
||||
#endif
|
||||
Console::get()->on_toggle = [this](bool open) { player_->setIgnoreInput(open); };
|
||||
if (Console::get()->isActive()) { player_->setIgnoreInput(true); }
|
||||
GameControl::change_player_skin = [this](const std::string& skin_name) -> void {
|
||||
@@ -119,6 +139,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;
|
||||
@@ -139,6 +179,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
|
||||
}
|
||||
|
||||
@@ -148,9 +194,30 @@ void Game::handleEvents() {
|
||||
while (SDL_PollEvent(&event)) {
|
||||
GlobalEvents::handle(event);
|
||||
#ifdef _DEBUG
|
||||
// En modo editor: click del ratón cierra la consola
|
||||
if (Console::get()->isActive() && MapEditor::get()->isActive() &&
|
||||
event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
|
||||
Console::get()->toggle();
|
||||
}
|
||||
|
||||
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 (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_8 && static_cast<int>(event.key.repeat) == 0 && MapEditor::get()->isActive()) {
|
||||
MapEditor::get()->showGrid(!MapEditor::get()->isGridEnabled());
|
||||
} else if (MapEditor::get()->isActive()) {
|
||||
MapEditor::get()->handleEvent(event);
|
||||
} else {
|
||||
handleDebugEvents(event);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -172,6 +239,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();
|
||||
@@ -240,6 +315,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_) {
|
||||
@@ -379,8 +462,20 @@ 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();
|
||||
renderRoomName();
|
||||
Screen::get()->render();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Dibuja los elementos del juego en orden
|
||||
room_->renderEnemies();
|
||||
room_->renderItems();
|
||||
if (mode_ == Mode::GAME) {
|
||||
@@ -819,14 +914,8 @@ void Game::checkRestoringJail(float delta_time) {
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializa el diccionario de las estadísticas
|
||||
// Inicializa las estadísticas
|
||||
void Game::initStats() { // NOLINT(readability-convert-member-functions-to-static)
|
||||
auto list = Resource::Cache::get()->getRooms();
|
||||
|
||||
for (const auto& room : list) {
|
||||
stats_->addDictionary(room.room->number, room.room->name);
|
||||
}
|
||||
|
||||
stats_->init();
|
||||
}
|
||||
|
||||
|
||||
@@ -157,14 +157,14 @@ void Console::redrawText() {
|
||||
|
||||
// Línea de input (siempre la última)
|
||||
const bool SHOW_CURSOR = cursor_visible_ && (static_cast<int>(input_line_.size()) < MAX_LINE_CHARS);
|
||||
const std::string INPUT_STR = "> " + input_line_ + (SHOW_CURSOR ? "_" : "");
|
||||
const std::string INPUT_STR = prompt_ + input_line_ + (SHOW_CURSOR ? "_" : "");
|
||||
text_->writeColored(PADDING_IN_H, y_pos, INPUT_STR, BORDER_COLOR);
|
||||
|
||||
Screen::get()->setRendererSurface(previous_renderer);
|
||||
}
|
||||
|
||||
// Actualiza la animación de la consola
|
||||
void Console::update(float delta_time) {
|
||||
void Console::update(float delta_time) { // NOLINT(readability-function-cognitive-complexity)
|
||||
if (status_ == Status::HIDDEN) {
|
||||
return;
|
||||
}
|
||||
@@ -196,9 +196,9 @@ void Console::update(float delta_time) {
|
||||
if (status_ == Status::ACTIVE && height_ != target_height_) {
|
||||
const float PREV_HEIGHT = height_;
|
||||
if (height_ < target_height_) {
|
||||
height_ = std::min(height_ + SLIDE_SPEED * delta_time, target_height_);
|
||||
height_ = std::min(height_ + (SLIDE_SPEED * delta_time), target_height_);
|
||||
} else {
|
||||
height_ = std::max(height_ - SLIDE_SPEED * delta_time, target_height_);
|
||||
height_ = std::max(height_ - (SLIDE_SPEED * delta_time), target_height_);
|
||||
}
|
||||
// Actualizar el Notifier incrementalmente con el delta de altura
|
||||
if (Notifier::get() != nullptr) {
|
||||
@@ -300,7 +300,7 @@ void Console::toggle() {
|
||||
}
|
||||
|
||||
// Procesa el evento SDL: entrada de texto, Backspace, Enter
|
||||
void Console::handleEvent(const SDL_Event& event) {
|
||||
void Console::handleEvent(const SDL_Event& event) { // NOLINT(readability-function-cognitive-complexity)
|
||||
if (status_ != Status::ACTIVE) { return; }
|
||||
|
||||
if (event.type == SDL_EVENT_TEXT_INPUT) {
|
||||
@@ -348,8 +348,8 @@ void Console::handleEvent(const SDL_Event& event) {
|
||||
std::string upper;
|
||||
for (unsigned char c : input_line_) { upper += static_cast<char>(std::toupper(c)); }
|
||||
|
||||
const size_t space_pos = upper.rfind(' ');
|
||||
if (space_pos == std::string::npos) {
|
||||
const size_t SPACE_POS = upper.rfind(' ');
|
||||
if (SPACE_POS == std::string::npos) {
|
||||
// Modo comando: ciclar keywords visibles que empiecen por el prefijo
|
||||
for (const auto& kw : registry_.getVisibleKeywords()) {
|
||||
if (upper.empty() || kw.starts_with(upper)) {
|
||||
@@ -357,12 +357,12 @@ void Console::handleEvent(const SDL_Event& event) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const std::string base_cmd = upper.substr(0, space_pos);
|
||||
const std::string sub_prefix = upper.substr(space_pos + 1);
|
||||
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);
|
||||
const std::string BASE_CMD = upper.substr(0, SPACE_POS);
|
||||
const std::string SUB_PREFIX = upper.substr(SPACE_POS + 1);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -444,3 +444,7 @@ auto Console::getVisibleHeight() -> int {
|
||||
if (status_ == Status::HIDDEN) { return 0; }
|
||||
return static_cast<int>(y_ + height_);
|
||||
}
|
||||
|
||||
// Scope de comandos
|
||||
void Console::setScope(const std::string& scope) { registry_.setScope(scope); }
|
||||
auto Console::getScope() const -> std::string { return registry_.getScope(); }
|
||||
|
||||
@@ -32,6 +32,13 @@ class Console {
|
||||
auto getVisibleHeight() -> int; // Píxeles visibles actuales (0 = oculta, height_ = totalmente visible)
|
||||
[[nodiscard]] auto getText() const -> std::shared_ptr<Text> { return text_; }
|
||||
|
||||
// Prompt configurable (por defecto "> ")
|
||||
void setPrompt(const std::string& prompt) { prompt_ = prompt; }
|
||||
|
||||
// Scope de comandos (filtra help y tab completion)
|
||||
void setScope(const std::string& scope);
|
||||
[[nodiscard]] auto getScope() const -> std::string;
|
||||
|
||||
// Callback llamado al abrir (true) o cerrar (false) la consola
|
||||
std::function<void(bool)> on_toggle;
|
||||
|
||||
@@ -51,7 +58,7 @@ class Console {
|
||||
|
||||
// Constantes de consola
|
||||
static constexpr std::string_view CONSOLE_NAME = "JDD Console";
|
||||
static constexpr std::string_view CONSOLE_VERSION = "v2.1";
|
||||
static constexpr std::string_view CONSOLE_VERSION = "v2.2";
|
||||
static constexpr int MAX_LINE_CHARS = 32;
|
||||
static constexpr int MAX_HISTORY_SIZE = 20;
|
||||
static constexpr float CURSOR_ON_TIME = 0.5F;
|
||||
@@ -84,6 +91,7 @@ class Console {
|
||||
// Estado de la entrada de texto
|
||||
std::vector<std::string> msg_lines_; // Líneas de mensaje (1 o más)
|
||||
std::string input_line_;
|
||||
std::string prompt_{"> "}; // Prompt configurable
|
||||
float cursor_timer_{0.0F};
|
||||
bool cursor_visible_{true};
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "core/rendering/render_info.hpp" // Para RenderInfo
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/resources/resource_helper.hpp" // Para Resource::Helper
|
||||
#include "core/resources/resource_list.hpp" // Para Resource::List
|
||||
#include "external/fkyaml_node.hpp" // Para fkyaml::node
|
||||
#include "game/game_control.hpp" // Para GameControl
|
||||
#include "game/options.hpp" // Para Options
|
||||
@@ -23,6 +24,7 @@
|
||||
|
||||
#ifdef _DEBUG
|
||||
#include "core/system/debug.hpp" // Para Debug
|
||||
#include "game/editor/map_editor.hpp" // Para MapEditor
|
||||
#endif
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
@@ -53,7 +55,7 @@ static auto boolToggle(
|
||||
// ── Command handlers ─────────────────────────────────────────────────────────
|
||||
|
||||
// SS [ON|OFF|SIZE|UPSCALE [NEAREST|LINEAR]|DOWNSCALE [BILINEAR|LANCZOS2|LANCZOS3]]
|
||||
static auto cmd_ss(const std::vector<std::string>& args) -> std::string {
|
||||
static auto cmdSs(const std::vector<std::string>& args) -> std::string { // NOLINT(readability-function-cognitive-complexity)
|
||||
if (!Screen::get()->isHardwareAccelerated()) { return "No GPU acceleration"; }
|
||||
static const std::array<std::string_view, 3> DOWNSCALE_NAMES = {"Bilinear", "Lanczos2", "Lanczos3"};
|
||||
if (!args.empty() && args[0] == "SIZE") {
|
||||
@@ -116,34 +118,34 @@ static auto applyPreset(const std::vector<std::string>& args) -> std::string {
|
||||
const bool IS_CRTPI = Options::video.shader.current_shader == Rendering::ShaderType::CRTPI;
|
||||
const auto& presets_postfx = Options::postfx_presets;
|
||||
const auto& presets_crtpi = Options::crtpi_presets;
|
||||
const std::string shader_label = IS_CRTPI ? "CrtPi" : "PostFX";
|
||||
const std::string SHADER_LABEL = IS_CRTPI ? "CrtPi" : "PostFX";
|
||||
|
||||
auto& current_idx = IS_CRTPI ? Options::video.shader.current_crtpi_preset
|
||||
: Options::video.shader.current_postfx_preset;
|
||||
const int count = static_cast<int>(IS_CRTPI ? presets_crtpi.size() : presets_postfx.size());
|
||||
const int COUNT = static_cast<int>(IS_CRTPI ? presets_crtpi.size() : presets_postfx.size());
|
||||
|
||||
if (count == 0) { return "No " + shader_label + " presets available"; }
|
||||
if (COUNT == 0) { return "No " + SHADER_LABEL + " presets available"; }
|
||||
|
||||
const auto presetName = [&]() -> std::string {
|
||||
const auto& name = IS_CRTPI ? presets_crtpi[static_cast<size_t>(current_idx)].name
|
||||
: presets_postfx[static_cast<size_t>(current_idx)].name;
|
||||
const auto PRESET_NAME = [&]() -> std::string {
|
||||
const auto& name = IS_CRTPI ? presets_crtpi[static_cast<size_t>(current_idx)].name // NOLINT(clang-analyzer-core.CallAndMessage)
|
||||
: presets_postfx[static_cast<size_t>(current_idx)].name; // NOLINT(clang-analyzer-core.CallAndMessage)
|
||||
return prettyName(name);
|
||||
};
|
||||
|
||||
if (args.empty()) {
|
||||
return shader_label + " preset: " + presetName();
|
||||
return SHADER_LABEL + " preset: " + PRESET_NAME();
|
||||
}
|
||||
|
||||
if (args[0] == "NEXT") {
|
||||
current_idx = (current_idx + 1) % count;
|
||||
current_idx = (current_idx + 1) % COUNT;
|
||||
} else if (args[0] == "PREV") {
|
||||
current_idx = (current_idx - 1 + count) % count;
|
||||
current_idx = (current_idx - 1 + COUNT) % COUNT;
|
||||
} else {
|
||||
// Buscar por nombre (case-insensitive, con guiones)
|
||||
std::string search = args[0];
|
||||
std::ranges::transform(search, search.begin(), ::toupper);
|
||||
bool found = false;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
for (int i = 0; i < COUNT; ++i) {
|
||||
const auto& name = IS_CRTPI ? presets_crtpi[static_cast<size_t>(i)].name
|
||||
: presets_postfx[static_cast<size_t>(i)].name;
|
||||
if (toUpper(name) == search) {
|
||||
@@ -164,11 +166,11 @@ static auto applyPreset(const std::vector<std::string>& args) -> std::string {
|
||||
} else {
|
||||
Screen::get()->reloadPostFX();
|
||||
}
|
||||
return shader_label + " preset: " + presetName();
|
||||
return SHADER_LABEL + " preset: " + PRESET_NAME();
|
||||
}
|
||||
|
||||
// SHADER [ON|OFF|NEXT|POSTFX|CRTPI|PRESET [NEXT|PREV|<name>]]
|
||||
static auto cmd_shader(const std::vector<std::string>& args) -> std::string {
|
||||
static auto cmdShader(const std::vector<std::string>& args) -> std::string {
|
||||
if (!Screen::get()->isHardwareAccelerated()) { return "No GPU acceleration"; }
|
||||
if (args.empty()) {
|
||||
Screen::get()->toggleShaders();
|
||||
@@ -198,19 +200,19 @@ static auto cmd_shader(const std::vector<std::string>& args) -> std::string {
|
||||
(Options::video.shader.current_shader == Rendering::ShaderType::CRTPI ? "CrtPi" : "PostFX");
|
||||
}
|
||||
if (args[0] == "PRESET") {
|
||||
const std::vector<std::string> rest(args.begin() + 1, args.end());
|
||||
return applyPreset(rest);
|
||||
const std::vector<std::string> REST(args.begin() + 1, args.end());
|
||||
return applyPreset(REST);
|
||||
}
|
||||
return "usage: shader [on|off|next|postfx|crtpi|preset [next|prev|<name>]]";
|
||||
}
|
||||
|
||||
// BORDER [ON|OFF]
|
||||
static auto cmd_border(const std::vector<std::string>& args) -> std::string {
|
||||
static auto cmdBorder(const std::vector<std::string>& args) -> std::string {
|
||||
return boolToggle("Border", Options::video.border.enabled, [] { Screen::get()->toggleBorder(); }, args);
|
||||
}
|
||||
|
||||
// FULLSCREEN [ON|OFF [PLEASE]]
|
||||
static auto cmd_fullscreen(const std::vector<std::string>& args) -> std::string {
|
||||
static auto cmdFullscreen(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";
|
||||
@@ -238,8 +240,8 @@ static auto cmd_fullscreen(const std::vector<std::string>& args) -> std::string
|
||||
}
|
||||
|
||||
// ZOOM UP/DOWN/<num>
|
||||
static auto cmd_zoom(const std::vector<std::string>& args) -> std::string {
|
||||
if (args.empty()) { return "usage: zoom [up|down|<1-" + std::to_string(Screen::get()->getMaxZoom()) + ">]"; }
|
||||
static auto cmdZoom(const std::vector<std::string>& args) -> std::string {
|
||||
if (args.empty()) { return "usage: zoom [up|down|<1-" + std::to_string(Screen::getMaxZoom()) + ">]"; }
|
||||
if (args[0] == "UP") {
|
||||
if (!Screen::get()->incWindowZoom()) { return "Max zoom reached"; }
|
||||
return "Zoom " + std::to_string(Options::window.zoom);
|
||||
@@ -250,7 +252,7 @@ static auto cmd_zoom(const std::vector<std::string>& args) -> std::string {
|
||||
}
|
||||
try {
|
||||
const int N = std::stoi(args[0]);
|
||||
const int MAX = Screen::get()->getMaxZoom();
|
||||
const int MAX = Screen::getMaxZoom();
|
||||
if (N < 1 || N > MAX) {
|
||||
return "Zoom must be between 1 and " + std::to_string(MAX);
|
||||
}
|
||||
@@ -258,11 +260,11 @@ static auto cmd_zoom(const std::vector<std::string>& args) -> std::string {
|
||||
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()) + ">]";
|
||||
return "usage: zoom [up|down|<1-" + std::to_string(Screen::getMaxZoom()) + ">]";
|
||||
}
|
||||
|
||||
// INTSCALE [ON|OFF]
|
||||
static auto cmd_intscale(const std::vector<std::string>& args) -> std::string {
|
||||
static auto cmdIntscale(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") {
|
||||
@@ -277,12 +279,12 @@ static auto cmd_intscale(const std::vector<std::string>& args) -> std::string {
|
||||
}
|
||||
|
||||
// VSYNC [ON|OFF]
|
||||
static auto cmd_vsync(const std::vector<std::string>& args) -> std::string {
|
||||
static auto cmdVsync(const std::vector<std::string>& args) -> std::string {
|
||||
return boolToggle("VSync", Options::video.vertical_sync, [] { Screen::get()->toggleVSync(); }, args);
|
||||
}
|
||||
|
||||
// DRIVER [LIST|AUTO|NONE|<name>]
|
||||
static auto cmd_driver(const std::vector<std::string>& args) -> std::string {
|
||||
static auto cmdDriver(const std::vector<std::string>& args) -> std::string {
|
||||
if (args.empty()) {
|
||||
const auto& driver = Screen::get()->getGPUDriver();
|
||||
return "GPU: " + (driver.empty() ? std::string("sdl") : driver);
|
||||
@@ -334,22 +336,22 @@ static auto cmd_driver(const std::vector<std::string>& args) -> std::string {
|
||||
}
|
||||
|
||||
// PALETTE NEXT/PREV/SORT/DEFAULT/<name>
|
||||
static auto cmd_palette(const std::vector<std::string>& args) -> std::string {
|
||||
const auto palName = []() -> std::string {
|
||||
static auto cmdPalette(const std::vector<std::string>& args) -> std::string {
|
||||
const auto PAL_NAME = []() -> std::string {
|
||||
return Screen::get()->getPalettePrettyName();
|
||||
};
|
||||
if (args.empty()) { return "usage: palette [next|prev|sort [original|luminance|spectrum]|default|<name>]"; }
|
||||
if (args[0] == "NEXT") {
|
||||
Screen::get()->nextPalette();
|
||||
return "Palette: " + palName();
|
||||
return "Palette: " + PAL_NAME();
|
||||
}
|
||||
if (args[0] == "PREV") {
|
||||
Screen::get()->previousPalette();
|
||||
return "Palette: " + palName();
|
||||
return "Palette: " + PAL_NAME();
|
||||
}
|
||||
if (args[0] == "DEFAULT") {
|
||||
Screen::get()->setPaletteByName(Defaults::Video::PALETTE_NAME);
|
||||
return "Palette: " + palName();
|
||||
return "Palette: " + PAL_NAME();
|
||||
}
|
||||
if (args[0] == "SORT") {
|
||||
if (args.size() == 1) {
|
||||
@@ -372,11 +374,11 @@ static auto cmd_palette(const std::vector<std::string>& args) -> std::string {
|
||||
std::ranges::transform(arg_lower, arg_lower.begin(), ::tolower);
|
||||
return "Unknown palette: " + arg_lower;
|
||||
}
|
||||
return "Palette: " + palName();
|
||||
return "Palette: " + PAL_NAME();
|
||||
}
|
||||
|
||||
// AUDIO [ON|OFF|VOL <0-100>]
|
||||
static auto cmd_audio(const std::vector<std::string>& args) -> std::string {
|
||||
static auto cmdAudio(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") +
|
||||
@@ -407,7 +409,7 @@ static auto cmd_audio(const std::vector<std::string>& args) -> std::string {
|
||||
}
|
||||
|
||||
// MUSIC [ON|OFF|VOL <0-100>]
|
||||
static auto cmd_music(const std::vector<std::string>& args) -> std::string {
|
||||
static auto cmdMusic(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") +
|
||||
@@ -442,7 +444,7 @@ static auto cmd_music(const std::vector<std::string>& args) -> std::string {
|
||||
}
|
||||
|
||||
// SOUND [ON|OFF|VOL <0-100>]
|
||||
static auto cmd_sound(const std::vector<std::string>& args) -> std::string {
|
||||
static auto cmdSound(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") +
|
||||
@@ -478,7 +480,7 @@ static auto cmd_sound(const std::vector<std::string>& args) -> std::string {
|
||||
|
||||
#ifdef _DEBUG
|
||||
// DEBUG [MODE [ON|OFF]|START [HERE|ROOM|POS|SCENE <name>]]
|
||||
static auto cmd_debug(const std::vector<std::string>& args) -> std::string {
|
||||
static auto cmdDebug(const std::vector<std::string>& args) -> std::string { // NOLINT(readability-function-cognitive-complexity)
|
||||
// --- START subcommands (START SCENE works from any scene) ---
|
||||
if (!args.empty() && args[0] == "START") {
|
||||
// START SCENE [<name>] — works from any scene
|
||||
@@ -560,33 +562,83 @@ static auto cmd_debug(const std::vector<std::string>& args) -> std::string {
|
||||
}
|
||||
|
||||
// ROOM <num>|NEXT|PREV
|
||||
static auto cmd_room(const std::vector<std::string>& args) -> std::string {
|
||||
// Helper: cambia de room teniendo en cuenta el editor (exit → change → re-enter)
|
||||
static auto changeRoomWithEditor(const std::string& room_file) -> std::string {
|
||||
if (!GameControl::change_room) { return "Game not initialized"; }
|
||||
|
||||
const bool EDITOR_WAS_ACTIVE = (MapEditor::get() != nullptr) && MapEditor::get()->isActive();
|
||||
|
||||
// Si el editor está activo, salir primero (guarda y recarga la room actual)
|
||||
if (EDITOR_WAS_ACTIVE && GameControl::exit_editor) {
|
||||
MapEditor::get()->setReenter(true);
|
||||
GameControl::exit_editor();
|
||||
}
|
||||
|
||||
// Cambiar de habitación
|
||||
if (!GameControl::change_room(room_file)) {
|
||||
// Si falla, re-entrar al editor en la room original
|
||||
if (EDITOR_WAS_ACTIVE && GameControl::enter_editor) {
|
||||
GameControl::enter_editor();
|
||||
}
|
||||
return std::string("Room not found: ") + room_file;
|
||||
}
|
||||
|
||||
// Si el editor estaba activo, re-entrar en la nueva room
|
||||
if (EDITOR_WAS_ACTIVE && GameControl::enter_editor) {
|
||||
GameControl::enter_editor();
|
||||
}
|
||||
|
||||
return std::string("Room: ") + room_file;
|
||||
}
|
||||
|
||||
static auto cmdRoom(const std::vector<std::string>& args) -> std::string { // NOLINT(readability-function-cognitive-complexity)
|
||||
if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; }
|
||||
if (args.empty()) { return "usage: room <1-60>|next|prev"; }
|
||||
if (args.empty()) { return "usage: room <1-60>|next|prev|left|right|up|down"; }
|
||||
|
||||
// DELETE: borrar la habitación actual
|
||||
if (args[0] == "DELETE") {
|
||||
if ((MapEditor::get() == nullptr) || !MapEditor::get()->isActive()) { return "Editor not active"; }
|
||||
return MapEditor::get()->deleteRoom();
|
||||
}
|
||||
|
||||
// NEW [LEFT|RIGHT|UP|DOWN]: crear habitación nueva (opcionalmente conectada)
|
||||
if (args[0] == "NEW") {
|
||||
if ((MapEditor::get() == nullptr) || !MapEditor::get()->isActive()) { return "Editor not active"; }
|
||||
std::string direction = (args.size() >= 2) ? args[1] : "";
|
||||
return MapEditor::get()->createNewRoom(direction);
|
||||
}
|
||||
|
||||
// Direcciones: LEFT, RIGHT, UP, DOWN
|
||||
if (args[0] == "LEFT" || args[0] == "RIGHT" || args[0] == "UP" || args[0] == "DOWN") {
|
||||
if (!GameControl::get_adjacent_room) { return "Game not initialized"; }
|
||||
const std::string ADJACENT = GameControl::get_adjacent_room(args[0]);
|
||||
if (ADJACENT == "0" || ADJACENT.empty()) {
|
||||
return "No room " + toLower(args[0]);
|
||||
}
|
||||
return changeRoomWithEditor(ADJACENT);
|
||||
}
|
||||
|
||||
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();
|
||||
const std::string CURRENT = GameControl::get_current_room();
|
||||
try {
|
||||
num = std::stoi(current.substr(0, current.find('.')));
|
||||
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"; }
|
||||
} catch (...) { return "usage: room <1-60>|next|prev|left|right|up|down"; }
|
||||
}
|
||||
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;
|
||||
return changeRoomWithEditor(buf);
|
||||
}
|
||||
|
||||
// ITEMS <0-200>
|
||||
static auto cmd_items(const std::vector<std::string>& args) -> std::string {
|
||||
static auto cmdItems(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: items <0-200>"; }
|
||||
int count = 0;
|
||||
@@ -600,7 +652,7 @@ static auto cmd_items(const std::vector<std::string>& args) -> std::string {
|
||||
}
|
||||
|
||||
// SCENE [LOGO|LOADING|TITLE|CREDITS|GAME|ENDING|ENDING2|RESTART]
|
||||
static auto cmd_scene(const std::vector<std::string>& args) -> std::string {
|
||||
static auto cmdScene(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]"; }
|
||||
|
||||
@@ -629,10 +681,121 @@ static auto cmd_scene(const std::vector<std::string>& args) -> std::string {
|
||||
if (args[0] == "ENDING2") { return GO_TO(SceneManager::Scene::ENDING2, "Ending 2"); }
|
||||
return "Unknown scene: " + args[0];
|
||||
}
|
||||
|
||||
// EDIT [ON|OFF|REVERT]
|
||||
static auto cmdEdit(const std::vector<std::string>& args) -> std::string { // NOLINT(readability-function-cognitive-complexity)
|
||||
if (args.empty()) {
|
||||
// Toggle: si está activo → off, si no → on
|
||||
if ((MapEditor::get() != nullptr) && MapEditor::get()->isActive()) {
|
||||
if (GameControl::exit_editor) { GameControl::exit_editor(); }
|
||||
return "Editor OFF";
|
||||
}
|
||||
if (GameControl::enter_editor) {
|
||||
GameControl::enter_editor();
|
||||
return "Editor ON";
|
||||
}
|
||||
return "Not in game";
|
||||
}
|
||||
if (args[0] == "ON") {
|
||||
if (GameControl::enter_editor) {
|
||||
GameControl::enter_editor();
|
||||
return "Editor ON";
|
||||
}
|
||||
return "Not in game";
|
||||
}
|
||||
if (args[0] == "OFF") {
|
||||
if (GameControl::exit_editor) {
|
||||
GameControl::exit_editor();
|
||||
return "Editor OFF";
|
||||
}
|
||||
return "Not in game";
|
||||
}
|
||||
if (args[0] == "REVERT") {
|
||||
if (GameControl::revert_editor) {
|
||||
return GameControl::revert_editor();
|
||||
}
|
||||
return "Editor not active";
|
||||
}
|
||||
// EDIT SHOW/HIDE INFO/GRID
|
||||
if ((args[0] == "SHOW" || args[0] == "HIDE") && args.size() >= 2) {
|
||||
if ((MapEditor::get() == nullptr) || !MapEditor::get()->isActive()) { return "Editor not active"; }
|
||||
bool show = (args[0] == "SHOW");
|
||||
if (args[1] == "INFO") { return MapEditor::get()->showInfo(show); }
|
||||
if (args[1] == "GRID") { return MapEditor::get()->showGrid(show); }
|
||||
}
|
||||
// EDIT MAPBG/MAPCONN <color>
|
||||
if (args[0] == "MAPBG" && args.size() >= 2) {
|
||||
if ((MapEditor::get() == nullptr) || !MapEditor::get()->isActive()) { return "Editor not active"; }
|
||||
return MapEditor::get()->setMiniMapBg(args[1]);
|
||||
}
|
||||
if (args[0] == "MAPCONN" && args.size() >= 2) {
|
||||
if ((MapEditor::get() == nullptr) || !MapEditor::get()->isActive()) { return "Editor not active"; }
|
||||
return MapEditor::get()->setMiniMapConn(args[1]);
|
||||
}
|
||||
return "usage: edit [on|off|revert|show|hide|mapbg|mapconn] [...]";
|
||||
}
|
||||
|
||||
// SET <property> <value> — modifica propiedad del enemigo seleccionado o de la habitación
|
||||
static auto cmdSet(const std::vector<std::string>& args) -> std::string {
|
||||
if ((MapEditor::get() == nullptr) || !MapEditor::get()->isActive()) { return "Editor not active"; }
|
||||
if (args.empty()) { return "usage: set <property> <value>"; }
|
||||
|
||||
// SET TILE no necesita argumento (abre el tile picker visual)
|
||||
if (args[0] == "TILE" && MapEditor::get()->hasSelectedItem()) {
|
||||
return MapEditor::get()->setItemProperty("TILE", "");
|
||||
}
|
||||
|
||||
if (args.size() < 2) { return "usage: set <property> <value>"; }
|
||||
|
||||
// Si hay enemigo seleccionado, aplicar a enemigo
|
||||
if (MapEditor::get()->hasSelectedEnemy()) {
|
||||
return MapEditor::get()->setEnemyProperty(args[0], args[1]);
|
||||
}
|
||||
|
||||
// Si hay item seleccionado, aplicar a item
|
||||
if (MapEditor::get()->hasSelectedItem()) {
|
||||
return MapEditor::get()->setItemProperty(args[0], args[1]);
|
||||
}
|
||||
|
||||
// Si no, aplicar a la habitación
|
||||
return MapEditor::get()->setRoomProperty(args[0], args[1]);
|
||||
}
|
||||
|
||||
// ENEMY [ADD|DELETE|DUPLICATE]
|
||||
static auto cmdEnemy(const std::vector<std::string>& args) -> std::string {
|
||||
if ((MapEditor::get() == nullptr) || !MapEditor::get()->isActive()) { return "Editor not active"; }
|
||||
if (args.empty()) { return "usage: enemy <add|delete|duplicate>"; }
|
||||
if (args[0] == "ADD") { return MapEditor::get()->addEnemy(); }
|
||||
if (args[0] == "DELETE") {
|
||||
if (!MapEditor::get()->hasSelectedEnemy()) { return "No enemy selected"; }
|
||||
return MapEditor::get()->deleteEnemy();
|
||||
}
|
||||
if (args[0] == "DUPLICATE") {
|
||||
if (!MapEditor::get()->hasSelectedEnemy()) { return "No enemy selected"; }
|
||||
return MapEditor::get()->duplicateEnemy();
|
||||
}
|
||||
return "usage: enemy <add|delete|duplicate>";
|
||||
}
|
||||
|
||||
// ITEM [ADD|DELETE|DUPLICATE]
|
||||
static auto cmdItem(const std::vector<std::string>& args) -> std::string {
|
||||
if ((MapEditor::get() == nullptr) || !MapEditor::get()->isActive()) { return "Editor not active"; }
|
||||
if (args.empty()) { return "usage: item <add|delete|duplicate>"; }
|
||||
if (args[0] == "ADD") { return MapEditor::get()->addItem(); }
|
||||
if (args[0] == "DELETE") {
|
||||
if (!MapEditor::get()->hasSelectedItem()) { return "No item selected"; }
|
||||
return MapEditor::get()->deleteItem();
|
||||
}
|
||||
if (args[0] == "DUPLICATE") {
|
||||
if (!MapEditor::get()->hasSelectedItem()) { return "No item selected"; }
|
||||
return MapEditor::get()->duplicateItem();
|
||||
}
|
||||
return "usage: item <add|delete|duplicate>";
|
||||
}
|
||||
#endif
|
||||
|
||||
// SHOW [INFO|NOTIFICATION|CHEEVO]
|
||||
static auto cmd_show(const std::vector<std::string>& args) -> std::string {
|
||||
static auto cmdShow(const std::vector<std::string>& args) -> std::string {
|
||||
#ifdef _DEBUG
|
||||
if (!args.empty() && args[0] == "NOTIFICATION") {
|
||||
Notifier::get()->show({"NOTIFICATION"});
|
||||
@@ -652,7 +815,7 @@ static auto cmd_show(const std::vector<std::string>& args) -> std::string {
|
||||
}
|
||||
|
||||
// HIDE [INFO]
|
||||
static auto cmd_hide(const std::vector<std::string>& args) -> std::string {
|
||||
static auto cmdHide(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();
|
||||
@@ -660,7 +823,7 @@ static auto cmd_hide(const std::vector<std::string>& args) -> std::string {
|
||||
}
|
||||
|
||||
// CHEAT [subcomando]
|
||||
static auto cmd_cheat(const std::vector<std::string>& args) -> std::string {
|
||||
static auto cmdCheat(const std::vector<std::string>& args) -> std::string { // NOLINT(readability-function-cognitive-complexity)
|
||||
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]"; }
|
||||
|
||||
@@ -722,7 +885,7 @@ static auto cmd_cheat(const std::vector<std::string>& args) -> std::string {
|
||||
}
|
||||
|
||||
// PLAYER SKIN / PLAYER COLOR
|
||||
static auto cmd_player(const std::vector<std::string>& args) -> std::string {
|
||||
static auto cmdPlayer(const std::vector<std::string>& args) -> std::string {
|
||||
if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; }
|
||||
|
||||
// PLAYER SKIN <name>
|
||||
@@ -757,14 +920,14 @@ static auto cmd_player(const std::vector<std::string>& args) -> std::string {
|
||||
}
|
||||
|
||||
// RESTART
|
||||
static auto cmd_restart(const std::vector<std::string>&) -> std::string {
|
||||
static auto cmdRestart(const std::vector<std::string>& /*unused*/) -> std::string {
|
||||
SceneManager::current = SceneManager::Scene::LOGO;
|
||||
Audio::get()->stopMusic();
|
||||
return "Restarting...";
|
||||
}
|
||||
|
||||
// KIOSK [ON|OFF PLEASE|PLEASE]
|
||||
static auto cmd_kiosk(const std::vector<std::string>& args) -> std::string {
|
||||
static auto cmdKiosk(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) {
|
||||
@@ -784,7 +947,7 @@ static auto cmd_kiosk(const std::vector<std::string>& args) -> std::string {
|
||||
}
|
||||
|
||||
// EXIT / QUIT
|
||||
static auto cmd_exit(const std::vector<std::string>& args) -> std::string {
|
||||
static auto cmdExit(const std::vector<std::string>& args) -> std::string {
|
||||
if (Options::kiosk.enabled && (args.empty() || args[0] != "PLEASE")) {
|
||||
return "Not allowed in kiosk mode";
|
||||
}
|
||||
@@ -793,7 +956,7 @@ static auto cmd_exit(const std::vector<std::string>& args) -> std::string {
|
||||
}
|
||||
|
||||
// SIZE
|
||||
static auto cmd_size(const std::vector<std::string>&) -> std::string {
|
||||
static auto cmdSize(const std::vector<std::string>& /*unused*/) -> std::string {
|
||||
int w = 0;
|
||||
int h = 0;
|
||||
SDL_GetWindowSize(SDL_GetRenderWindow(Screen::get()->getRenderer()), &w, &h);
|
||||
@@ -802,33 +965,37 @@ static auto cmd_size(const std::vector<std::string>&) -> std::string {
|
||||
|
||||
// ── CommandRegistry ──────────────────────────────────────────────────────────
|
||||
|
||||
void CommandRegistry::registerHandlers() {
|
||||
handlers_["cmd_ss"] = cmd_ss;
|
||||
handlers_["cmd_shader"] = cmd_shader;
|
||||
handlers_["cmd_border"] = cmd_border;
|
||||
handlers_["cmd_fullscreen"] = cmd_fullscreen;
|
||||
handlers_["cmd_zoom"] = cmd_zoom;
|
||||
handlers_["cmd_intscale"] = cmd_intscale;
|
||||
handlers_["cmd_vsync"] = cmd_vsync;
|
||||
handlers_["cmd_driver"] = cmd_driver;
|
||||
handlers_["cmd_palette"] = cmd_palette;
|
||||
handlers_["cmd_audio"] = cmd_audio;
|
||||
handlers_["cmd_music"] = cmd_music;
|
||||
handlers_["cmd_sound"] = cmd_sound;
|
||||
handlers_["cmd_show"] = cmd_show;
|
||||
handlers_["cmd_hide"] = cmd_hide;
|
||||
handlers_["cmd_cheat"] = cmd_cheat;
|
||||
handlers_["cmd_player"] = cmd_player;
|
||||
handlers_["cmd_restart"] = cmd_restart;
|
||||
handlers_["cmd_kiosk"] = cmd_kiosk;
|
||||
handlers_["cmd_exit"] = cmd_exit;
|
||||
handlers_["cmd_quit"] = cmd_exit; // QUIT usa el mismo handler que EXIT
|
||||
handlers_["cmd_size"] = cmd_size;
|
||||
void CommandRegistry::registerHandlers() { // NOLINT(readability-function-cognitive-complexity)
|
||||
handlers_["cmd_ss"] = cmdSs;
|
||||
handlers_["cmd_shader"] = cmdShader;
|
||||
handlers_["cmd_border"] = cmdBorder;
|
||||
handlers_["cmd_fullscreen"] = cmdFullscreen;
|
||||
handlers_["cmd_zoom"] = cmdZoom;
|
||||
handlers_["cmd_intscale"] = cmdIntscale;
|
||||
handlers_["cmd_vsync"] = cmdVsync;
|
||||
handlers_["cmd_driver"] = cmdDriver;
|
||||
handlers_["cmd_palette"] = cmdPalette;
|
||||
handlers_["cmd_audio"] = cmdAudio;
|
||||
handlers_["cmd_music"] = cmdMusic;
|
||||
handlers_["cmd_sound"] = cmdSound;
|
||||
handlers_["cmd_show"] = cmdShow;
|
||||
handlers_["cmd_hide"] = cmdHide;
|
||||
handlers_["cmd_cheat"] = cmdCheat;
|
||||
handlers_["cmd_player"] = cmdPlayer;
|
||||
handlers_["cmd_restart"] = cmdRestart;
|
||||
handlers_["cmd_kiosk"] = cmdKiosk;
|
||||
handlers_["cmd_exit"] = cmdExit;
|
||||
handlers_["cmd_quit"] = cmdExit; // QUIT usa el mismo handler que EXIT
|
||||
handlers_["cmd_size"] = cmdSize;
|
||||
#ifdef _DEBUG
|
||||
handlers_["cmd_debug"] = cmd_debug;
|
||||
handlers_["cmd_items"] = cmd_items;
|
||||
handlers_["cmd_room"] = cmd_room;
|
||||
handlers_["cmd_scene"] = cmd_scene;
|
||||
handlers_["cmd_debug"] = cmdDebug;
|
||||
handlers_["cmd_items"] = cmdItems;
|
||||
handlers_["cmd_room"] = cmdRoom;
|
||||
handlers_["cmd_scene"] = cmdScene;
|
||||
handlers_["cmd_edit"] = cmdEdit;
|
||||
handlers_["cmd_set"] = cmdSet;
|
||||
handlers_["cmd_enemy"] = cmdEnemy;
|
||||
handlers_["cmd_item"] = cmdItem;
|
||||
#endif
|
||||
// HELP se registra en load() como lambda que captura this
|
||||
|
||||
@@ -855,9 +1022,66 @@ void CommandRegistry::registerHandlers() {
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Colores de la paleta (compartido por SET COLOR, BGCOLOR, BORDER, ITEMCOLOR1, ITEMCOLOR2)
|
||||
auto color_provider = []() -> std::vector<std::string> {
|
||||
return {"BLACK", "BRIGHT_BLACK", "BLUE", "BRIGHT_BLUE", "RED", "BRIGHT_RED", "MAGENTA", "BRIGHT_MAGENTA", "GREEN", "BRIGHT_GREEN", "CYAN", "BRIGHT_CYAN", "YELLOW", "BRIGHT_YELLOW", "WHITE", "BRIGHT_WHITE"};
|
||||
};
|
||||
dynamic_providers_["SET COLOR"] = color_provider;
|
||||
dynamic_providers_["SET BGCOLOR"] = color_provider;
|
||||
dynamic_providers_["EDIT MAPBG"] = color_provider;
|
||||
dynamic_providers_["EDIT MAPCONN"] = color_provider;
|
||||
|
||||
// SET: propiedades dinámicas según selección en el editor
|
||||
dynamic_providers_["SET"] = []() -> std::vector<std::string> {
|
||||
if (MapEditor::get() != nullptr && MapEditor::get()->isActive()) {
|
||||
return MapEditor::get()->getSetCompletions();
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
// SET COLOR/BGCOLOR/etc. ya tienen sus providers arriba
|
||||
|
||||
// HELP: lista de comandos visibles en el scope activo
|
||||
dynamic_providers_["HELP"] = [this]() -> std::vector<std::string> {
|
||||
return getVisibleKeywords();
|
||||
};
|
||||
dynamic_providers_["SET BORDER"] = color_provider;
|
||||
dynamic_providers_["SET ITEMCOLOR1"] = color_provider;
|
||||
dynamic_providers_["SET ITEMCOLOR2"] = color_provider;
|
||||
|
||||
// SET ANIMATION: animaciones de enemigos (nombres sin extensión, UPPERCASE)
|
||||
dynamic_providers_["SET ANIMATION"] = []() -> std::vector<std::string> {
|
||||
std::vector<std::string> result;
|
||||
auto list = Resource::List::get()->getListByType(Resource::List::Type::ANIMATION);
|
||||
for (const auto& path : list) {
|
||||
if (path.find("enemies") == std::string::npos) { continue; }
|
||||
std::string name = getFileName(path);
|
||||
auto dot = name.rfind('.');
|
||||
if (dot != std::string::npos) { name = name.substr(0, dot); }
|
||||
result.push_back(toUpper(name));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// SET TILESET: tilesets disponibles (nombres sin extensión, UPPERCASE)
|
||||
dynamic_providers_["SET TILESET"] = []() -> std::vector<std::string> {
|
||||
std::vector<std::string> result;
|
||||
auto list = Resource::List::get()->getListByType(Resource::List::Type::BITMAP);
|
||||
for (const auto& path : list) {
|
||||
if (path.find("tilesets") == std::string::npos) { continue; }
|
||||
std::string name = getFileName(path);
|
||||
auto dot = name.rfind('.');
|
||||
if (dot != std::string::npos) { name = name.substr(0, dot); }
|
||||
result.push_back(toUpper(name));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandRegistry::load(const std::string& yaml_path) {
|
||||
void CommandRegistry::load(const std::string& yaml_path) { // NOLINT(readability-function-cognitive-complexity)
|
||||
registerHandlers();
|
||||
|
||||
// Cargar y parsear el YAML
|
||||
@@ -879,8 +1103,19 @@ void CommandRegistry::load(const std::string& yaml_path) {
|
||||
if (!yaml.contains("categories")) { return; }
|
||||
|
||||
for (const auto& cat_node : yaml["categories"]) {
|
||||
const std::string category = cat_node["name"].get_value<std::string>();
|
||||
const bool cat_debug_only = cat_node.contains("debug_only") && cat_node["debug_only"].get_value<bool>();
|
||||
const auto CATEGORY = cat_node["name"].get_value<std::string>();
|
||||
const bool CAT_DEBUG_ONLY = cat_node.contains("debug_only") && cat_node["debug_only"].get_value<bool>();
|
||||
|
||||
// Scopes por defecto de la categoría
|
||||
std::vector<std::string> cat_scopes;
|
||||
if (cat_node.contains("scope")) {
|
||||
const auto& scope_node = cat_node["scope"];
|
||||
if (scope_node.is_sequence()) {
|
||||
for (const auto& s : scope_node) { cat_scopes.push_back(s.get_value<std::string>()); }
|
||||
} else {
|
||||
cat_scopes.push_back(scope_node.get_value<std::string>());
|
||||
}
|
||||
}
|
||||
|
||||
if (!cat_node.contains("commands")) { continue; }
|
||||
|
||||
@@ -888,20 +1123,34 @@ void CommandRegistry::load(const std::string& yaml_path) {
|
||||
CommandDef def;
|
||||
def.keyword = cmd_node["keyword"].get_value<std::string>();
|
||||
def.handler_id = cmd_node["handler"].get_value<std::string>();
|
||||
def.category = category;
|
||||
def.category = CATEGORY;
|
||||
def.description = cmd_node.contains("description") ? cmd_node["description"].get_value<std::string>() : "";
|
||||
def.usage = cmd_node.contains("usage") ? cmd_node["usage"].get_value<std::string>() : def.keyword;
|
||||
def.instant = cmd_node.contains("instant") && cmd_node["instant"].get_value<bool>();
|
||||
def.hidden = cmd_node.contains("hidden") && cmd_node["hidden"].get_value<bool>();
|
||||
def.debug_only = cat_debug_only || (cmd_node.contains("debug_only") && cmd_node["debug_only"].get_value<bool>());
|
||||
def.debug_only = CAT_DEBUG_ONLY || (cmd_node.contains("debug_only") && cmd_node["debug_only"].get_value<bool>());
|
||||
def.help_hidden = cmd_node.contains("help_hidden") && cmd_node["help_hidden"].get_value<bool>();
|
||||
def.dynamic_completions = cmd_node.contains("dynamic_completions") && cmd_node["dynamic_completions"].get_value<bool>();
|
||||
|
||||
// Scopes: del comando, o hereda de la categoría, o "global" por defecto
|
||||
if (cmd_node.contains("scope")) {
|
||||
const auto& scope_node = cmd_node["scope"];
|
||||
if (scope_node.is_sequence()) {
|
||||
for (const auto& s : scope_node) { def.scopes.push_back(s.get_value<std::string>()); }
|
||||
} else {
|
||||
def.scopes.push_back(scope_node.get_value<std::string>());
|
||||
}
|
||||
} else if (!cat_scopes.empty()) {
|
||||
def.scopes = cat_scopes;
|
||||
} else {
|
||||
def.scopes.emplace_back("global");
|
||||
}
|
||||
|
||||
// Completions estáticas
|
||||
if (cmd_node.contains("completions")) {
|
||||
auto completions_node = cmd_node["completions"];
|
||||
for (auto it = completions_node.begin(); it != completions_node.end(); ++it) {
|
||||
std::string path = it.key().get_value<std::string>();
|
||||
auto path = it.key().get_value<std::string>();
|
||||
std::vector<std::string> opts;
|
||||
for (const auto& opt : *it) {
|
||||
opts.push_back(opt.get_value<std::string>());
|
||||
@@ -917,11 +1166,12 @@ void CommandRegistry::load(const std::string& yaml_path) {
|
||||
if (extras.contains("description")) { def.description = extras["description"].get_value<std::string>(); }
|
||||
if (extras.contains("usage")) { def.usage = extras["usage"].get_value<std::string>(); }
|
||||
if (extras.contains("hidden")) { def.hidden = extras["hidden"].get_value<bool>(); }
|
||||
if (extras.contains("help_hidden")) { def.help_hidden = extras["help_hidden"].get_value<bool>(); }
|
||||
if (extras.contains("completions")) {
|
||||
def.completions.clear();
|
||||
auto extras_completions = extras["completions"];
|
||||
for (auto it = extras_completions.begin(); it != extras_completions.end(); ++it) {
|
||||
std::string path = it.key().get_value<std::string>();
|
||||
auto path = it.key().get_value<std::string>();
|
||||
std::vector<std::string> opts;
|
||||
for (const auto& opt : *it) {
|
||||
opts.push_back(opt.get_value<std::string>());
|
||||
@@ -942,7 +1192,29 @@ void CommandRegistry::load(const std::string& yaml_path) {
|
||||
}
|
||||
|
||||
// Registrar el handler de HELP (captura this)
|
||||
handlers_["cmd_help"] = [this](const std::vector<std::string>&) -> std::string {
|
||||
handlers_["cmd_help"] = [this](const std::vector<std::string>& args) -> std::string {
|
||||
if (!args.empty()) {
|
||||
// HELP <command>: mostrar ayuda detallada de un comando
|
||||
const auto* cmd = findCommand(args[0]);
|
||||
if (cmd != nullptr) {
|
||||
std::string kw_lower = cmd->keyword;
|
||||
std::ranges::transform(kw_lower, kw_lower.begin(), ::tolower);
|
||||
std::string result = kw_lower + ": " + cmd->description + "\n" + cmd->usage;
|
||||
|
||||
// Listar subcomandos/opciones si hay completions
|
||||
auto opts = getCompletions(cmd->keyword);
|
||||
if (!opts.empty()) {
|
||||
result += "\noptions:";
|
||||
for (const auto& opt : opts) {
|
||||
std::string opt_lower = opt;
|
||||
std::ranges::transform(opt_lower, opt_lower.begin(), ::tolower);
|
||||
result += " " + opt_lower;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return "Unknown command: " + args[0];
|
||||
}
|
||||
std::cout << generateTerminalHelp();
|
||||
return generateConsoleHelp();
|
||||
};
|
||||
@@ -965,9 +1237,9 @@ auto CommandRegistry::findCommand(const std::string& keyword) const -> const Com
|
||||
auto CommandRegistry::execute(const std::string& keyword, const std::vector<std::string>& args) const -> std::string {
|
||||
const auto* def = findCommand(keyword);
|
||||
if (def == nullptr) { return ""; }
|
||||
const auto it = handlers_.find(def->handler_id);
|
||||
if (it == handlers_.end()) { return "Handler not found: " + def->handler_id; }
|
||||
return it->second(args);
|
||||
const auto IT = handlers_.find(def->handler_id);
|
||||
if (IT == handlers_.end()) { return "Handler not found: " + def->handler_id; }
|
||||
return IT->second(args);
|
||||
}
|
||||
|
||||
auto CommandRegistry::generateTerminalHelp() const -> std::string {
|
||||
@@ -998,50 +1270,92 @@ auto CommandRegistry::generateTerminalHelp() const -> std::string {
|
||||
return out.str();
|
||||
}
|
||||
|
||||
auto CommandRegistry::generateConsoleHelp() const -> std::string {
|
||||
std::string release_cmds;
|
||||
auto CommandRegistry::generateConsoleHelp() const -> std::string { // NOLINT(readability-function-cognitive-complexity)
|
||||
// Agrupar comandos visibles por scope
|
||||
std::string global_cmds;
|
||||
std::string debug_cmds;
|
||||
std::string editor_cmds;
|
||||
|
||||
for (const auto& cmd : commands_) {
|
||||
if (cmd.help_hidden) { continue; }
|
||||
if (!isCommandVisible(cmd)) { continue; }
|
||||
|
||||
// Convertir keyword a minúsculas para la lista
|
||||
std::string kw_lower = cmd.keyword;
|
||||
std::ranges::transform(kw_lower, kw_lower.begin(), ::tolower);
|
||||
|
||||
if (cmd.debug_only) {
|
||||
// Clasificar por el PRIMER scope del comando
|
||||
const std::string& primary = cmd.scopes.empty() ? "global" : cmd.scopes[0];
|
||||
|
||||
if (primary == "editor") {
|
||||
if (!editor_cmds.empty()) { editor_cmds += ", "; }
|
||||
editor_cmds += kw_lower;
|
||||
} else if (primary == "debug") {
|
||||
if (!debug_cmds.empty()) { debug_cmds += ", "; }
|
||||
debug_cmds += kw_lower;
|
||||
} else {
|
||||
if (!release_cmds.empty()) { release_cmds += ", "; }
|
||||
release_cmds += kw_lower;
|
||||
if (!global_cmds.empty()) { global_cmds += ", "; }
|
||||
global_cmds += kw_lower;
|
||||
}
|
||||
}
|
||||
|
||||
std::string result = "Commands:\n" + release_cmds + "\n";
|
||||
if (!debug_cmds.empty()) {
|
||||
result += "\nDebug commands:\n" + debug_cmds + "\n";
|
||||
// Construir resultado
|
||||
std::string result;
|
||||
|
||||
if (active_scope_ == "editor" && !editor_cmds.empty()) {
|
||||
result += "Editor:\n" + editor_cmds + "\n";
|
||||
result += "keys: 9=editor 8=grid e=eraser m=map\n";
|
||||
}
|
||||
|
||||
if (!debug_cmds.empty()) {
|
||||
if (!result.empty()) { result += "\n"; }
|
||||
result += "Debug:\n" + debug_cmds + "\n";
|
||||
}
|
||||
|
||||
if (!result.empty()) { result += "\n"; }
|
||||
result += "Commands:\n" + global_cmds + "\n";
|
||||
result += "-- more info on the terminal";
|
||||
return result;
|
||||
}
|
||||
|
||||
auto CommandRegistry::getCompletions(const std::string& path) const -> std::vector<std::string> {
|
||||
// Verificar que el comando raíz es visible en el scope activo
|
||||
if (!active_scope_.empty()) {
|
||||
std::string root = path;
|
||||
auto space = root.find(' ');
|
||||
if (space != std::string::npos) { root = root.substr(0, space); }
|
||||
const auto* cmd = findCommand(root);
|
||||
if (cmd != nullptr && !isCommandVisible(*cmd)) { return {}; }
|
||||
}
|
||||
|
||||
// Primero: buscar proveedor dinámico (tiene prioridad si existe)
|
||||
const auto dyn_it = dynamic_providers_.find(path);
|
||||
if (dyn_it != dynamic_providers_.end()) {
|
||||
return dyn_it->second();
|
||||
const auto DYN_IT = dynamic_providers_.find(path);
|
||||
if (DYN_IT != dynamic_providers_.end()) {
|
||||
return DYN_IT->second();
|
||||
}
|
||||
// Fallback: completions estáticas del YAML
|
||||
const auto it = completions_map_.find(path);
|
||||
if (it != completions_map_.end()) { return it->second; }
|
||||
const auto IT = completions_map_.find(path);
|
||||
if (IT != completions_map_.end()) { return IT->second; }
|
||||
return {};
|
||||
}
|
||||
|
||||
// Comprueba si un comando es utilizable en el scope activo (acumulativo)
|
||||
// Release: global + game
|
||||
// Debug: global + game + debug
|
||||
// Editor: global + game + debug + editor
|
||||
auto CommandRegistry::isCommandVisible(const CommandDef& cmd) const -> bool {
|
||||
if (cmd.hidden) { return false; }
|
||||
|
||||
return std::ranges::any_of(cmd.scopes, [this](const auto& s) {
|
||||
if (s == "global" || s == "game") { return true; }
|
||||
if (s == "debug" && (active_scope_ == "debug" || active_scope_ == "editor")) { return true; }
|
||||
return s == "editor" && active_scope_ == "editor";
|
||||
});
|
||||
}
|
||||
|
||||
auto CommandRegistry::getVisibleKeywords() const -> std::vector<std::string> {
|
||||
std::vector<std::string> result;
|
||||
for (const auto& cmd : commands_) {
|
||||
if (!cmd.hidden) {
|
||||
if (isCommandVisible(cmd)) {
|
||||
result.push_back(cmd.keyword);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ struct CommandDef {
|
||||
bool debug_only{false};
|
||||
bool help_hidden{false};
|
||||
bool dynamic_completions{false};
|
||||
std::vector<std::string> scopes; // Ámbitos: "global", "game", "editor", "debug"
|
||||
std::unordered_map<std::string, std::vector<std::string>> completions;
|
||||
};
|
||||
|
||||
@@ -40,9 +41,11 @@ class CommandRegistry {
|
||||
[[nodiscard]] auto generateTerminalHelp() const -> std::string;
|
||||
[[nodiscard]] auto generateConsoleHelp() const -> std::string;
|
||||
|
||||
// Scope activo (filtra comandos visibles en help y tab completion)
|
||||
void setScope(const std::string& scope) { active_scope_ = scope; }
|
||||
[[nodiscard]] auto getScope() const -> const std::string& { return active_scope_; }
|
||||
|
||||
// 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>;
|
||||
|
||||
@@ -51,6 +54,8 @@ class CommandRegistry {
|
||||
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_;
|
||||
std::string active_scope_; // Scope activo ("" = sin filtro, muestra todo)
|
||||
|
||||
void registerHandlers();
|
||||
[[nodiscard]] auto isCommandVisible(const CommandDef& cmd) const -> bool;
|
||||
};
|
||||
|
||||
@@ -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.12"; // Versión por defecto
|
||||
constexpr const char* VERSION = "1.13"; // Versión por defecto
|
||||
} // namespace Texts
|
||||
|
||||
// Tamaño de bloque
|
||||
|
||||
@@ -139,7 +139,7 @@ if [[ ${#FILES[@]} -gt 0 ]]; then
|
||||
else
|
||||
# Comportamiento original: procesar todos los archivos
|
||||
echo "=== Escaneando recursivamente source/ (excluyendo external/ y jail_audio.hpp) ==="
|
||||
find "$SOURCE_DIR" \( -name '*.cpp' -o -name '*.h' -o -name '*.hpp' \) -not -path "*/external/*" -not -path "*/core/audio/jail_audio.hpp" -print0 | \
|
||||
find "$SOURCE_DIR" \( -name '*.cpp' -o -name '*.h' -o -name '*.hpp' \) -not -path "*/external/*" -not -path "*/core/audio/jail_audio.hpp" -not -name '*_spv.h' -print0 | \
|
||||
xargs -0 -P4 -I{} bash -c 'echo "Procesando: {}"; clang-tidy {} -p '"$BUILD_DIR"' '"$FIX_FLAG"
|
||||
|
||||
echo
|
||||
|
||||
Reference in New Issue
Block a user