20 Commits
v1.12 ... main

Author SHA1 Message Date
c14774478c treballant en el minimapa 2026-04-02 18:46:28 +02:00
3c3e012386 - afegides opcions persistents al editor
- afegida rejilla
2026-04-02 17:38:03 +02:00
44b6f6830d primera versió del editor de tiles 2026-04-02 16:55:56 +02:00
0d12591925 tile_picker ara gasta spacing_in i spacing_out 2026-04-02 14:57:54 +02:00
22d6ac2fbf treballant en editor de items i tile_picker 2026-04-02 14:49:26 +02:00
acaf434e5c reorganitzada la status bar del editor 2026-04-02 14:13:05 +02:00
5f25562d52 editar les propietats de la habitacio 2026-04-02 14:06:35 +02:00
fc28586940 - faltava propietat mirror
- al fer clic es tanca la consola
2026-04-02 13:02:35 +02:00
273d9304dc editar propietats del enemic 2026-04-02 12:45:29 +02:00
3a5b16346b RoomSaver ja no reventa els yamals 2026-04-02 11:52:53 +02:00
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
40 changed files with 3126 additions and 85 deletions

154
CLAUDE.md
View File

@@ -53,7 +53,7 @@ cmake --build build --clean-first
./jaildoctors_dilemma
```
**Important:** The build directory is `/Users/sergio/Gitea/jaildoctors_dilemma/build` and the output executable is placed in the project root directory.
**Important:** The build directory is `build/` relative to the project root and the output executable is placed in the project root directory.
### Testing in Headless Environment (SSH/Remote Server)
@@ -67,17 +67,12 @@ sudo apt-get install xvfb
#### Running the Game in Headless Mode
**Option 1: Using the wrapper script (RECOMMENDED)**
```bash
./run_headless.sh
```
**Option 2: Using xvfb-run directly**
**Option 1: Using xvfb-run directly (RECOMMENDED)**
```bash
xvfb-run -a ./jaildoctors_dilemma
```
**Option 3: Custom display configuration**
**Option 2: Custom display configuration**
```bash
xvfb-run -a -s "-screen 0 1280x720x24" ./jaildoctors_dilemma
```
@@ -238,26 +233,39 @@ The architecture follows a **layered, modular design** with clear separation of
source/
├── core/ # Core engine systems
│ ├── audio/ # Audio management
│ │ ── audio.hpp/cpp # Audio singleton (music, sounds, volumes)
│ │ ── audio.hpp/cpp # Audio singleton (music, sounds, volumes)
│ │ └── jail_audio.hpp # Custom jail_audio library wrapper
│ ├── input/ # Input handling
│ │ ├── input.hpp/cpp # Input manager (keyboard, gamepad)
│ │ ├── input_types.hpp # Input type definitions
│ │ ├── global_inputs.hpp # Global input state
│ │ └── mouse.hpp # Mouse input
│ ├── locale/ # Localization
│ │ └── locale.hpp # Locale/language support
│ ├── rendering/ # Graphics rendering
│ │ ├── screen.hpp/cpp # Screen/window singleton, SDL renderer
│ │ ├── surface.hpp/cpp # 8-bit indexed color surface abstraction
│ │ ├── surface_sprite.hpp # Static sprite rendering
│ │ ├── surface_animated_sprite.hpp # Animated sprite with frame data
│ │ ├── surface_moving_sprite.hpp # Moving sprite with velocity
│ │ ├── texture.hpp/cpp # SDL texture wrapper
│ │ ├── sprite/ # Sprite rendering classes
│ │ ├── sprite.hpp # Static sprite rendering
│ │ │ ├── animated_sprite.hpp # Animated sprite with frame data
│ │ │ ├── moving_sprite.hpp # Moving sprite with velocity
│ │ │ └── dissolve_sprite.hpp # Dissolve transition sprite
│ │ ├── text.hpp/cpp # Text rendering system
│ │ ├── gif.hpp/cpp # GIF image loader
│ │ ├── opengl/ # OpenGL shader backend
│ │ │ └── opengl_shader.hpp/cpp # CRT shader effects
│ │ ├── pixel_reveal.hpp # Pixel reveal effect
│ │ ├── render_info.hpp # Render information data
│ │ ├── palette_manager.hpp # Palette management
│ │ ├── sdl3gpu/ # SDL3 GPU shader backend
│ │ │ ├── sdl3gpu_shader.hpp/cpp # CRT/post-processing shader effects
│ │ │ └── *_spv.h # Pre-compiled SPIR-V shader headers
│ │ └── shader_backend.hpp # Abstract shader interface
│ ├── resources/ # Asset & Resource management
│ │ ├── asset.hpp/cpp # Asset registry (file path mapping)
│ │ ── resource.hpp/cpp # Resource singleton (loads/caches assets)
│ │ ├── resource_list.hpp # Asset path registry (O(1) lookups)
│ │ ── resource_cache.hpp # Resource caching singleton
│ │ ├── resource_loader.hpp # Asset loading logic
│ │ ├── resource_pack.hpp # Resource pack handling
│ │ ├── resource_helper.hpp # Resource utility functions
│ │ └── resource_types.hpp # Resource type definitions
│ └── system/ # System management
│ ├── director.hpp/cpp # Main application controller
│ ├── debug.hpp/cpp # Debug info overlay
@@ -268,8 +276,13 @@ source/
│ │ ├── enemy.hpp/cpp # Enemy entities
│ │ └── item.hpp/cpp # Collectible items
│ ├── gameplay/ # Core gameplay systems
│ │ ├── room.hpp/cpp # Room/level logic, tilemap, collision
│ │ ├── room.hpp/cpp # Room/level logic, collision
│ │ ├── room_loader.hpp/cpp # Room loading from YAML files
│ │ ├── room_tracker.hpp/cpp # Tracks visited rooms
│ │ ├── collision_map.hpp/cpp # Collision map data
│ │ ├── tilemap_renderer.hpp/cpp # Tilemap rendering
│ │ ├── enemy_manager.hpp/cpp # Enemy lifecycle management
│ │ ├── item_manager.hpp/cpp # Item lifecycle management
│ │ ├── scoreboard.hpp/cpp # Score display & data
│ │ ├── item_tracker.hpp/cpp # Tracks collected items
│ │ ├── stats.hpp/cpp # Game statistics
@@ -284,18 +297,20 @@ source/
│ │ ├── ending2.hpp/cpp # Ending sequence 2
│ │ └── credits.hpp/cpp # Credits screen
│ ├── ui/ # User interface
│ │ ── notifier.hpp/cpp # Achievement/notification display
│ │ ── notifier.hpp/cpp # Achievement/notification display
│ │ ├── console.hpp/cpp # In-game debug console
│ │ └── console_commands.hpp/cpp # Console command definitions
│ ├── options.hpp/cpp # Game configuration/options
│ ├── game_control.hpp # Game control logic
│ ├── scene_manager.hpp # Scene flow state machine
── defaults.hpp # Game defaults constants
│ └── gameplay.hpp # Gameplay constants
── defaults.hpp # Game defaults constants
├── external/ # Third-party libraries
│ ├── jail_audio.hpp/cpp # Custom audio library
│ ├── jail_audio.h # C interface for jail_audio
│ ├── fkyaml_node.hpp # YAML parsing library
│ ├── stb_image.h # Image loading library
│ └── stb_vorbis.h # OGG Vorbis audio decoding
├── utils/ # Utility code
│ ├── delta_timer.hpp/cpp # Frame-rate independent timing
│ ├── easing_functions.hpp # Easing/interpolation functions
│ ├── defines.hpp # Game constants (resolutions, block sizes)
│ └── utils.hpp/cpp # Helper functions (colors, math)
└── main.cpp # Application entry point
@@ -360,7 +375,7 @@ The game uses a scene manager to control application flow:
// namespace SceneManager
enum class Scene {
LOGO, LOADING_SCREEN, TITLE, CREDITS, GAME, DEMO,
GAME_OVER, ENDING, ENDING2, QUIT
GAME_OVER, ENDING, ENDING2, RESTART_CURRENT, QUIT
};
inline Scene current = Scene::LOGO; // Global scene state
@@ -397,9 +412,10 @@ Display
**Key Components:**
- `Surface` - 8-bit indexed pixel buffer with palette support
- `SurfaceSprite` - Renders a fixed region of a surface
- `SurfaceAnimatedSprite` - Frame-based animation on top of sprite
- `SurfaceMovingSprite` - Adds velocity/position to animated sprite
- `Sprite` - Renders a fixed region of a surface
- `AnimatedSprite` - Frame-based animation on top of sprite
- `MovingSprite` - Adds velocity/position to animated sprite
- `DissolveSprite` - Dissolve transition effect sprite
- Supports color replacement, palette swapping, and shader effects (CRT)
### 3.5 Tile-Based Collision System
@@ -439,8 +455,8 @@ struct AnimationData {
int counter;
};
// Loaded from .ani files (list of animation names)
// Rendered with SurfaceAnimatedSprite
// Loaded from .yaml animation definition files
// Rendered with AnimatedSprite
```
---
@@ -454,7 +470,7 @@ main()
Director::Director() [Initialization]
├─ Resource::List::init() - Initialize asset registry singleton
├─ Director::setFileList() - Load assets.yaml configuration (no verification)
├─ Director::setFileList() - Load assets.yaml via Resource::List (no verification)
├─ Options::loadFromFile() - Load game configuration
├─ Audio::init() - Initialize SDL audio
├─ Screen::init() - Create window, SDL renderer
@@ -510,7 +526,7 @@ Game::run() {
```
Director::setFileList()
└─ Asset::loadFromFile(config_path, PREFIX, system_folder_)
└─ Resource::List::loadFromFile(config_path, PREFIX, system_folder_)
├─ Read config/assets.yaml - Parse text configuration file
├─ Parse YAML structure: assets grouped by category
├─ Replace variables (${PREFIX}, ${SYSTEM_FOLDER})
@@ -570,7 +586,7 @@ Game code
**Classes:**
- `PascalCase` for classes: `Player`, `Room`, `Screen`, `Director`
- Suffix `Sprite` for sprite classes: `SurfaceSprite`, `SurfaceAnimatedSprite`
- Suffix `Sprite` for sprite classes: `Sprite`, `AnimatedSprite`, `MovingSprite`
**Methods:**
- `get*()` for getters: `getWidth()`, `getRect()`
@@ -613,8 +629,9 @@ Provides **time scaling** for slow-motion effects.
### 5.4 Palette System
- 8-bit indexed color (256 colors per palette)
- Multiple palettes can be loaded and swapped
- `Surface::setPalette()` changes rendering colors
- Multiple palettes can be loaded and swapped via `PaletteManager`
- `Screen::setPaletteByName()` changes the active palette
- Supports palette sort modes (by luminosity, similarity to Spectrum palette, etc.)
- Supports color replacement per-render: `renderWithColorReplace()`
- CRT shader effects can modify colors in real-time
@@ -664,7 +681,7 @@ Achievements trigger notifications on unlock.
| Component | Technology | Version | Role |
|-----------|-----------|---------|------|
| **Graphics** | SDL3 | Latest | Window, rendering, input |
| **GPU Rendering** | OpenGL | 3.2+ | Shader effects (CRT) |
| **GPU Rendering** | SDL3 GPU | Latest | Shader effects (CRT, post-processing via SPIR-V) |
| **Audio** | SDL3 Audio | Latest | Audio device, mixing |
| **Audio Decoding** | jail_audio (custom) | 1.x | OGG/WAV playback |
| **Image Loading** | stb_image | v2.x | PNG/GIF image loading |
@@ -700,6 +717,7 @@ Achievements trigger notifications on unlock.
| `Audio` | Music and SFX playback | Singleton |
| `Resource::Cache` | Asset caching and loading (with detailed error messages) | Singleton |
| `Resource::List` | Asset path registry from config/assets.yaml, O(1) lookups, variable substitution | Singleton |
| `PaletteManager` | Palette loading, switching, and sorting | Managed by Screen |
| `Debug` | Debug overlay information | Singleton |
| `globalEvents` | Global SDL event handling (quit, device reset, mouse) | Namespace |
@@ -709,7 +727,12 @@ Achievements trigger notifications on unlock.
|-------|---------|
| `Game` | Main gameplay scene, orchestrates update/render |
| `Player` | Player entity with physics and animation |
| `Room` | Level data, collision detection, tilemap rendering |
| `Room` | Level data, collision detection |
| `RoomLoader` | Room loading from YAML files |
| `TilemapRenderer` | Tilemap rendering |
| `CollisionMap` | Collision map data |
| `EnemyManager` | Enemy lifecycle management |
| `ItemManager` | Item lifecycle management |
| `Enemy` | Enemy entity behavior and rendering |
| `Item` | Collectible items |
| `Scoreboard` | HUD display data |
@@ -722,17 +745,20 @@ Achievements trigger notifications on unlock.
| Class | Purpose |
|-------|---------|
| `Surface` | 8-bit indexed color pixel buffer with palette |
| `SurfaceSprite` | Renders a sprite region |
| `SurfaceAnimatedSprite` | Frame-based animation rendering |
| `SurfaceMovingSprite` | Sprite with velocity/position |
| `Sprite` | Renders a sprite region |
| `AnimatedSprite` | Frame-based animation rendering |
| `MovingSprite` | Sprite with velocity/position |
| `DissolveSprite` | Dissolve transition sprite |
| `Text` | Text rendering system |
| `OpenGLShader` | Shader compilation and effects |
| `Sdl3gpuShader` | SDL3 GPU shader compilation and effects |
| `PaletteManager` | Palette loading and management |
### Utility Classes
| Class | Purpose |
| Class/Header | Purpose |
|-------|---------|
| `DeltaTimer` | Frame-rate independent timing |
| `easing_functions.hpp` | Easing/interpolation functions for animations |
---
@@ -848,7 +874,7 @@ assets:
- type: BITMAP
path: ${PREFIX}/data/font/smb2.gif
- type: FONT
path: ${PREFIX}/data/font/smb2.txt
path: ${PREFIX}/data/font/smb2.fnt
# PLAYER
player:
@@ -892,23 +918,26 @@ assets:
- `${PREFIX}` - Replaced with `/../Resources` on macOS bundles, empty otherwise
- `${SYSTEM_FOLDER}` - Replaced with user's system config folder
### Animation Files (.ani)
List of animation names, one per line:
```
default
jump
run
fall
### Animation Files (.yaml)
YAML-based animation definitions with frame data:
```yaml
animations:
- name: default
frameWidth: 16
frameHeight: 16
speed: 100
loop: 0
frames: [...]
```
### Room Data Files (.room)
Key-value pairs defining room properties:
```
number=01
name=Starting Room
bg_color=0x000000
border_color=0xFF00FF
...
### Room Data Files (.yaml)
YAML-based room definitions:
```yaml
room:
name_en: Starting Room
bgColor: 0x000000
connections: [...]
tilemap: [...]
```
### Tilemap Files (.tmx)
@@ -923,7 +952,8 @@ Binary 256-color palette format (256 × 4 bytes RGBA).
### For Graphics Issues
- `Screen::render()` - Main rendering method
- `Screen::setPalete()` - Palette application
- `Screen::setPaletteByName()` - Palette switching
- `PaletteManager` - Palette loading and sorting
- `Surface` class - Pixel buffer operations
### For Input Issues
@@ -942,10 +972,10 @@ Binary 256-color palette format (256 × 4 bytes RGBA).
### For Asset Management
- `config/assets.yaml` - Asset configuration file (text-based, no recompilation needed)
- `Asset::loadFromFile()` - Loads assets from config file
- `Resource::List::loadFromFile()` - Loads asset registry from config file
- `Resource::List::get()` - Retrieves asset path (O(1) lookup with unordered_map)
- `Resource::load()` - Asset loading
- `Director::setFileList()` - Calls `Asset::loadFromFile()` with PREFIX and system_folder
- `Resource::Cache` - Asset loading and caching
- `Director::setFileList()` - Calls `Resource::List::loadFromFile()` with PREFIX and system_folder
---
@@ -985,6 +1015,6 @@ Binary 256-color palette format (256 × 4 bytes RGBA).
---
**Last Updated:** November 2022 (per README)
**Last Updated:** April 2026
**Original Author:** JailDesigner
**Repository:** Gitea (internal)

View File

@@ -96,6 +96,13 @@ set(APP_SOURCES
source/game/scenes/logo.cpp
source/game/scenes/title.cpp
# Game - Editor (debug only, guarded by #ifdef _DEBUG in source)
source/game/editor/map_editor.cpp
source/game/editor/editor_statusbar.cpp
source/game/editor/room_saver.cpp
source/game/editor/tile_picker.cpp
source/game/editor/mini_map.cpp
# Game - UI
source/game/ui/console.cpp
source/game/ui/console_commands.cpp

View File

@@ -96,6 +96,10 @@ assets:
path: ${SYSTEM_FOLDER}/debug.yaml
required: false
absolute: true
- type: DATA
path: ${SYSTEM_FOLDER}/editor.yaml
required: false
absolute: true
- type: DATA
path: ${SYSTEM_FOLDER}/stats_buffer.csv
required: false

View File

@@ -201,9 +201,9 @@ categories:
- keyword: ROOM
handler: cmd_room
description: "Change to room number (GAME only)"
usage: "ROOM <1-60>|NEXT|PREV"
usage: "ROOM <1-60>|NEXT|PREV|LEFT|RIGHT|UP|DOWN"
completions:
ROOM: [NEXT, PREV]
ROOM: [NEXT, PREV, LEFT, RIGHT, UP, DOWN]
- keyword: SCENE
handler: cmd_scene
@@ -212,6 +212,40 @@ categories:
completions:
SCENE: [LOGO, LOADING, TITLE, CREDITS, GAME, ENDING, ENDING2, RESTART]
- keyword: EDIT
handler: cmd_edit
description: "Map editor mode (GAME only)"
usage: "EDIT [ON|OFF|REVERT|SHOW|HIDE] [INFO|GRID]"
completions:
EDIT: [ON, OFF, REVERT, SHOW, HIDE]
EDIT SHOW: [INFO, GRID]
EDIT HIDE: [INFO, GRID]
- keyword: ENEMY
handler: cmd_enemy
description: "Add, delete or duplicate enemy (editor)"
usage: "ENEMY <ADD|DELETE|DUPLICATE>"
completions:
ENEMY: [ADD, DELETE, DUPLICATE]
- keyword: ITEM
handler: cmd_item
description: "Add, delete or duplicate item (editor)"
usage: "ITEM <ADD|DELETE|DUPLICATE>"
completions:
ITEM: [ADD, DELETE, DUPLICATE]
- keyword: SET
handler: cmd_set
description: "Set property (enemy or room, editor mode)"
usage: "SET <property> <value>"
dynamic_completions: true
completions:
SET: [ANIMATION, COLOR, VX, VY, FLIP, MIRROR, BGCOLOR, BORDER, ITEMCOLOR1, ITEMCOLOR2, CONVEYOR, TILESET, UP, DOWN, LEFT, RIGHT, TILE, COUNTER]
SET FLIP: [ON, OFF]
SET MIRROR: [ON, OFF]
SET CONVEYOR: [LEFT, NONE, RIGHT]
- name: CHEATS
commands:
- keyword: CHEAT

View File

@@ -141,3 +141,5 @@ game:
cheat_jail_open: "JAIL OBERTA"
debug_enabled: "DEBUG ACTIVAT"
debug_disabled: "DEBUG DESACTIVAT"
editor_enabled: "EDITOR ACTIVAT"
editor_disabled: "EDITOR DESACTIVAT"

View File

@@ -141,3 +141,5 @@ game:
cheat_jail_open: "JAIL IS OPEN"
debug_enabled: "DEBUG ENABLED"
debug_disabled: "DEBUG DISABLED"
editor_enabled: "EDITOR ENABLED"
editor_disabled: "EDITOR DISABLED"

View File

@@ -47,14 +47,12 @@ class AnimatedSprite : public MovingSprite {
void setCurrentAnimation(int index = 0); // Establece la animación actual por índice
void resetAnimation(); // Reinicia la animación
void setCurrentAnimationFrame(int num); // Establece el frame actual de la animación
void animate(float delta_time); // Calcula el frame correspondiente a la animación actual (time-based)
protected:
// Constructor per a ús de subclasses que gestionen la surface directament (sense YAML)
AnimatedSprite(std::shared_ptr<Surface> surface, SDL_FRect pos);
// Métodos protegidos
void animate(float delta_time); // Calcula el frame correspondiente a la animación actual (time-based)
private:
// Variables miembro
std::vector<AnimationData> animations_; // Vector con las diferentes animaciones

View File

@@ -139,6 +139,7 @@ class Surface {
// Paleta
void setPalette(const std::array<Uint32, 256>& palette) { palette_ = palette; }
[[nodiscard]] auto getPaletteColor(Uint8 index) const -> Uint32 { return palette_[index]; }
// Inicializa la sub paleta
static void initializeSubPalette(SubPalette& palette) { std::iota(palette.begin(), palette.end(), 0); }

View File

@@ -4,6 +4,7 @@
#include <algorithm> // Para find_if
#include <cstdlib> // Para exit, size_t
#include <fstream> // Para ifstream, istreambuf_iterator
#include <iostream> // Para basic_ostream, operator<<, endl, cout
#include <stdexcept> // Para runtime_error
#include <utility>
@@ -15,6 +16,7 @@
#include "core/resources/resource_list.hpp" // Para List, List::Type
#include "game/defaults.hpp" // Para Defaults namespace
#include "game/gameplay/room.hpp" // Para RoomData, loadRoomFile, loadRoomTileFile
#include "game/gameplay/room_loader.hpp" // Para RoomLoader::loadFromString
#include "game/options.hpp" // Para Options, OptionsGame, options
#include "utils/defines.hpp" // Para WINDOW_CAPTION
#include "utils/utils.hpp" // Para getFileName, printWithDots, PaletteColor
@@ -173,6 +175,34 @@ namespace Resource {
throw std::runtime_error("Habitación no encontrada: " + name);
}
#ifdef _DEBUG
// Recarga una habitación desde disco (para el editor de mapas)
// Lee directamente del filesystem (no del resource pack) para obtener los cambios del editor
void Cache::reloadRoom(const std::string& name) {
auto file_path = List::get()->get(name);
if (file_path.empty()) {
std::cerr << "reloadRoom: Cannot resolve path for " << name << '\n';
return;
}
// Leer directamente del filesystem (evita el resource pack que tiene datos antiguos)
std::ifstream file(file_path);
if (!file.is_open()) {
std::cerr << "reloadRoom: Cannot open " << file_path << '\n';
return;
}
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
file.close();
// Parsear y actualizar el cache
auto it = std::ranges::find_if(rooms_, [&name](const auto& r) -> bool { return r.name == name; });
if (it != rooms_.end()) {
*(it->room) = RoomLoader::loadFromString(content, name);
std::cout << "reloadRoom: " << name << " reloaded from filesystem\n";
}
}
#endif
// Obtiene todas las habitaciones
auto Cache::getRooms() -> std::vector<RoomResource>& {
return rooms_;

View File

@@ -26,6 +26,9 @@ namespace Resource {
auto getRooms() -> std::vector<RoomResource>&;
void reload(); // Recarga todos los recursos
#ifdef _DEBUG
void reloadRoom(const std::string& name); // Recarga una habitación desde disco
#endif
private:
// Estructura para llevar la cuenta de los recursos cargados

View File

@@ -36,7 +36,8 @@
#include "utils/defines.hpp" // Para WINDOW_CAPTION
#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
#ifndef _WIN32
@@ -183,6 +184,7 @@ Director::Director() {
Debug::get()->setDebugFile(Resource::List::get()->get("debug.yaml"));
Debug::get()->loadFromFile();
SceneManager::current = Debug::get()->getInitialScene();
MapEditor::init();
#endif
std::cout << "\n"; // Fin de inicialización de sistemas
@@ -217,6 +219,7 @@ Director::~Director() {
Cheevos::destroy();
Locale::destroy();
#ifdef _DEBUG
MapEditor::destroy();
Debug::destroy();
#endif
Input::destroy();

View 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, toLower
// 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();
}
void EditorStatusBar::setMouseTile(int tile_x, int tile_y) {
mouse_tile_x_ = tile_x;
mouse_tile_y_ = tile_y;
}
void EditorStatusBar::setLine2(const std::string& text) { line2_ = text; }
void EditorStatusBar::setLine3(const std::string& text) { line3_ = text; }
void EditorStatusBar::setLine4(const std::string& text) { line4_ = text; }
void EditorStatusBar::setLine5(const std::string& text) { line5_ = text; }
// Dibuja los elementos en la surface
void EditorStatusBar::fillTexture() {
auto previous_renderer = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(surface_);
surface_->clear(stringToColor("black"));
auto text = Resource::Cache::get()->getText("8bithud");
const Uint8 LABEL_COLOR = stringToColor("bright_cyan");
const Uint8 VALUE_COLOR = stringToColor("white");
const Uint8 DETAIL_COLOR = stringToColor("bright_yellow");
// Línea 1: Nombre de la habitación
text->writeColored(LEFT_X, LINE1_Y, toLower(room_number_ + " " + room_name_), LABEL_COLOR);
// Línea 2: Propiedades de room o info de enemigo
if (!line2_.empty()) {
text->writeColored(LEFT_X, LINE2_Y, toLower(line2_), DETAIL_COLOR);
}
// Línea 3: Conexiones+items o propiedades del enemigo
if (!line3_.empty()) {
text->writeColored(LEFT_X, LINE3_Y, toLower(line3_), VALUE_COLOR);
}
// Línea 4: Extra
if (!line4_.empty()) {
text->writeColored(LEFT_X, LINE4_Y, toLower(line4_), DETAIL_COLOR);
}
// Línea 5: Tile coords + drag info
const std::string TILE_X_STR = (mouse_tile_x_ < 10 ? "0" : "") + std::to_string(mouse_tile_x_);
const std::string TILE_Y_STR = (mouse_tile_y_ < 10 ? "0" : "") + std::to_string(mouse_tile_y_);
std::string line5 = "tile:" + TILE_X_STR + "," + TILE_Y_STR;
if (!line5_.empty()) {
line5 += " " + line5_;
}
text->writeColored(LEFT_X, LINE5_Y, toLower(line5), stringToColor("bright_green"));
Screen::get()->setRendererSurface(previous_renderer);
}
#endif // _DEBUG

View File

@@ -0,0 +1,52 @@
#pragma once
#ifdef _DEBUG
#include <SDL3/SDL.h>
#include <memory> // Para shared_ptr
#include <string> // Para string
class Surface;
class EditorStatusBar {
public:
EditorStatusBar(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 setLine2(const std::string& text);
void setLine3(const std::string& text);
void setLine4(const std::string& text);
void setLine5(const std::string& text);
private:
void fillTexture(); // Dibuja los elementos en la surface
// Constantes de posición (en pixels dentro de la surface de 256x48)
// Font 8bithud lowercase = 6px alto → 5 líneas con 8px de separación
static constexpr int LINE1_Y = 2; // Nombre de la habitación
static constexpr int LINE2_Y = 10; // Propiedades de room / enemy info
static constexpr int LINE3_Y = 18; // Conexiones+items / enemy detail
static constexpr int LINE4_Y = 26; // Extra
static constexpr int LINE5_Y = 34; // Tile coords + drag info
static constexpr int LEFT_X = 4; // Margen izquierdo
// Objetos
std::shared_ptr<Surface> surface_; // Surface donde dibujar la barra
SDL_FRect surface_dest_{}; // Rectángulo destino en pantalla
// Variables
std::string room_number_; // Número de la habitación
std::string room_name_; // Nombre de la habitación
int mouse_tile_x_{0}; // Coordenada X del ratón en tiles
int mouse_tile_y_{0}; // Coordenada Y del ratón en tiles
std::string line2_; // Contenido de la línea 2
std::string line3_; // Contenido de la línea 3
std::string line4_; // Contenido de la línea 4
std::string line5_; // Contenido de la línea 5
};
#endif // _DEBUG

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,151 @@
#pragma once
#ifdef _DEBUG
#include <SDL3/SDL.h>
#include <memory> // Para shared_ptr, unique_ptr
#include <string> // Para string
#include "external/fkyaml_node.hpp" // Para fkyaml::node
#include "game/editor/mini_map.hpp" // Para MiniMap
#include "game/editor/tile_picker.hpp" // Para TilePicker
#include "game/entities/enemy.hpp" // Para Enemy::Data
#include "game/entities/item.hpp" // Para Item::Data
#include "game/entities/player.hpp" // Para Player::SpawnData
#include "game/gameplay/room.hpp" // Para Room::Data
#include "game/gameplay/scoreboard.hpp" // Para Scoreboard::Data
#include "game/options.hpp" // Para Options::Cheat
class EditorStatusBar;
class MapEditor {
public:
static void init(); // [SINGLETON] Crea el objeto
static void destroy(); // [SINGLETON] Destruye el objeto
static auto get() -> MapEditor*; // [SINGLETON] Obtiene el objeto
void enter(std::shared_ptr<Room> room, std::shared_ptr<Player> player, const std::string& room_path, std::shared_ptr<Scoreboard::Data> scoreboard_data);
void exit();
[[nodiscard]] auto isActive() const -> bool { return active_; }
void update(float delta_time);
void render();
void handleEvent(const SDL_Event& event);
auto revert() -> std::string;
// Comandos para enemigos (llamados desde console_commands)
auto setEnemyProperty(const std::string& property, const std::string& value) -> std::string;
auto addEnemy() -> std::string;
auto deleteEnemy() -> std::string;
auto duplicateEnemy() -> std::string;
[[nodiscard]] auto hasSelectedEnemy() const -> bool;
// Comandos para propiedades de la habitación
auto setRoomProperty(const std::string& property, const std::string& value) -> std::string;
// Opciones del editor (llamados desde console_commands / teclas)
auto showInfo(bool show) -> std::string;
auto showGrid(bool show) -> std::string;
[[nodiscard]] auto isGridEnabled() const -> bool { return settings_.grid; }
void toggleMiniMap();
// Comandos para items
auto setItemProperty(const std::string& property, const std::string& value) -> std::string;
auto addItem() -> std::string;
auto deleteItem() -> std::string;
auto duplicateItem() -> std::string;
[[nodiscard]] auto hasSelectedItem() const -> bool;
void openTilePicker(const std::string& tileset_name, int current_tile);
private:
static MapEditor* instance_; // [SINGLETON] Objeto privado
MapEditor(); // Constructor
~MapEditor(); // Destructor
// Opciones persistentes del editor
struct Settings {
bool grid{false};
bool show_render_info{false};
};
Settings settings_;
void loadSettings();
void saveSettings();
// 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 renderGrid();
void handleMouseDown(float game_x, float game_y);
void handleMouseUp();
void updateDrag();
void autosave();
void updateStatusBarInfo();
static auto snapToGrid(float value) -> float;
static auto pointInRect(float px, float py, const SDL_FRect& rect) -> bool;
// Estado del editor
bool active_{false};
DragState drag_;
int selected_enemy_{-1}; // Índice del enemigo seleccionado (-1 = ninguno)
int selected_item_{-1}; // Índice del item seleccionado (-1 = ninguno)
static constexpr int NO_BRUSH = -2; // Sin brush activo
static constexpr int ERASER_BRUSH = -1; // Brush borrador (pinta tile vacío = -1)
int brush_tile_{NO_BRUSH}; // Tile activo para pintar
bool painting_{false}; // true mientras se está pintando con click izquierdo mantenido
// Datos de la habitación
Room::Data room_data_;
std::string room_path_;
std::string file_path_;
// YAML: nodo original (para campos que no se editan: name_ca, etc.)
fkyaml::node yaml_;
fkyaml::node yaml_backup_;
// Referencias a objetos vivos
std::shared_ptr<Room> room_;
std::shared_ptr<Player> player_;
std::shared_ptr<Scoreboard::Data> scoreboard_data_;
// Barra de estado del editor
std::unique_ptr<EditorStatusBar> statusbar_;
// Tile picker y mini mapa
TilePicker tile_picker_;
std::unique_ptr<MiniMap> mini_map_;
bool mini_map_visible_{false};
// Estado del ratón
float mouse_game_x_{0.0F};
float mouse_game_y_{0.0F};
int mouse_tile_x_{0};
int mouse_tile_y_{0};
// Estado previo (para restaurar al salir)
Options::Cheat::State invincible_before_editor_{Options::Cheat::State::DISABLED};
bool render_info_before_editor_{false};
};
#endif // _DEBUG

View File

@@ -0,0 +1,298 @@
#ifdef _DEBUG
#include "game/editor/mini_map.hpp"
#include <algorithm> // Para std::max, std::min
#include <array> // Para std::array
#include <iostream> // Para cout
#include <map> // Para std::map
#include <queue> // Para queue (BFS)
#include <set> // Para set
#include "core/rendering/screen.hpp" // Para Screen
#include "core/rendering/surface.hpp" // Para Surface
#include "core/resources/resource_cache.hpp" // Para Resource::Cache
#include "game/gameplay/room.hpp" // Para Room::Data
#include "utils/defines.hpp" // Para Tile::SIZE, PlayArea
#include "utils/utils.hpp" // Para stringToColor
// Constructor: construye todo el minimapa
MiniMap::MiniMap() {
buildTileColorTable("standard.gif");
layoutRooms();
buildRoomSurfaces();
composeFinalSurface();
}
// Analiza el tileset y crea tabla: tile_index → color predominante
void MiniMap::buildTileColorTable(const std::string& tileset_name) {
auto tileset = Resource::Cache::get()->getSurface(tileset_name);
if (!tileset) { return; }
tileset_width_ = static_cast<int>(tileset->getWidth()) / Tile::SIZE;
int tileset_height = static_cast<int>(tileset->getHeight()) / Tile::SIZE;
int total_tiles = tileset_width_ * tileset_height;
tile_colors_.resize(total_tiles, 0);
for (int tile = 0; tile < total_tiles; ++tile) {
int tile_x = (tile % tileset_width_) * Tile::SIZE;
int tile_y = (tile / tileset_width_) * Tile::SIZE;
// Contar frecuencia de cada color en el tile (ignorar transparente=255 y negro=0)
std::array<int, 256> freq{};
for (int y = 0; y < Tile::SIZE; ++y) {
for (int x = 0; x < Tile::SIZE; ++x) {
Uint8 pixel = tileset->getPixel(tile_x + x, tile_y + y);
if (pixel != 0 && pixel != 255) {
freq[pixel]++;
}
}
}
// Encontrar el color más frecuente
Uint8 best_color = 0;
int best_count = 0;
for (int c = 1; c < 256; ++c) {
if (freq[c] > best_count) {
best_count = freq[c];
best_color = static_cast<Uint8>(c);
}
}
tile_colors_[tile] = best_color;
}
}
// Posiciona las rooms en un grid usando BFS desde las conexiones
void MiniMap::layoutRooms() {
auto& rooms = Resource::Cache::get()->getRooms();
if (rooms.empty()) { return; }
// Mapa de nombre → Room::Data
std::unordered_map<std::string, std::shared_ptr<Room::Data>> room_map;
for (const auto& r : rooms) {
room_map[r.name] = r.room;
}
// BFS para posicionar rooms
std::set<std::string> visited;
std::queue<std::pair<std::string, GridPos>> bfs;
// Empezar por la primera room
const std::string& start = rooms[0].name;
bfs.push({start, {0, 0}});
visited.insert(start);
// Grid ocupado: posición → nombre de room
std::map<std::pair<int, int>, std::string> grid_occupied;
while (!bfs.empty()) {
auto [name, pos] = bfs.front();
bfs.pop();
auto key = std::make_pair(pos.x, pos.y);
if (grid_occupied.contains(key)) { continue; }
grid_occupied[key] = name;
room_positions_[name] = RoomMini{.surface = nullptr, .pos = pos};
auto it = room_map.find(name);
if (it == room_map.end()) { continue; }
const auto& data = it->second;
// Vecinos: up, down, left, right
struct Neighbor {
std::string room;
int dx, dy;
};
std::array<Neighbor, 4> neighbors = {{
{data->upper_room, 0, -1},
{data->lower_room, 0, 1},
{data->left_room, -1, 0},
{data->right_room, 1, 0},
}};
for (const auto& [neighbor_name, dx, dy] : neighbors) {
if (neighbor_name == "0" || neighbor_name.empty()) { continue; }
if (visited.contains(neighbor_name)) { continue; }
GridPos neighbor_pos = {pos.x + dx, pos.y + dy};
auto nkey = std::make_pair(neighbor_pos.x, neighbor_pos.y);
if (!grid_occupied.contains(nkey)) {
visited.insert(neighbor_name);
bfs.push({neighbor_name, neighbor_pos});
}
}
}
// Calcular bounds del grid
min_grid_x_ = 0;
min_grid_y_ = 0;
int max_grid_x = 0;
int max_grid_y = 0;
for (const auto& [name, mini] : room_positions_) {
min_grid_x_ = std::min(min_grid_x_, mini.pos.x);
min_grid_y_ = std::min(min_grid_y_, mini.pos.y);
max_grid_x = std::max(max_grid_x, mini.pos.x);
max_grid_y = std::max(max_grid_y, mini.pos.y);
}
int cols = max_grid_x - min_grid_x_ + 1;
int rows = max_grid_y - min_grid_y_ + 1;
map_width_ = cols * (CELL_W + GAP) - GAP + PADDING * 2;
map_height_ = rows * (CELL_H + GAP) - GAP + PADDING * 2;
std::cout << "MiniMap: " << room_positions_.size() << " rooms, grid " << cols << "x" << rows
<< "" << map_width_ << "x" << map_height_ << " px\n";
}
// Genera una mini-surface de 32x16 por room
void MiniMap::buildRoomSurfaces() {
for (auto& [name, mini] : room_positions_) {
mini.surface = getRoomMiniSurface(name);
}
}
// Genera la mini-surface de una room: 1 pixel por tile, color predominante
auto MiniMap::getRoomMiniSurface(const std::string& room_name) -> std::shared_ptr<Surface> {
auto room_data = Resource::Cache::get()->getRoom(room_name);
if (!room_data) { return nullptr; }
auto surface = std::make_shared<Surface>(ROOM_W, ROOM_H);
auto prev = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(surface);
surface->clear(stringToColor(room_data->bg_color));
const auto& tile_map = room_data->tile_map;
for (int y = 0; y < ROOM_H; ++y) {
for (int x = 0; x < ROOM_W; ++x) {
int index = y * ROOM_W + x;
if (index >= static_cast<int>(tile_map.size())) { continue; }
int tile = tile_map[index];
if (tile < 0 || tile >= static_cast<int>(tile_colors_.size())) { continue; }
Uint8 color = tile_colors_[tile];
if (color != 0) {
surface->putPixel(x, y, color);
}
}
}
Screen::get()->setRendererSurface(prev);
return surface;
}
// Compone la surface final con todas las rooms posicionadas
void MiniMap::composeFinalSurface() {
if (map_width_ <= 0 || map_height_ <= 0) { return; }
// Surface un poco más grande para la sombra del borde inferior/derecho
map_surface_ = std::make_shared<Surface>(map_width_ + SHADOW_OFFSET, map_height_ + SHADOW_OFFSET);
auto prev = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(map_surface_);
// 1. Fondo general
map_surface_->clear(COLOR_BACKGROUND);
// 2. Líneas de conexión entre rooms (debajo de todo)
drawConnections();
// 3. Sombras de las rooms (desplazadas 1px abajo-derecha)
for (const auto& [name, mini] : room_positions_) {
if (!mini.surface) { continue; }
int px = cellPixelX(mini.pos.x) + SHADOW_OFFSET;
int py = cellPixelY(mini.pos.y) + SHADOW_OFFSET;
SDL_FRect shadow = {.x = static_cast<float>(px), .y = static_cast<float>(py),
.w = static_cast<float>(CELL_W), .h = static_cast<float>(CELL_H)};
map_surface_->fillRect(&shadow, COLOR_SHADOW);
}
// 4. Borde negro de cada room + contenido de la room
for (const auto& [name, mini] : room_positions_) {
if (!mini.surface) { continue; }
int px = cellPixelX(mini.pos.x);
int py = cellPixelY(mini.pos.y);
// Borde negro (la celda entera)
SDL_FRect cell = {.x = static_cast<float>(px), .y = static_cast<float>(py),
.w = static_cast<float>(CELL_W), .h = static_cast<float>(CELL_H)};
map_surface_->fillRect(&cell, COLOR_ROOM_BORDER);
// Miniroom dentro del borde
SDL_FRect dst = {.x = static_cast<float>(px + BORDER), .y = static_cast<float>(py + BORDER),
.w = static_cast<float>(ROOM_W), .h = static_cast<float>(ROOM_H)};
mini.surface->render(nullptr, &dst);
}
Screen::get()->setRendererSurface(prev);
}
// Dibuja las líneas de conexión entre rooms vecinas
void MiniMap::drawConnections() {
for (const auto& [name, mini] : room_positions_) {
auto room_data = Resource::Cache::get()->getRoom(name);
if (!room_data) { continue; }
int px = cellPixelX(mini.pos.x);
int py = cellPixelY(mini.pos.y);
// Conexión derecha: línea horizontal desde el borde derecho de esta room hasta el borde izquierdo de la vecina
if (room_data->right_room != "0" && !room_data->right_room.empty() && room_positions_.contains(room_data->right_room)) {
int x1 = px + CELL_W;
int y_mid = py + CELL_H / 2;
SDL_FRect line = {.x = static_cast<float>(x1), .y = static_cast<float>(y_mid),
.w = static_cast<float>(GAP), .h = 1.0F};
map_surface_->fillRect(&line, COLOR_CONNECTION);
}
// Conexión abajo: línea vertical desde el borde inferior de esta room hasta el borde superior de la vecina
if (room_data->lower_room != "0" && !room_data->lower_room.empty() && room_positions_.contains(room_data->lower_room)) {
int x_mid = px + CELL_W / 2;
int y1 = py + CELL_H;
SDL_FRect line = {.x = static_cast<float>(x_mid), .y = static_cast<float>(y1),
.w = 1.0F, .h = static_cast<float>(GAP)};
map_surface_->fillRect(&line, COLOR_CONNECTION);
}
}
}
// Renderiza el minimapa centrado en la room actual
void MiniMap::render(const std::string& current_room) {
if (!map_surface_) { return; }
auto game_surface = Screen::get()->getRendererSurface();
if (!game_surface) { return; }
// Fondo negro en el play area
SDL_FRect bg = {.x = 0, .y = 0, .w = static_cast<float>(PlayArea::WIDTH), .h = static_cast<float>(PlayArea::HEIGHT)};
game_surface->fillRect(&bg, 0);
// Centrar el minimapa en el play area, centrado en la room actual
auto it = room_positions_.find(current_room);
if (it == room_positions_.end()) { return; }
const auto& current_pos = it->second.pos;
int room_cx = cellPixelX(current_pos.x) + CELL_W / 2;
int room_cy = cellPixelY(current_pos.y) + CELL_H / 2;
int offset_x = PlayArea::WIDTH / 2 - room_cx;
int offset_y = PlayArea::HEIGHT / 2 - room_cy;
// Renderizar la surface del minimapa con offset
SDL_FRect dst = {.x = static_cast<float>(offset_x), .y = static_cast<float>(offset_y),
.w = static_cast<float>(map_width_ + SHADOW_OFFSET), .h = static_cast<float>(map_height_ + SHADOW_OFFSET)};
map_surface_->render(nullptr, &dst);
// Highlight de la room actual (borde bright_white alrededor de la celda)
float cur_x = static_cast<float>(offset_x + cellPixelX(current_pos.x));
float cur_y = static_cast<float>(offset_y + cellPixelY(current_pos.y));
SDL_FRect highlight = {.x = cur_x - 1, .y = cur_y - 1,
.w = static_cast<float>(CELL_W + 2), .h = static_cast<float>(CELL_H + 2)};
game_surface->drawRectBorder(&highlight, stringToColor("bright_white"));
}
#endif // _DEBUG

View File

@@ -0,0 +1,85 @@
#pragma once
#ifdef _DEBUG
#include <SDL3/SDL.h>
#include <memory> // Para shared_ptr
#include <string> // Para string
#include <unordered_map> // Para unordered_map
#include <vector> // Para vector
class Surface;
/**
* @brief Minimapa global del juego para el editor
*
* Genera una vista en miniatura de todas las habitaciones del juego,
* posicionadas según sus conexiones.
* Cada tile del mapa se representa como 1 pixel del color predominante de ese tile.
* Resultado: cada room = 32x16 pixels.
*/
class MiniMap {
public:
MiniMap();
~MiniMap() = default;
void render(const std::string& current_room); // Dibuja el minimapa centrado en la room actual
[[nodiscard]] auto isReady() const -> bool { return !room_positions_.empty(); }
private:
// Posición de una room en el grid del minimapa
struct GridPos {
int x{0};
int y{0};
};
// Una room renderizada
struct RoomMini {
std::shared_ptr<Surface> surface; // 32x16 pixels
GridPos pos; // Posición en el grid
};
void buildTileColorTable(const std::string& tileset_name);
void buildRoomSurfaces();
void layoutRooms();
void composeFinalSurface();
auto getRoomMiniSurface(const std::string& room_name) -> std::shared_ptr<Surface>;
void drawConnections();
auto cellPixelX(int grid_x) const -> int { return PADDING + (grid_x - min_grid_x_) * (CELL_W + GAP); }
auto cellPixelY(int grid_y) const -> int { return PADDING + (grid_y - min_grid_y_) * (CELL_H + GAP); }
// Tabla de color predominante por tile index
std::vector<Uint8> tile_colors_; // tile_index → palette color index
int tileset_width_{0}; // Ancho del tileset en tiles
// Rooms renderizadas y posicionadas
std::unordered_map<std::string, RoomMini> room_positions_;
// Surface final compuesta
std::shared_ptr<Surface> map_surface_;
int map_width_{0}; // Ancho en pixels
int map_height_{0}; // Alto en pixels
// Offset para normalizar coordenadas
int min_grid_x_{0};
int min_grid_y_{0};
// Constantes
static constexpr int ROOM_W = 32; // Ancho de una room en pixels del minimapa
static constexpr int ROOM_H = 16; // Alto de una room en pixels del minimapa
static constexpr int BORDER = 1; // Borde alrededor de cada room
static constexpr int CELL_W = ROOM_W + BORDER * 2; // Room + borde
static constexpr int CELL_H = ROOM_H + BORDER * 2;
static constexpr int GAP = 4; // Separación entre celdas
static constexpr int SHADOW_OFFSET = 1; // Desplazamiento de la sombra
static constexpr int PADDING = 4; // Padding alrededor del minimapa
// Colores del minimapa (índices de paleta)
static constexpr Uint8 COLOR_BACKGROUND = 2; // Fondo general (blue, si border es 0 usamos 2)
static constexpr Uint8 COLOR_ROOM_BORDER = 0; // Borde de cada miniroom
static constexpr Uint8 COLOR_SHADOW = 1; // Sombra de cada miniroom
static constexpr Uint8 COLOR_CONNECTION = 14; // Líneas de conexión entre rooms
};
#endif // _DEBUG

View 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

View 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

View File

@@ -0,0 +1,245 @@
#ifdef _DEBUG
#include "game/editor/tile_picker.hpp"
#include <algorithm> // Para std::clamp, std::min
#include "core/rendering/screen.hpp" // Para Screen
#include "core/rendering/surface.hpp" // Para Surface
#include "core/resources/resource_cache.hpp" // Para Resource::Cache
#include "utils/defines.hpp" // Para Tile::SIZE, PlayArea
#include "utils/utils.hpp" // Para stringToColor
// Margen del borde alrededor del tileset (en pixels)
static constexpr int BORDER_PAD = 3;
// Abre el picker con un tileset
void TilePicker::open(const std::string& tileset_name, int current_tile,
int bg_color, int source_color, int target_color,
int tile_spacing_in, int tile_spacing_out) {
tileset_ = Resource::Cache::get()->getSurface(tileset_name);
if (!tileset_) {
open_ = false;
return;
}
spacing_in_ = tile_spacing_in;
spacing_out_ = tile_spacing_out;
// Calcular dimensiones del tileset en tiles (teniendo en cuenta spacing de entrada)
int src_cell = Tile::SIZE + spacing_in_;
tileset_width_ = static_cast<int>(tileset_->getWidth()) / src_cell;
tileset_height_ = static_cast<int>(tileset_->getHeight()) / src_cell;
// Corregir si el último tile cabe sin spacing
if (tileset_width_ == 0 && tileset_->getWidth() >= Tile::SIZE) { tileset_width_ = 1; }
if (tileset_height_ == 0 && tileset_->getHeight() >= Tile::SIZE) { tileset_height_ = 1; }
current_tile_ = current_tile;
hover_tile_ = -1;
scroll_y_ = 0;
// Dimensiones de salida (con spacing visual entre tiles)
int out_cell = Tile::SIZE + spacing_out_;
int display_w = tileset_width_ * out_cell - spacing_out_; // Sin trailing
int display_h = tileset_height_ * out_cell - spacing_out_;
// Frame: display + borde
int frame_w = display_w + BORDER_PAD * 2;
int frame_h = display_h + BORDER_PAD * 2;
frame_surface_ = std::make_shared<Surface>(frame_w, frame_h);
// Componer: fondo + borde + tiles uno a uno
{
auto prev = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(frame_surface_);
Uint8 fill_color = (bg_color >= 0) ? static_cast<Uint8>(bg_color) : stringToColor("black");
frame_surface_->clear(fill_color);
// Borde doble
SDL_FRect outer = {.x = 0, .y = 0, .w = static_cast<float>(frame_w), .h = static_cast<float>(frame_h)};
frame_surface_->drawRectBorder(&outer, stringToColor("bright_white"));
SDL_FRect inner = {.x = 1, .y = 1, .w = static_cast<float>(frame_w - 2), .h = static_cast<float>(frame_h - 2)};
frame_surface_->drawRectBorder(&inner, stringToColor("white"));
// Renderizar cada tile individualmente
constexpr float TS = static_cast<float>(Tile::SIZE);
for (int row = 0; row < tileset_height_; ++row) {
for (int col = 0; col < tileset_width_; ++col) {
// Fuente: posición en el tileset original
SDL_FRect src = {
.x = static_cast<float>(col * src_cell),
.y = static_cast<float>(row * src_cell),
.w = TS, .h = TS};
// Destino: posición en el frame con spacing de salida
int dst_x = BORDER_PAD + col * out_cell;
int dst_y = BORDER_PAD + row * out_cell;
if (source_color >= 0 && target_color >= 0) {
tileset_->renderWithColorReplace(dst_x, dst_y,
static_cast<Uint8>(source_color),
static_cast<Uint8>(target_color), &src);
} else {
SDL_FRect dst = {.x = static_cast<float>(dst_x), .y = static_cast<float>(dst_y), .w = TS, .h = TS};
tileset_->render(&src, &dst);
}
}
}
Screen::get()->setRendererSurface(prev);
}
// Centrar en el play area
offset_x_ = (PlayArea::WIDTH - frame_w) / 2;
int offset_y = (PlayArea::HEIGHT - frame_h) / 2;
if (offset_y < 0) { offset_y = 0; }
visible_height_ = PlayArea::HEIGHT;
frame_dst_ = {.x = static_cast<float>(offset_x_), .y = static_cast<float>(offset_y),
.w = static_cast<float>(frame_w), .h = static_cast<float>(frame_h)};
// Si el frame es más alto que el play area, necesitará scroll
if (frame_h > visible_height_) {
frame_dst_.y = 0;
if (current_tile_ >= 0) {
int tile_row = current_tile_ / tileset_width_;
int tile_y_px = tile_row * out_cell;
if (tile_y_px > visible_height_ / 2) {
scroll_y_ = tile_y_px - visible_height_ / 2;
}
}
}
open_ = true;
updateMousePosition();
}
// Cierra el picker
void TilePicker::close() {
open_ = false;
tileset_.reset();
frame_surface_.reset();
}
// Renderiza el picker
void TilePicker::render() {
if (!open_ || !frame_surface_) { return; }
auto game_surface = Screen::get()->getRendererSurface();
if (!game_surface) { return; }
int frame_h = static_cast<int>(frame_dst_.h);
if (frame_h <= visible_height_) {
frame_surface_->render(nullptr, &frame_dst_);
} else {
int max_scroll = frame_h - visible_height_;
scroll_y_ = std::clamp(scroll_y_, 0, max_scroll);
SDL_FRect src = {.x = 0, .y = static_cast<float>(scroll_y_),
.w = frame_dst_.w, .h = static_cast<float>(visible_height_)};
SDL_FRect dst = {.x = frame_dst_.x, .y = 0, .w = frame_dst_.w, .h = static_cast<float>(visible_height_)};
frame_surface_->render(&src, &dst);
}
// Highlights (en game_surface, encima del frame)
int out_cell = Tile::SIZE + spacing_out_;
float tileset_screen_x = frame_dst_.x + BORDER_PAD;
float tileset_screen_y = frame_dst_.y + BORDER_PAD - static_cast<float>(scroll_y_);
constexpr float TS = static_cast<float>(Tile::SIZE);
// Highlight del tile bajo el cursor (blanco)
if (hover_tile_ >= 0) {
int col = hover_tile_ % tileset_width_;
int row = hover_tile_ / tileset_width_;
float hx = tileset_screen_x + static_cast<float>(col * out_cell);
float hy = tileset_screen_y + static_cast<float>(row * out_cell);
if (hy >= 0 && hy + TS <= visible_height_) {
SDL_FRect highlight = {.x = hx, .y = hy, .w = TS, .h = TS};
game_surface->drawRectBorder(&highlight, stringToColor("bright_white"));
}
}
// Highlight del tile actual (verde)
if (current_tile_ >= 0) {
int col = current_tile_ % tileset_width_;
int row = current_tile_ / tileset_width_;
float cx = tileset_screen_x + static_cast<float>(col * out_cell);
float cy = tileset_screen_y + static_cast<float>(row * out_cell);
if (cy >= 0 && cy + TS <= visible_height_) {
SDL_FRect cur_rect = {.x = cx, .y = cy, .w = TS, .h = TS};
game_surface->drawRectBorder(&cur_rect, stringToColor("bright_green"));
}
}
}
// Maneja eventos del picker
void TilePicker::handleEvent(const SDL_Event& event) {
if (!open_) { return; }
if (event.type == SDL_EVENT_MOUSE_MOTION) {
updateMousePosition();
}
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
if (event.button.button == SDL_BUTTON_LEFT && hover_tile_ >= 0) {
if (on_select) { on_select(hover_tile_); }
close();
} else if (event.button.button == SDL_BUTTON_RIGHT) {
close();
}
}
if (event.type == SDL_EVENT_MOUSE_WHEEL) {
scroll_y_ -= static_cast<int>(event.wheel.y) * Tile::SIZE * 2;
int max_scroll = static_cast<int>(frame_dst_.h) - visible_height_;
if (max_scroll < 0) { max_scroll = 0; }
scroll_y_ = std::clamp(scroll_y_, 0, max_scroll);
updateMousePosition();
}
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE) {
close();
}
}
// Calcula qué tile está bajo el cursor
void TilePicker::updateMousePosition() {
float mouse_x = 0.0F;
float mouse_y = 0.0F;
SDL_GetMouseState(&mouse_x, &mouse_y);
float render_x = 0.0F;
float render_y = 0.0F;
SDL_RenderCoordinatesFromWindow(Screen::get()->getRenderer(), mouse_x, mouse_y, &render_x, &render_y);
SDL_FRect dst_rect = Screen::get()->getGameSurfaceDstRect();
float game_x = render_x - dst_rect.x;
float game_y = render_y - dst_rect.y;
// Coordenada relativa al contenido del frame (con scroll)
float rel_x = game_x - frame_dst_.x - BORDER_PAD;
float rel_y = game_y - frame_dst_.y - BORDER_PAD + static_cast<float>(scroll_y_);
// Convertir a tile teniendo en cuenta el spacing de salida
int out_cell = Tile::SIZE + spacing_out_;
int tile_x = static_cast<int>(rel_x) / out_cell;
int tile_y = static_cast<int>(rel_y) / out_cell;
// Verificar que estamos sobre un tile y no sobre el spacing
int local_x = static_cast<int>(rel_x) % out_cell;
int local_y = static_cast<int>(rel_y) % out_cell;
bool on_tile = (local_x < Tile::SIZE && local_y < Tile::SIZE);
if (on_tile && rel_x >= 0 && rel_y >= 0 &&
tile_x >= 0 && tile_x < tileset_width_ &&
tile_y >= 0 && tile_y < tileset_height_) {
hover_tile_ = tile_y * tileset_width_ + tile_x;
} else {
hover_tile_ = -1;
}
}
#endif // _DEBUG

View File

@@ -0,0 +1,66 @@
#pragma once
#ifdef _DEBUG
#include <SDL3/SDL.h>
#include <functional> // Para function
#include <memory> // Para shared_ptr
#include <string> // Para string
class Surface;
/**
* @brief Selector visual de tiles de un tileset
*
* Muestra el tileset centrado en el play area.
* Hover ilumina el tile bajo el cursor.
* Click selecciona el tile y cierra el picker.
* Mouse wheel para scroll si el tileset es más alto que el play area.
* ESC o click derecho para cancelar.
*/
class TilePicker {
public:
TilePicker() = default;
~TilePicker() = default;
// Abre el picker con un tileset
// bg_color: color de fondo del panel (-1 = negro)
// source_color/target_color: sustitución de color (-1 = sin sustitución)
// tile_spacing_in: pixels de separación entre tiles en el fichero fuente
// tile_spacing_out: pixels de separación visual entre tiles al mostrar
void open(const std::string& tileset_name, int current_tile = -1,
int bg_color = -1, int source_color = -1, int target_color = -1,
int tile_spacing_in = 0, int tile_spacing_out = 1);
void close();
[[nodiscard]] auto isOpen() const -> bool { return open_; }
void render();
void handleEvent(const SDL_Event& event);
// Callback al seleccionar un tile (índice del tile)
std::function<void(int)> on_select;
private:
void updateMousePosition();
bool open_{false};
std::shared_ptr<Surface> tileset_; // Surface del tileset original
std::shared_ptr<Surface> frame_surface_; // Surface compuesta: borde + tileset
SDL_FRect frame_dst_{}; // Posición del frame en pantalla
int tileset_width_{0}; // Ancho del tileset en tiles
int tileset_height_{0}; // Alto del tileset en tiles
int current_tile_{-1}; // Tile actualmente seleccionado (highlight)
int hover_tile_{-1}; // Tile bajo el cursor
// Spacing
int spacing_in_{0}; // Spacing en el fichero fuente
int spacing_out_{1}; // Spacing visual al mostrar
// Scroll y posicionamiento
int scroll_y_{0}; // Scroll vertical en pixels
int offset_x_{0}; // Offset X para centrar en pantalla
int visible_height_{0}; // Altura visible en pixels
};
#endif // _DEBUG

View File

@@ -48,6 +48,27 @@ void Enemy::update(float delta_time) {
collider_ = getRect();
}
#ifdef _DEBUG
// Solo actualiza la animación sin mover al enemigo
void Enemy::updateAnimation(float delta_time) {
sprite_->animate(delta_time);
}
// Resetea el enemigo a su posición inicial (para editor)
void Enemy::resetToInitialPosition(const Data& data) {
sprite_->setPosX(data.x);
sprite_->setPosY(data.y);
sprite_->setVelX(data.vx);
sprite_->setVelY(data.vy);
const int FLIP = (should_flip_ && data.vx < 0.0F) ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE;
const int MIRROR = should_mirror_ ? SDL_FLIP_VERTICAL : SDL_FLIP_NONE;
sprite_->setFlip(static_cast<SDL_FlipMode>(FLIP | MIRROR));
collider_ = getRect();
}
#endif
// Comprueba si ha llegado al limite del recorrido para darse media vuelta
void Enemy::checkPath() { // NOLINT(readability-make-member-function-const)
if (sprite_->getPosX() > x2_ || sprite_->getPosX() < x1_) {

View File

@@ -29,6 +29,10 @@ class Enemy {
void render(); // Pinta el enemigo en pantalla
void update(float delta_time); // Actualiza las variables del objeto
#ifdef _DEBUG
void updateAnimation(float delta_time); // Solo actualiza la animación sin mover al enemigo
void resetToInitialPosition(const Data& data); // Resetea el enemigo a su posición inicial (para editor)
#endif
auto getRect() -> SDL_FRect; // Devuelve el rectangulo que contiene al enemigo
auto getCollider() -> SDL_FRect&; // Obtiene el rectangulo de colision del enemigo

View File

@@ -41,6 +41,21 @@ auto Item::getPos() -> SDL_FPoint { // NOLINT(readability-convert-member-functi
return P;
}
#ifdef _DEBUG
// Establece la posición del item (para editor)
void Item::setPosition(float x, float y) {
sprite_->setPosition(x, y);
collider_ = sprite_->getRect();
}
#endif
#ifdef _DEBUG
// Cambia el tile del item (para editor)
void Item::setTile(int tile) {
sprite_->setClip((tile % 10) * ITEM_SIZE, (tile / 10) * ITEM_SIZE, ITEM_SIZE, ITEM_SIZE);
}
#endif
// Asigna los colores del objeto
void Item::setColors(Uint8 col1, Uint8 col2) {
// Reinicializa el vector de colores

View File

@@ -29,6 +29,10 @@ class Item {
auto getCollider() -> SDL_FRect& { return collider_; } // Obtiene el rectangulo de colision del objeto
auto getPos() -> SDL_FPoint; // Obtiene su ubicación
void setColors(Uint8 col1, Uint8 col2); // Asigna los colores del objeto
#ifdef _DEBUG
void setPosition(float x, float y); // Establece la posición del item (para editor)
void setTile(int tile); // Cambia el tile del item (para editor)
#endif
private:
static constexpr float ITEM_SIZE = 8.0F; // Tamaño del item en pixels

View File

@@ -23,5 +23,11 @@ namespace GameControl {
inline std::function<std::string()> set_initial_room;
// Registrada por Game::Game() — guarda la posición/flip actuales del jugador como posición de inicio en debug.yaml
inline std::function<std::string()> set_initial_pos;
// Registradas por Game::Game() — control del editor de mapas
inline std::function<void()> enter_editor;
inline std::function<void()> exit_editor;
inline std::function<std::string()> revert_editor;
inline std::function<void()> reload_current_room; // Recarga la habitación actual desde disco
inline std::function<std::string(const std::string&)> get_adjacent_room; // Obtiene la room adyacente (UP/DOWN/LEFT/RIGHT)
} // namespace GameControl
#endif

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
auto EnemyManager::checkCollision(SDL_FRect& rect) -> bool {
return std::ranges::any_of(enemies_, [&rect](const auto& enemy) {

View File

@@ -5,7 +5,7 @@
#include <memory> // Para shared_ptr
#include <vector> // Para vector
class Enemy;
#include "game/entities/enemy.hpp" // Para Enemy, Enemy::Data
/**
* @brief Gestor de enemigos de una habitación
@@ -40,6 +40,13 @@ class EnemyManager {
// Detección de colisiones
auto checkCollision(SDL_FRect& rect) -> bool; // Comprueba si hay colisión con algún enemigo
#ifdef _DEBUG
void updateAnimations(float delta_time); // Solo actualiza animaciones sin mover enemigos
void resetPositions(const std::vector<Enemy::Data>& enemy_data); // Resetea todos los enemigos a su posición inicial
[[nodiscard]] auto getCount() const -> int; // Número de enemigos
auto getEnemy(int index) -> std::shared_ptr<Enemy>&; // Acceso a un enemigo por índice
#endif
private:
std::vector<std::shared_ptr<Enemy>> enemies_; // Colección de enemigos
};

View File

@@ -6,9 +6,8 @@
#include <string> // Para string
#include <vector> // Para vector
#include "scoreboard.hpp" // Para Scoreboard::Data
class Item;
#include "game/entities/item.hpp" // Para Item, Item::Data
#include "scoreboard.hpp" // Para Scoreboard::Data
/**
* @brief Gestor de items de una habitación
@@ -47,6 +46,11 @@ class ItemManager {
// Estado
void setPaused(bool paused); // Pausa/despausa todos los items
#ifdef _DEBUG
[[nodiscard]] auto getCount() const -> int { return static_cast<int>(items_.size()); } // Número de items
auto getItem(int index) -> std::shared_ptr<Item>& { return items_.at(index); } // Acceso a un item por índice
#endif
// Detección de colisiones
/**
* @brief Comprueba si hay colisión con algún item

View File

@@ -121,6 +121,45 @@ void Room::renderItems() {
void Room::redrawMap() {
tilemap_renderer_->redrawMap(collision_map_.get());
}
// Actualiza animaciones sin mover enemigos (para editor de mapas)
void Room::updateEditorMode(float delta_time) {
tilemap_renderer_->update(delta_time);
enemy_manager_->updateAnimations(delta_time);
item_manager_->update(delta_time);
}
// Resetea enemigos a posiciones iniciales (para editor de mapas)
void Room::resetEnemyPositions(const std::vector<Enemy::Data>& enemy_data) {
enemy_manager_->resetPositions(enemy_data);
}
// Cambia un tile y repinta la celda (para editor)
void Room::setTile(int index, int tile_value) {
if (index >= 0 && index < static_cast<int>(tile_map_.size())) {
tile_map_[index] = tile_value;
tilemap_renderer_->setTile(index, tile_value);
}
}
// Cambia color de fondo y redibuja el mapa (para editor)
void Room::setBgColor(const std::string& color) {
bg_color_ = color;
tilemap_renderer_->setBgColor(color);
tilemap_renderer_->redrawMap(collision_map_.get());
}
// Cambia colores de items en vivo (para editor)
void Room::setItemColors(const std::string& color1, const std::string& color2) {
item_color1_ = color1;
item_color2_ = color2;
Uint8 c1 = stringToColor(color1);
Uint8 c2 = stringToColor(color2);
auto* item_mgr = item_manager_.get();
for (int i = 0; i < static_cast<int>(item_mgr->getCount()); ++i) {
item_mgr->getItem(i)->setColors(c1, c2);
}
}
#endif
// Actualiza las variables y objetos de la habitación

View File

@@ -69,7 +69,16 @@ class Room {
void renderEnemies(); // Dibuja los enemigos en pantalla
void renderItems(); // Dibuja los objetos en pantalla
#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)
void setBgColor(const std::string& color); // Cambia color de fondo y redibuja (para editor)
void setItemColors(const std::string& color1, const std::string& color2); // Cambia colores de items (para editor)
void setTile(int index, int tile_value); // Cambia un tile y redibuja (para editor)
[[nodiscard]] auto getTileSetFile() const -> const std::string& { return tile_set_file_; }
[[nodiscard]] auto getTileSetWidth() const -> int { return tile_set_width_; }
#endif
void update(float delta_time); // Actualiza las variables y objetos de la habitación
auto getRoom(Border border) -> std::string; // Devuelve la cadena del fichero de la habitación contigua segun el borde

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
auto RoomLoader::loadYAML(const std::string& file_path, bool verbose) -> Room::Data { // NOLINT(readability-convert-member-functions-to-static)
Room::Data room;

View File

@@ -46,6 +46,9 @@ class RoomLoader {
* - items: lista de items (opcional)
*/
static auto loadYAML(const std::string& file_path, bool verbose = false) -> Room::Data;
#ifdef _DEBUG
static auto loadFromString(const std::string& yaml_content, const std::string& file_name) -> Room::Data;
#endif
private:
/**

View File

@@ -100,6 +100,37 @@ static void renderDebugCollisionSurfaces(const CollisionMap* collision_map) {
void TilemapRenderer::redrawMap(const CollisionMap* collision_map) {
fillMapTexture(collision_map);
}
// Cambia un tile y repinta solo esa celda en la map_surface
void TilemapRenderer::setTile(int index, int tile_value) {
if (index < 0 || index >= static_cast<int>(tile_map_.size())) { return; }
tile_map_[index] = tile_value;
int col = index % MAP_WIDTH;
int row = index / MAP_WIDTH;
auto previous_renderer = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(map_surface_);
// Borrar la celda con el color de fondo
SDL_FRect cell = {.x = static_cast<float>(col * TILE_SIZE), .y = static_cast<float>(row * TILE_SIZE),
.w = static_cast<float>(TILE_SIZE), .h = static_cast<float>(TILE_SIZE)};
map_surface_->fillRect(&cell, stringToColor(bg_color_));
// Dibujar el nuevo tile (si no es vacío ni animado)
if (tile_value > -1) {
const bool IS_ANIMATED = (tile_value >= 18 * tile_set_width_) && (tile_value < 19 * tile_set_width_);
if (!IS_ANIMATED) {
SDL_FRect clip = {.x = static_cast<float>((tile_value % tile_set_width_) * TILE_SIZE),
.y = static_cast<float>((tile_value / tile_set_width_) * TILE_SIZE),
.w = static_cast<float>(TILE_SIZE), .h = static_cast<float>(TILE_SIZE)};
tileset_surface_->render(col * TILE_SIZE, row * TILE_SIZE, &clip);
}
}
Screen::get()->setRendererSurface(previous_renderer);
}
#endif
// Pinta el mapa estático y debug lines

View File

@@ -69,6 +69,8 @@ class TilemapRenderer {
* Llamado cuando se activa/desactiva el modo debug para actualizar la visualización
*/
void redrawMap(const CollisionMap* collision_map);
void setBgColor(const std::string& color) { bg_color_ = color; }
void setTile(int index, int tile_value); // Cambia un tile y repinta esa celda
#endif
/**

View File

@@ -32,7 +32,8 @@
#include "utils/utils.hpp" // Para PaletteColor, stringToColor
#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
// Constructor
@@ -119,6 +120,26 @@ Game::Game(Mode mode)
Debug::get()->saveToFile();
return "Pos:" + std::to_string(tile_x) + "," + std::to_string(tile_y);
};
GameControl::enter_editor = [this]() -> void {
MapEditor::get()->enter(room_, player_, current_room_, scoreboard_data_);
};
GameControl::exit_editor = [this]() -> void {
MapEditor::get()->exit();
// Recargar la habitación desde disco (con los cambios del editor)
Resource::Cache::get()->reloadRoom(current_room_);
changeRoom(current_room_);
player_->setRoom(room_);
};
GameControl::revert_editor = []() -> std::string {
return MapEditor::get()->revert();
};
GameControl::get_adjacent_room = [this](const std::string& direction) -> std::string {
if (direction == "UP") { return room_->getRoom(Room::Border::TOP); }
if (direction == "DOWN") { return room_->getRoom(Room::Border::BOTTOM); }
if (direction == "LEFT") { return room_->getRoom(Room::Border::LEFT); }
if (direction == "RIGHT") { return room_->getRoom(Room::Border::RIGHT); }
return "0";
};
#endif
SceneManager::current = (mode_ == Mode::GAME) ? SceneManager::Scene::GAME : SceneManager::Scene::DEMO;
@@ -139,6 +160,12 @@ Game::~Game() {
GameControl::toggle_debug_mode = nullptr;
GameControl::set_initial_room = nullptr;
GameControl::set_initial_pos = nullptr;
if (MapEditor::get()->isActive()) { MapEditor::get()->exit(); }
GameControl::enter_editor = nullptr;
GameControl::exit_editor = nullptr;
GameControl::revert_editor = nullptr;
GameControl::reload_current_room = nullptr;
GameControl::get_adjacent_room = nullptr;
#endif
}
@@ -148,8 +175,29 @@ void Game::handleEvents() {
while (SDL_PollEvent(&event)) {
GlobalEvents::handle(event);
#ifdef _DEBUG
// En modo editor: click del ratón cierra la consola
if (Console::get()->isActive() && MapEditor::get()->isActive() &&
event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
Console::get()->toggle();
}
if (!Console::get()->isActive()) {
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 (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_8 && static_cast<int>(event.key.repeat) == 0 && MapEditor::get()->isActive()) {
MapEditor::get()->showGrid(!MapEditor::get()->isGridEnabled());
} else if (MapEditor::get()->isActive()) {
MapEditor::get()->handleEvent(event);
} else {
handleDebugEvents(event);
}
}
#endif
}
@@ -172,6 +220,14 @@ void Game::handleInput() {
return;
}
#ifdef _DEBUG
// Si el editor de mapas está activo, no procesar inputs del juego
if (MapEditor::get()->isActive()) {
GlobalInputs::handle();
return;
}
#endif
// Durante fade/postfade, solo procesar inputs globales
if (state_ != State::PLAYING) {
GlobalInputs::handle();
@@ -240,6 +296,14 @@ void Game::update() {
// Actualiza el juego en estado PLAYING
void Game::updatePlaying(float delta_time) {
#ifdef _DEBUG
// Si el editor de mapas está activo, delegar en él y no ejecutar gameplay
if (MapEditor::get()->isActive()) {
MapEditor::get()->update(delta_time);
return;
}
#endif
// Actualiza los objetos
room_->update(delta_time);
switch (mode_) {
@@ -379,8 +443,19 @@ void Game::renderPlaying() {
// Prepara para dibujar el frame
Screen::get()->start();
// Dibuja los elementos del juego en orden
// Dibuja el mapa de tiles (siempre)
room_->renderMap();
#ifdef _DEBUG
// Si el editor está activo, delegar el renderizado de entidades y statusbar
if (MapEditor::get()->isActive()) {
MapEditor::get()->render();
Screen::get()->render();
return;
}
#endif
// Dibuja los elementos del juego en orden
room_->renderEnemies();
room_->renderItems();
if (mode_ == Mode::GAME) {

View File

@@ -157,7 +157,7 @@ void Console::redrawText() {
// Línea de input (siempre la última)
const bool SHOW_CURSOR = cursor_visible_ && (static_cast<int>(input_line_.size()) < MAX_LINE_CHARS);
const std::string INPUT_STR = "> " + input_line_ + (SHOW_CURSOR ? "_" : "");
const std::string INPUT_STR = prompt_ + input_line_ + (SHOW_CURSOR ? "_" : "");
text_->writeColored(PADDING_IN_H, y_pos, INPUT_STR, BORDER_COLOR);
Screen::get()->setRendererSurface(previous_renderer);

View File

@@ -32,6 +32,9 @@ class Console {
auto getVisibleHeight() -> int; // Píxeles visibles actuales (0 = oculta, height_ = totalmente visible)
[[nodiscard]] auto getText() const -> std::shared_ptr<Text> { return text_; }
// Prompt configurable (por defecto "> ")
void setPrompt(const std::string& prompt) { prompt_ = prompt; }
// Callback llamado al abrir (true) o cerrar (false) la consola
std::function<void(bool)> on_toggle;
@@ -84,6 +87,7 @@ class Console {
// Estado de la entrada de texto
std::vector<std::string> msg_lines_; // Líneas de mensaje (1 o más)
std::string input_line_;
std::string prompt_{"> "}; // Prompt configurable
float cursor_timer_{0.0F};
bool cursor_visible_{true};

View File

@@ -14,6 +14,7 @@
#include "core/rendering/render_info.hpp" // Para RenderInfo
#include "core/rendering/screen.hpp" // Para Screen
#include "core/resources/resource_helper.hpp" // Para Resource::Helper
#include "core/resources/resource_list.hpp" // Para Resource::List
#include "external/fkyaml_node.hpp" // Para fkyaml::node
#include "game/game_control.hpp" // Para GameControl
#include "game/options.hpp" // Para Options
@@ -22,7 +23,8 @@
#include "utils/utils.hpp" // Para toUpper, prettyName
#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
// ── Helpers ──────────────────────────────────────────────────────────────────
@@ -560,9 +562,48 @@ static auto cmd_debug(const std::vector<std::string>& args) -> std::string {
}
// 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 {
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;
if (args[0] == "NEXT" || args[0] == "PREV") {
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 {
try {
num = std::stoi(args[0]);
} catch (...) { return "usage: room <1-60>|next|prev"; }
} catch (...) { return "usage: room <1-60>|next|prev|left|right|up|down"; }
}
if (num < 1 || num > 60) { return "Room must be between 1 and 60"; }
char buf[16];
std::snprintf(buf, sizeof(buf), "%02d.yaml", num);
if (GameControl::change_room && GameControl::change_room(buf)) {
return std::string("Room: ") + buf;
}
return std::string("Room not found: ") + buf;
return changeRoomWithEditor(buf);
}
// ITEMS <0-200>
@@ -629,6 +667,108 @@ static auto cmd_scene(const std::vector<std::string>& args) -> std::string {
if (args[0] == "ENDING2") { return GO_TO(SceneManager::Scene::ENDING2, "Ending 2"); }
return "Unknown scene: " + args[0];
}
// EDIT [ON|OFF|REVERT]
static auto 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";
}
// EDIT SHOW/HIDE INFO/GRID
if ((args[0] == "SHOW" || args[0] == "HIDE") && args.size() >= 2) {
if (!MapEditor::get() || !MapEditor::get()->isActive()) { return "Editor not active"; }
bool show = (args[0] == "SHOW");
if (args[1] == "INFO") { return MapEditor::get()->showInfo(show); }
if (args[1] == "GRID") { return MapEditor::get()->showGrid(show); }
}
return "usage: edit [on|off|revert|show|hide] [info|grid]";
}
// SET <property> <value> — modifica propiedad del enemigo seleccionado o de la habitación
static auto cmd_set(const std::vector<std::string>& args) -> std::string {
if (!MapEditor::get() || !MapEditor::get()->isActive()) { return "Editor not active"; }
if (args.empty()) { return "usage: set <property> <value>"; }
// SET TILE no necesita argumento (abre el tile picker visual)
if (args[0] == "TILE" && MapEditor::get()->hasSelectedItem()) {
return MapEditor::get()->setItemProperty("TILE", "");
}
if (args.size() < 2) { return "usage: set <property> <value>"; }
// Si hay enemigo seleccionado, aplicar a enemigo
if (MapEditor::get()->hasSelectedEnemy()) {
return MapEditor::get()->setEnemyProperty(args[0], args[1]);
}
// Si hay item seleccionado, aplicar a item
if (MapEditor::get()->hasSelectedItem()) {
return MapEditor::get()->setItemProperty(args[0], args[1]);
}
// Si no, aplicar a la habitación
return MapEditor::get()->setRoomProperty(args[0], args[1]);
}
// ENEMY [ADD|DELETE|DUPLICATE]
static auto 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>";
}
// ITEM [ADD|DELETE|DUPLICATE]
static auto cmd_item(const std::vector<std::string>& args) -> std::string {
if (!MapEditor::get() || !MapEditor::get()->isActive()) { return "Editor not active"; }
if (args.empty()) { return "usage: item <add|delete|duplicate>"; }
if (args[0] == "ADD") { return MapEditor::get()->addItem(); }
if (args[0] == "DELETE") {
if (!MapEditor::get()->hasSelectedItem()) { return "No item selected"; }
return MapEditor::get()->deleteItem();
}
if (args[0] == "DUPLICATE") {
if (!MapEditor::get()->hasSelectedItem()) { return "No item selected"; }
return MapEditor::get()->duplicateItem();
}
return "usage: item <add|delete|duplicate>";
}
#endif
// SHOW [INFO|NOTIFICATION|CHEEVO]
@@ -829,6 +969,10 @@ void CommandRegistry::registerHandlers() {
handlers_["cmd_items"] = cmd_items;
handlers_["cmd_room"] = cmd_room;
handlers_["cmd_scene"] = cmd_scene;
handlers_["cmd_edit"] = cmd_edit;
handlers_["cmd_set"] = cmd_set;
handlers_["cmd_enemy"] = cmd_enemy;
handlers_["cmd_item"] = cmd_item;
#endif
// HELP se registra en load() como lambda que captura this
@@ -855,6 +999,46 @@ void CommandRegistry::registerHandlers() {
}
return result;
};
#ifdef _DEBUG
// Colores de la paleta (compartido por SET COLOR, BGCOLOR, BORDER, ITEMCOLOR1, ITEMCOLOR2)
auto color_provider = []() -> std::vector<std::string> {
return {"BLACK", "BRIGHT_BLACK", "BLUE", "BRIGHT_BLUE", "RED", "BRIGHT_RED", "MAGENTA", "BRIGHT_MAGENTA", "GREEN", "BRIGHT_GREEN", "CYAN", "BRIGHT_CYAN", "YELLOW", "BRIGHT_YELLOW", "WHITE", "BRIGHT_WHITE"};
};
dynamic_providers_["SET COLOR"] = color_provider;
dynamic_providers_["SET BGCOLOR"] = color_provider;
dynamic_providers_["SET BORDER"] = color_provider;
dynamic_providers_["SET ITEMCOLOR1"] = color_provider;
dynamic_providers_["SET ITEMCOLOR2"] = color_provider;
// SET ANIMATION: animaciones de enemigos (nombres sin extensión, UPPERCASE)
dynamic_providers_["SET ANIMATION"] = []() -> std::vector<std::string> {
std::vector<std::string> result;
auto list = Resource::List::get()->getListByType(Resource::List::Type::ANIMATION);
for (const auto& path : list) {
if (path.find("enemies") == std::string::npos) { continue; }
std::string name = getFileName(path);
auto dot = name.rfind('.');
if (dot != std::string::npos) { name = name.substr(0, dot); }
result.push_back(toUpper(name));
}
return result;
};
// SET TILESET: tilesets disponibles (nombres sin extensión, UPPERCASE)
dynamic_providers_["SET TILESET"] = []() -> std::vector<std::string> {
std::vector<std::string> result;
auto list = Resource::List::get()->getListByType(Resource::List::Type::BITMAP);
for (const auto& path : list) {
if (path.find("tilesets") == std::string::npos) { continue; }
std::string name = getFileName(path);
auto dot = name.rfind('.');
if (dot != std::string::npos) { name = name.substr(0, dot); }
result.push_back(toUpper(name));
}
return result;
};
#endif
}
void CommandRegistry::load(const std::string& yaml_path) {

View File

@@ -6,7 +6,7 @@
namespace Texts {
constexpr const char* WINDOW_CAPTION = "© 2022 JailDoctor's Dilemma — JailDesigner";
constexpr const char* COPYRIGHT = "@2022 JailDesigner";
constexpr const char* VERSION = "1.12"; // Versión por defecto
constexpr const char* VERSION = "1.13"; // Versión por defecto
} // namespace Texts
// Tamaño de bloque