Compare commits
13 Commits
v1.12
...
fc28586940
| Author | SHA1 | Date | |
|---|---|---|---|
| fc28586940 | |||
| 273d9304dc | |||
| 3a5b16346b | |||
| 0e61b94848 | |||
| eca5a55d3c | |||
| dccd0d41e4 | |||
| 20bac58814 | |||
| a6fae7b001 | |||
| b31346830f | |||
| b6fec3eba7 | |||
| 606388227c | |||
| a2caf95005 | |||
| 0bfb535d4d |
154
CLAUDE.md
154
CLAUDE.md
@@ -53,7 +53,7 @@ cmake --build build --clean-first
|
|||||||
./jaildoctors_dilemma
|
./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)
|
### Testing in Headless Environment (SSH/Remote Server)
|
||||||
|
|
||||||
@@ -67,17 +67,12 @@ sudo apt-get install xvfb
|
|||||||
|
|
||||||
#### Running the Game in Headless Mode
|
#### Running the Game in Headless Mode
|
||||||
|
|
||||||
**Option 1: Using the wrapper script (RECOMMENDED)**
|
**Option 1: Using xvfb-run directly (RECOMMENDED)**
|
||||||
```bash
|
|
||||||
./run_headless.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
**Option 2: Using xvfb-run directly**
|
|
||||||
```bash
|
```bash
|
||||||
xvfb-run -a ./jaildoctors_dilemma
|
xvfb-run -a ./jaildoctors_dilemma
|
||||||
```
|
```
|
||||||
|
|
||||||
**Option 3: Custom display configuration**
|
**Option 2: Custom display configuration**
|
||||||
```bash
|
```bash
|
||||||
xvfb-run -a -s "-screen 0 1280x720x24" ./jaildoctors_dilemma
|
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/
|
source/
|
||||||
├── core/ # Core engine systems
|
├── core/ # Core engine systems
|
||||||
│ ├── audio/ # Audio management
|
│ ├── 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/ # Input handling
|
||||||
│ │ ├── input.hpp/cpp # Input manager (keyboard, gamepad)
|
│ │ ├── input.hpp/cpp # Input manager (keyboard, gamepad)
|
||||||
|
│ │ ├── input_types.hpp # Input type definitions
|
||||||
│ │ ├── global_inputs.hpp # Global input state
|
│ │ ├── global_inputs.hpp # Global input state
|
||||||
│ │ └── mouse.hpp # Mouse input
|
│ │ └── mouse.hpp # Mouse input
|
||||||
|
│ ├── locale/ # Localization
|
||||||
|
│ │ └── locale.hpp # Locale/language support
|
||||||
│ ├── rendering/ # Graphics rendering
|
│ ├── rendering/ # Graphics rendering
|
||||||
│ │ ├── screen.hpp/cpp # Screen/window singleton, SDL renderer
|
│ │ ├── screen.hpp/cpp # Screen/window singleton, SDL renderer
|
||||||
│ │ ├── surface.hpp/cpp # 8-bit indexed color surface abstraction
|
│ │ ├── surface.hpp/cpp # 8-bit indexed color surface abstraction
|
||||||
│ │ ├── surface_sprite.hpp # Static sprite rendering
|
│ │ ├── sprite/ # Sprite rendering classes
|
||||||
│ │ ├── surface_animated_sprite.hpp # Animated sprite with frame data
|
│ │ │ ├── sprite.hpp # Static sprite rendering
|
||||||
│ │ ├── surface_moving_sprite.hpp # Moving sprite with velocity
|
│ │ │ ├── animated_sprite.hpp # Animated sprite with frame data
|
||||||
│ │ ├── texture.hpp/cpp # SDL texture wrapper
|
│ │ │ ├── moving_sprite.hpp # Moving sprite with velocity
|
||||||
|
│ │ │ └── dissolve_sprite.hpp # Dissolve transition sprite
|
||||||
│ │ ├── text.hpp/cpp # Text rendering system
|
│ │ ├── text.hpp/cpp # Text rendering system
|
||||||
│ │ ├── gif.hpp/cpp # GIF image loader
|
│ │ ├── gif.hpp/cpp # GIF image loader
|
||||||
│ │ ├── opengl/ # OpenGL shader backend
|
│ │ ├── pixel_reveal.hpp # Pixel reveal effect
|
||||||
│ │ │ └── opengl_shader.hpp/cpp # CRT shader effects
|
│ │ ├── 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
|
│ │ └── shader_backend.hpp # Abstract shader interface
|
||||||
│ ├── resources/ # Asset & Resource management
|
│ ├── resources/ # Asset & Resource management
|
||||||
│ │ ├── asset.hpp/cpp # Asset registry (file path mapping)
|
│ │ ├── resource_list.hpp # Asset path registry (O(1) lookups)
|
||||||
│ │ └── resource.hpp/cpp # Resource singleton (loads/caches assets)
|
│ │ ├── 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
|
│ └── system/ # System management
|
||||||
│ ├── director.hpp/cpp # Main application controller
|
│ ├── director.hpp/cpp # Main application controller
|
||||||
│ ├── debug.hpp/cpp # Debug info overlay
|
│ ├── debug.hpp/cpp # Debug info overlay
|
||||||
@@ -268,8 +276,13 @@ source/
|
|||||||
│ │ ├── enemy.hpp/cpp # Enemy entities
|
│ │ ├── enemy.hpp/cpp # Enemy entities
|
||||||
│ │ └── item.hpp/cpp # Collectible items
|
│ │ └── item.hpp/cpp # Collectible items
|
||||||
│ ├── gameplay/ # Core gameplay systems
|
│ ├── 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
|
│ │ ├── 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
|
│ │ ├── scoreboard.hpp/cpp # Score display & data
|
||||||
│ │ ├── item_tracker.hpp/cpp # Tracks collected items
|
│ │ ├── item_tracker.hpp/cpp # Tracks collected items
|
||||||
│ │ ├── stats.hpp/cpp # Game statistics
|
│ │ ├── stats.hpp/cpp # Game statistics
|
||||||
@@ -284,18 +297,20 @@ source/
|
|||||||
│ │ ├── ending2.hpp/cpp # Ending sequence 2
|
│ │ ├── ending2.hpp/cpp # Ending sequence 2
|
||||||
│ │ └── credits.hpp/cpp # Credits screen
|
│ │ └── credits.hpp/cpp # Credits screen
|
||||||
│ ├── ui/ # User interface
|
│ ├── 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
|
│ ├── options.hpp/cpp # Game configuration/options
|
||||||
|
│ ├── game_control.hpp # Game control logic
|
||||||
│ ├── scene_manager.hpp # Scene flow state machine
|
│ ├── scene_manager.hpp # Scene flow state machine
|
||||||
│ ├── defaults.hpp # Game defaults constants
|
│ └── defaults.hpp # Game defaults constants
|
||||||
│ └── gameplay.hpp # Gameplay constants
|
|
||||||
├── external/ # Third-party libraries
|
├── external/ # Third-party libraries
|
||||||
│ ├── jail_audio.hpp/cpp # Custom audio library
|
│ ├── fkyaml_node.hpp # YAML parsing library
|
||||||
│ ├── jail_audio.h # C interface for jail_audio
|
|
||||||
│ ├── stb_image.h # Image loading library
|
│ ├── stb_image.h # Image loading library
|
||||||
│ └── stb_vorbis.h # OGG Vorbis audio decoding
|
│ └── stb_vorbis.h # OGG Vorbis audio decoding
|
||||||
├── utils/ # Utility code
|
├── utils/ # Utility code
|
||||||
│ ├── delta_timer.hpp/cpp # Frame-rate independent timing
|
│ ├── delta_timer.hpp/cpp # Frame-rate independent timing
|
||||||
|
│ ├── easing_functions.hpp # Easing/interpolation functions
|
||||||
│ ├── defines.hpp # Game constants (resolutions, block sizes)
|
│ ├── defines.hpp # Game constants (resolutions, block sizes)
|
||||||
│ └── utils.hpp/cpp # Helper functions (colors, math)
|
│ └── utils.hpp/cpp # Helper functions (colors, math)
|
||||||
└── main.cpp # Application entry point
|
└── main.cpp # Application entry point
|
||||||
@@ -360,7 +375,7 @@ The game uses a scene manager to control application flow:
|
|||||||
// namespace SceneManager
|
// namespace SceneManager
|
||||||
enum class Scene {
|
enum class Scene {
|
||||||
LOGO, LOADING_SCREEN, TITLE, CREDITS, GAME, DEMO,
|
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
|
inline Scene current = Scene::LOGO; // Global scene state
|
||||||
@@ -397,9 +412,10 @@ Display
|
|||||||
|
|
||||||
**Key Components:**
|
**Key Components:**
|
||||||
- `Surface` - 8-bit indexed pixel buffer with palette support
|
- `Surface` - 8-bit indexed pixel buffer with palette support
|
||||||
- `SurfaceSprite` - Renders a fixed region of a surface
|
- `Sprite` - Renders a fixed region of a surface
|
||||||
- `SurfaceAnimatedSprite` - Frame-based animation on top of sprite
|
- `AnimatedSprite` - Frame-based animation on top of sprite
|
||||||
- `SurfaceMovingSprite` - Adds velocity/position to animated sprite
|
- `MovingSprite` - Adds velocity/position to animated sprite
|
||||||
|
- `DissolveSprite` - Dissolve transition effect sprite
|
||||||
- Supports color replacement, palette swapping, and shader effects (CRT)
|
- Supports color replacement, palette swapping, and shader effects (CRT)
|
||||||
|
|
||||||
### 3.5 Tile-Based Collision System
|
### 3.5 Tile-Based Collision System
|
||||||
@@ -439,8 +455,8 @@ struct AnimationData {
|
|||||||
int counter;
|
int counter;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Loaded from .ani files (list of animation names)
|
// Loaded from .yaml animation definition files
|
||||||
// Rendered with SurfaceAnimatedSprite
|
// Rendered with AnimatedSprite
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -454,7 +470,7 @@ main()
|
|||||||
↓
|
↓
|
||||||
Director::Director() [Initialization]
|
Director::Director() [Initialization]
|
||||||
├─ Resource::List::init() - Initialize asset registry singleton
|
├─ 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
|
├─ Options::loadFromFile() - Load game configuration
|
||||||
├─ Audio::init() - Initialize SDL audio
|
├─ Audio::init() - Initialize SDL audio
|
||||||
├─ Screen::init() - Create window, SDL renderer
|
├─ Screen::init() - Create window, SDL renderer
|
||||||
@@ -510,7 +526,7 @@ Game::run() {
|
|||||||
|
|
||||||
```
|
```
|
||||||
Director::setFileList()
|
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
|
├─ Read config/assets.yaml - Parse text configuration file
|
||||||
├─ Parse YAML structure: assets grouped by category
|
├─ Parse YAML structure: assets grouped by category
|
||||||
├─ Replace variables (${PREFIX}, ${SYSTEM_FOLDER})
|
├─ Replace variables (${PREFIX}, ${SYSTEM_FOLDER})
|
||||||
@@ -570,7 +586,7 @@ Game code
|
|||||||
|
|
||||||
**Classes:**
|
**Classes:**
|
||||||
- `PascalCase` for classes: `Player`, `Room`, `Screen`, `Director`
|
- `PascalCase` for classes: `Player`, `Room`, `Screen`, `Director`
|
||||||
- Suffix `Sprite` for sprite classes: `SurfaceSprite`, `SurfaceAnimatedSprite`
|
- Suffix `Sprite` for sprite classes: `Sprite`, `AnimatedSprite`, `MovingSprite`
|
||||||
|
|
||||||
**Methods:**
|
**Methods:**
|
||||||
- `get*()` for getters: `getWidth()`, `getRect()`
|
- `get*()` for getters: `getWidth()`, `getRect()`
|
||||||
@@ -613,8 +629,9 @@ Provides **time scaling** for slow-motion effects.
|
|||||||
### 5.4 Palette System
|
### 5.4 Palette System
|
||||||
|
|
||||||
- 8-bit indexed color (256 colors per palette)
|
- 8-bit indexed color (256 colors per palette)
|
||||||
- Multiple palettes can be loaded and swapped
|
- Multiple palettes can be loaded and swapped via `PaletteManager`
|
||||||
- `Surface::setPalette()` changes rendering colors
|
- `Screen::setPaletteByName()` changes the active palette
|
||||||
|
- Supports palette sort modes (by luminosity, similarity to Spectrum palette, etc.)
|
||||||
- Supports color replacement per-render: `renderWithColorReplace()`
|
- Supports color replacement per-render: `renderWithColorReplace()`
|
||||||
- CRT shader effects can modify colors in real-time
|
- CRT shader effects can modify colors in real-time
|
||||||
|
|
||||||
@@ -664,7 +681,7 @@ Achievements trigger notifications on unlock.
|
|||||||
| Component | Technology | Version | Role |
|
| Component | Technology | Version | Role |
|
||||||
|-----------|-----------|---------|------|
|
|-----------|-----------|---------|------|
|
||||||
| **Graphics** | SDL3 | Latest | Window, rendering, input |
|
| **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** | SDL3 Audio | Latest | Audio device, mixing |
|
||||||
| **Audio Decoding** | jail_audio (custom) | 1.x | OGG/WAV playback |
|
| **Audio Decoding** | jail_audio (custom) | 1.x | OGG/WAV playback |
|
||||||
| **Image Loading** | stb_image | v2.x | PNG/GIF image loading |
|
| **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 |
|
| `Audio` | Music and SFX playback | Singleton |
|
||||||
| `Resource::Cache` | Asset caching and loading (with detailed error messages) | 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 |
|
| `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 |
|
| `Debug` | Debug overlay information | Singleton |
|
||||||
| `globalEvents` | Global SDL event handling (quit, device reset, mouse) | Namespace |
|
| `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 |
|
| `Game` | Main gameplay scene, orchestrates update/render |
|
||||||
| `Player` | Player entity with physics and animation |
|
| `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 |
|
| `Enemy` | Enemy entity behavior and rendering |
|
||||||
| `Item` | Collectible items |
|
| `Item` | Collectible items |
|
||||||
| `Scoreboard` | HUD display data |
|
| `Scoreboard` | HUD display data |
|
||||||
@@ -722,17 +745,20 @@ Achievements trigger notifications on unlock.
|
|||||||
| Class | Purpose |
|
| Class | Purpose |
|
||||||
|-------|---------|
|
|-------|---------|
|
||||||
| `Surface` | 8-bit indexed color pixel buffer with palette |
|
| `Surface` | 8-bit indexed color pixel buffer with palette |
|
||||||
| `SurfaceSprite` | Renders a sprite region |
|
| `Sprite` | Renders a sprite region |
|
||||||
| `SurfaceAnimatedSprite` | Frame-based animation rendering |
|
| `AnimatedSprite` | Frame-based animation rendering |
|
||||||
| `SurfaceMovingSprite` | Sprite with velocity/position |
|
| `MovingSprite` | Sprite with velocity/position |
|
||||||
|
| `DissolveSprite` | Dissolve transition sprite |
|
||||||
| `Text` | Text rendering system |
|
| `Text` | Text rendering system |
|
||||||
| `OpenGLShader` | Shader compilation and effects |
|
| `Sdl3gpuShader` | SDL3 GPU shader compilation and effects |
|
||||||
|
| `PaletteManager` | Palette loading and management |
|
||||||
|
|
||||||
### Utility Classes
|
### Utility Classes
|
||||||
|
|
||||||
| Class | Purpose |
|
| Class/Header | Purpose |
|
||||||
|-------|---------|
|
|-------|---------|
|
||||||
| `DeltaTimer` | Frame-rate independent timing |
|
| `DeltaTimer` | Frame-rate independent timing |
|
||||||
|
| `easing_functions.hpp` | Easing/interpolation functions for animations |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -848,7 +874,7 @@ assets:
|
|||||||
- type: BITMAP
|
- type: BITMAP
|
||||||
path: ${PREFIX}/data/font/smb2.gif
|
path: ${PREFIX}/data/font/smb2.gif
|
||||||
- type: FONT
|
- type: FONT
|
||||||
path: ${PREFIX}/data/font/smb2.txt
|
path: ${PREFIX}/data/font/smb2.fnt
|
||||||
|
|
||||||
# PLAYER
|
# PLAYER
|
||||||
player:
|
player:
|
||||||
@@ -892,23 +918,26 @@ assets:
|
|||||||
- `${PREFIX}` - Replaced with `/../Resources` on macOS bundles, empty otherwise
|
- `${PREFIX}` - Replaced with `/../Resources` on macOS bundles, empty otherwise
|
||||||
- `${SYSTEM_FOLDER}` - Replaced with user's system config folder
|
- `${SYSTEM_FOLDER}` - Replaced with user's system config folder
|
||||||
|
|
||||||
### Animation Files (.ani)
|
### Animation Files (.yaml)
|
||||||
List of animation names, one per line:
|
YAML-based animation definitions with frame data:
|
||||||
```
|
```yaml
|
||||||
default
|
animations:
|
||||||
jump
|
- name: default
|
||||||
run
|
frameWidth: 16
|
||||||
fall
|
frameHeight: 16
|
||||||
|
speed: 100
|
||||||
|
loop: 0
|
||||||
|
frames: [...]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Room Data Files (.room)
|
### Room Data Files (.yaml)
|
||||||
Key-value pairs defining room properties:
|
YAML-based room definitions:
|
||||||
```
|
```yaml
|
||||||
number=01
|
room:
|
||||||
name=Starting Room
|
name_en: Starting Room
|
||||||
bg_color=0x000000
|
bgColor: 0x000000
|
||||||
border_color=0xFF00FF
|
connections: [...]
|
||||||
...
|
tilemap: [...]
|
||||||
```
|
```
|
||||||
|
|
||||||
### Tilemap Files (.tmx)
|
### Tilemap Files (.tmx)
|
||||||
@@ -923,7 +952,8 @@ Binary 256-color palette format (256 × 4 bytes RGBA).
|
|||||||
|
|
||||||
### For Graphics Issues
|
### For Graphics Issues
|
||||||
- `Screen::render()` - Main rendering method
|
- `Screen::render()` - Main rendering method
|
||||||
- `Screen::setPalete()` - Palette application
|
- `Screen::setPaletteByName()` - Palette switching
|
||||||
|
- `PaletteManager` - Palette loading and sorting
|
||||||
- `Surface` class - Pixel buffer operations
|
- `Surface` class - Pixel buffer operations
|
||||||
|
|
||||||
### For Input Issues
|
### For Input Issues
|
||||||
@@ -942,10 +972,10 @@ Binary 256-color palette format (256 × 4 bytes RGBA).
|
|||||||
|
|
||||||
### For Asset Management
|
### For Asset Management
|
||||||
- `config/assets.yaml` - Asset configuration file (text-based, no recompilation needed)
|
- `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::List::get()` - Retrieves asset path (O(1) lookup with unordered_map)
|
||||||
- `Resource::load()` - Asset loading
|
- `Resource::Cache` - Asset loading and caching
|
||||||
- `Director::setFileList()` - Calls `Asset::loadFromFile()` with PREFIX and system_folder
|
- `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
|
**Original Author:** JailDesigner
|
||||||
**Repository:** Gitea (internal)
|
**Repository:** Gitea (internal)
|
||||||
|
|||||||
@@ -96,6 +96,11 @@ set(APP_SOURCES
|
|||||||
source/game/scenes/logo.cpp
|
source/game/scenes/logo.cpp
|
||||||
source/game/scenes/title.cpp
|
source/game/scenes/title.cpp
|
||||||
|
|
||||||
|
# Game - Editor (debug only, guarded by #ifdef _DEBUG in source)
|
||||||
|
source/game/editor/map_editor.cpp
|
||||||
|
source/game/editor/editor_statusbar.cpp
|
||||||
|
source/game/editor/room_saver.cpp
|
||||||
|
|
||||||
# Game - UI
|
# Game - UI
|
||||||
source/game/ui/console.cpp
|
source/game/ui/console.cpp
|
||||||
source/game/ui/console_commands.cpp
|
source/game/ui/console_commands.cpp
|
||||||
|
|||||||
@@ -201,9 +201,9 @@ categories:
|
|||||||
- keyword: ROOM
|
- keyword: ROOM
|
||||||
handler: cmd_room
|
handler: cmd_room
|
||||||
description: "Change to room number (GAME only)"
|
description: "Change to room number (GAME only)"
|
||||||
usage: "ROOM <1-60>|NEXT|PREV"
|
usage: "ROOM <1-60>|NEXT|PREV|LEFT|RIGHT|UP|DOWN"
|
||||||
completions:
|
completions:
|
||||||
ROOM: [NEXT, PREV]
|
ROOM: [NEXT, PREV, LEFT, RIGHT, UP, DOWN]
|
||||||
|
|
||||||
- keyword: SCENE
|
- keyword: SCENE
|
||||||
handler: cmd_scene
|
handler: cmd_scene
|
||||||
@@ -212,6 +212,30 @@ categories:
|
|||||||
completions:
|
completions:
|
||||||
SCENE: [LOGO, LOADING, TITLE, CREDITS, GAME, ENDING, ENDING2, RESTART]
|
SCENE: [LOGO, LOADING, TITLE, CREDITS, GAME, ENDING, ENDING2, RESTART]
|
||||||
|
|
||||||
|
- keyword: EDIT
|
||||||
|
handler: cmd_edit
|
||||||
|
description: "Map editor mode (GAME only)"
|
||||||
|
usage: "EDIT [ON|OFF|REVERT]"
|
||||||
|
completions:
|
||||||
|
EDIT: [ON, OFF, REVERT]
|
||||||
|
|
||||||
|
- keyword: ENEMY
|
||||||
|
handler: cmd_enemy
|
||||||
|
description: "Add, delete or duplicate enemy (editor)"
|
||||||
|
usage: "ENEMY <ADD|DELETE|DUPLICATE>"
|
||||||
|
completions:
|
||||||
|
ENEMY: [ADD, DELETE, DUPLICATE]
|
||||||
|
|
||||||
|
- keyword: SET
|
||||||
|
handler: cmd_set
|
||||||
|
description: "Set enemy property (editor, select enemy first)"
|
||||||
|
usage: "SET <ANIMATION|COLOR|VX|VY|FLIP|MIRROR> <value>"
|
||||||
|
dynamic_completions: true
|
||||||
|
completions:
|
||||||
|
SET: [ANIMATION, COLOR, VX, VY, FLIP, MIRROR]
|
||||||
|
SET FLIP: [ON, OFF]
|
||||||
|
SET MIRROR: [ON, OFF]
|
||||||
|
|
||||||
- name: CHEATS
|
- name: CHEATS
|
||||||
commands:
|
commands:
|
||||||
- keyword: CHEAT
|
- keyword: CHEAT
|
||||||
|
|||||||
@@ -141,3 +141,5 @@ game:
|
|||||||
cheat_jail_open: "JAIL OBERTA"
|
cheat_jail_open: "JAIL OBERTA"
|
||||||
debug_enabled: "DEBUG ACTIVAT"
|
debug_enabled: "DEBUG ACTIVAT"
|
||||||
debug_disabled: "DEBUG DESACTIVAT"
|
debug_disabled: "DEBUG DESACTIVAT"
|
||||||
|
editor_enabled: "EDITOR ACTIVAT"
|
||||||
|
editor_disabled: "EDITOR DESACTIVAT"
|
||||||
|
|||||||
@@ -141,3 +141,5 @@ game:
|
|||||||
cheat_jail_open: "JAIL IS OPEN"
|
cheat_jail_open: "JAIL IS OPEN"
|
||||||
debug_enabled: "DEBUG ENABLED"
|
debug_enabled: "DEBUG ENABLED"
|
||||||
debug_disabled: "DEBUG DISABLED"
|
debug_disabled: "DEBUG DISABLED"
|
||||||
|
editor_enabled: "EDITOR ENABLED"
|
||||||
|
editor_disabled: "EDITOR DISABLED"
|
||||||
|
|||||||
@@ -47,14 +47,12 @@ class AnimatedSprite : public MovingSprite {
|
|||||||
void setCurrentAnimation(int index = 0); // Establece la animación actual por índice
|
void setCurrentAnimation(int index = 0); // Establece la animación actual por índice
|
||||||
void resetAnimation(); // Reinicia la animación
|
void resetAnimation(); // Reinicia la animación
|
||||||
void setCurrentAnimationFrame(int num); // Establece el frame actual de 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:
|
protected:
|
||||||
// Constructor per a ús de subclasses que gestionen la surface directament (sense YAML)
|
// Constructor per a ús de subclasses que gestionen la surface directament (sense YAML)
|
||||||
AnimatedSprite(std::shared_ptr<Surface> surface, SDL_FRect pos);
|
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:
|
private:
|
||||||
// Variables miembro
|
// Variables miembro
|
||||||
std::vector<AnimationData> animations_; // Vector con las diferentes animaciones
|
std::vector<AnimationData> animations_; // Vector con las diferentes animaciones
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <algorithm> // Para find_if
|
#include <algorithm> // Para find_if
|
||||||
#include <cstdlib> // Para exit, size_t
|
#include <cstdlib> // Para exit, size_t
|
||||||
|
#include <fstream> // Para ifstream, istreambuf_iterator
|
||||||
#include <iostream> // Para basic_ostream, operator<<, endl, cout
|
#include <iostream> // Para basic_ostream, operator<<, endl, cout
|
||||||
#include <stdexcept> // Para runtime_error
|
#include <stdexcept> // Para runtime_error
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -15,6 +16,7 @@
|
|||||||
#include "core/resources/resource_list.hpp" // Para List, List::Type
|
#include "core/resources/resource_list.hpp" // Para List, List::Type
|
||||||
#include "game/defaults.hpp" // Para Defaults namespace
|
#include "game/defaults.hpp" // Para Defaults namespace
|
||||||
#include "game/gameplay/room.hpp" // Para RoomData, loadRoomFile, loadRoomTileFile
|
#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 "game/options.hpp" // Para Options, OptionsGame, options
|
||||||
#include "utils/defines.hpp" // Para WINDOW_CAPTION
|
#include "utils/defines.hpp" // Para WINDOW_CAPTION
|
||||||
#include "utils/utils.hpp" // Para getFileName, printWithDots, PaletteColor
|
#include "utils/utils.hpp" // Para getFileName, printWithDots, PaletteColor
|
||||||
@@ -173,6 +175,34 @@ namespace Resource {
|
|||||||
throw std::runtime_error("Habitación no encontrada: " + name);
|
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
|
// Obtiene todas las habitaciones
|
||||||
auto Cache::getRooms() -> std::vector<RoomResource>& {
|
auto Cache::getRooms() -> std::vector<RoomResource>& {
|
||||||
return rooms_;
|
return rooms_;
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ namespace Resource {
|
|||||||
auto getRooms() -> std::vector<RoomResource>&;
|
auto getRooms() -> std::vector<RoomResource>&;
|
||||||
|
|
||||||
void reload(); // Recarga todos los recursos
|
void reload(); // Recarga todos los recursos
|
||||||
|
#ifdef _DEBUG
|
||||||
|
void reloadRoom(const std::string& name); // Recarga una habitación desde disco
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Estructura para llevar la cuenta de los recursos cargados
|
// Estructura para llevar la cuenta de los recursos cargados
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
#include "core/system/debug.hpp" // Para Debug
|
#include "core/system/debug.hpp" // Para Debug
|
||||||
|
#include "game/editor/map_editor.hpp" // Para MapEditor
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
@@ -183,6 +184,7 @@ Director::Director() {
|
|||||||
Debug::get()->setDebugFile(Resource::List::get()->get("debug.yaml"));
|
Debug::get()->setDebugFile(Resource::List::get()->get("debug.yaml"));
|
||||||
Debug::get()->loadFromFile();
|
Debug::get()->loadFromFile();
|
||||||
SceneManager::current = Debug::get()->getInitialScene();
|
SceneManager::current = Debug::get()->getInitialScene();
|
||||||
|
MapEditor::init();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::cout << "\n"; // Fin de inicialización de sistemas
|
std::cout << "\n"; // Fin de inicialización de sistemas
|
||||||
@@ -217,6 +219,7 @@ Director::~Director() {
|
|||||||
Cheevos::destroy();
|
Cheevos::destroy();
|
||||||
Locale::destroy();
|
Locale::destroy();
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
|
MapEditor::destroy();
|
||||||
Debug::destroy();
|
Debug::destroy();
|
||||||
#endif
|
#endif
|
||||||
Input::destroy();
|
Input::destroy();
|
||||||
|
|||||||
88
source/game/editor/editor_statusbar.cpp
Normal file
88
source/game/editor/editor_statusbar.cpp
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#ifdef _DEBUG
|
||||||
|
|
||||||
|
#include "game/editor/editor_statusbar.hpp"
|
||||||
|
|
||||||
|
#include <string> // Para to_string
|
||||||
|
|
||||||
|
#include "core/rendering/screen.hpp" // Para Screen
|
||||||
|
#include "core/rendering/surface.hpp" // Para Surface
|
||||||
|
#include "core/rendering/text.hpp" // Para Text
|
||||||
|
#include "core/resources/resource_cache.hpp" // Para Resource::Cache
|
||||||
|
#include "game/options.hpp" // Para Options::game
|
||||||
|
#include "utils/defines.hpp" // Para Tile::SIZE
|
||||||
|
#include "utils/utils.hpp" // Para stringToColor
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
EditorStatusBar::EditorStatusBar(const std::string& room_number, const std::string& room_name)
|
||||||
|
: room_number_(room_number),
|
||||||
|
room_name_(room_name) {
|
||||||
|
const float SURFACE_WIDTH = Options::game.width;
|
||||||
|
constexpr float SURFACE_HEIGHT = 6.0F * Tile::SIZE; // 48 pixels, igual que el scoreboard
|
||||||
|
|
||||||
|
surface_ = std::make_shared<Surface>(SURFACE_WIDTH, SURFACE_HEIGHT);
|
||||||
|
surface_dest_ = {.x = 0, .y = Options::game.height - SURFACE_HEIGHT, .w = SURFACE_WIDTH, .h = SURFACE_HEIGHT};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pinta la barra de estado en pantalla
|
||||||
|
void EditorStatusBar::render() {
|
||||||
|
surface_->render(nullptr, &surface_dest_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualiza la barra de estado
|
||||||
|
void EditorStatusBar::update([[maybe_unused]] float delta_time) {
|
||||||
|
fillTexture();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establece las coordenadas del ratón en tiles
|
||||||
|
void EditorStatusBar::setMouseTile(int tile_x, int tile_y) {
|
||||||
|
mouse_tile_x_ = tile_x;
|
||||||
|
mouse_tile_y_ = tile_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establece la información de la entidad seleccionada
|
||||||
|
void EditorStatusBar::setSelectionInfo(const std::string& info) {
|
||||||
|
selection_info_ = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establece el detalle adicional
|
||||||
|
void EditorStatusBar::setSelectionDetail(const std::string& detail) {
|
||||||
|
selection_detail_ = detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dibuja los elementos en la surface
|
||||||
|
void EditorStatusBar::fillTexture() {
|
||||||
|
auto previous_renderer = Screen::get()->getRendererSurface();
|
||||||
|
Screen::get()->setRendererSurface(surface_);
|
||||||
|
|
||||||
|
surface_->clear(stringToColor("black"));
|
||||||
|
|
||||||
|
auto text = Resource::Cache::get()->getText("8bithud");
|
||||||
|
const Uint8 LABEL_COLOR = stringToColor("bright_cyan");
|
||||||
|
const Uint8 VALUE_COLOR = stringToColor("white");
|
||||||
|
|
||||||
|
// Línea 1: Número y nombre de la habitación
|
||||||
|
const std::string ROOM_TEXT = toLower(room_number_ + " " + room_name_);
|
||||||
|
text->writeColored(LEFT_X, LINE1_Y, ROOM_TEXT, LABEL_COLOR);
|
||||||
|
|
||||||
|
// Línea 2: Coordenadas del ratón en tiles
|
||||||
|
const std::string TILE_X_STR = (mouse_tile_x_ < 10 ? "0" : "") + std::to_string(mouse_tile_x_);
|
||||||
|
const std::string TILE_Y_STR = (mouse_tile_y_ < 10 ? "0" : "") + std::to_string(mouse_tile_y_);
|
||||||
|
text->writeColored(LEFT_X, LINE2_Y, toLower("tile:"), LABEL_COLOR);
|
||||||
|
text->writeColored(LEFT_X + 30, LINE2_Y, TILE_X_STR + "," + TILE_Y_STR, VALUE_COLOR);
|
||||||
|
|
||||||
|
// Línea 2 (continuación): info de selección o indicador de modo editor
|
||||||
|
if (!selection_info_.empty()) {
|
||||||
|
text->writeColored(LEFT_X + 60, LINE2_Y, toLower(selection_info_), stringToColor("bright_yellow"));
|
||||||
|
} else {
|
||||||
|
text->writeColored(176, LINE2_Y, toLower("editor"), stringToColor("bright_green"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Línea 3: detalle de la selección (propiedades del enemigo)
|
||||||
|
if (!selection_detail_.empty()) {
|
||||||
|
text->writeColored(LEFT_X, LINE3_Y, toLower(selection_detail_), stringToColor("bright_white"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Screen::get()->setRendererSurface(previous_renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // _DEBUG
|
||||||
45
source/game/editor/editor_statusbar.hpp
Normal file
45
source/game/editor/editor_statusbar.hpp
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <memory> // Para shared_ptr
|
||||||
|
#include <string> // Para string
|
||||||
|
|
||||||
|
class Surface;
|
||||||
|
|
||||||
|
class EditorStatusBar {
|
||||||
|
public:
|
||||||
|
EditorStatusBar(const std::string& room_number, const std::string& room_name);
|
||||||
|
~EditorStatusBar() = default;
|
||||||
|
|
||||||
|
void render();
|
||||||
|
void update(float delta_time);
|
||||||
|
void setMouseTile(int tile_x, int tile_y);
|
||||||
|
void setSelectionInfo(const std::string& info);
|
||||||
|
void setSelectionDetail(const std::string& detail);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void fillTexture(); // Dibuja los elementos en la surface
|
||||||
|
|
||||||
|
// Constantes de posición (en pixels dentro de la surface de 256x48)
|
||||||
|
static constexpr int LINE1_Y = 4; // Nombre de la habitación
|
||||||
|
static constexpr int LINE2_Y = 14; // Coordenadas del ratón + selección
|
||||||
|
static constexpr int LINE3_Y = 24; // Línea extra disponible
|
||||||
|
static constexpr int LEFT_X = 4; // Margen izquierdo
|
||||||
|
|
||||||
|
// Objetos
|
||||||
|
std::shared_ptr<Surface> surface_; // Surface donde dibujar la barra
|
||||||
|
SDL_FRect surface_dest_{}; // Rectángulo destino en pantalla
|
||||||
|
|
||||||
|
// Variables
|
||||||
|
std::string room_number_; // Número de la habitación
|
||||||
|
std::string room_name_; // Nombre de la habitación
|
||||||
|
int mouse_tile_x_{0}; // Coordenada X del ratón en tiles
|
||||||
|
int mouse_tile_y_{0}; // Coordenada Y del ratón en tiles
|
||||||
|
std::string selection_info_; // Información de la entidad seleccionada (línea 2)
|
||||||
|
std::string selection_detail_; // Detalle adicional (línea 3)
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // _DEBUG
|
||||||
768
source/game/editor/map_editor.cpp
Normal file
768
source/game/editor/map_editor.cpp
Normal file
@@ -0,0 +1,768 @@
|
|||||||
|
#ifdef _DEBUG
|
||||||
|
|
||||||
|
#include "game/editor/map_editor.hpp"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <cmath> // Para std::round
|
||||||
|
#include <iostream> // Para cout
|
||||||
|
|
||||||
|
#include "core/input/mouse.hpp" // Para Mouse
|
||||||
|
#include "core/rendering/screen.hpp" // Para Screen
|
||||||
|
#include "core/rendering/surface.hpp" // Para Surface
|
||||||
|
#include "core/resources/resource_cache.hpp" // Para Resource::Cache
|
||||||
|
#include "core/resources/resource_list.hpp" // Para Resource::List
|
||||||
|
#include "game/editor/editor_statusbar.hpp" // Para EditorStatusBar
|
||||||
|
#include "game/ui/console.hpp" // Para Console
|
||||||
|
#include "game/editor/room_saver.hpp" // Para RoomSaver
|
||||||
|
#include "game/entities/player.hpp" // Para Player
|
||||||
|
#include "game/gameplay/enemy_manager.hpp" // Para EnemyManager
|
||||||
|
#include "game/gameplay/item_manager.hpp" // Para ItemManager
|
||||||
|
#include "game/gameplay/room.hpp" // Para Room
|
||||||
|
#include "game/options.hpp" // Para Options
|
||||||
|
#include "utils/defines.hpp" // Para Tile::SIZE, PlayArea
|
||||||
|
#include "utils/utils.hpp" // Para stringToColor
|
||||||
|
|
||||||
|
// Singleton
|
||||||
|
MapEditor* MapEditor::instance_ = nullptr;
|
||||||
|
|
||||||
|
void MapEditor::init() {
|
||||||
|
instance_ = new MapEditor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MapEditor::destroy() {
|
||||||
|
delete instance_;
|
||||||
|
instance_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto MapEditor::get() -> MapEditor* {
|
||||||
|
return instance_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
MapEditor::MapEditor() = default;
|
||||||
|
|
||||||
|
// Destructor
|
||||||
|
MapEditor::~MapEditor() = default;
|
||||||
|
|
||||||
|
// Entra en modo editor
|
||||||
|
void MapEditor::enter(std::shared_ptr<Room> room, std::shared_ptr<Player> player, const std::string& room_path, std::shared_ptr<Scoreboard::Data> scoreboard_data) {
|
||||||
|
if (active_) { return; }
|
||||||
|
|
||||||
|
room_ = std::move(room);
|
||||||
|
player_ = std::move(player);
|
||||||
|
room_path_ = room_path;
|
||||||
|
scoreboard_data_ = std::move(scoreboard_data);
|
||||||
|
|
||||||
|
// Cargar una copia de los datos de la habitación (para boundaries y edición)
|
||||||
|
auto room_data_ptr = Resource::Cache::get()->getRoom(room_path);
|
||||||
|
if (room_data_ptr) {
|
||||||
|
room_data_ = *room_data_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtener la ruta completa y cargar el YAML original (para edición parcial y backup)
|
||||||
|
file_path_ = Resource::List::get()->get(room_path_);
|
||||||
|
if (!file_path_.empty()) {
|
||||||
|
yaml_ = RoomSaver::loadYAML(file_path_);
|
||||||
|
yaml_backup_ = yaml_; // Copia profunda para revert
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guardar estado de invencibilidad y forzarla
|
||||||
|
invincible_before_editor_ = Options::cheats.invincible;
|
||||||
|
Options::cheats.invincible = Options::Cheat::State::ENABLED;
|
||||||
|
player_->setColor();
|
||||||
|
|
||||||
|
// Resetear enemigos a su posición inicial (pueden haberse movido durante el gameplay)
|
||||||
|
room_->resetEnemyPositions(room_data_.enemies);
|
||||||
|
|
||||||
|
// Crear la barra de estado
|
||||||
|
statusbar_ = std::make_unique<EditorStatusBar>(room_->getNumber(), room_->getName());
|
||||||
|
|
||||||
|
// Resetear estado
|
||||||
|
drag_ = {};
|
||||||
|
selected_enemy_ = -1;
|
||||||
|
|
||||||
|
active_ = true;
|
||||||
|
std::cout << "MapEditor: ON (room " << room_path_ << ")\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sale del modo editor
|
||||||
|
void MapEditor::exit() {
|
||||||
|
if (!active_) { return; }
|
||||||
|
|
||||||
|
active_ = false;
|
||||||
|
|
||||||
|
// Restaurar invencibilidad
|
||||||
|
Options::cheats.invincible = invincible_before_editor_;
|
||||||
|
player_->setColor();
|
||||||
|
|
||||||
|
// Restaurar prompt de la consola y limpiar estado
|
||||||
|
selected_enemy_ = -1;
|
||||||
|
Console::get()->setPrompt("> ");
|
||||||
|
drag_ = {};
|
||||||
|
statusbar_.reset();
|
||||||
|
room_.reset();
|
||||||
|
player_.reset();
|
||||||
|
scoreboard_data_.reset();
|
||||||
|
|
||||||
|
std::cout << "MapEditor: OFF\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revierte todos los cambios al estado original
|
||||||
|
auto MapEditor::revert() -> std::string {
|
||||||
|
if (!active_) { return "Editor not active"; }
|
||||||
|
if (file_path_.empty()) { return "Error: No file path"; }
|
||||||
|
|
||||||
|
// Restaurar el YAML original y reescribir el fichero
|
||||||
|
yaml_ = yaml_backup_;
|
||||||
|
auto room_data_ptr = Resource::Cache::get()->getRoom(room_path_);
|
||||||
|
if (room_data_ptr) {
|
||||||
|
room_data_ = *room_data_ptr;
|
||||||
|
}
|
||||||
|
RoomSaver::saveYAML(file_path_, yaml_, room_data_);
|
||||||
|
|
||||||
|
// Resetear los sprites vivos a las posiciones originales
|
||||||
|
room_->resetEnemyPositions(room_data_.enemies);
|
||||||
|
|
||||||
|
// Resetear items (recargar posiciones)
|
||||||
|
auto* item_mgr = room_->getItemManager();
|
||||||
|
for (int i = 0; i < item_mgr->getCount() && i < static_cast<int>(room_data_.items.size()); ++i) {
|
||||||
|
item_mgr->getItem(i)->setPosition(room_data_.items[i].x, room_data_.items[i].y);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Reverted to original";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-guarda al YAML tras soltar una entidad
|
||||||
|
void MapEditor::autosave() {
|
||||||
|
if (file_path_.empty()) { return; }
|
||||||
|
|
||||||
|
// Sincronizar posiciones de items desde los sprites vivos a room_data_
|
||||||
|
auto* item_mgr = room_->getItemManager();
|
||||||
|
for (int i = 0; i < item_mgr->getCount() && i < static_cast<int>(room_data_.items.size()); ++i) {
|
||||||
|
SDL_FPoint pos = item_mgr->getItem(i)->getPos();
|
||||||
|
room_data_.items[i].x = pos.x;
|
||||||
|
room_data_.items[i].y = pos.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
RoomSaver::saveYAML(file_path_, yaml_, room_data_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualiza el editor
|
||||||
|
void MapEditor::update(float delta_time) {
|
||||||
|
// Mantener el ratón siempre visible
|
||||||
|
SDL_ShowCursor();
|
||||||
|
Mouse::last_mouse_move_time = SDL_GetTicks();
|
||||||
|
|
||||||
|
// Actualizar animaciones de enemigos e items (sin mover enemigos)
|
||||||
|
room_->updateEditorMode(delta_time);
|
||||||
|
|
||||||
|
// Actualizar posición del ratón
|
||||||
|
updateMousePosition();
|
||||||
|
|
||||||
|
// Si estamos arrastrando, actualizar la posición snapped
|
||||||
|
if (drag_.target != DragTarget::NONE) {
|
||||||
|
updateDrag();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar la barra de estado
|
||||||
|
updateStatusBarInfo();
|
||||||
|
if (statusbar_) {
|
||||||
|
statusbar_->update(delta_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renderiza el editor
|
||||||
|
void MapEditor::render() {
|
||||||
|
// El tilemap ya ha sido renderizado por Game::renderPlaying() antes de llamar aquí
|
||||||
|
|
||||||
|
// Renderizar los marcadores de boundaries y líneas de ruta (debajo de los sprites)
|
||||||
|
renderEnemyBoundaries();
|
||||||
|
|
||||||
|
// Renderizar entidades normales: enemigos (animados en posición inicial), items, jugador
|
||||||
|
room_->renderEnemies();
|
||||||
|
room_->renderItems();
|
||||||
|
player_->render();
|
||||||
|
|
||||||
|
// Renderizar highlight de selección (encima de los sprites)
|
||||||
|
renderSelectionHighlight();
|
||||||
|
|
||||||
|
// Renderizar barra de estado del editor (reemplaza al scoreboard)
|
||||||
|
if (statusbar_) {
|
||||||
|
statusbar_->render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maneja eventos del editor
|
||||||
|
void MapEditor::handleEvent(const SDL_Event& event) {
|
||||||
|
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && event.button.button == SDL_BUTTON_LEFT) {
|
||||||
|
handleMouseDown(mouse_game_x_, mouse_game_y_);
|
||||||
|
} else if (event.type == SDL_EVENT_MOUSE_BUTTON_UP && event.button.button == SDL_BUTTON_LEFT) {
|
||||||
|
handleMouseUp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Procesa click del ratón: hit test + inicio de drag
|
||||||
|
void MapEditor::handleMouseDown(float game_x, float game_y) {
|
||||||
|
// Prioridad de hit test: jugador → enemigos (initial) → enemigos (boundaries) → items
|
||||||
|
|
||||||
|
// Helper para iniciar drag
|
||||||
|
auto startDrag = [&](DragTarget target, int index, float entity_x, float entity_y) {
|
||||||
|
drag_.target = target;
|
||||||
|
drag_.index = index;
|
||||||
|
drag_.offset_x = game_x - entity_x;
|
||||||
|
drag_.offset_y = game_y - entity_y;
|
||||||
|
drag_.snap_x = entity_x;
|
||||||
|
drag_.snap_y = entity_y;
|
||||||
|
drag_.moved = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1. Hit test sobre el jugador (8x16)
|
||||||
|
SDL_FRect player_rect = player_->getRect();
|
||||||
|
if (pointInRect(game_x, game_y, player_rect)) {
|
||||||
|
startDrag(DragTarget::PLAYER, -1, player_rect.x, player_rect.y);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Hit test sobre enemigos: posición inicial (usan el rect del sprite vivo)
|
||||||
|
auto* enemy_mgr = room_->getEnemyManager();
|
||||||
|
for (int i = 0; i < enemy_mgr->getCount(); ++i) {
|
||||||
|
SDL_FRect enemy_rect = enemy_mgr->getEnemy(i)->getRect();
|
||||||
|
if (pointInRect(game_x, game_y, enemy_rect)) {
|
||||||
|
startDrag(DragTarget::ENEMY_INITIAL, i, enemy_rect.x, enemy_rect.y);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Hit test sobre boundaries de enemigos (rectángulos 8x8 en las posiciones de room_data_)
|
||||||
|
for (int i = 0; i < static_cast<int>(room_data_.enemies.size()); ++i) {
|
||||||
|
const auto& ed = room_data_.enemies[i];
|
||||||
|
constexpr float SZ = static_cast<float>(Tile::SIZE);
|
||||||
|
|
||||||
|
SDL_FRect b1_rect = {.x = static_cast<float>(ed.x1), .y = static_cast<float>(ed.y1), .w = SZ, .h = SZ};
|
||||||
|
if (pointInRect(game_x, game_y, b1_rect)) {
|
||||||
|
startDrag(DragTarget::ENEMY_BOUND1, i, b1_rect.x, b1_rect.y);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_FRect b2_rect = {.x = static_cast<float>(ed.x2), .y = static_cast<float>(ed.y2), .w = SZ, .h = SZ};
|
||||||
|
if (pointInRect(game_x, game_y, b2_rect)) {
|
||||||
|
startDrag(DragTarget::ENEMY_BOUND2, i, b2_rect.x, b2_rect.y);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Hit test sobre items (8x8)
|
||||||
|
auto* item_mgr = room_->getItemManager();
|
||||||
|
for (int i = 0; i < item_mgr->getCount(); ++i) {
|
||||||
|
SDL_FRect item_rect = item_mgr->getItem(i)->getCollider();
|
||||||
|
if (pointInRect(game_x, game_y, item_rect)) {
|
||||||
|
startDrag(DragTarget::ITEM, i, item_rect.x, item_rect.y);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click en el fondo: deseleccionar
|
||||||
|
selected_enemy_ = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Procesa soltar el ratón: commit del drag
|
||||||
|
void MapEditor::handleMouseUp() {
|
||||||
|
if (drag_.target == DragTarget::NONE) { return; }
|
||||||
|
|
||||||
|
const int IDX = drag_.index;
|
||||||
|
|
||||||
|
// Si no se movió: fue un click → seleccionar/deseleccionar
|
||||||
|
if (!drag_.moved) {
|
||||||
|
if (drag_.target == DragTarget::ENEMY_INITIAL) {
|
||||||
|
// Toggle selección: si ya estaba seleccionado, deseleccionar
|
||||||
|
selected_enemy_ = (selected_enemy_ == IDX) ? -1 : IDX;
|
||||||
|
} else {
|
||||||
|
// Click en otro tipo de entidad: deseleccionar enemigo
|
||||||
|
selected_enemy_ = -1;
|
||||||
|
}
|
||||||
|
drag_ = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hubo movimiento: commit del drag
|
||||||
|
const int SNAP_X = static_cast<int>(drag_.snap_x);
|
||||||
|
const int SNAP_Y = static_cast<int>(drag_.snap_y);
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
switch (drag_.target) {
|
||||||
|
case DragTarget::PLAYER:
|
||||||
|
player_->setDebugPosition(drag_.snap_x, drag_.snap_y);
|
||||||
|
player_->finalizeDebugTeleport();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DragTarget::ENEMY_INITIAL:
|
||||||
|
if (IDX >= 0 && IDX < static_cast<int>(room_data_.enemies.size())) {
|
||||||
|
room_data_.enemies[IDX].x = drag_.snap_x;
|
||||||
|
room_data_.enemies[IDX].y = drag_.snap_y;
|
||||||
|
room_->getEnemyManager()->getEnemy(IDX)->resetToInitialPosition(room_data_.enemies[IDX]);
|
||||||
|
selected_enemy_ = IDX; // Seleccionar el enemigo arrastrado
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DragTarget::ENEMY_BOUND1:
|
||||||
|
if (IDX >= 0 && IDX < static_cast<int>(room_data_.enemies.size())) {
|
||||||
|
room_data_.enemies[IDX].x1 = SNAP_X;
|
||||||
|
room_data_.enemies[IDX].y1 = SNAP_Y;
|
||||||
|
selected_enemy_ = IDX;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DragTarget::ENEMY_BOUND2:
|
||||||
|
if (IDX >= 0 && IDX < static_cast<int>(room_data_.enemies.size())) {
|
||||||
|
room_data_.enemies[IDX].x2 = SNAP_X;
|
||||||
|
room_data_.enemies[IDX].y2 = SNAP_Y;
|
||||||
|
selected_enemy_ = IDX;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DragTarget::ITEM:
|
||||||
|
if (IDX >= 0 && IDX < room_->getItemManager()->getCount()) {
|
||||||
|
room_->getItemManager()->getItem(IDX)->setPosition(drag_.snap_x, drag_.snap_y);
|
||||||
|
selected_enemy_ = -1;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DragTarget::NONE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) { autosave(); }
|
||||||
|
drag_ = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualiza la posición snapped durante el drag
|
||||||
|
void MapEditor::updateDrag() {
|
||||||
|
float raw_x = mouse_game_x_ - drag_.offset_x;
|
||||||
|
float raw_y = mouse_game_y_ - drag_.offset_y;
|
||||||
|
|
||||||
|
float new_snap_x = snapToGrid(raw_x);
|
||||||
|
float new_snap_y = snapToGrid(raw_y);
|
||||||
|
|
||||||
|
// Detectar si hubo movimiento real (el snap cambió respecto al inicio)
|
||||||
|
if (new_snap_x != drag_.snap_x || new_snap_y != drag_.snap_y) {
|
||||||
|
drag_.moved = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
drag_.snap_x = new_snap_x;
|
||||||
|
drag_.snap_y = new_snap_y;
|
||||||
|
|
||||||
|
// Mientras arrastramos, mover la entidad visualmente a la posición snapped
|
||||||
|
switch (drag_.target) {
|
||||||
|
case DragTarget::PLAYER:
|
||||||
|
player_->setDebugPosition(drag_.snap_x, drag_.snap_y);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DragTarget::ENEMY_INITIAL:
|
||||||
|
if (drag_.index >= 0 && drag_.index < room_->getEnemyManager()->getCount()) {
|
||||||
|
// Mover el sprite vivo del enemigo durante el arrastre
|
||||||
|
auto& enemy = room_->getEnemyManager()->getEnemy(drag_.index);
|
||||||
|
Enemy::Data temp_data = room_data_.enemies[drag_.index];
|
||||||
|
temp_data.x = drag_.snap_x;
|
||||||
|
temp_data.y = drag_.snap_y;
|
||||||
|
enemy->resetToInitialPosition(temp_data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DragTarget::ENEMY_BOUND1:
|
||||||
|
// Los boundaries se actualizan visualmente en renderEnemyBoundaries() via drag_.snap
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DragTarget::ENEMY_BOUND2:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DragTarget::ITEM:
|
||||||
|
if (drag_.index >= 0 && drag_.index < room_->getItemManager()->getCount()) {
|
||||||
|
room_->getItemManager()->getItem(drag_.index)->setPosition(drag_.snap_x, drag_.snap_y);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DragTarget::NONE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dibuja highlight del elemento seleccionado/arrastrado
|
||||||
|
void MapEditor::renderSelectionHighlight() {
|
||||||
|
auto game_surface = Screen::get()->getRendererSurface();
|
||||||
|
if (!game_surface) { return; }
|
||||||
|
|
||||||
|
constexpr float SZ = static_cast<float>(Tile::SIZE);
|
||||||
|
|
||||||
|
// Highlight del enemigo seleccionado (persistente, color bright_green)
|
||||||
|
if (selected_enemy_ >= 0 && selected_enemy_ < room_->getEnemyManager()->getCount()) {
|
||||||
|
SDL_FRect enemy_rect = room_->getEnemyManager()->getEnemy(selected_enemy_)->getRect();
|
||||||
|
SDL_FRect border = {
|
||||||
|
.x = enemy_rect.x - 1,
|
||||||
|
.y = enemy_rect.y - 1,
|
||||||
|
.w = enemy_rect.w + 2,
|
||||||
|
.h = enemy_rect.h + 2};
|
||||||
|
game_surface->drawRectBorder(&border, stringToColor("bright_green"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight del drag activo (temporal, color bright_white)
|
||||||
|
if (drag_.target == DragTarget::NONE || !drag_.moved) { return; }
|
||||||
|
|
||||||
|
const Uint8 DRAG_COLOR = stringToColor("bright_white");
|
||||||
|
SDL_FRect highlight_rect{};
|
||||||
|
|
||||||
|
switch (drag_.target) {
|
||||||
|
case DragTarget::PLAYER:
|
||||||
|
highlight_rect = player_->getRect();
|
||||||
|
break;
|
||||||
|
case DragTarget::ENEMY_INITIAL:
|
||||||
|
if (drag_.index >= 0 && drag_.index < room_->getEnemyManager()->getCount()) {
|
||||||
|
highlight_rect = room_->getEnemyManager()->getEnemy(drag_.index)->getRect();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DragTarget::ENEMY_BOUND1:
|
||||||
|
case DragTarget::ENEMY_BOUND2:
|
||||||
|
highlight_rect = {.x = drag_.snap_x, .y = drag_.snap_y, .w = SZ, .h = SZ};
|
||||||
|
break;
|
||||||
|
case DragTarget::ITEM:
|
||||||
|
if (drag_.index >= 0 && drag_.index < room_->getItemManager()->getCount()) {
|
||||||
|
highlight_rect = room_->getItemManager()->getItem(drag_.index)->getCollider();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DragTarget::NONE:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_FRect border = {
|
||||||
|
.x = highlight_rect.x - 1,
|
||||||
|
.y = highlight_rect.y - 1,
|
||||||
|
.w = highlight_rect.w + 2,
|
||||||
|
.h = highlight_rect.h + 2};
|
||||||
|
game_surface->drawRectBorder(&border, DRAG_COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alinea un valor a la cuadrícula de 8x8
|
||||||
|
auto MapEditor::snapToGrid(float value) -> float {
|
||||||
|
return std::round(value / static_cast<float>(Tile::SIZE)) * static_cast<float>(Tile::SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hit test: punto dentro de rectángulo
|
||||||
|
auto MapEditor::pointInRect(float px, float py, const SDL_FRect& rect) -> bool {
|
||||||
|
return px >= rect.x && px < rect.x + rect.w && py >= rect.y && py < rect.y + rect.h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dibuja marcadores de boundaries y líneas de ruta para los enemigos
|
||||||
|
void MapEditor::renderEnemyBoundaries() {
|
||||||
|
auto game_surface = Screen::get()->getRendererSurface();
|
||||||
|
if (!game_surface) { return; }
|
||||||
|
|
||||||
|
const Uint8 COLOR_BOUND1 = stringToColor("bright_cyan");
|
||||||
|
const Uint8 COLOR_BOUND2 = stringToColor("bright_yellow");
|
||||||
|
const Uint8 COLOR_ROUTE = stringToColor("bright_white");
|
||||||
|
|
||||||
|
for (int i = 0; i < static_cast<int>(room_data_.enemies.size()); ++i) {
|
||||||
|
const auto& enemy = room_data_.enemies[i];
|
||||||
|
constexpr float HALF = Tile::SIZE / 2.0F;
|
||||||
|
|
||||||
|
// Posiciones base (pueden estar siendo arrastradas)
|
||||||
|
float init_x = enemy.x;
|
||||||
|
float init_y = enemy.y;
|
||||||
|
float b1_x = static_cast<float>(enemy.x1);
|
||||||
|
float b1_y = static_cast<float>(enemy.y1);
|
||||||
|
float b2_x = static_cast<float>(enemy.x2);
|
||||||
|
float b2_y = static_cast<float>(enemy.y2);
|
||||||
|
|
||||||
|
// Si estamos arrastrando una boundary de este enemigo, usar la posición snapped
|
||||||
|
if (drag_.index == i) {
|
||||||
|
if (drag_.target == DragTarget::ENEMY_BOUND1) {
|
||||||
|
b1_x = drag_.snap_x;
|
||||||
|
b1_y = drag_.snap_y;
|
||||||
|
} else if (drag_.target == DragTarget::ENEMY_BOUND2) {
|
||||||
|
b2_x = drag_.snap_x;
|
||||||
|
b2_y = drag_.snap_y;
|
||||||
|
} else if (drag_.target == DragTarget::ENEMY_INITIAL) {
|
||||||
|
init_x = drag_.snap_x;
|
||||||
|
init_y = drag_.snap_y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dibujar líneas de ruta
|
||||||
|
game_surface->drawLine(b1_x + HALF, b1_y + HALF, init_x + HALF, init_y + HALF, COLOR_ROUTE);
|
||||||
|
game_surface->drawLine(init_x + HALF, init_y + HALF, b2_x + HALF, b2_y + HALF, COLOR_ROUTE);
|
||||||
|
|
||||||
|
// Marcadores en las boundaries
|
||||||
|
renderBoundaryMarker(b1_x, b1_y, COLOR_BOUND1);
|
||||||
|
renderBoundaryMarker(b2_x, b2_y, COLOR_BOUND2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dibuja un marcador de boundary (rectángulo pequeño) en una posición
|
||||||
|
void MapEditor::renderBoundaryMarker(float x, float y, Uint8 color) {
|
||||||
|
auto game_surface = Screen::get()->getRendererSurface();
|
||||||
|
if (!game_surface) { return; }
|
||||||
|
|
||||||
|
SDL_FRect marker = {.x = x, .y = y, .w = static_cast<float>(Tile::SIZE), .h = static_cast<float>(Tile::SIZE)};
|
||||||
|
game_surface->drawRectBorder(&marker, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convierte coordenadas de ventana a coordenadas de juego y tile
|
||||||
|
void MapEditor::updateMousePosition() {
|
||||||
|
float mouse_x = 0.0F;
|
||||||
|
float mouse_y = 0.0F;
|
||||||
|
SDL_GetMouseState(&mouse_x, &mouse_y);
|
||||||
|
|
||||||
|
float render_x = 0.0F;
|
||||||
|
float render_y = 0.0F;
|
||||||
|
SDL_RenderCoordinatesFromWindow(Screen::get()->getRenderer(), mouse_x, mouse_y, &render_x, &render_y);
|
||||||
|
|
||||||
|
SDL_FRect dst_rect = Screen::get()->getGameSurfaceDstRect();
|
||||||
|
mouse_game_x_ = render_x - dst_rect.x;
|
||||||
|
mouse_game_y_ = render_y - dst_rect.y;
|
||||||
|
|
||||||
|
// Convertir a coordenadas de tile (clampeadas al área de juego)
|
||||||
|
mouse_tile_x_ = static_cast<int>(mouse_game_x_) / Tile::SIZE;
|
||||||
|
mouse_tile_y_ = static_cast<int>(mouse_game_y_) / Tile::SIZE;
|
||||||
|
|
||||||
|
// Clampear a los límites del mapa
|
||||||
|
if (mouse_tile_x_ < 0) { mouse_tile_x_ = 0; }
|
||||||
|
if (mouse_tile_x_ >= PlayArea::WIDTH / Tile::SIZE) { mouse_tile_x_ = PlayArea::WIDTH / Tile::SIZE - 1; }
|
||||||
|
if (mouse_tile_y_ < 0) { mouse_tile_y_ = 0; }
|
||||||
|
if (mouse_tile_y_ >= PlayArea::HEIGHT / Tile::SIZE) { mouse_tile_y_ = PlayArea::HEIGHT / Tile::SIZE - 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualiza la información de la barra de estado
|
||||||
|
void MapEditor::updateStatusBarInfo() {
|
||||||
|
if (!statusbar_) { return; }
|
||||||
|
|
||||||
|
statusbar_->setMouseTile(mouse_tile_x_, mouse_tile_y_);
|
||||||
|
|
||||||
|
std::string sel_info;
|
||||||
|
std::string sel_detail;
|
||||||
|
|
||||||
|
// Info de drag activo (prioridad)
|
||||||
|
if (drag_.target != DragTarget::NONE && drag_.moved) {
|
||||||
|
switch (drag_.target) {
|
||||||
|
case DragTarget::PLAYER: sel_info = "player"; break;
|
||||||
|
case DragTarget::ENEMY_INITIAL: sel_info = "enemy " + std::to_string(drag_.index); break;
|
||||||
|
case DragTarget::ENEMY_BOUND1: sel_info = "e" + std::to_string(drag_.index) + " bound1"; break;
|
||||||
|
case DragTarget::ENEMY_BOUND2: sel_info = "e" + std::to_string(drag_.index) + " bound2"; break;
|
||||||
|
case DragTarget::ITEM: sel_info = "item " + std::to_string(drag_.index); break;
|
||||||
|
case DragTarget::NONE: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Info de enemigo seleccionado (persistente)
|
||||||
|
else if (selected_enemy_ >= 0 && selected_enemy_ < static_cast<int>(room_data_.enemies.size())) {
|
||||||
|
const auto& e = room_data_.enemies[selected_enemy_];
|
||||||
|
// Extraer nombre corto de la animación (sin .yaml)
|
||||||
|
std::string anim = e.animation_path;
|
||||||
|
auto dot = anim.rfind('.');
|
||||||
|
if (dot != std::string::npos) { anim = anim.substr(0, dot); }
|
||||||
|
|
||||||
|
sel_info = "enemy " + std::to_string(selected_enemy_) + ": " + anim;
|
||||||
|
sel_detail = e.color + " vx:" + std::to_string(static_cast<int>(e.vx)) +
|
||||||
|
" vy:" + std::to_string(static_cast<int>(e.vy));
|
||||||
|
if (e.flip) { sel_detail += " flip"; }
|
||||||
|
if (e.mirror) { sel_detail += " mirror"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
statusbar_->setSelectionInfo(sel_info);
|
||||||
|
statusbar_->setSelectionDetail(sel_detail);
|
||||||
|
|
||||||
|
// Actualizar el prompt de la consola según la selección
|
||||||
|
if (selected_enemy_ >= 0) {
|
||||||
|
Console::get()->setPrompt("enemy " + std::to_string(selected_enemy_) + "> ");
|
||||||
|
} else {
|
||||||
|
Console::get()->setPrompt("> ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ¿Hay un enemigo seleccionado?
|
||||||
|
auto MapEditor::hasSelectedEnemy() const -> bool {
|
||||||
|
return selected_enemy_ >= 0 && selected_enemy_ < static_cast<int>(room_data_.enemies.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modifica una propiedad del enemigo seleccionado
|
||||||
|
auto MapEditor::setEnemyProperty(const std::string& property, const std::string& value) -> std::string {
|
||||||
|
if (!active_) { return "Editor not active"; }
|
||||||
|
if (!hasSelectedEnemy()) { return "No enemy selected"; }
|
||||||
|
|
||||||
|
auto& enemy = room_data_.enemies[selected_enemy_];
|
||||||
|
|
||||||
|
if (property == "ANIMATION") {
|
||||||
|
std::string anim = toLower(value);
|
||||||
|
if (anim.find('.') == std::string::npos) { anim += ".yaml"; }
|
||||||
|
|
||||||
|
// Intentar recrear el enemigo con la nueva animación
|
||||||
|
std::string old_anim = enemy.animation_path;
|
||||||
|
enemy.animation_path = anim;
|
||||||
|
try {
|
||||||
|
auto* enemy_mgr = room_->getEnemyManager();
|
||||||
|
enemy_mgr->getEnemy(selected_enemy_) = std::make_shared<Enemy>(enemy);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
enemy.animation_path = old_anim; // Restaurar si falla
|
||||||
|
return std::string("Error: ") + e.what();
|
||||||
|
}
|
||||||
|
|
||||||
|
autosave();
|
||||||
|
return "animation: " + anim;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property == "COLOR") {
|
||||||
|
std::string color = toLower(value);
|
||||||
|
|
||||||
|
// Intentar recrear el enemigo con el nuevo color
|
||||||
|
std::string old_color = enemy.color;
|
||||||
|
enemy.color = color;
|
||||||
|
try {
|
||||||
|
auto* enemy_mgr = room_->getEnemyManager();
|
||||||
|
enemy_mgr->getEnemy(selected_enemy_) = std::make_shared<Enemy>(enemy);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
enemy.color = old_color;
|
||||||
|
return std::string("Error: ") + e.what();
|
||||||
|
}
|
||||||
|
|
||||||
|
autosave();
|
||||||
|
return "color: " + color;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property == "VX") {
|
||||||
|
try {
|
||||||
|
enemy.vx = std::stof(value);
|
||||||
|
} catch (...) { return "Invalid value: " + value; }
|
||||||
|
enemy.vy = 0.0F; // No se permiten velocidades en los dos ejes
|
||||||
|
|
||||||
|
room_->getEnemyManager()->getEnemy(selected_enemy_)->resetToInitialPosition(enemy);
|
||||||
|
autosave();
|
||||||
|
return "vx: " + std::to_string(static_cast<int>(enemy.vx)) + " vy: 0";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property == "VY") {
|
||||||
|
try {
|
||||||
|
enemy.vy = std::stof(value);
|
||||||
|
} catch (...) { return "Invalid value: " + value; }
|
||||||
|
enemy.vx = 0.0F; // No se permiten velocidades en los dos ejes
|
||||||
|
|
||||||
|
room_->getEnemyManager()->getEnemy(selected_enemy_)->resetToInitialPosition(enemy);
|
||||||
|
autosave();
|
||||||
|
return "vy: " + std::to_string(static_cast<int>(enemy.vy)) + " vx: 0";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property == "FLIP") {
|
||||||
|
enemy.flip = (value == "ON" || value == "TRUE" || value == "1");
|
||||||
|
if (enemy.flip) { enemy.mirror = false; } // Mutuamente excluyentes
|
||||||
|
|
||||||
|
// Recrear el enemigo (flip/mirror se aplican en el constructor)
|
||||||
|
try {
|
||||||
|
auto* enemy_mgr = room_->getEnemyManager();
|
||||||
|
enemy_mgr->getEnemy(selected_enemy_) = std::make_shared<Enemy>(enemy);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
return std::string("Error: ") + e.what();
|
||||||
|
}
|
||||||
|
|
||||||
|
autosave();
|
||||||
|
return std::string("flip: ") + (enemy.flip ? "on" : "off");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property == "MIRROR") {
|
||||||
|
enemy.mirror = (value == "ON" || value == "TRUE" || value == "1");
|
||||||
|
if (enemy.mirror) { enemy.flip = false; } // Mutuamente excluyentes
|
||||||
|
|
||||||
|
// Recrear el enemigo (flip/mirror se aplican en el constructor)
|
||||||
|
try {
|
||||||
|
auto* enemy_mgr = room_->getEnemyManager();
|
||||||
|
enemy_mgr->getEnemy(selected_enemy_) = std::make_shared<Enemy>(enemy);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
return std::string("Error: ") + e.what();
|
||||||
|
}
|
||||||
|
|
||||||
|
autosave();
|
||||||
|
return std::string("mirror: ") + (enemy.mirror ? "on" : "off");
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown property: " + property + " (use: animation, color, vx, vy, flip, mirror)";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crea un nuevo enemigo con valores por defecto, centrado en la habitación
|
||||||
|
auto MapEditor::addEnemy() -> std::string {
|
||||||
|
if (!active_) { return "Editor not active"; }
|
||||||
|
|
||||||
|
constexpr float CENTER_X = PlayArea::CENTER_X;
|
||||||
|
constexpr float CENTER_Y = PlayArea::CENTER_Y;
|
||||||
|
constexpr float ROUTE_HALF = 2.0F * Tile::SIZE; // 2 tiles a cada lado (5 tiles total con el centro)
|
||||||
|
|
||||||
|
Enemy::Data new_enemy;
|
||||||
|
new_enemy.animation_path = "spider.yaml";
|
||||||
|
new_enemy.x = CENTER_X;
|
||||||
|
new_enemy.y = CENTER_Y;
|
||||||
|
new_enemy.vx = 24.0F;
|
||||||
|
new_enemy.vy = 0.0F;
|
||||||
|
new_enemy.x1 = static_cast<int>(CENTER_X - ROUTE_HALF);
|
||||||
|
new_enemy.y1 = static_cast<int>(CENTER_Y);
|
||||||
|
new_enemy.x2 = static_cast<int>(CENTER_X + ROUTE_HALF);
|
||||||
|
new_enemy.y2 = static_cast<int>(CENTER_Y);
|
||||||
|
new_enemy.color = "white";
|
||||||
|
new_enemy.flip = true;
|
||||||
|
new_enemy.frame = -1;
|
||||||
|
|
||||||
|
// Añadir a los datos y crear el sprite vivo
|
||||||
|
room_data_.enemies.push_back(new_enemy);
|
||||||
|
room_->getEnemyManager()->addEnemy(std::make_shared<Enemy>(new_enemy));
|
||||||
|
|
||||||
|
// Seleccionar el nuevo enemigo
|
||||||
|
selected_enemy_ = static_cast<int>(room_data_.enemies.size()) - 1;
|
||||||
|
|
||||||
|
autosave();
|
||||||
|
return "Added enemy " + std::to_string(selected_enemy_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Elimina el enemigo seleccionado
|
||||||
|
auto MapEditor::deleteEnemy() -> std::string {
|
||||||
|
if (!active_) { return "Editor not active"; }
|
||||||
|
if (!hasSelectedEnemy()) { return "No enemy selected"; }
|
||||||
|
|
||||||
|
const int IDX = selected_enemy_;
|
||||||
|
|
||||||
|
// Eliminar de los datos
|
||||||
|
room_data_.enemies.erase(room_data_.enemies.begin() + IDX);
|
||||||
|
|
||||||
|
// Recrear todos los enemigos vivos (los índices cambian al borrar)
|
||||||
|
auto* enemy_mgr = room_->getEnemyManager();
|
||||||
|
enemy_mgr->clear();
|
||||||
|
for (const auto& enemy_data : room_data_.enemies) {
|
||||||
|
enemy_mgr->addEnemy(std::make_shared<Enemy>(enemy_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
selected_enemy_ = -1;
|
||||||
|
autosave();
|
||||||
|
return "Deleted enemy " + std::to_string(IDX);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Duplica el enemigo seleccionado (lo pone un tile a la derecha)
|
||||||
|
auto MapEditor::duplicateEnemy() -> std::string {
|
||||||
|
if (!active_) { return "Editor not active"; }
|
||||||
|
if (!hasSelectedEnemy()) { return "No enemy selected"; }
|
||||||
|
|
||||||
|
// Copiar datos del enemigo seleccionado
|
||||||
|
Enemy::Data copy = room_data_.enemies[selected_enemy_];
|
||||||
|
|
||||||
|
// Desplazar un tile a la derecha
|
||||||
|
copy.x += Tile::SIZE;
|
||||||
|
copy.x1 += Tile::SIZE;
|
||||||
|
copy.x2 += Tile::SIZE;
|
||||||
|
|
||||||
|
// Añadir y crear sprite vivo
|
||||||
|
room_data_.enemies.push_back(copy);
|
||||||
|
room_->getEnemyManager()->addEnemy(std::make_shared<Enemy>(copy));
|
||||||
|
|
||||||
|
// Seleccionar el nuevo enemigo
|
||||||
|
selected_enemy_ = static_cast<int>(room_data_.enemies.size()) - 1;
|
||||||
|
|
||||||
|
autosave();
|
||||||
|
return "Duplicated as enemy " + std::to_string(selected_enemy_);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // _DEBUG
|
||||||
106
source/game/editor/map_editor.hpp
Normal file
106
source/game/editor/map_editor.hpp
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <memory> // Para shared_ptr, unique_ptr
|
||||||
|
#include <string> // Para string
|
||||||
|
|
||||||
|
#include "external/fkyaml_node.hpp" // Para fkyaml::node
|
||||||
|
#include "game/entities/enemy.hpp" // Para Enemy::Data
|
||||||
|
#include "game/entities/item.hpp" // Para Item::Data
|
||||||
|
#include "game/entities/player.hpp" // Para Player::SpawnData
|
||||||
|
#include "game/gameplay/room.hpp" // Para Room::Data
|
||||||
|
#include "game/gameplay/scoreboard.hpp" // Para Scoreboard::Data
|
||||||
|
#include "game/options.hpp" // Para Options::Cheat
|
||||||
|
|
||||||
|
class EditorStatusBar;
|
||||||
|
|
||||||
|
class MapEditor {
|
||||||
|
public:
|
||||||
|
static void init(); // [SINGLETON] Crea el objeto
|
||||||
|
static void destroy(); // [SINGLETON] Destruye el objeto
|
||||||
|
static auto get() -> MapEditor*; // [SINGLETON] Obtiene el objeto
|
||||||
|
|
||||||
|
void enter(std::shared_ptr<Room> room, std::shared_ptr<Player> player, const std::string& room_path, std::shared_ptr<Scoreboard::Data> scoreboard_data);
|
||||||
|
void exit();
|
||||||
|
[[nodiscard]] auto isActive() const -> bool { return active_; }
|
||||||
|
|
||||||
|
void update(float delta_time);
|
||||||
|
void render();
|
||||||
|
void handleEvent(const SDL_Event& event);
|
||||||
|
auto revert() -> std::string;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static MapEditor* instance_; // [SINGLETON] Objeto privado
|
||||||
|
|
||||||
|
MapEditor(); // Constructor
|
||||||
|
~MapEditor(); // Destructor
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
void renderBoundaryMarker(float x, float y, Uint8 color);
|
||||||
|
void renderSelectionHighlight();
|
||||||
|
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)
|
||||||
|
|
||||||
|
// 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_;
|
||||||
|
|
||||||
|
// 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 de invencibilidad
|
||||||
|
Options::Cheat::State invincible_before_editor_{Options::Cheat::State::DISABLED};
|
||||||
|
};
|
||||||
|
|
||||||
|
#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 {
|
||||||
|
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 std::string 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
|
||||||
@@ -48,6 +48,27 @@ void Enemy::update(float delta_time) {
|
|||||||
collider_ = getRect();
|
collider_ = getRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
// Solo actualiza la animación sin mover al enemigo
|
||||||
|
void Enemy::updateAnimation(float delta_time) {
|
||||||
|
sprite_->animate(delta_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resetea el enemigo a su posición inicial (para editor)
|
||||||
|
void Enemy::resetToInitialPosition(const Data& data) {
|
||||||
|
sprite_->setPosX(data.x);
|
||||||
|
sprite_->setPosY(data.y);
|
||||||
|
sprite_->setVelX(data.vx);
|
||||||
|
sprite_->setVelY(data.vy);
|
||||||
|
|
||||||
|
const int FLIP = (should_flip_ && data.vx < 0.0F) ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
|
||||||
|
const int MIRROR = should_mirror_ ? SDL_FLIP_VERTICAL : SDL_FLIP_NONE;
|
||||||
|
sprite_->setFlip(static_cast<SDL_FlipMode>(FLIP | MIRROR));
|
||||||
|
|
||||||
|
collider_ = getRect();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Comprueba si ha llegado al limite del recorrido para darse media vuelta
|
// Comprueba si ha llegado al limite del recorrido para darse media vuelta
|
||||||
void Enemy::checkPath() { // NOLINT(readability-make-member-function-const)
|
void Enemy::checkPath() { // NOLINT(readability-make-member-function-const)
|
||||||
if (sprite_->getPosX() > x2_ || sprite_->getPosX() < x1_) {
|
if (sprite_->getPosX() > x2_ || sprite_->getPosX() < x1_) {
|
||||||
|
|||||||
@@ -29,6 +29,10 @@ class Enemy {
|
|||||||
|
|
||||||
void render(); // Pinta el enemigo en pantalla
|
void render(); // Pinta el enemigo en pantalla
|
||||||
void update(float delta_time); // Actualiza las variables del objeto
|
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 getRect() -> SDL_FRect; // Devuelve el rectangulo que contiene al enemigo
|
||||||
auto getCollider() -> SDL_FRect&; // Obtiene el rectangulo de colision del enemigo
|
auto getCollider() -> SDL_FRect&; // Obtiene el rectangulo de colision del enemigo
|
||||||
|
|||||||
@@ -41,6 +41,14 @@ auto Item::getPos() -> SDL_FPoint { // NOLINT(readability-convert-member-functi
|
|||||||
return P;
|
return P;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
// Establece la posición del item (para editor)
|
||||||
|
void Item::setPosition(float x, float y) {
|
||||||
|
sprite_->setPosition(x, y);
|
||||||
|
collider_ = sprite_->getRect();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Asigna los colores del objeto
|
// Asigna los colores del objeto
|
||||||
void Item::setColors(Uint8 col1, Uint8 col2) {
|
void Item::setColors(Uint8 col1, Uint8 col2) {
|
||||||
// Reinicializa el vector de colores
|
// Reinicializa el vector de colores
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ class Item {
|
|||||||
auto getCollider() -> SDL_FRect& { return collider_; } // Obtiene el rectangulo de colision del objeto
|
auto getCollider() -> SDL_FRect& { return collider_; } // Obtiene el rectangulo de colision del objeto
|
||||||
auto getPos() -> SDL_FPoint; // Obtiene su ubicación
|
auto getPos() -> SDL_FPoint; // Obtiene su ubicación
|
||||||
void setColors(Uint8 col1, Uint8 col2); // Asigna los colores del objeto
|
void setColors(Uint8 col1, Uint8 col2); // Asigna los colores del objeto
|
||||||
|
#ifdef _DEBUG
|
||||||
|
void setPosition(float x, float y); // Establece la posición del item (para editor)
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr float ITEM_SIZE = 8.0F; // Tamaño del item en pixels
|
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;
|
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
|
// 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;
|
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
|
} // namespace GameControl
|
||||||
#endif
|
#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
|
// Comprueba si hay colisión con algún enemigo
|
||||||
auto EnemyManager::checkCollision(SDL_FRect& rect) -> bool {
|
auto EnemyManager::checkCollision(SDL_FRect& rect) -> bool {
|
||||||
return std::ranges::any_of(enemies_, [&rect](const auto& enemy) {
|
return std::ranges::any_of(enemies_, [&rect](const auto& enemy) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#include <memory> // Para shared_ptr
|
#include <memory> // Para shared_ptr
|
||||||
#include <vector> // Para vector
|
#include <vector> // Para vector
|
||||||
|
|
||||||
class Enemy;
|
#include "game/entities/enemy.hpp" // Para Enemy, Enemy::Data
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Gestor de enemigos de una habitación
|
* @brief Gestor de enemigos de una habitación
|
||||||
@@ -40,6 +40,13 @@ class EnemyManager {
|
|||||||
// Detección de colisiones
|
// Detección de colisiones
|
||||||
auto checkCollision(SDL_FRect& rect) -> bool; // Comprueba si hay colisión con algún enemigo
|
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:
|
private:
|
||||||
std::vector<std::shared_ptr<Enemy>> enemies_; // Colección de enemigos
|
std::vector<std::shared_ptr<Enemy>> enemies_; // Colección de enemigos
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,10 +6,9 @@
|
|||||||
#include <string> // Para string
|
#include <string> // Para string
|
||||||
#include <vector> // Para vector
|
#include <vector> // Para vector
|
||||||
|
|
||||||
|
#include "game/entities/item.hpp" // Para Item, Item::Data
|
||||||
#include "scoreboard.hpp" // Para Scoreboard::Data
|
#include "scoreboard.hpp" // Para Scoreboard::Data
|
||||||
|
|
||||||
class Item;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Gestor de items de una habitación
|
* @brief Gestor de items de una habitación
|
||||||
*
|
*
|
||||||
@@ -47,6 +46,11 @@ class ItemManager {
|
|||||||
// Estado
|
// Estado
|
||||||
void setPaused(bool paused); // Pausa/despausa todos los items
|
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
|
// Detección de colisiones
|
||||||
/**
|
/**
|
||||||
* @brief Comprueba si hay colisión con algún item
|
* @brief Comprueba si hay colisión con algún item
|
||||||
|
|||||||
@@ -121,6 +121,18 @@ void Room::renderItems() {
|
|||||||
void Room::redrawMap() {
|
void Room::redrawMap() {
|
||||||
tilemap_renderer_->redrawMap(collision_map_.get());
|
tilemap_renderer_->redrawMap(collision_map_.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Actualiza animaciones sin mover enemigos (para editor de mapas)
|
||||||
|
void Room::updateEditorMode(float delta_time) {
|
||||||
|
tilemap_renderer_->update(delta_time);
|
||||||
|
enemy_manager_->updateAnimations(delta_time);
|
||||||
|
item_manager_->update(delta_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resetea enemigos a posiciones iniciales (para editor de mapas)
|
||||||
|
void Room::resetEnemyPositions(const std::vector<Enemy::Data>& enemy_data) {
|
||||||
|
enemy_manager_->resetPositions(enemy_data);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Actualiza las variables y objetos de la habitación
|
// Actualiza las variables y objetos de la habitación
|
||||||
|
|||||||
@@ -70,6 +70,10 @@ class Room {
|
|||||||
void renderItems(); // Dibuja los objetos en pantalla
|
void renderItems(); // Dibuja los objetos en pantalla
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
void redrawMap(); // Redibuja el mapa (para actualizar modo debug)
|
void redrawMap(); // Redibuja el mapa (para actualizar modo debug)
|
||||||
|
void updateEditorMode(float delta_time); // Actualiza animaciones sin mover enemigos (para editor)
|
||||||
|
void resetEnemyPositions(const std::vector<Enemy::Data>& enemy_data); // Resetea enemigos a posiciones iniciales
|
||||||
|
auto getEnemyManager() -> EnemyManager* { return enemy_manager_.get(); } // Acceso al gestor de enemigos (para editor)
|
||||||
|
auto getItemManager() -> ItemManager* { return item_manager_.get(); } // Acceso al gestor de items (para editor)
|
||||||
#endif
|
#endif
|
||||||
void update(float delta_time); // Actualiza las variables y objetos de la habitación
|
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
|
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
|
// 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)
|
auto RoomLoader::loadYAML(const std::string& file_path, bool verbose) -> Room::Data { // NOLINT(readability-convert-member-functions-to-static)
|
||||||
Room::Data room;
|
Room::Data room;
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ class RoomLoader {
|
|||||||
* - items: lista de items (opcional)
|
* - items: lista de items (opcional)
|
||||||
*/
|
*/
|
||||||
static auto loadYAML(const std::string& file_path, bool verbose = false) -> Room::Data;
|
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:
|
private:
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
#include "core/system/debug.hpp" // Para Debug
|
#include "core/system/debug.hpp" // Para Debug
|
||||||
|
#include "game/editor/map_editor.hpp" // Para MapEditor
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
@@ -119,6 +120,26 @@ Game::Game(Mode mode)
|
|||||||
Debug::get()->saveToFile();
|
Debug::get()->saveToFile();
|
||||||
return "Pos:" + std::to_string(tile_x) + "," + std::to_string(tile_y);
|
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
|
#endif
|
||||||
|
|
||||||
SceneManager::current = (mode_ == Mode::GAME) ? SceneManager::Scene::GAME : SceneManager::Scene::DEMO;
|
SceneManager::current = (mode_ == Mode::GAME) ? SceneManager::Scene::GAME : SceneManager::Scene::DEMO;
|
||||||
@@ -139,6 +160,12 @@ Game::~Game() {
|
|||||||
GameControl::toggle_debug_mode = nullptr;
|
GameControl::toggle_debug_mode = nullptr;
|
||||||
GameControl::set_initial_room = nullptr;
|
GameControl::set_initial_room = nullptr;
|
||||||
GameControl::set_initial_pos = 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
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,9 +175,28 @@ void Game::handleEvents() {
|
|||||||
while (SDL_PollEvent(&event)) {
|
while (SDL_PollEvent(&event)) {
|
||||||
GlobalEvents::handle(event);
|
GlobalEvents::handle(event);
|
||||||
#ifdef _DEBUG
|
#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()) {
|
if (!Console::get()->isActive()) {
|
||||||
|
// Tecla 9: toggle editor (funciona tanto dentro como fuera del editor)
|
||||||
|
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_9 && static_cast<int>(event.key.repeat) == 0) {
|
||||||
|
if (MapEditor::get()->isActive()) {
|
||||||
|
GameControl::exit_editor();
|
||||||
|
Notifier::get()->show({Locale::get()->get("game.editor_disabled")}); // NOLINT(readability-static-accessed-through-instance)
|
||||||
|
} else {
|
||||||
|
GameControl::enter_editor();
|
||||||
|
Notifier::get()->show({Locale::get()->get("game.editor_enabled")}); // NOLINT(readability-static-accessed-through-instance)
|
||||||
|
}
|
||||||
|
} else if (MapEditor::get()->isActive()) {
|
||||||
|
MapEditor::get()->handleEvent(event);
|
||||||
|
} else {
|
||||||
handleDebugEvents(event);
|
handleDebugEvents(event);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,6 +218,14 @@ void Game::handleInput() {
|
|||||||
return;
|
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
|
// Durante fade/postfade, solo procesar inputs globales
|
||||||
if (state_ != State::PLAYING) {
|
if (state_ != State::PLAYING) {
|
||||||
GlobalInputs::handle();
|
GlobalInputs::handle();
|
||||||
@@ -240,6 +294,14 @@ void Game::update() {
|
|||||||
|
|
||||||
// Actualiza el juego en estado PLAYING
|
// Actualiza el juego en estado PLAYING
|
||||||
void Game::updatePlaying(float delta_time) {
|
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
|
// Actualiza los objetos
|
||||||
room_->update(delta_time);
|
room_->update(delta_time);
|
||||||
switch (mode_) {
|
switch (mode_) {
|
||||||
@@ -379,8 +441,19 @@ void Game::renderPlaying() {
|
|||||||
// Prepara para dibujar el frame
|
// Prepara para dibujar el frame
|
||||||
Screen::get()->start();
|
Screen::get()->start();
|
||||||
|
|
||||||
// Dibuja los elementos del juego en orden
|
// Dibuja el mapa de tiles (siempre)
|
||||||
room_->renderMap();
|
room_->renderMap();
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
// Si el editor está activo, delegar el renderizado de entidades y statusbar
|
||||||
|
if (MapEditor::get()->isActive()) {
|
||||||
|
MapEditor::get()->render();
|
||||||
|
Screen::get()->render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Dibuja los elementos del juego en orden
|
||||||
room_->renderEnemies();
|
room_->renderEnemies();
|
||||||
room_->renderItems();
|
room_->renderItems();
|
||||||
if (mode_ == Mode::GAME) {
|
if (mode_ == Mode::GAME) {
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ void Console::redrawText() {
|
|||||||
|
|
||||||
// Línea de input (siempre la última)
|
// Línea de input (siempre la última)
|
||||||
const bool SHOW_CURSOR = cursor_visible_ && (static_cast<int>(input_line_.size()) < MAX_LINE_CHARS);
|
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);
|
text_->writeColored(PADDING_IN_H, y_pos, INPUT_STR, BORDER_COLOR);
|
||||||
|
|
||||||
Screen::get()->setRendererSurface(previous_renderer);
|
Screen::get()->setRendererSurface(previous_renderer);
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ class Console {
|
|||||||
auto getVisibleHeight() -> int; // Píxeles visibles actuales (0 = oculta, height_ = totalmente visible)
|
auto getVisibleHeight() -> int; // Píxeles visibles actuales (0 = oculta, height_ = totalmente visible)
|
||||||
[[nodiscard]] auto getText() const -> std::shared_ptr<Text> { return text_; }
|
[[nodiscard]] auto getText() const -> std::shared_ptr<Text> { return text_; }
|
||||||
|
|
||||||
|
// Prompt configurable (por defecto "> ")
|
||||||
|
void setPrompt(const std::string& prompt) { prompt_ = prompt; }
|
||||||
|
|
||||||
// Callback llamado al abrir (true) o cerrar (false) la consola
|
// Callback llamado al abrir (true) o cerrar (false) la consola
|
||||||
std::function<void(bool)> on_toggle;
|
std::function<void(bool)> on_toggle;
|
||||||
|
|
||||||
@@ -84,6 +87,7 @@ class Console {
|
|||||||
// Estado de la entrada de texto
|
// Estado de la entrada de texto
|
||||||
std::vector<std::string> msg_lines_; // Líneas de mensaje (1 o más)
|
std::vector<std::string> msg_lines_; // Líneas de mensaje (1 o más)
|
||||||
std::string input_line_;
|
std::string input_line_;
|
||||||
|
std::string prompt_{"> "}; // Prompt configurable
|
||||||
float cursor_timer_{0.0F};
|
float cursor_timer_{0.0F};
|
||||||
bool cursor_visible_{true};
|
bool cursor_visible_{true};
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include "core/rendering/render_info.hpp" // Para RenderInfo
|
#include "core/rendering/render_info.hpp" // Para RenderInfo
|
||||||
#include "core/rendering/screen.hpp" // Para Screen
|
#include "core/rendering/screen.hpp" // Para Screen
|
||||||
#include "core/resources/resource_helper.hpp" // Para Resource::Helper
|
#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 "external/fkyaml_node.hpp" // Para fkyaml::node
|
||||||
#include "game/game_control.hpp" // Para GameControl
|
#include "game/game_control.hpp" // Para GameControl
|
||||||
#include "game/options.hpp" // Para Options
|
#include "game/options.hpp" // Para Options
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
#include "core/system/debug.hpp" // Para Debug
|
#include "core/system/debug.hpp" // Para Debug
|
||||||
|
#include "game/editor/map_editor.hpp" // Para MapEditor
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// ── Helpers ──────────────────────────────────────────────────────────────────
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
@@ -560,9 +562,48 @@ static auto cmd_debug(const std::vector<std::string>& args) -> std::string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ROOM <num>|NEXT|PREV
|
// ROOM <num>|NEXT|PREV
|
||||||
|
// 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() && MapEditor::get()->isActive();
|
||||||
|
|
||||||
|
// Si el editor está activo, salir primero (guarda y recarga la room actual)
|
||||||
|
if (EDITOR_WAS_ACTIVE && GameControl::exit_editor) {
|
||||||
|
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 cmd_room(const std::vector<std::string>& args) -> std::string {
|
static auto cmd_room(const std::vector<std::string>& args) -> std::string {
|
||||||
if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; }
|
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"; }
|
||||||
|
|
||||||
|
// 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;
|
int num = 0;
|
||||||
if (args[0] == "NEXT" || args[0] == "PREV") {
|
if (args[0] == "NEXT" || args[0] == "PREV") {
|
||||||
if (!GameControl::get_current_room) { return "Game not initialized"; }
|
if (!GameControl::get_current_room) { return "Game not initialized"; }
|
||||||
@@ -574,15 +615,12 @@ static auto cmd_room(const std::vector<std::string>& args) -> std::string {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
num = std::stoi(args[0]);
|
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"; }
|
if (num < 1 || num > 60) { return "Room must be between 1 and 60"; }
|
||||||
char buf[16];
|
char buf[16];
|
||||||
std::snprintf(buf, sizeof(buf), "%02d.yaml", num);
|
std::snprintf(buf, sizeof(buf), "%02d.yaml", num);
|
||||||
if (GameControl::change_room && GameControl::change_room(buf)) {
|
return changeRoomWithEditor(buf);
|
||||||
return std::string("Room: ") + buf;
|
|
||||||
}
|
|
||||||
return std::string("Room not found: ") + buf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ITEMS <0-200>
|
// ITEMS <0-200>
|
||||||
@@ -629,6 +667,67 @@ static auto cmd_scene(const std::vector<std::string>& args) -> std::string {
|
|||||||
if (args[0] == "ENDING2") { return GO_TO(SceneManager::Scene::ENDING2, "Ending 2"); }
|
if (args[0] == "ENDING2") { return GO_TO(SceneManager::Scene::ENDING2, "Ending 2"); }
|
||||||
return "Unknown scene: " + args[0];
|
return "Unknown scene: " + args[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EDIT [ON|OFF|REVERT]
|
||||||
|
static auto cmd_edit(const std::vector<std::string>& args) -> std::string {
|
||||||
|
if (args.empty()) {
|
||||||
|
// Toggle: si está activo → off, si no → on
|
||||||
|
if (MapEditor::get() && 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";
|
||||||
|
}
|
||||||
|
return "usage: edit [on|off|revert]";
|
||||||
|
}
|
||||||
|
|
||||||
|
// SET <property> <value> — modifica propiedad del enemigo seleccionado en el editor
|
||||||
|
static auto cmd_set(const std::vector<std::string>& args) -> std::string {
|
||||||
|
if (!MapEditor::get() || !MapEditor::get()->isActive()) { return "Editor not active"; }
|
||||||
|
if (!MapEditor::get()->hasSelectedEnemy()) { return "No enemy selected"; }
|
||||||
|
if (args.size() < 2) { return "usage: set <animation|color|vx|vy|flip> <value>"; }
|
||||||
|
return MapEditor::get()->setEnemyProperty(args[0], args[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ENEMY [ADD|DELETE|DUPLICATE]
|
||||||
|
static auto cmd_enemy(const std::vector<std::string>& args) -> std::string {
|
||||||
|
if (!MapEditor::get() || !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>";
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// SHOW [INFO|NOTIFICATION|CHEEVO]
|
// SHOW [INFO|NOTIFICATION|CHEEVO]
|
||||||
@@ -829,6 +928,9 @@ void CommandRegistry::registerHandlers() {
|
|||||||
handlers_["cmd_items"] = cmd_items;
|
handlers_["cmd_items"] = cmd_items;
|
||||||
handlers_["cmd_room"] = cmd_room;
|
handlers_["cmd_room"] = cmd_room;
|
||||||
handlers_["cmd_scene"] = cmd_scene;
|
handlers_["cmd_scene"] = cmd_scene;
|
||||||
|
handlers_["cmd_edit"] = cmd_edit;
|
||||||
|
handlers_["cmd_set"] = cmd_set;
|
||||||
|
handlers_["cmd_enemy"] = cmd_enemy;
|
||||||
#endif
|
#endif
|
||||||
// HELP se registra en load() como lambda que captura this
|
// HELP se registra en load() como lambda que captura this
|
||||||
|
|
||||||
@@ -855,6 +957,29 @@ void CommandRegistry::registerHandlers() {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
// SET COLOR: colores de la paleta (UPPERCASE, sin .yaml)
|
||||||
|
dynamic_providers_["SET COLOR"] = []() -> 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"};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandRegistry::load(const std::string& yaml_path) {
|
void CommandRegistry::load(const std::string& yaml_path) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
namespace Texts {
|
namespace Texts {
|
||||||
constexpr const char* WINDOW_CAPTION = "© 2022 JailDoctor's Dilemma — JailDesigner";
|
constexpr const char* WINDOW_CAPTION = "© 2022 JailDoctor's Dilemma — JailDesigner";
|
||||||
constexpr const char* COPYRIGHT = "@2022 JailDesigner";
|
constexpr const char* COPYRIGHT = "@2022 JailDesigner";
|
||||||
constexpr const char* VERSION = "1.12"; // Versión por defecto
|
constexpr const char* VERSION = "1.13"; // Versión por defecto
|
||||||
} // namespace Texts
|
} // namespace Texts
|
||||||
|
|
||||||
// Tamaño de bloque
|
// Tamaño de bloque
|
||||||
|
|||||||
Reference in New Issue
Block a user