Compare commits

...

10 Commits

Author SHA1 Message Date
0e61b94848 - es pot canviar d'habitacio amb el editor obert
- nous comandos per a navegar per les habitacions (left, right, up i down)
2026-04-02 11:38:46 +02:00
eca5a55d3c nova font per al editor 2026-04-02 11:30:16 +02:00
dccd0d41e4 tecla 9 per entrar i eixir del editor 2026-04-02 11:25:03 +02:00
20bac58814 al eixir del editor, recarrega la habitació nova 2026-04-02 11:21:08 +02:00
a6fae7b001 guardat dels canvis en la habitacio 2026-04-02 11:10:03 +02:00
b31346830f drag'n drop de enemics, boundaries i items 2026-04-02 10:53:21 +02:00
b6fec3eba7 drag'n drop del jugador 2026-04-02 10:39:57 +02:00
606388227c mostra les rutes dels enemics al editor 2026-04-02 10:36:41 +02:00
a2caf95005 començem a treballar en el editor 2026-04-02 10:28:19 +02:00
0bfb535d4d - actualització masiva de CLAUDE.md
- pujat a versió 1.13
2026-04-02 09:20:27 +02:00
31 changed files with 1882 additions and 133 deletions

154
CLAUDE.md
View File

@@ -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)

View File

@@ -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

View File

@@ -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,13 @@ 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]
- name: CHEATS - name: CHEATS
commands: commands:
- keyword: CHEAT - keyword: CHEAT

View File

@@ -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"

View File

@@ -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"

View File

@@ -1,58 +1,568 @@
# VOID MAIN enemies:
-
animation: code.yaml
boundaries:
position1:
x: 3
y: 2
position2:
x: 20
y: 2
color: yellow
position:
x: 8
y: 2
velocity:
x: 24.0
y: 0
items:
-
counter: 1
position:
x: 21
y: 13
tile: 42
tileSetFile: items.gif
room: room:
name_en: "VOID MAIN"
name_ca: "VOID MAIN"
bgColor: black bgColor: black
border: magenta border: magenta
tileSetFile: standard.gif
# Conexiones de la habitación (null = sin conexión)
connections: connections:
up: null
down: null down: null
left: 02.yaml left: 02.yaml
right: null right: null
up: null
# Colores de los objetos conveyorBelt: left
itemColor1: bright_cyan itemColor1: bright_cyan
itemColor2: yellow itemColor2: yellow
name_ca: VOID MAIN
# Dirección de la cinta transportadora: left, none, right name_en: VOID MAIN
conveyorBelt: left tileSetFile: standard.gif
# Tilemap: 16 filas × 32 columnas (256×192 píxeles @ 8px/tile)
# Índices de tiles (-1 = vacío)
tilemap: tilemap:
- [24, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 24, 24] -
- [24, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 24, 24] - 24
- [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 24, 24] - 24
- [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 24, 24] - -1
- [24, 24, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, -1, -1, 504, 24, 24, 24, 24, 24, 24, 24, 24] - -1
- [24, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 504, -1, 24, -1, -1, -1, -1, -1, 24, 24] - -1
- [24, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 504, -1, -1, 24, -1, -1, -1, -1, -1, 24, 24] - -1
- [24, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 504, -1, -1, -1, 24, -1, -1, -1, -1, -1, 24, 24] - -1
- [24, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 504, -1, -1, -1, -1, 24, -1, -1, -1, -1, -1, 24, 24] - -1
- [24, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 504, -1, -1, -1, -1, -1, 24, -1, -1, -1, -1, -1, 24, 24] - -1
- [24, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 504, -1, -1, -1, -1, -1, -1, 24, -1, -1, -1, -1, -1, 24, 24] - -1
- [24, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 504, -1, -1, -1, -1, -1, -1, -1, 24, 24, 24, -1, -1, -1, 24, 24] - -1
- [24, 24, -1, -1, -1, -1, -1, -1, -1, 252, 252, 252, 252, 252, 252, 252, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 24, 24] - -1
- [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 134, 24] - -1
- [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 440, 440, 440, 440, -1, -1, -1, -1, -1, -1, -1, -1, 134, 24] - -1
- [24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24] - -1
- -1
# Enemigos en esta habitación - -1
enemies: - -1
- animation: code.yaml - -1
position: {x: 3, y: 2} - -1
velocity: {x: 24.0, y: 0} - -1
boundaries: - -1
position1: {x: 3, y: 2} - -1
position2: {x: 27, y: 2} - -1
color: yellow - -1
- -1
# Objetos en esta habitación - -1
items: - -1
- tileSetFile: items.gif - -1
tile: 42 - -1
position: {x: 21, y: 13} - 24
counter: 1 - 24
-
- 24
- 24
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- 24
- 24
-
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- 24
- 24
-
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- 24
- 24
-
- 24
- 24
- 252
- 252
- 252
- 252
- 252
- 252
- 252
- 252
- 252
- 252
- 252
- 252
- 252
- 252
- 252
- 252
- 252
- 252
- 252
- -1
- -1
- 504
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
-
- 24
- 24
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- 504
- -1
- 24
- -1
- -1
- -1
- -1
- -1
- 24
- 24
-
- 24
- 24
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- 504
- -1
- -1
- 24
- -1
- -1
- -1
- -1
- -1
- 24
- 24
-
- 24
- 24
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- 504
- -1
- -1
- -1
- 24
- -1
- -1
- -1
- -1
- -1
- 24
- 24
-
- 24
- 24
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- 504
- -1
- -1
- -1
- -1
- 24
- -1
- -1
- -1
- -1
- -1
- 24
- 24
-
- 24
- 24
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- 504
- -1
- -1
- -1
- -1
- -1
- 24
- -1
- -1
- -1
- -1
- -1
- 24
- 24
-
- 24
- 24
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- 504
- -1
- -1
- -1
- -1
- -1
- -1
- 24
- -1
- -1
- -1
- -1
- -1
- 24
- 24
-
- 24
- 24
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- 504
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- 24
- 24
- 24
- -1
- -1
- -1
- 24
- 24
-
- 24
- 24
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- 252
- 252
- 252
- 252
- 252
- 252
- 252
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- 24
- 24
-
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- 134
- 24
-
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- 440
- 440
- 440
- 440
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- -1
- 134
- 24
-
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24
- 24

View File

@@ -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

View File

@@ -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_;

View File

@@ -25,7 +25,10 @@ namespace Resource {
auto getRoom(const std::string& name) -> std::shared_ptr<Room::Data>; auto getRoom(const std::string& name) -> std::shared_ptr<Room::Data>;
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

View File

@@ -36,7 +36,8 @@
#include "utils/defines.hpp" // Para WINDOW_CAPTION #include "utils/defines.hpp" // Para WINDOW_CAPTION
#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();

View File

@@ -0,0 +1,78 @@
#ifdef _DEBUG
#include "game/editor/editor_statusbar.hpp"
#include <string> // Para to_string
#include "core/rendering/screen.hpp" // Para Screen
#include "core/rendering/surface.hpp" // Para Surface
#include "core/rendering/text.hpp" // Para Text
#include "core/resources/resource_cache.hpp" // Para Resource::Cache
#include "game/options.hpp" // Para Options::game
#include "utils/defines.hpp" // Para Tile::SIZE
#include "utils/utils.hpp" // Para stringToColor
// Constructor
EditorStatusBar::EditorStatusBar(const std::string& room_number, const std::string& room_name)
: room_number_(room_number),
room_name_(room_name) {
const float SURFACE_WIDTH = Options::game.width;
constexpr float SURFACE_HEIGHT = 6.0F * Tile::SIZE; // 48 pixels, igual que el scoreboard
surface_ = std::make_shared<Surface>(SURFACE_WIDTH, SURFACE_HEIGHT);
surface_dest_ = {.x = 0, .y = Options::game.height - SURFACE_HEIGHT, .w = SURFACE_WIDTH, .h = SURFACE_HEIGHT};
}
// Pinta la barra de estado en pantalla
void EditorStatusBar::render() {
surface_->render(nullptr, &surface_dest_);
}
// Actualiza la barra de estado
void EditorStatusBar::update([[maybe_unused]] float delta_time) {
fillTexture();
}
// Establece las coordenadas del ratón en tiles
void EditorStatusBar::setMouseTile(int tile_x, int tile_y) {
mouse_tile_x_ = tile_x;
mouse_tile_y_ = tile_y;
}
// Establece la información de la entidad seleccionada
void EditorStatusBar::setSelectionInfo(const std::string& info) {
selection_info_ = info;
}
// Dibuja los elementos en la surface
void EditorStatusBar::fillTexture() {
auto previous_renderer = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(surface_);
surface_->clear(stringToColor("black"));
auto text = Resource::Cache::get()->getText("8bithud");
const Uint8 LABEL_COLOR = stringToColor("bright_cyan");
const Uint8 VALUE_COLOR = stringToColor("white");
// Línea 1: Número y nombre de la habitación
const std::string ROOM_TEXT = toLower(room_number_ + " " + room_name_);
text->writeColored(LEFT_X, LINE1_Y, ROOM_TEXT, LABEL_COLOR);
// Línea 2: Coordenadas del ratón en tiles
const std::string TILE_X_STR = (mouse_tile_x_ < 10 ? "0" : "") + std::to_string(mouse_tile_x_);
const std::string TILE_Y_STR = (mouse_tile_y_ < 10 ? "0" : "") + std::to_string(mouse_tile_y_);
text->writeColored(LEFT_X, LINE2_Y, toLower("tile:"), LABEL_COLOR);
text->writeColored(LEFT_X + 30, LINE2_Y, TILE_X_STR + "," + TILE_Y_STR, VALUE_COLOR);
// Info de selección o indicador de modo editor
if (!selection_info_.empty()) {
text->writeColored(LEFT_X + 80, LINE2_Y, toLower(selection_info_), stringToColor("bright_yellow"));
} else {
text->writeColored(176, LINE2_Y, toLower("editor"), stringToColor("bright_green"));
}
Screen::get()->setRendererSurface(previous_renderer);
}
#endif // _DEBUG

View File

@@ -0,0 +1,43 @@
#pragma once
#ifdef _DEBUG
#include <SDL3/SDL.h>
#include <memory> // Para shared_ptr
#include <string> // Para string
class Surface;
class EditorStatusBar {
public:
EditorStatusBar(const std::string& room_number, const std::string& room_name);
~EditorStatusBar() = default;
void render();
void update(float delta_time);
void setMouseTile(int tile_x, int tile_y);
void setSelectionInfo(const std::string& info);
private:
void fillTexture(); // Dibuja los elementos en la surface
// Constantes de posición (en pixels dentro de la surface de 256x48)
static constexpr int LINE1_Y = 4; // Nombre de la habitación
static constexpr int LINE2_Y = 14; // Coordenadas del ratón + selección
static constexpr int LINE3_Y = 24; // Línea extra disponible
static constexpr int LEFT_X = 4; // Margen izquierdo
// Objetos
std::shared_ptr<Surface> surface_; // Surface donde dibujar la barra
SDL_FRect surface_dest_{}; // Rectángulo destino en pantalla
// Variables
std::string room_number_; // Número de la habitación
std::string room_name_; // Nombre de la habitación
int mouse_tile_x_{0}; // Coordenada X del ratón en tiles
int mouse_tile_y_{0}; // Coordenada Y del ratón en tiles
std::string selection_info_; // Información de la entidad seleccionada
};
#endif // _DEBUG

View File

@@ -0,0 +1,523 @@
#ifdef _DEBUG
#include "game/editor/map_editor.hpp"
#include <SDL3/SDL.h>
#include <cmath> // Para std::round
#include <iostream> // Para cout
#include "core/input/mouse.hpp" // Para Mouse
#include "core/rendering/screen.hpp" // Para Screen
#include "core/rendering/surface.hpp" // Para Surface
#include "core/resources/resource_cache.hpp" // Para Resource::Cache
#include "core/resources/resource_list.hpp" // Para Resource::List
#include "game/editor/editor_statusbar.hpp" // Para EditorStatusBar
#include "game/editor/room_saver.hpp" // Para RoomSaver
#include "game/entities/player.hpp" // Para Player
#include "game/gameplay/enemy_manager.hpp" // Para EnemyManager
#include "game/gameplay/item_manager.hpp" // Para ItemManager
#include "game/gameplay/room.hpp" // Para Room
#include "game/options.hpp" // Para Options
#include "utils/defines.hpp" // Para Tile::SIZE, PlayArea
#include "utils/utils.hpp" // Para stringToColor
// Singleton
MapEditor* MapEditor::instance_ = nullptr;
void MapEditor::init() {
instance_ = new MapEditor();
}
void MapEditor::destroy() {
delete instance_;
instance_ = nullptr;
}
auto MapEditor::get() -> MapEditor* {
return instance_;
}
// Constructor
MapEditor::MapEditor() = default;
// Destructor
MapEditor::~MapEditor() = default;
// Entra en modo editor
void MapEditor::enter(std::shared_ptr<Room> room, std::shared_ptr<Player> player, const std::string& room_path, std::shared_ptr<Scoreboard::Data> scoreboard_data) {
if (active_) { return; }
room_ = std::move(room);
player_ = std::move(player);
room_path_ = room_path;
scoreboard_data_ = std::move(scoreboard_data);
// Cargar una copia de los datos de la habitación (para boundaries y edición)
auto room_data_ptr = Resource::Cache::get()->getRoom(room_path);
if (room_data_ptr) {
room_data_ = *room_data_ptr;
}
// Obtener la ruta completa y cargar el YAML original (para edición parcial y backup)
file_path_ = Resource::List::get()->get(room_path_);
if (!file_path_.empty()) {
yaml_ = RoomSaver::loadYAML(file_path_);
yaml_backup_ = yaml_; // Copia profunda para revert
}
// Guardar estado de invencibilidad y forzarla
invincible_before_editor_ = Options::cheats.invincible;
Options::cheats.invincible = Options::Cheat::State::ENABLED;
player_->setColor();
// Resetear enemigos a su posición inicial (pueden haberse movido durante el gameplay)
room_->resetEnemyPositions(room_data_.enemies);
// Crear la barra de estado
statusbar_ = std::make_unique<EditorStatusBar>(room_->getNumber(), room_->getName());
// Resetear estado de drag
drag_ = {};
active_ = true;
std::cout << "MapEditor: ON (room " << room_path_ << ")\n";
}
// Sale del modo editor
void MapEditor::exit() {
if (!active_) { return; }
active_ = false;
// Restaurar invencibilidad
Options::cheats.invincible = invincible_before_editor_;
player_->setColor();
// Liberar recursos
drag_ = {};
statusbar_.reset();
room_.reset();
player_.reset();
scoreboard_data_.reset();
std::cout << "MapEditor: OFF\n";
}
// Revierte todos los cambios al estado original
auto MapEditor::revert() -> std::string {
if (!active_) { return "Editor not active"; }
if (file_path_.empty()) { return "Error: No file path"; }
// Restaurar el YAML al backup original
yaml_ = yaml_backup_;
RoomSaver::saveYAML(file_path_, yaml_);
// Recargar room_data_ desde el backup
auto room_data_ptr = Resource::Cache::get()->getRoom(room_path_);
if (room_data_ptr) {
room_data_ = *room_data_ptr;
}
// Resetear los sprites vivos a las posiciones originales
room_->resetEnemyPositions(room_data_.enemies);
// Resetear items (recargar posiciones)
auto* item_mgr = room_->getItemManager();
for (int i = 0; i < item_mgr->getCount() && i < static_cast<int>(room_data_.items.size()); ++i) {
item_mgr->getItem(i)->setPosition(room_data_.items[i].x, room_data_.items[i].y);
}
return "Reverted to original";
}
// Auto-guarda los cambios puntuales al YAML tras soltar una entidad
void MapEditor::autosave() {
if (file_path_.empty()) { return; }
RoomSaver::saveYAML(file_path_, yaml_);
}
// Actualiza el editor
void MapEditor::update(float delta_time) {
// Mantener el ratón siempre visible
SDL_ShowCursor();
Mouse::last_mouse_move_time = SDL_GetTicks();
// Actualizar animaciones de enemigos e items (sin mover enemigos)
room_->updateEditorMode(delta_time);
// Actualizar posición del ratón
updateMousePosition();
// Si estamos arrastrando, actualizar la posición snapped
if (drag_.target != DragTarget::NONE) {
updateDrag();
}
// Actualizar la barra de estado con las coordenadas del ratón y la selección
if (statusbar_) {
statusbar_->setMouseTile(mouse_tile_x_, mouse_tile_y_);
// Construir info de selección
std::string sel_info;
if (drag_.target != DragTarget::NONE) {
switch (drag_.target) {
case DragTarget::PLAYER:
sel_info = "PLAYER";
break;
case DragTarget::ENEMY_INITIAL:
sel_info = "ENEMY " + std::to_string(drag_.index);
break;
case DragTarget::ENEMY_BOUND1:
sel_info = "E" + std::to_string(drag_.index) + " BOUND1";
break;
case DragTarget::ENEMY_BOUND2:
sel_info = "E" + std::to_string(drag_.index) + " BOUND2";
break;
case DragTarget::ITEM:
sel_info = "ITEM " + std::to_string(drag_.index);
break;
case DragTarget::NONE:
break;
}
}
statusbar_->setSelectionInfo(sel_info);
statusbar_->update(delta_time);
}
}
// Renderiza el editor
void MapEditor::render() {
// El tilemap ya ha sido renderizado por Game::renderPlaying() antes de llamar aquí
// Renderizar los marcadores de boundaries y líneas de ruta (debajo de los sprites)
renderEnemyBoundaries();
// Renderizar entidades normales: enemigos (animados en posición inicial), items, jugador
room_->renderEnemies();
room_->renderItems();
player_->render();
// Renderizar highlight de selección (encima de los sprites)
renderSelectionHighlight();
// Renderizar barra de estado del editor (reemplaza al scoreboard)
if (statusbar_) {
statusbar_->render();
}
}
// Maneja eventos del editor
void MapEditor::handleEvent(const SDL_Event& event) {
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && event.button.button == SDL_BUTTON_LEFT) {
handleMouseDown(mouse_game_x_, mouse_game_y_);
} else if (event.type == SDL_EVENT_MOUSE_BUTTON_UP && event.button.button == SDL_BUTTON_LEFT) {
handleMouseUp();
}
}
// Procesa click del ratón: hit test + inicio de drag
void MapEditor::handleMouseDown(float game_x, float game_y) {
// Prioridad de hit test: jugador → enemigos (initial) → enemigos (boundaries) → items
// Helper para iniciar drag
auto startDrag = [&](DragTarget target, int index, float entity_x, float entity_y) {
drag_.target = target;
drag_.index = index;
drag_.offset_x = game_x - entity_x;
drag_.offset_y = game_y - entity_y;
drag_.snap_x = entity_x;
drag_.snap_y = entity_y;
};
// 1. Hit test sobre el jugador (8x16)
SDL_FRect player_rect = player_->getRect();
if (pointInRect(game_x, game_y, player_rect)) {
startDrag(DragTarget::PLAYER, -1, player_rect.x, player_rect.y);
return;
}
// 2. Hit test sobre enemigos: posición inicial (usan el rect del sprite vivo)
auto* enemy_mgr = room_->getEnemyManager();
for (int i = 0; i < enemy_mgr->getCount(); ++i) {
SDL_FRect enemy_rect = enemy_mgr->getEnemy(i)->getRect();
if (pointInRect(game_x, game_y, enemy_rect)) {
startDrag(DragTarget::ENEMY_INITIAL, i, enemy_rect.x, enemy_rect.y);
return;
}
}
// 3. Hit test sobre boundaries de enemigos (rectángulos 8x8 en las posiciones de room_data_)
for (int i = 0; i < static_cast<int>(room_data_.enemies.size()); ++i) {
const auto& ed = room_data_.enemies[i];
constexpr float SZ = static_cast<float>(Tile::SIZE);
SDL_FRect b1_rect = {.x = static_cast<float>(ed.x1), .y = static_cast<float>(ed.y1), .w = SZ, .h = SZ};
if (pointInRect(game_x, game_y, b1_rect)) {
startDrag(DragTarget::ENEMY_BOUND1, i, b1_rect.x, b1_rect.y);
return;
}
SDL_FRect b2_rect = {.x = static_cast<float>(ed.x2), .y = static_cast<float>(ed.y2), .w = SZ, .h = SZ};
if (pointInRect(game_x, game_y, b2_rect)) {
startDrag(DragTarget::ENEMY_BOUND2, i, b2_rect.x, b2_rect.y);
return;
}
}
// 4. Hit test sobre items (8x8)
auto* item_mgr = room_->getItemManager();
for (int i = 0; i < item_mgr->getCount(); ++i) {
SDL_FRect item_rect = item_mgr->getItem(i)->getCollider();
if (pointInRect(game_x, game_y, item_rect)) {
startDrag(DragTarget::ITEM, i, item_rect.x, item_rect.y);
return;
}
}
}
// Procesa soltar el ratón: commit del drag
void MapEditor::handleMouseUp() {
if (drag_.target == DragTarget::NONE) { return; }
const int IDX = drag_.index;
const int SNAP_X = static_cast<int>(drag_.snap_x);
const int SNAP_Y = static_cast<int>(drag_.snap_y);
bool changed = false;
switch (drag_.target) {
case DragTarget::PLAYER:
player_->setDebugPosition(drag_.snap_x, drag_.snap_y);
player_->finalizeDebugTeleport();
// El jugador no se guarda en el YAML de la habitación (es dato de spawn global)
break;
case DragTarget::ENEMY_INITIAL:
if (IDX >= 0 && IDX < static_cast<int>(room_data_.enemies.size())) {
room_data_.enemies[IDX].x = drag_.snap_x;
room_data_.enemies[IDX].y = drag_.snap_y;
room_->getEnemyManager()->getEnemy(IDX)->resetToInitialPosition(room_data_.enemies[IDX]);
RoomSaver::updateEnemyPosition(yaml_, IDX, drag_.snap_x, drag_.snap_y);
changed = true;
}
break;
case DragTarget::ENEMY_BOUND1:
if (IDX >= 0 && IDX < static_cast<int>(room_data_.enemies.size())) {
room_data_.enemies[IDX].x1 = SNAP_X;
room_data_.enemies[IDX].y1 = SNAP_Y;
RoomSaver::updateEnemyBound1(yaml_, IDX, SNAP_X, SNAP_Y);
changed = true;
}
break;
case DragTarget::ENEMY_BOUND2:
if (IDX >= 0 && IDX < static_cast<int>(room_data_.enemies.size())) {
room_data_.enemies[IDX].x2 = SNAP_X;
room_data_.enemies[IDX].y2 = SNAP_Y;
RoomSaver::updateEnemyBound2(yaml_, IDX, SNAP_X, SNAP_Y);
changed = true;
}
break;
case DragTarget::ITEM:
if (IDX >= 0 && IDX < room_->getItemManager()->getCount()) {
room_->getItemManager()->getItem(IDX)->setPosition(drag_.snap_x, drag_.snap_y);
RoomSaver::updateItemPosition(yaml_, IDX, drag_.snap_x, drag_.snap_y);
changed = true;
}
break;
case DragTarget::NONE:
break;
}
// Auto-guardar si hubo cambio
if (changed) {
autosave();
}
// Resetear estado de drag
drag_ = {};
}
// Actualiza la posición snapped durante el drag
void MapEditor::updateDrag() {
float raw_x = mouse_game_x_ - drag_.offset_x;
float raw_y = mouse_game_y_ - drag_.offset_y;
drag_.snap_x = snapToGrid(raw_x);
drag_.snap_y = snapToGrid(raw_y);
// Mientras arrastramos, mover la entidad visualmente a la posición snapped
switch (drag_.target) {
case DragTarget::PLAYER:
player_->setDebugPosition(drag_.snap_x, drag_.snap_y);
break;
case DragTarget::ENEMY_INITIAL:
if (drag_.index >= 0 && drag_.index < room_->getEnemyManager()->getCount()) {
// Mover el sprite vivo del enemigo durante el arrastre
auto& enemy = room_->getEnemyManager()->getEnemy(drag_.index);
Enemy::Data temp_data = room_data_.enemies[drag_.index];
temp_data.x = drag_.snap_x;
temp_data.y = drag_.snap_y;
enemy->resetToInitialPosition(temp_data);
}
break;
case DragTarget::ENEMY_BOUND1:
// Los boundaries se actualizan visualmente en renderEnemyBoundaries() via drag_.snap
break;
case DragTarget::ENEMY_BOUND2:
break;
case DragTarget::ITEM:
if (drag_.index >= 0 && drag_.index < room_->getItemManager()->getCount()) {
room_->getItemManager()->getItem(drag_.index)->setPosition(drag_.snap_x, drag_.snap_y);
}
break;
case DragTarget::NONE:
break;
}
}
// Dibuja highlight del elemento seleccionado/arrastrado
void MapEditor::renderSelectionHighlight() {
if (drag_.target == DragTarget::NONE) { return; }
auto game_surface = Screen::get()->getRendererSurface();
if (!game_surface) { return; }
const Uint8 HIGHLIGHT_COLOR = stringToColor("bright_white");
constexpr float SZ = static_cast<float>(Tile::SIZE);
SDL_FRect highlight_rect{};
switch (drag_.target) {
case DragTarget::PLAYER:
highlight_rect = player_->getRect();
break;
case DragTarget::ENEMY_INITIAL:
if (drag_.index >= 0 && drag_.index < room_->getEnemyManager()->getCount()) {
highlight_rect = room_->getEnemyManager()->getEnemy(drag_.index)->getRect();
}
break;
case DragTarget::ENEMY_BOUND1:
case DragTarget::ENEMY_BOUND2:
highlight_rect = {.x = drag_.snap_x, .y = drag_.snap_y, .w = SZ, .h = SZ};
break;
case DragTarget::ITEM:
if (drag_.index >= 0 && drag_.index < room_->getItemManager()->getCount()) {
highlight_rect = room_->getItemManager()->getItem(drag_.index)->getCollider();
}
break;
case DragTarget::NONE:
return;
}
// Dibujar rectángulo de highlight alrededor de la entidad
SDL_FRect border = {
.x = highlight_rect.x - 1,
.y = highlight_rect.y - 1,
.w = highlight_rect.w + 2,
.h = highlight_rect.h + 2};
game_surface->drawRectBorder(&border, HIGHLIGHT_COLOR);
}
// Alinea un valor a la cuadrícula de 8x8
auto MapEditor::snapToGrid(float value) -> float {
return std::round(value / static_cast<float>(Tile::SIZE)) * static_cast<float>(Tile::SIZE);
}
// Hit test: punto dentro de rectángulo
auto MapEditor::pointInRect(float px, float py, const SDL_FRect& rect) -> bool {
return px >= rect.x && px < rect.x + rect.w && py >= rect.y && py < rect.y + rect.h;
}
// Dibuja marcadores de boundaries y líneas de ruta para los enemigos
void MapEditor::renderEnemyBoundaries() {
auto game_surface = Screen::get()->getRendererSurface();
if (!game_surface) { return; }
const Uint8 COLOR_BOUND1 = stringToColor("bright_cyan");
const Uint8 COLOR_BOUND2 = stringToColor("bright_yellow");
const Uint8 COLOR_ROUTE = stringToColor("bright_white");
for (int i = 0; i < static_cast<int>(room_data_.enemies.size()); ++i) {
const auto& enemy = room_data_.enemies[i];
constexpr float HALF = Tile::SIZE / 2.0F;
// Posiciones base (pueden estar siendo arrastradas)
float init_x = enemy.x;
float init_y = enemy.y;
float b1_x = static_cast<float>(enemy.x1);
float b1_y = static_cast<float>(enemy.y1);
float b2_x = static_cast<float>(enemy.x2);
float b2_y = static_cast<float>(enemy.y2);
// Si estamos arrastrando una boundary de este enemigo, usar la posición snapped
if (drag_.index == i) {
if (drag_.target == DragTarget::ENEMY_BOUND1) {
b1_x = drag_.snap_x;
b1_y = drag_.snap_y;
} else if (drag_.target == DragTarget::ENEMY_BOUND2) {
b2_x = drag_.snap_x;
b2_y = drag_.snap_y;
} else if (drag_.target == DragTarget::ENEMY_INITIAL) {
init_x = drag_.snap_x;
init_y = drag_.snap_y;
}
}
// Dibujar líneas de ruta
game_surface->drawLine(b1_x + HALF, b1_y + HALF, init_x + HALF, init_y + HALF, COLOR_ROUTE);
game_surface->drawLine(init_x + HALF, init_y + HALF, b2_x + HALF, b2_y + HALF, COLOR_ROUTE);
// Marcadores en las boundaries
renderBoundaryMarker(b1_x, b1_y, COLOR_BOUND1);
renderBoundaryMarker(b2_x, b2_y, COLOR_BOUND2);
}
}
// Dibuja un marcador de boundary (rectángulo pequeño) en una posición
void MapEditor::renderBoundaryMarker(float x, float y, Uint8 color) {
auto game_surface = Screen::get()->getRendererSurface();
if (!game_surface) { return; }
SDL_FRect marker = {.x = x, .y = y, .w = static_cast<float>(Tile::SIZE), .h = static_cast<float>(Tile::SIZE)};
game_surface->drawRectBorder(&marker, color);
}
// Convierte coordenadas de ventana a coordenadas de juego y tile
void MapEditor::updateMousePosition() {
float mouse_x = 0.0F;
float mouse_y = 0.0F;
SDL_GetMouseState(&mouse_x, &mouse_y);
float render_x = 0.0F;
float render_y = 0.0F;
SDL_RenderCoordinatesFromWindow(Screen::get()->getRenderer(), mouse_x, mouse_y, &render_x, &render_y);
SDL_FRect dst_rect = Screen::get()->getGameSurfaceDstRect();
mouse_game_x_ = render_x - dst_rect.x;
mouse_game_y_ = render_y - dst_rect.y;
// Convertir a coordenadas de tile (clampeadas al área de juego)
mouse_tile_x_ = static_cast<int>(mouse_game_x_) / Tile::SIZE;
mouse_tile_y_ = static_cast<int>(mouse_game_y_) / Tile::SIZE;
// Clampear a los límites del mapa
if (mouse_tile_x_ < 0) { mouse_tile_x_ = 0; }
if (mouse_tile_x_ >= PlayArea::WIDTH / Tile::SIZE) { mouse_tile_x_ = PlayArea::WIDTH / Tile::SIZE - 1; }
if (mouse_tile_y_ < 0) { mouse_tile_y_ = 0; }
if (mouse_tile_y_ >= PlayArea::HEIGHT / Tile::SIZE) { mouse_tile_y_ = PlayArea::HEIGHT / Tile::SIZE - 1; }
}
#endif // _DEBUG

View File

@@ -0,0 +1,101 @@
#pragma once
#ifdef _DEBUG
#include <SDL3/SDL.h>
#include <memory> // Para shared_ptr, unique_ptr
#include <string> // Para string
#include "external/fkyaml_node.hpp" // Para fkyaml::node
#include "game/entities/enemy.hpp" // Para Enemy::Data
#include "game/entities/item.hpp" // Para Item::Data
#include "game/entities/player.hpp" // Para Player::SpawnData
#include "game/gameplay/room.hpp" // Para Room::Data
#include "game/gameplay/scoreboard.hpp" // Para Scoreboard::Data
#include "game/options.hpp" // Para Options::Cheat
class EditorStatusBar;
class MapEditor {
public:
static void init(); // [SINGLETON] Crea el objeto
static void destroy(); // [SINGLETON] Destruye el objeto
static auto get() -> MapEditor*; // [SINGLETON] Obtiene el objeto
void enter(std::shared_ptr<Room> room, std::shared_ptr<Player> player, const std::string& room_path, std::shared_ptr<Scoreboard::Data> scoreboard_data);
void exit();
[[nodiscard]] auto isActive() const -> bool { return active_; }
void update(float delta_time);
void render();
void handleEvent(const SDL_Event& event);
auto revert() -> std::string; // Revierte todos los cambios al estado original
private:
static MapEditor* instance_; // [SINGLETON] Objeto privado
MapEditor(); // Constructor
~MapEditor(); // Destructor
// Tipos para drag & drop
enum class DragTarget { NONE,
PLAYER,
ENEMY_INITIAL,
ENEMY_BOUND1,
ENEMY_BOUND2,
ITEM };
struct DragState {
DragTarget target{DragTarget::NONE};
int index{-1}; // Índice del enemigo o item en room_data_
float offset_x{0.0F}; // Offset entre el cursor y el origen de la entidad al inicio del drag
float offset_y{0.0F};
float snap_x{0.0F}; // Posición snapped actual (para preview)
float snap_y{0.0F};
};
// Métodos internos
void updateMousePosition(); // Convierte coordenadas de ventana a coordenadas de juego y tile
void renderEnemyBoundaries(); // Dibuja marcadores de boundaries y líneas de ruta
void renderBoundaryMarker(float x, float y, Uint8 color); // Dibuja un marcador de boundary en una posición
void renderSelectionHighlight(); // Dibuja highlight del elemento seleccionado/arrastrado
void handleMouseDown(float game_x, float game_y); // Procesa click del ratón (hit test + inicio de drag)
void handleMouseUp(); // Procesa soltar el ratón (commit del drag + autosave)
void updateDrag(); // Actualiza la posición snapped durante el drag
void autosave(); // Guarda los cambios puntuales al YAML
static auto snapToGrid(float value) -> float; // Alinea un valor a la cuadrícula de 8x8
static auto pointInRect(float px, float py, const SDL_FRect& rect) -> bool; // Hit test punto en rectángulo
// Estado del editor
bool active_{false};
DragState drag_;
// Datos de la habitación
Room::Data room_data_; // Copia mutable (para boundaries y edición)
std::string room_path_; // Nombre del fichero (ej: "06.yaml")
std::string file_path_; // Ruta completa del fichero en disco
// YAML: nodo vivo (se edita parcialmente) y backup para revert
fkyaml::node yaml_; // Nodo YAML actual (editado parcialmente)
fkyaml::node yaml_backup_; // Backup del YAML original al entrar
// Referencias a objetos vivos (para rendering)
std::shared_ptr<Room> room_;
std::shared_ptr<Player> player_;
std::shared_ptr<Scoreboard::Data> scoreboard_data_;
// Barra de estado del editor
std::unique_ptr<EditorStatusBar> statusbar_;
// Estado del ratón en coordenadas de juego
float mouse_game_x_{0.0F};
float mouse_game_y_{0.0F};
int mouse_tile_x_{0};
int mouse_tile_y_{0};
// Estado previo de invencibilidad (para restaurar al salir)
Options::Cheat::State invincible_before_editor_{Options::Cheat::State::DISABLED};
};
#endif // _DEBUG

View File

@@ -0,0 +1,118 @@
#ifdef _DEBUG
#include "game/editor/room_saver.hpp"
#include <cmath> // Para std::round
#include <fstream> // Para ifstream, ofstream, istreambuf_iterator
#include <iostream> // Para cout, cerr
#include "utils/defines.hpp" // Para Tile::SIZE
// Carga el YAML original directamente del filesystem (no del resource pack)
auto RoomSaver::loadYAML(const std::string& file_path) -> fkyaml::node {
std::ifstream file(file_path);
if (!file.is_open()) {
std::cerr << "RoomSaver: Cannot open " << file_path << "\n";
return {};
}
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
file.close();
return fkyaml::node::deserialize(content);
}
// Guarda el nodo YAML completo a disco
auto RoomSaver::saveYAML(const std::string& file_path, const fkyaml::node& yaml) -> std::string {
std::string content = fkyaml::node::serialize(yaml);
std::ofstream file(file_path);
if (!file.is_open()) {
std::cerr << "RoomSaver: Cannot write to " << file_path << "\n";
return "Error: Cannot write to " + file_path;
}
file << content;
file.close();
const std::string FILE_NAME = file_path.substr(file_path.find_last_of("\\/") + 1);
std::cout << "RoomSaver: Saved " << FILE_NAME << "\n";
return "Saved " + FILE_NAME;
}
// Actualiza la posición inicial de un enemigo (pixels → tiles)
void RoomSaver::updateEnemyPosition(fkyaml::node& yaml, int index, float x, float y) {
if (!yaml.contains("enemies")) { return; }
auto& enemies = yaml["enemies"];
if (index < 0 || index >= static_cast<int>(enemies.size())) { return; }
auto& enemy = enemies[index];
int tile_x = static_cast<int>(std::round(x / Tile::SIZE));
int tile_y = static_cast<int>(std::round(y / Tile::SIZE));
if (!enemy.contains("position")) {
enemy["position"] = fkyaml::node::mapping();
}
enemy["position"]["x"] = tile_x;
enemy["position"]["y"] = tile_y;
}
// Actualiza boundary1 de un enemigo (pixels → tiles)
void RoomSaver::updateEnemyBound1(fkyaml::node& yaml, int index, int x, int y) {
if (!yaml.contains("enemies")) { return; }
auto& enemies = yaml["enemies"];
if (index < 0 || index >= static_cast<int>(enemies.size())) { return; }
auto& enemy = enemies[index];
int tile_x = x / Tile::SIZE;
int tile_y = y / Tile::SIZE;
if (!enemy.contains("boundaries")) {
enemy["boundaries"] = fkyaml::node::mapping();
}
auto& bounds = enemy["boundaries"];
if (!bounds.contains("position1")) {
bounds["position1"] = fkyaml::node::mapping();
}
bounds["position1"]["x"] = tile_x;
bounds["position1"]["y"] = tile_y;
}
// Actualiza boundary2 de un enemigo (pixels → tiles)
void RoomSaver::updateEnemyBound2(fkyaml::node& yaml, int index, int x, int y) {
if (!yaml.contains("enemies")) { return; }
auto& enemies = yaml["enemies"];
if (index < 0 || index >= static_cast<int>(enemies.size())) { return; }
auto& enemy = enemies[index];
int tile_x = x / Tile::SIZE;
int tile_y = y / Tile::SIZE;
if (!enemy.contains("boundaries")) {
enemy["boundaries"] = fkyaml::node::mapping();
}
auto& bounds = enemy["boundaries"];
if (!bounds.contains("position2")) {
bounds["position2"] = fkyaml::node::mapping();
}
bounds["position2"]["x"] = tile_x;
bounds["position2"]["y"] = tile_y;
}
// Actualiza la posición de un item (pixels → tiles)
void RoomSaver::updateItemPosition(fkyaml::node& yaml, int index, float x, float y) {
if (!yaml.contains("items")) { return; }
auto& items = yaml["items"];
if (index < 0 || index >= static_cast<int>(items.size())) { return; }
auto& item = items[index];
int tile_x = static_cast<int>(std::round(x / Tile::SIZE));
int tile_y = static_cast<int>(std::round(y / Tile::SIZE));
if (!item.contains("position")) {
item["position"] = fkyaml::node::mapping();
}
item["position"]["x"] = tile_x;
item["position"]["y"] = tile_y;
}
#endif // _DEBUG

View File

@@ -0,0 +1,36 @@
#pragma once
#ifdef _DEBUG
#include <string> // Para string
#include "external/fkyaml_node.hpp" // Para fkyaml::node
#include "game/entities/enemy.hpp" // Para Enemy::Data
#include "game/entities/item.hpp" // Para Item::Data
#include "game/entities/player.hpp" // Para Player::SpawnData
/**
* @brief Edición parcial de archivos YAML de habitaciones
*
* Lee el YAML original, modifica solo los campos editados y serializa.
* Preserva todos los campos que no se editan (name_ca, comentarios del formato, etc.)
* Solo se usa en builds de debug (editor de mapas).
*/
class RoomSaver {
public:
RoomSaver() = delete;
// Carga el YAML original desde disco (llamar al entrar al editor)
static auto loadYAML(const std::string& file_path) -> fkyaml::node;
// Guarda el nodo YAML completo a disco
static auto saveYAML(const std::string& file_path, const fkyaml::node& yaml) -> std::string;
// Modificaciones puntuales sobre el nodo YAML (posiciones en pixels, se convierten a tiles)
static void updateEnemyPosition(fkyaml::node& yaml, int index, float x, float y);
static void updateEnemyBound1(fkyaml::node& yaml, int index, int x, int y);
static void updateEnemyBound2(fkyaml::node& yaml, int index, int x, int y);
static void updateItemPosition(fkyaml::node& yaml, int index, float x, float y);
};
#endif // _DEBUG

View File

@@ -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_) {

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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) {

View File

@@ -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
}; };

View File

@@ -6,9 +6,8 @@
#include <string> // Para string #include <string> // Para string
#include <vector> // Para vector #include <vector> // Para vector
#include "scoreboard.hpp" // Para Scoreboard::Data #include "game/entities/item.hpp" // Para Item, Item::Data
#include "scoreboard.hpp" // Para Scoreboard::Data
class Item;
/** /**
* @brief Gestor de items de una habitación * @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

View File

@@ -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

View File

@@ -69,7 +69,11 @@ class Room {
void renderEnemies(); // Dibuja los enemigos en pantalla void renderEnemies(); // Dibuja los enemigos en pantalla
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

View File

@@ -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;

View File

@@ -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:
/** /**

View File

@@ -32,7 +32,8 @@
#include "utils/utils.hpp" // Para PaletteColor, stringToColor #include "utils/utils.hpp" // Para PaletteColor, stringToColor
#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
} }
@@ -149,7 +176,20 @@ void Game::handleEvents() {
GlobalEvents::handle(event); GlobalEvents::handle(event);
#ifdef _DEBUG #ifdef _DEBUG
if (!Console::get()->isActive()) { if (!Console::get()->isActive()) {
handleDebugEvents(event); // Tecla 9: toggle editor (funciona tanto dentro como fuera del editor)
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_9 && static_cast<int>(event.key.repeat) == 0) {
if (MapEditor::get()->isActive()) {
GameControl::exit_editor();
Notifier::get()->show({Locale::get()->get("game.editor_disabled")}); // NOLINT(readability-static-accessed-through-instance)
} else {
GameControl::enter_editor();
Notifier::get()->show({Locale::get()->get("game.editor_enabled")}); // NOLINT(readability-static-accessed-through-instance)
}
} else if (MapEditor::get()->isActive()) {
MapEditor::get()->handleEvent(event);
} else {
handleDebugEvents(event);
}
} }
#endif #endif
} }
@@ -172,6 +212,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 +288,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 +435,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) {

View File

@@ -22,7 +22,8 @@
#include "utils/utils.hpp" // Para toUpper, prettyName #include "utils/utils.hpp" // Para toUpper, prettyName
#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 +561,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 +614,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 +666,43 @@ 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]";
}
#endif #endif
// SHOW [INFO|NOTIFICATION|CHEEVO] // SHOW [INFO|NOTIFICATION|CHEEVO]
@@ -829,6 +903,7 @@ 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;
#endif #endif
// HELP se registra en load() como lambda que captura this // HELP se registra en load() como lambda que captura this

View File

@@ -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