afegit RoomFormat per a centralitzar la creació i edició de fitxers d'habitacions

This commit is contained in:
2026-04-10 17:58:25 +02:00
parent faae650a53
commit 077b86ea4a
19 changed files with 924 additions and 1090 deletions

View File

@@ -16,7 +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/gameplay/room_format.hpp" // Para RoomFormat::loadFromString
#include "game/options.hpp" // Para Options, OptionsGame, options
#include "utils/defines.hpp" // Para WINDOW_CAPTION
#include "utils/utils.hpp" // Para getFileName, printWithDots, PaletteColor
@@ -226,7 +226,7 @@ namespace Resource {
// 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);
*(it->room) = RoomFormat::loadFromString(content, name);
std::cout << "reloadRoom: " << name << " reloaded from filesystem\n";
}
}

View File

@@ -165,7 +165,7 @@ Director::Director() {
#endif
// ZoneManager debe inicializarse antes que Resource::Cache: el cache carga
// las rooms en eager loading, y RoomLoader necesita consultar las zonas para
// las rooms en eager loading, y RoomFormat necesita consultar las zonas para
// resolver tileSetFile/music. ZoneManager carga su yaml directamente del
// filesystem (vía Resource::Helper::loadFile) así que no depende del cache.
ZoneManager::init();

View File

@@ -11,6 +11,7 @@
#include <iostream> // Para cout
#include <set> // Para set
#include "external/fkyaml_node.hpp" // Para fkyaml::node (loadSettings)
#include "core/input/mouse.hpp" // Para Mouse
#include "core/rendering/render_info.hpp" // Para RenderInfo
#include "core/rendering/screen.hpp" // Para Screen
@@ -19,7 +20,7 @@
#include "core/resources/resource_list.hpp" // Para Resource::List
#include "core/resources/resource_types.hpp" // Para RoomResource
#include "game/editor/editor_statusbar.hpp" // Para EditorStatusBar
#include "game/editor/room_saver.hpp" // Para RoomSaver
#include "game/gameplay/room_format.hpp" // Para RoomFormat
#include "game/entities/player.hpp" // Para Player
#include "game/game_control.hpp" // Para GameControl
#include "game/gameplay/enemy_manager.hpp" // Para EnemyManager
@@ -191,12 +192,8 @@ void MapEditor::enter(std::shared_ptr<Room> room, std::shared_ptr<Player> player
room_data_ = *room_data_ptr;
}
// Obtener la ruta completa y cargar el YAML original (para edición parcial y backup)
// Obtener la ruta completa al fichero del editor (para autosave en disco)
file_path_ = Resource::List::get()->get(room_path_);
if (!file_path_.empty()) {
yaml_ = RoomSaver::loadYAML(file_path_);
yaml_backup_ = yaml_; // Copia profunda para revert
}
bool is_reenter = reenter_;
if (!reenter_) {
@@ -278,13 +275,12 @@ auto MapEditor::revert() -> std::string {
if (!active_) { return "Editor not active"; }
if (file_path_.empty()) { return "Error: No file path"; }
// Restaurar el YAML original y reescribir el fichero
yaml_ = yaml_backup_;
// Restaurar room_data_ desde el cache (que mantiene la versión original) y persistir
auto room_data_ptr = Resource::Cache::get()->getRoom(room_path_);
if (room_data_ptr) {
room_data_ = *room_data_ptr;
}
RoomSaver::saveYAML(file_path_, yaml_, room_data_);
RoomFormat::saveYAML(file_path_, room_data_);
// Rebuild all entities from room_data_
auto* enemy_mgr = room_->getEnemyManager();
@@ -334,7 +330,7 @@ void MapEditor::autosave() {
// Platforms are already synced via resetToInitialPosition during drag commit
RoomSaver::saveYAML(file_path_, yaml_, room_data_);
RoomFormat::saveYAML(file_path_, room_data_);
}
// Actualiza el editor
@@ -1573,8 +1569,7 @@ auto MapEditor::setRoomProperty(const std::string& property, const std::string&
// Guardar la otra room
std::string other_path = Resource::List::get()->get(*our_field);
if (!other_path.empty()) {
auto other_yaml = RoomSaver::loadYAML(other_path);
RoomSaver::saveYAML(other_path, other_yaml, *old_other);
RoomFormat::saveYAML(other_path, *old_other);
}
}
}
@@ -1610,8 +1605,7 @@ auto MapEditor::setRoomProperty(const std::string& property, const std::string&
}
std::string other_path = Resource::List::get()->get(connection);
if (!other_path.empty()) {
auto other_yaml = RoomSaver::loadYAML(other_path);
RoomSaver::saveYAML(other_path, other_yaml, *other);
RoomFormat::saveYAML(other_path, *other);
}
}
}
@@ -1669,19 +1663,9 @@ auto MapEditor::createNewRoom(const std::string& direction) -> std::string { //
std::string room_dir = ref_path.substr(0, ref_path.find_last_of("\\/") + 1);
std::string new_path = room_dir + new_name;
// Crear Room::Data por defecto con conexión recíproca
Room::Data new_room;
// Construir Room::Data por defecto desde la autoridad del formato
Room::Data new_room = RoomFormat::createDefault();
new_room.number = std::string(name_buf).substr(0, std::string(name_buf).find('.'));
new_room.tile_set_file = "standard.gif";
new_room.item_color1 = 11;
new_room.item_color2 = 12;
new_room.upper_room = "0";
new_room.lower_room = "0";
new_room.left_room = "0";
new_room.right_room = "0";
new_room.conveyor_belt_direction = 0;
new_room.tile_map.resize(Map::WIDTH * Map::HEIGHT, -1);
new_room.collision_tile_map.resize(Map::WIDTH * Map::HEIGHT, 0);
// Conexión recíproca: la nueva room conecta de vuelta a la actual
if (direction == "UP") {
@@ -1694,40 +1678,9 @@ auto MapEditor::createNewRoom(const std::string& direction) -> std::string { //
new_room.left_room = room_path_;
}
// Conexiones del YAML
auto conn_str = [](const std::string& c) -> std::string { return (c == "0") ? "null" : c; };
// Crear el YAML
std::ofstream file(new_path);
if (!file.is_open()) { return "Error: cannot create " + new_path; }
file << "# NO_NAME\n";
file << "room:\n";
file << " name_en: \"NO_NAME\"\n";
file << " name_ca: \"NO_NAME\"\n";
file << " tileSetFile: standard.gif\n";
file << "\n";
file << " connections:\n";
file << " up: " << conn_str(new_room.upper_room) << "\n";
file << " down: " << conn_str(new_room.lower_room) << "\n";
file << " left: " << conn_str(new_room.left_room) << "\n";
file << " right: " << conn_str(new_room.right_room) << "\n";
file << "\n";
file << " itemColor1: bright_cyan\n";
file << " itemColor2: yellow\n";
file << "\n";
file << " conveyorBelt: none\n";
file << "\n";
file << "tilemap:\n";
for (int row = 0; row < Map::HEIGHT; ++row) {
file << " - [";
for (int col = 0; col < Map::WIDTH; ++col) {
file << "-1";
if (col < Map::WIDTH - 1) { file << ", "; }
}
file << "]\n";
}
file.close();
// Persistir vía la autoridad del formato (no más std::ofstream a pelo)
auto save_result = RoomFormat::saveYAML(new_path, new_room);
if (save_result.find("Error") == 0) { return save_result; }
// Registrar en Resource::List (mapa + assets.yaml) y cache
Resource::List::get()->addAsset(new_path, Resource::List::Type::ROOM);
@@ -1803,8 +1756,7 @@ auto MapEditor::deleteRoom() -> std::string { // NOLINT(readability-function-co
// Guardar la otra room
std::string other_path = Resource::List::get()->get(neighbor);
if (!other_path.empty()) {
auto other_yaml = RoomSaver::loadYAML(other_path);
RoomSaver::saveYAML(other_path, other_yaml, *other);
RoomFormat::saveYAML(other_path, *other);
}
};

View File

@@ -7,7 +7,6 @@
#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
@@ -172,10 +171,6 @@ class MapEditor {
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_;

View File

@@ -1,213 +0,0 @@
#ifdef _DEBUG
#include "game/editor/room_saver.hpp"
#include <cmath> // Para std::round
#include <fstream> // Para ifstream, ofstream, istreambuf_iterator
#include <iostream> // Para cout, cerr
#include <sstream> // Para ostringstream
#include "utils/defines.hpp" // Para Tile::SIZE
// Carga el YAML original directamente del filesystem (no del resource pack)
auto RoomSaver::loadYAML(const std::string& file_path) -> fkyaml::node {
std::ifstream file(file_path);
if (!file.is_open()) {
std::cerr << "RoomSaver: Cannot open " << file_path << "\n";
return {};
}
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
file.close();
return fkyaml::node::deserialize(content);
}
// Convierte una room connection al formato YAML
auto RoomSaver::roomConnectionToYAML(const std::string& connection) -> std::string {
if (connection == "0" || connection.empty()) { return "null"; }
return connection;
}
// Convierte la dirección del conveyor belt a string
auto RoomSaver::conveyorBeltToString(int direction) -> std::string {
if (direction < 0) { return "left"; }
if (direction > 0) { return "right"; }
return "none";
}
// Genera el YAML completo como texto con formato compacto
auto RoomSaver::buildYAML(const fkyaml::node& original_yaml, const Room::Data& room_data) -> std::string { // NOLINT(readability-function-cognitive-complexity)
(void)original_yaml; // Ya no se usa; mantenido para compatibilidad con la firma de saveYAML
std::ostringstream out;
// --- Sección room ---
out << "room:\n";
// zone es siempre obligatoria
out << " zone: " << room_data.zone << "\n";
// bgColor ya no se escribe en el YAML
// tileSetFile solo si es override explícito del valor heredado de la zona
if (room_data.tile_set_overridden) {
out << " tileSetFile: " << room_data.tile_set_file << "\n";
}
// music solo si es override explícito del valor heredado de la zona
if (room_data.music_overridden) {
out << " music: " << room_data.music << "\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: " << static_cast<int>(room_data.item_color1) << "\n";
out << " itemColor2: " << static_cast<int>(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 (MAP_HEIGHT filas × MAP_WIDTH columnas, formato flow) ---
out << "\n";
out << "# Tilemap: " << Map::HEIGHT << " filas x " << Map::WIDTH << " columnas @ " << Tile::SIZE << "px/tile\n";
out << "tilemap:\n";
// Mapa de dibujo
out << " # Mapa de dibujo (indices de tiles, -1 = vacio)\n";
out << " draw:\n";
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";
}
// Mapa de colisiones
out << " # Mapa de colisiones (0 = vacio, 1 = solido)\n";
out << " collision:\n";
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.collision_tile_map.size())) {
out << room_data.collision_tile_map[index];
} else {
out << 0;
}
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";
if (enemy.type != "path") { out << " type: " << enemy.type << "\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.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";
}
}
// --- Plataformas ---
if (!room_data.platforms.empty()) {
out << "# Plataformas móviles en esta habitación\n";
out << "platforms:\n";
for (const auto& plat : room_data.platforms) {
out << " - animation: " << plat.animation_path << "\n";
out << " speed: " << plat.speed << "\n";
out << " loop: " << (plat.loop == LoopMode::CIRCULAR ? "circular" : "pingpong") << "\n";
if (plat.easing != "linear") { out << " easing: " << plat.easing << "\n"; }
if (plat.frame != -1) { out << " frame: " << plat.frame << "\n"; }
out << " path:\n";
for (const auto& wp : plat.path) {
int wx = static_cast<int>(std::round(wp.x / Tile::SIZE));
int wy = static_cast<int>(std::round(wp.y / Tile::SIZE));
out << " - {x: " << wx << ", y: " << wy;
if (wp.wait > 0.0F) { out << ", wait: " << wp.wait; }
out << "}\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

@@ -1,35 +0,0 @@
#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

@@ -14,7 +14,7 @@
#include "game/gameplay/key_manager.hpp" // Para KeyManager
#include "game/gameplay/key_tracker.hpp" // Para KeyTracker
#include "game/gameplay/platform_manager.hpp" // Para PlatformManager
#include "game/gameplay/room_loader.hpp" // Para RoomLoader
#include "game/gameplay/room_format.hpp" // Para RoomFormat
#include "game/gameplay/scoreboard.hpp" // Para Scoreboard::Data
#include "game/gameplay/tilemap_renderer.hpp" // Para TilemapRenderer
#include "utils/defines.hpp" // Para TILE_SIZE
@@ -309,7 +309,7 @@ auto Room::checkPlayerOnPlatform(const SDL_FRect& player_collider, float player_
return platform_manager_->checkPlayerOnPlatform(player_collider, player_vy);
}
// Carga una habitación desde un archivo YAML (delegado a RoomLoader)
// Carga una habitación desde un archivo YAML (delegado a RoomFormat)
auto Room::loadYAML(const std::string& file_path, bool verbose) -> Data { // NOLINT(readability-convert-member-functions-to-static)
return RoomLoader::loadYAML(file_path, verbose);
return RoomFormat::loadYAML(file_path, verbose);
}

View File

@@ -105,7 +105,7 @@ class Room {
[[nodiscard]] auto getCollisionTileMap() const -> const std::vector<int>&;
void updateCollisionBorders(const CollisionMap::AdjacentData& adjacent);
// Método de carga de archivos YAML (delegado a RoomLoader)
// Método de carga de archivos YAML (delegado a RoomFormat)
static auto loadYAML(const std::string& file_path, bool verbose = false) -> Data;
private:

View File

@@ -0,0 +1,761 @@
#include "game/gameplay/room_format.hpp"
#include <exception> // Para exception
#include <iostream> // Para cout, cerr
#include "core/resources/resource_helper.hpp" // Para Resource::Helper
#include "external/fkyaml_node.hpp" // Para fkyaml::node
#include "game/gameplay/zone.hpp" // Para Zone::Data
#include "game/gameplay/zone_manager.hpp" // Para ZoneManager
#include "utils/defines.hpp" // Para Tile::SIZE, Map::WIDTH/HEIGHT
#include "utils/utils.hpp" // Para safeStoi
#ifdef _DEBUG
#include <cmath> // Para std::round
#include <fstream> // Para ofstream
#include <sstream> // Para ostringstream
#endif
// ============================================================================
// Helpers no-miembro (parser)
// ============================================================================
namespace {
// Lee un nodo de color como Uint8 (acepta entero directo o string numérico)
auto readColorNode(const fkyaml::node& node) -> Uint8 {
if (node.is_integer()) { return static_cast<Uint8>(node.get_value<int>()); }
if (node.is_string()) { return static_cast<Uint8>(safeStoi(node.get_value<std::string>(), 0)); }
return 0;
}
// Lee un array 2D de enteros desde un nodo YAML
auto readTilemap2D(const fkyaml::node& node) -> std::vector<std::vector<int>> {
std::vector<std::vector<int>> tilemap_2d;
tilemap_2d.reserve(Map::HEIGHT);
for (const auto& row_node : node) {
std::vector<int> row;
row.reserve(Map::WIDTH);
for (const auto& tile_node : row_node) {
row.push_back(tile_node.get_value<int>());
}
tilemap_2d.push_back(row);
}
return tilemap_2d;
}
} // namespace
// ============================================================================
// Conversores básicos
// ============================================================================
auto RoomFormat::convertRoomConnection(const std::string& value) -> std::string {
if (value == "null" || value.empty()) {
return "0";
}
if (value.size() > 5 && value.substr(value.size() - 5) == ".yaml") {
return value;
}
return value + ".yaml";
}
auto RoomFormat::convertAutoSurface(const fkyaml::node& node) -> int {
if (node.is_integer()) {
return node.get_value<int>();
}
if (node.is_string()) {
const auto VALUE = node.get_value<std::string>();
if (VALUE == "left") { return -1; }
if (VALUE == "right") { return 1; }
}
return 0;
}
auto RoomFormat::flattenTilemap(const std::vector<std::vector<int>>& tilemap_2d) -> std::vector<int> {
std::vector<int> tilemap_flat;
tilemap_flat.reserve(Map::WIDTH * Map::HEIGHT);
for (const auto& row : tilemap_2d) {
for (int tile : row) {
tilemap_flat.push_back(tile);
}
}
return tilemap_flat;
}
// ============================================================================
// Parseo de la sección room
// ============================================================================
void RoomFormat::parseRoomConfig(const fkyaml::node& yaml, Room::Data& room, const std::string& file_name) {
if (!yaml.contains("room")) {
return;
}
const auto& room_node = yaml["room"];
// Extract room number from filename (e.g., "01.yaml" → "01")
room.number = file_name.substr(0, file_name.find_last_of('.'));
// --- Resolución de zona + overrides (tileSetFile, music) ---
std::string zone_name;
if (room_node.contains("zone")) {
zone_name = room_node["zone"].get_value<std::string>();
} else {
std::cerr << "Warning: room " << file_name << " has no 'zone' field, using default\n";
const Zone::Data* default_zone = ZoneManager::get()->getDefaultZone();
if (default_zone != nullptr) { zone_name = default_zone->name; }
}
room.zone = zone_name;
const Zone::Data* zone = ZoneManager::get()->getZone(zone_name);
if (zone == nullptr) {
std::cerr << "Warning: unknown zone '" << zone_name << "' in " << file_name << ", using default\n";
zone = ZoneManager::get()->getDefaultZone();
}
// tileSetFile: zona, override si está en el yaml
if (room_node.contains("tileSetFile")) {
room.tile_set_file = room_node["tileSetFile"].get_value<std::string>();
room.tile_set_overridden = true;
} else if (zone != nullptr) {
room.tile_set_file = zone->tile_set_file;
}
// music: zona, override si está en el yaml
if (room_node.contains("music")) {
room.music = room_node["music"].get_value<std::string>();
room.music_overridden = true;
} else if (zone != nullptr) {
room.music = zone->music;
}
// Room connections
if (room_node.contains("connections")) {
parseRoomConnections(room_node["connections"], room);
}
// Item colors
if (room_node.contains("itemColor1")) {
room.item_color1 = readColorNode(room_node["itemColor1"]);
}
if (room_node.contains("itemColor2")) {
room.item_color2 = readColorNode(room_node["itemColor2"]);
}
// Dirección de la cinta transportadora (left/none/right)
room.conveyor_belt_direction = room_node.contains("conveyorBelt")
? convertAutoSurface(room_node["conveyorBelt"])
: 0;
}
void RoomFormat::parseRoomConnections(const fkyaml::node& conn_node, Room::Data& room) {
room.upper_room = conn_node.contains("up")
? convertRoomConnection(conn_node["up"].get_value_or<std::string>("null"))
: "0";
room.lower_room = conn_node.contains("down")
? convertRoomConnection(conn_node["down"].get_value_or<std::string>("null"))
: "0";
room.left_room = conn_node.contains("left")
? convertRoomConnection(conn_node["left"].get_value_or<std::string>("null"))
: "0";
room.right_room = conn_node.contains("right")
? convertRoomConnection(conn_node["right"].get_value_or<std::string>("null"))
: "0";
}
// ============================================================================
// Parseo del tilemap
// ============================================================================
// Solo soporta el formato actual: tilemap.draw + tilemap.collision.
// La rama de retrocompatibilidad para tilemap directo se eliminó porque dejaba
// collision_tile_map vacío y causaba un crash diferido en CollisionMap.
void RoomFormat::parseTilemap(const fkyaml::node& yaml, Room::Data& room, const std::string& file_name, bool verbose) {
if (!yaml.contains("tilemap")) {
std::cerr << "Error: No tilemap found in " << file_name << '\n';
return;
}
const auto& tilemap_node = yaml["tilemap"];
if (!tilemap_node.contains("draw") || !tilemap_node.contains("collision")) {
std::cerr << "Error: " << file_name << " has malformed tilemap (missing draw or collision)\n";
return;
}
room.tile_map = flattenTilemap(readTilemap2D(tilemap_node["draw"]));
room.collision_tile_map = flattenTilemap(readTilemap2D(tilemap_node["collision"]));
if (verbose) {
std::cout << "Loaded tilemap: " << room.tile_map.size() << " tiles + collision: "
<< room.collision_tile_map.size() << " tiles\n";
}
}
// ============================================================================
// Parseo de enemigos
// ============================================================================
void RoomFormat::parseEnemyBoundaries(const fkyaml::node& bounds_node, Enemy::Data& enemy) {
// Nuevo formato: position1 y position2
if (bounds_node.contains("position1")) {
const auto& pos1 = bounds_node["position1"];
if (pos1.contains("x")) { enemy.x1 = pos1["x"].get_value<int>() * Tile::SIZE; }
if (pos1.contains("y")) { enemy.y1 = pos1["y"].get_value<int>() * Tile::SIZE; }
}
if (bounds_node.contains("position2")) {
const auto& pos2 = bounds_node["position2"];
if (pos2.contains("x")) { enemy.x2 = pos2["x"].get_value<int>() * Tile::SIZE; }
if (pos2.contains("y")) { enemy.y2 = pos2["y"].get_value<int>() * Tile::SIZE; }
}
// Formato antiguo: x1/y1/x2/y2 (compatibilidad)
if (bounds_node.contains("x1")) { enemy.x1 = bounds_node["x1"].get_value<int>() * Tile::SIZE; }
if (bounds_node.contains("y1")) { enemy.y1 = bounds_node["y1"].get_value<int>() * Tile::SIZE; }
if (bounds_node.contains("x2")) { enemy.x2 = bounds_node["x2"].get_value<int>() * Tile::SIZE; }
if (bounds_node.contains("y2")) { enemy.y2 = bounds_node["y2"].get_value<int>() * Tile::SIZE; }
}
auto RoomFormat::parseEnemyData(const fkyaml::node& enemy_node) -> Enemy::Data {
Enemy::Data enemy;
if (enemy_node.contains("type")) {
enemy.type = enemy_node["type"].get_value<std::string>();
}
if (enemy_node.contains("animation")) {
enemy.animation_path = enemy_node["animation"].get_value<std::string>();
}
if (enemy_node.contains("position")) {
const auto& pos = enemy_node["position"];
if (pos.contains("x")) { enemy.x = pos["x"].get_value<float>() * Tile::SIZE; }
if (pos.contains("y")) { enemy.y = pos["y"].get_value<float>() * Tile::SIZE; }
}
if (enemy_node.contains("velocity")) {
const auto& vel = enemy_node["velocity"];
if (vel.contains("x")) { enemy.vx = vel["x"].get_value<float>(); }
if (vel.contains("y")) { enemy.vy = vel["y"].get_value<float>(); }
}
if (enemy_node.contains("boundaries")) {
parseEnemyBoundaries(enemy_node["boundaries"], enemy);
}
enemy.flip = enemy_node.contains("flip")
? enemy_node["flip"].get_value_or<bool>(false)
: false;
enemy.mirror = enemy_node.contains("mirror")
? enemy_node["mirror"].get_value_or<bool>(false)
: false;
enemy.frame = enemy_node.contains("frame")
? enemy_node["frame"].get_value_or<int>(-1)
: -1;
return enemy;
}
void RoomFormat::parseEnemies(const fkyaml::node& yaml, Room::Data& room, bool verbose) {
if (!yaml.contains("enemies") || yaml["enemies"].is_null()) {
return;
}
for (const auto& enemy_node : yaml["enemies"]) {
room.enemies.push_back(parseEnemyData(enemy_node));
}
if (verbose) {
std::cout << "Loaded " << room.enemies.size() << " enemies\n";
}
}
// ============================================================================
// Parseo de items
// ============================================================================
auto RoomFormat::parseItemData(const fkyaml::node& item_node, const Room::Data& room) -> Item::Data {
Item::Data item;
if (item_node.contains("tileSetFile")) {
item.tile_set_file = item_node["tileSetFile"].get_value<std::string>();
}
if (item_node.contains("tile")) {
item.tile = item_node["tile"].get_value<int>();
}
if (item_node.contains("position")) {
const auto& pos = item_node["position"];
if (pos.contains("x")) { item.x = pos["x"].get_value<float>() * Tile::SIZE; }
if (pos.contains("y")) { item.y = pos["y"].get_value<float>() * Tile::SIZE; }
}
item.counter = item_node.contains("counter")
? item_node["counter"].get_value_or<int>(0)
: 0;
item.color1 = room.item_color1;
item.color2 = room.item_color2;
return item;
}
void RoomFormat::parseItems(const fkyaml::node& yaml, Room::Data& room, bool verbose) {
if (!yaml.contains("items") || yaml["items"].is_null()) {
return;
}
for (const auto& item_node : yaml["items"]) {
room.items.push_back(parseItemData(item_node, room));
}
if (verbose) {
std::cout << "Loaded " << room.items.size() << " items\n";
}
}
// ============================================================================
// Parseo de plataformas
// ============================================================================
auto RoomFormat::parsePlatformData(const fkyaml::node& platform_node) -> MovingPlatform::Data {
MovingPlatform::Data platform;
if (platform_node.contains("animation")) {
platform.animation_path = platform_node["animation"].get_value<std::string>();
}
if (platform_node.contains("speed")) {
platform.speed = platform_node["speed"].get_value<float>();
}
if (platform_node.contains("loop")) {
auto loop_str = platform_node["loop"].get_value<std::string>();
platform.loop = (loop_str == "circular") ? LoopMode::CIRCULAR : LoopMode::PINGPONG;
}
if (platform_node.contains("easing")) {
platform.easing = platform_node["easing"].get_value<std::string>();
}
platform.frame = platform_node.contains("frame")
? platform_node["frame"].get_value_or<int>(-1)
: -1;
if (platform_node.contains("path")) {
for (const auto& wp_node : platform_node["path"]) {
Waypoint wp;
wp.x = wp_node["x"].get_value<float>() * Tile::SIZE;
wp.y = wp_node["y"].get_value<float>() * Tile::SIZE;
if (wp_node.contains("wait")) {
wp.wait = wp_node["wait"].get_value<float>();
}
platform.path.push_back(wp);
}
}
return platform;
}
void RoomFormat::parsePlatforms(const fkyaml::node& yaml, Room::Data& room, bool verbose) {
if (!yaml.contains("platforms") || yaml["platforms"].is_null()) {
return;
}
for (const auto& platform_node : yaml["platforms"]) {
room.platforms.push_back(parsePlatformData(platform_node));
}
if (verbose) {
std::cout << "Loaded " << room.platforms.size() << " platforms\n";
}
}
// ============================================================================
// Parseo de llaves
// ============================================================================
auto RoomFormat::parseKeyData(const fkyaml::node& key_node) -> Key::Data {
Key::Data key;
if (key_node.contains("animation")) {
key.animation_path = key_node["animation"].get_value<std::string>();
}
if (key_node.contains("id")) {
key.id = key_node["id"].get_value<std::string>();
}
if (key_node.contains("position")) {
const auto& pos = key_node["position"];
if (pos.contains("x")) { key.x = pos["x"].get_value<float>() * Tile::SIZE; }
if (pos.contains("y")) { key.y = pos["y"].get_value<float>() * Tile::SIZE; }
}
return key;
}
void RoomFormat::parseKeys(const fkyaml::node& yaml, Room::Data& room, bool verbose) {
if (!yaml.contains("keys") || yaml["keys"].is_null()) {
return;
}
for (const auto& key_node : yaml["keys"]) {
room.keys.push_back(parseKeyData(key_node));
}
if (verbose) {
std::cout << "Loaded " << room.keys.size() << " keys\n";
}
}
// ============================================================================
// Parseo de puertas
// ============================================================================
auto RoomFormat::parseDoorData(const fkyaml::node& door_node) -> Door::Data {
Door::Data door;
if (door_node.contains("animation")) {
door.animation_path = door_node["animation"].get_value<std::string>();
}
if (door_node.contains("id")) {
door.id = door_node["id"].get_value<std::string>();
}
if (door_node.contains("position")) {
const auto& pos = door_node["position"];
if (pos.contains("x")) { door.x = pos["x"].get_value<float>() * Tile::SIZE; }
if (pos.contains("y")) { door.y = pos["y"].get_value<float>() * Tile::SIZE; }
}
return door;
}
void RoomFormat::parseDoors(const fkyaml::node& yaml, Room::Data& room, bool verbose) {
if (!yaml.contains("doors") || yaml["doors"].is_null()) {
return;
}
for (const auto& door_node : yaml["doors"]) {
room.doors.push_back(parseDoorData(door_node));
}
if (verbose) {
std::cout << "Loaded " << room.doors.size() << " doors\n";
}
}
// ============================================================================
// Punto de entrada del parser
// ============================================================================
void RoomFormat::parseAll(const fkyaml::node& yaml, Room::Data& room, const std::string& file_name, bool verbose) {
parseRoomConfig(yaml, room, file_name);
parseTilemap(yaml, room, file_name, verbose);
parseEnemies(yaml, room, verbose);
parseItems(yaml, room, verbose);
parsePlatforms(yaml, room, verbose);
parseKeys(yaml, room, verbose);
parseDoors(yaml, room, verbose);
}
auto RoomFormat::loadYAML(const std::string& file_path, bool verbose) -> Room::Data {
Room::Data room;
const std::string FILE_NAME = file_path.substr(file_path.find_last_of("\\/") + 1);
try {
auto file_data = Resource::Helper::loadFile(file_path);
if (file_data.empty()) {
std::cerr << "Error: Unable to load file " << FILE_NAME << '\n';
return room;
}
const std::string YAML_CONTENT(file_data.begin(), file_data.end());
auto yaml = fkyaml::node::deserialize(YAML_CONTENT);
parseAll(yaml, room, FILE_NAME, verbose);
if (verbose) {
std::cout << "Room loaded successfully: " << FILE_NAME << '\n';
}
} 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;
}
#ifdef _DEBUG
auto RoomFormat::loadFromString(const std::string& yaml_content, const std::string& file_name) -> Room::Data {
Room::Data room;
try {
auto yaml = fkyaml::node::deserialize(yaml_content);
parseAll(yaml, room, file_name, 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;
}
// ============================================================================
// createDefault: construye un Room::Data válido para una room nueva
// ============================================================================
auto RoomFormat::createDefault() -> Room::Data {
Room::Data data;
// Zona por defecto (resuelve tile_set_file y music desde ZoneManager)
const Zone::Data* zone = ZoneManager::get()->getDefaultZone();
if (zone != nullptr) {
data.zone = zone->name;
data.tile_set_file = zone->tile_set_file;
data.music = zone->music;
}
data.tile_set_overridden = false;
data.music_overridden = false;
// Conexiones a cero
data.upper_room = "0";
data.lower_room = "0";
data.left_room = "0";
data.right_room = "0";
// Colores de items por defecto (números, no strings)
data.item_color1 = 11;
data.item_color2 = 12;
// Conveyor belt apagado
data.conveyor_belt_direction = 0;
// Tilemaps del tamaño correcto, vacíos
data.tile_map.resize(Map::WIDTH * Map::HEIGHT, -1);
data.collision_tile_map.resize(Map::WIDTH * Map::HEIGHT, 0);
return data;
}
// ============================================================================
// Serialización
// ============================================================================
auto RoomFormat::roomConnectionToYAML(const std::string& connection) -> std::string {
if (connection == "0" || connection.empty()) { return "null"; }
return connection;
}
auto RoomFormat::conveyorBeltToString(int direction) -> std::string {
if (direction < 0) { return "left"; }
if (direction > 0) { return "right"; }
return "none";
}
auto RoomFormat::buildContent(const Room::Data& room_data) -> std::string { // NOLINT(readability-function-cognitive-complexity)
std::ostringstream out;
// --- Sección room ---
out << "room:\n";
// zone es siempre obligatoria
out << " zone: " << room_data.zone << "\n";
// tileSetFile solo si es override explícito del valor heredado de la zona
if (room_data.tile_set_overridden) {
out << " tileSetFile: " << room_data.tile_set_file << "\n";
}
// music solo si es override explícito del valor heredado de la zona
if (room_data.music_overridden) {
out << " music: " << room_data.music << "\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: " << static_cast<int>(room_data.item_color1) << "\n";
out << " itemColor2: " << static_cast<int>(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 (MAP_HEIGHT filas × MAP_WIDTH columnas, formato flow) ---
out << "\n";
out << "# Tilemap: " << Map::HEIGHT << " filas x " << Map::WIDTH << " columnas @ " << Tile::SIZE << "px/tile\n";
out << "tilemap:\n";
// Mapa de dibujo
out << " # Mapa de dibujo (indices de tiles, -1 = vacio)\n";
out << " draw:\n";
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";
}
// Mapa de colisiones
out << " # Mapa de colisiones (0 = vacio, 1 = solido)\n";
out << " collision:\n";
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.collision_tile_map.size())) {
out << room_data.collision_tile_map[index];
} else {
out << 0;
}
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";
if (enemy.type != "path") { out << " type: " << enemy.type << "\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.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";
}
}
// --- Plataformas ---
if (!room_data.platforms.empty()) {
out << "# Plataformas móviles en esta habitación\n";
out << "platforms:\n";
for (const auto& plat : room_data.platforms) {
out << " - animation: " << plat.animation_path << "\n";
out << " speed: " << plat.speed << "\n";
out << " loop: " << (plat.loop == LoopMode::CIRCULAR ? "circular" : "pingpong") << "\n";
if (plat.easing != "linear") { out << " easing: " << plat.easing << "\n"; }
if (plat.frame != -1) { out << " frame: " << plat.frame << "\n"; }
out << " path:\n";
for (const auto& wp : plat.path) {
int wx = static_cast<int>(std::round(wp.x / Tile::SIZE));
int wy = static_cast<int>(std::round(wp.y / Tile::SIZE));
out << " - {x: " << wx << ", y: " << wy;
if (wp.wait > 0.0F) { out << ", wait: " << wp.wait; }
out << "}\n";
}
out << "\n";
}
}
// --- Llaves ---
if (!room_data.keys.empty()) {
out << "# Llaves en esta habitación\n";
out << "keys:\n";
for (const auto& key : room_data.keys) {
out << " - animation: " << key.animation_path << "\n";
out << " id: \"" << key.id << "\"\n";
int kx = static_cast<int>(std::round(key.x / Tile::SIZE));
int ky = static_cast<int>(std::round(key.y / Tile::SIZE));
out << " position: {x: " << kx << ", y: " << ky << "}\n";
out << "\n";
}
}
// --- Puertas ---
if (!room_data.doors.empty()) {
out << "# Puertas en esta habitación\n";
out << "doors:\n";
for (const auto& door : room_data.doors) {
out << " - animation: " << door.animation_path << "\n";
out << " id: \"" << door.id << "\"\n";
int dx = static_cast<int>(std::round(door.x / Tile::SIZE));
int dy = static_cast<int>(std::round(door.y / Tile::SIZE));
out << " position: {x: " << dx << ", y: " << dy << "}\n";
out << "\n";
}
}
return out.str();
}
auto RoomFormat::saveYAML(const std::string& file_path, const Room::Data& data) -> std::string {
const std::string CONTENT = buildContent(data);
std::ofstream file(file_path);
if (!file.is_open()) {
std::cerr << "RoomFormat: 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 << "RoomFormat: Saved " << FILE_NAME << "\n";
return "Saved " + FILE_NAME;
}
#endif // _DEBUG

View File

@@ -0,0 +1,95 @@
#pragma once
#include <string> // Para string
#include <vector> // Para vector
#include "external/fkyaml_node.hpp" // Para fkyaml::node
#include "game/entities/door.hpp" // Para Door::Data
#include "game/entities/enemy.hpp" // Para Enemy::Data
#include "game/entities/item.hpp" // Para Item::Data
#include "game/entities/key.hpp" // Para Key::Data
#include "game/entities/moving_platform.hpp" // Para MovingPlatform::Data
#include "game/gameplay/room.hpp" // Para Room::Data
/**
* @brief Autoridad única del formato yaml de habitaciones
*
* Esta clase es el ÚNICO sitio del código que conoce la estructura del
* fichero room.yaml. Combina parser (loadYAML) y serializador (saveYAML, en
* builds de debug) en un mismo módulo para forzar que cuando se añada un
* campo nuevo se actualicen ambos lados al mismo tiempo.
*
* Reemplaza al antiguo `RoomLoader` y `RoomSaver`. La versión nueva:
* - Elimina la rama de retrocompatibilidad para tilemaps con formato antiguo
* (un yaml sin `tilemap.draw`/`tilemap.collision` ahora da error claro,
* no un crash diferido por collision_tile_map vacío).
* - Simplifica saveYAML para no recibir el yaml original (ya no se usaba).
* - Añade createDefault() para que MapEditor::createNewRoom no tenga que
* conocer el formato.
*/
class RoomFormat {
public:
RoomFormat() = delete;
~RoomFormat() = delete;
RoomFormat(const RoomFormat&) = delete;
auto operator=(const RoomFormat&) -> RoomFormat& = delete;
RoomFormat(RoomFormat&&) = delete;
auto operator=(RoomFormat&&) -> RoomFormat& = delete;
/**
* @brief Carga un room.yaml desde disco/pack y devuelve Room::Data
*
* Usa Resource::Helper::loadFile, que soporta tanto el resource pack
* como el filesystem. Disponible en runtime (lo usa Resource::Cache).
*/
static auto loadYAML(const std::string& file_path, bool verbose = false) -> Room::Data;
#ifdef _DEBUG
/**
* @brief Carga un room desde un string yaml (usado por reloadRoom del cache)
*/
static auto loadFromString(const std::string& yaml_content, const std::string& file_name) -> Room::Data;
/**
* @brief Serializa Room::Data al disco. Solo el editor escribe rooms.
* @return mensaje de éxito o error (prefijo "Error" si falla)
*/
static auto saveYAML(const std::string& file_path, const Room::Data& data) -> std::string;
/**
* @brief Construye un Room::Data válido y completo con valores por defecto
*
* Lo usa MapEditor::createNewRoom para no tener que conocer el formato.
* El campo `number` y las conexiones recíprocas los rellena el caller.
*/
static auto createDefault() -> Room::Data;
#endif
private:
// --- Parsing helpers (siempre disponibles, los usa loadYAML) ---
static auto convertRoomConnection(const std::string& value) -> std::string;
static auto convertAutoSurface(const fkyaml::node& node) -> int;
static auto flattenTilemap(const std::vector<std::vector<int>>& tilemap_2d) -> std::vector<int>; // NOLINT(readability-avoid-const-params-in-decls)
static void parseRoomConfig(const fkyaml::node& yaml, Room::Data& room, const std::string& file_name);
static void parseRoomConnections(const fkyaml::node& conn_node, Room::Data& room);
static void parseTilemap(const fkyaml::node& yaml, Room::Data& room, const std::string& file_name, bool verbose);
static void parseEnemies(const fkyaml::node& yaml, Room::Data& room, bool verbose);
static auto parseEnemyData(const fkyaml::node& enemy_node) -> Enemy::Data;
static void parseEnemyBoundaries(const fkyaml::node& bounds_node, Enemy::Data& enemy);
static void parseItems(const fkyaml::node& yaml, Room::Data& room, bool verbose);
static auto parseItemData(const fkyaml::node& item_node, const Room::Data& room) -> Item::Data;
static void parsePlatforms(const fkyaml::node& yaml, Room::Data& room, bool verbose);
static auto parsePlatformData(const fkyaml::node& platform_node) -> MovingPlatform::Data;
static void parseKeys(const fkyaml::node& yaml, Room::Data& room, bool verbose);
static auto parseKeyData(const fkyaml::node& key_node) -> Key::Data;
static void parseDoors(const fkyaml::node& yaml, Room::Data& room, bool verbose);
static auto parseDoorData(const fkyaml::node& door_node) -> Door::Data;
static void parseAll(const fkyaml::node& yaml, Room::Data& room, const std::string& file_name, bool verbose);
#ifdef _DEBUG
// --- Serialization helpers (solo en debug, los usa saveYAML) ---
static auto buildContent(const Room::Data& room_data) -> std::string;
static auto roomConnectionToYAML(const std::string& connection) -> std::string;
static auto conveyorBeltToString(int direction) -> std::string;
#endif
};

View File

@@ -1,561 +0,0 @@
#include "room_loader.hpp"
#include <exception> // Para exception
#include <iostream> // Para cout, cerr
#include "core/resources/resource_helper.hpp" // Para Resource::Helper
#include "external/fkyaml_node.hpp" // Para fkyaml::node
#include "game/gameplay/zone.hpp" // Para Zone::Data
#include "game/gameplay/zone_manager.hpp" // Para ZoneManager
#include "utils/defines.hpp" // Para Tile::SIZE
#include "utils/utils.hpp"
// Convierte room connection de YAML a formato interno
auto RoomLoader::convertRoomConnection(const std::string& value) -> std::string { // NOLINT(readability-convert-member-functions-to-static)
if (value == "null" || value.empty()) {
return "0";
}
// Si ya tiene .yaml, devolverlo tal cual; si no, añadirlo
if (value.size() > 5 && value.substr(value.size() - 5) == ".yaml") {
return value;
}
return value + ".yaml";
}
// Lee un nodo de color como Uint8 (acepta entero directo o string numérico)
static auto readColorNode(const fkyaml::node& node) -> Uint8 {
if (node.is_integer()) { return static_cast<Uint8>(node.get_value<int>()); }
if (node.is_string()) { return static_cast<Uint8>(safeStoi(node.get_value<std::string>(), 0)); }
return 0;
}
// Convierte string de autoSurface a int
auto RoomLoader::convertAutoSurface(const fkyaml::node& node) -> int { // NOLINT(readability-convert-member-functions-to-static)
if (node.is_integer()) {
return node.get_value<int>();
}
if (node.is_string()) {
const auto VALUE = node.get_value<std::string>();
if (VALUE == "left") {
return -1;
}
if (VALUE == "right") {
return 1;
}
}
return 0; // "none" o default
}
// Convierte un tilemap 2D a vector 1D flat
auto RoomLoader::flattenTilemap(const std::vector<std::vector<int>>& tilemap_2d) -> std::vector<int> { // NOLINT(readability-convert-member-functions-to-static, readability-named-parameter)
std::vector<int> tilemap_flat;
tilemap_flat.reserve(Map::WIDTH * Map::HEIGHT);
for (const auto& row : tilemap_2d) {
for (int tile : row) {
tilemap_flat.push_back(tile);
}
}
return tilemap_flat;
}
// Parsea la configuración general de la habitación
void RoomLoader::parseRoomConfig(const fkyaml::node& yaml, Room::Data& room, const std::string& file_name) { // NOLINT(readability-convert-member-functions-to-static)
if (!yaml.contains("room")) {
return;
}
const auto& room_node = yaml["room"];
// Extract room number from filename (e.g., "01.yaml" → "01")
room.number = file_name.substr(0, file_name.find_last_of('.'));
// Basic properties
// bgColor ya no se lee del YAML; bg_color queda siempre a 0
// --- Resolución de zona + overrides (tileSetFile, music) ---
// Obtener zona declarada (o caer al default si no existe)
std::string zone_name;
if (room_node.contains("zone")) {
zone_name = room_node["zone"].get_value<std::string>();
} else {
std::cerr << "Warning: room " << file_name << " has no 'zone' field, using default\n";
const Zone::Data* default_zone = ZoneManager::get()->getDefaultZone();
if (default_zone != nullptr) { zone_name = default_zone->name; }
}
room.zone = zone_name;
// Localizar la zona en el catálogo (fallback al default si no existe)
const Zone::Data* zone = ZoneManager::get()->getZone(zone_name);
if (zone == nullptr) {
std::cerr << "Warning: unknown zone '" << zone_name << "' in " << file_name << ", using default\n";
zone = ZoneManager::get()->getDefaultZone();
}
// tileSetFile: zona, override si está en el yaml
if (room_node.contains("tileSetFile")) {
room.tile_set_file = room_node["tileSetFile"].get_value<std::string>();
room.tile_set_overridden = true;
} else if (zone != nullptr) {
room.tile_set_file = zone->tile_set_file;
}
// music: zona, override si está en el yaml
if (room_node.contains("music")) {
room.music = room_node["music"].get_value<std::string>();
room.music_overridden = true;
} else if (zone != nullptr) {
room.music = zone->music;
}
// Room connections
if (room_node.contains("connections")) {
parseRoomConnections(room_node["connections"], room);
}
// Item colors
if (room_node.contains("itemColor1")) {
room.item_color1 = readColorNode(room_node["itemColor1"]);
}
if (room_node.contains("itemColor2")) {
room.item_color2 = readColorNode(room_node["itemColor2"]);
}
// Dirección de la cinta transportadora (left/none/right)
room.conveyor_belt_direction = room_node.contains("conveyorBelt")
? convertAutoSurface(room_node["conveyorBelt"])
: 0;
}
// Parsea las conexiones de la habitación (arriba/abajo/izq/der)
void RoomLoader::parseRoomConnections(const fkyaml::node& conn_node, Room::Data& room) {
room.upper_room = conn_node.contains("up")
? convertRoomConnection(conn_node["up"].get_value_or<std::string>("null"))
: "0";
room.lower_room = conn_node.contains("down")
? convertRoomConnection(conn_node["down"].get_value_or<std::string>("null"))
: "0";
room.left_room = conn_node.contains("left")
? convertRoomConnection(conn_node["left"].get_value_or<std::string>("null"))
: "0";
room.right_room = conn_node.contains("right")
? convertRoomConnection(conn_node["right"].get_value_or<std::string>("null"))
: "0";
}
// Lee un array 2D de enteros desde un nodo YAML
static auto readTilemap2D(const fkyaml::node& node) -> std::vector<std::vector<int>> {
std::vector<std::vector<int>> tilemap_2d;
tilemap_2d.reserve(Map::HEIGHT);
for (const auto& row_node : node) {
std::vector<int> row;
row.reserve(Map::WIDTH);
for (const auto& tile_node : row_node) {
row.push_back(tile_node.get_value<int>());
}
tilemap_2d.push_back(row);
}
return tilemap_2d;
}
// Parsea el tilemap de la habitación
void RoomLoader::parseTilemap(const fkyaml::node& yaml, Room::Data& room, const std::string& file_name, bool verbose) { // NOLINT(readability-convert-member-functions-to-static)
if (!yaml.contains("tilemap")) {
std::cerr << "Warning: No tilemap found in " << file_name << '\n';
return;
}
const auto& tilemap_node = yaml["tilemap"];
// Nuevo formato: tilemap.draw + tilemap.collision
if (tilemap_node.contains("draw")) {
room.tile_map = flattenTilemap(readTilemap2D(tilemap_node["draw"]));
if (tilemap_node.contains("collision")) {
room.collision_tile_map = flattenTilemap(readTilemap2D(tilemap_node["collision"]));
}
} else {
// Formato antiguo: tilemap es directamente el array 2D de dibujo
room.tile_map = flattenTilemap(readTilemap2D(tilemap_node));
}
if (verbose) {
std::cout << "Loaded tilemap: " << room.tile_map.size() << " tiles";
if (!room.collision_tile_map.empty()) {
std::cout << " + collision: " << room.collision_tile_map.size() << " tiles";
}
std::cout << '\n';
}
}
// Parsea los límites de movimiento de un enemigo
void RoomLoader::parseEnemyBoundaries(const fkyaml::node& bounds_node, Enemy::Data& enemy) { // NOLINT(readability-convert-member-functions-to-static)
// Nuevo formato: position1 y position2
if (bounds_node.contains("position1")) {
const auto& pos1 = bounds_node["position1"];
if (pos1.contains("x")) {
enemy.x1 = pos1["x"].get_value<int>() * Tile::SIZE;
}
if (pos1.contains("y")) {
enemy.y1 = pos1["y"].get_value<int>() * Tile::SIZE;
}
}
if (bounds_node.contains("position2")) {
const auto& pos2 = bounds_node["position2"];
if (pos2.contains("x")) {
enemy.x2 = pos2["x"].get_value<int>() * Tile::SIZE;
}
if (pos2.contains("y")) {
enemy.y2 = pos2["y"].get_value<int>() * Tile::SIZE;
}
}
// Formato antiguo: x1/y1/x2/y2 (compatibilidad)
if (bounds_node.contains("x1")) {
enemy.x1 = bounds_node["x1"].get_value<int>() * Tile::SIZE;
}
if (bounds_node.contains("y1")) {
enemy.y1 = bounds_node["y1"].get_value<int>() * Tile::SIZE;
}
if (bounds_node.contains("x2")) {
enemy.x2 = bounds_node["x2"].get_value<int>() * Tile::SIZE;
}
if (bounds_node.contains("y2")) {
enemy.y2 = bounds_node["y2"].get_value<int>() * Tile::SIZE;
}
}
// Parsea los datos de un enemigo individual
auto RoomLoader::parseEnemyData(const fkyaml::node& enemy_node) -> Enemy::Data { // NOLINT(readability-convert-member-functions-to-static)
Enemy::Data enemy;
// Enemy type (default: "path")
if (enemy_node.contains("type")) {
enemy.type = enemy_node["type"].get_value<std::string>();
}
// Animation path
if (enemy_node.contains("animation")) {
enemy.animation_path = enemy_node["animation"].get_value<std::string>();
}
// Position (in tiles, convert to pixels)
if (enemy_node.contains("position")) {
const auto& pos = enemy_node["position"];
if (pos.contains("x")) {
enemy.x = pos["x"].get_value<float>() * Tile::SIZE;
}
if (pos.contains("y")) {
enemy.y = pos["y"].get_value<float>() * Tile::SIZE;
}
}
// Velocity (already in pixels/second)
if (enemy_node.contains("velocity")) {
const auto& vel = enemy_node["velocity"];
if (vel.contains("x")) {
enemy.vx = vel["x"].get_value<float>();
}
if (vel.contains("y")) {
enemy.vy = vel["y"].get_value<float>();
}
}
// Boundaries (in tiles, convert to pixels)
if (enemy_node.contains("boundaries")) {
parseEnemyBoundaries(enemy_node["boundaries"], enemy);
}
// Optional fields
enemy.flip = enemy_node.contains("flip")
? enemy_node["flip"].get_value_or<bool>(false)
: false;
enemy.mirror = enemy_node.contains("mirror")
? enemy_node["mirror"].get_value_or<bool>(false)
: false;
enemy.frame = enemy_node.contains("frame")
? enemy_node["frame"].get_value_or<int>(-1)
: -1;
return enemy;
}
// Parsea la lista de enemigos de la habitación
void RoomLoader::parseEnemies(const fkyaml::node& yaml, Room::Data& room, bool verbose) { // NOLINT(readability-convert-member-functions-to-static)
if (!yaml.contains("enemies") || yaml["enemies"].is_null()) {
return;
}
const auto& enemies_node = yaml["enemies"];
for (const auto& enemy_node : enemies_node) {
room.enemies.push_back(parseEnemyData(enemy_node));
}
if (verbose) {
std::cout << "Loaded " << room.enemies.size() << " enemies\n";
}
}
// Parsea los datos de un item individual
auto RoomLoader::parseItemData(const fkyaml::node& item_node, const Room::Data& room) -> Item::Data { // NOLINT(readability-convert-member-functions-to-static)
Item::Data item;
// Tileset file
if (item_node.contains("tileSetFile")) {
item.tile_set_file = item_node["tileSetFile"].get_value<std::string>();
}
// Tile index
if (item_node.contains("tile")) {
item.tile = item_node["tile"].get_value<int>();
}
// Position (in tiles, convert to pixels)
if (item_node.contains("position")) {
const auto& pos = item_node["position"];
if (pos.contains("x")) {
item.x = pos["x"].get_value<float>() * Tile::SIZE;
}
if (pos.contains("y")) {
item.y = pos["y"].get_value<float>() * Tile::SIZE;
}
}
// Counter
item.counter = item_node.contains("counter")
? item_node["counter"].get_value_or<int>(0)
: 0;
// Colors (assigned from room defaults)
item.color1 = room.item_color1;
item.color2 = room.item_color2;
return item;
}
// Parsea la lista de items de la habitación
void RoomLoader::parseItems(const fkyaml::node& yaml, Room::Data& room, bool verbose) { // NOLINT(readability-convert-member-functions-to-static)
if (!yaml.contains("items") || yaml["items"].is_null()) {
return;
}
const auto& items_node = yaml["items"];
for (const auto& item_node : items_node) {
room.items.push_back(parseItemData(item_node, room));
}
if (verbose) {
std::cout << "Loaded " << room.items.size() << " items\n";
}
}
// Parsea los datos de una plataforma individual
auto RoomLoader::parsePlatformData(const fkyaml::node& platform_node) -> MovingPlatform::Data {
MovingPlatform::Data platform;
if (platform_node.contains("animation")) {
platform.animation_path = platform_node["animation"].get_value<std::string>();
}
if (platform_node.contains("speed")) {
platform.speed = platform_node["speed"].get_value<float>();
}
if (platform_node.contains("loop")) {
auto loop_str = platform_node["loop"].get_value<std::string>();
platform.loop = (loop_str == "circular") ? LoopMode::CIRCULAR : LoopMode::PINGPONG;
}
if (platform_node.contains("easing")) {
platform.easing = platform_node["easing"].get_value<std::string>();
}
platform.frame = platform_node.contains("frame")
? platform_node["frame"].get_value_or<int>(-1)
: -1;
// Path: lista de waypoints en tiles → pixels
if (platform_node.contains("path")) {
for (const auto& wp_node : platform_node["path"]) {
Waypoint wp;
wp.x = wp_node["x"].get_value<float>() * Tile::SIZE;
wp.y = wp_node["y"].get_value<float>() * Tile::SIZE;
if (wp_node.contains("wait")) {
wp.wait = wp_node["wait"].get_value<float>();
}
platform.path.push_back(wp);
}
}
return platform;
}
// Parsea la lista de plataformas de la habitación
void RoomLoader::parsePlatforms(const fkyaml::node& yaml, Room::Data& room, bool verbose) {
if (!yaml.contains("platforms") || yaml["platforms"].is_null()) {
return;
}
const auto& platforms_node = yaml["platforms"];
for (const auto& platform_node : platforms_node) {
room.platforms.push_back(parsePlatformData(platform_node));
}
if (verbose) {
std::cout << "Loaded " << room.platforms.size() << " platforms\n";
}
}
// Parsea los datos de una llave individual
auto RoomLoader::parseKeyData(const fkyaml::node& key_node) -> Key::Data { // NOLINT(readability-convert-member-functions-to-static)
Key::Data key;
if (key_node.contains("animation")) {
key.animation_path = key_node["animation"].get_value<std::string>();
}
if (key_node.contains("id")) {
key.id = key_node["id"].get_value<std::string>();
}
if (key_node.contains("position")) {
const auto& pos = key_node["position"];
if (pos.contains("x")) {
key.x = pos["x"].get_value<float>() * Tile::SIZE;
}
if (pos.contains("y")) {
key.y = pos["y"].get_value<float>() * Tile::SIZE;
}
}
return key;
}
// Parsea la lista de llaves de la habitación
void RoomLoader::parseKeys(const fkyaml::node& yaml, Room::Data& room, bool verbose) { // NOLINT(readability-convert-member-functions-to-static)
if (!yaml.contains("keys") || yaml["keys"].is_null()) {
return;
}
const auto& keys_node = yaml["keys"];
for (const auto& key_node : keys_node) {
room.keys.push_back(parseKeyData(key_node));
}
if (verbose) {
std::cout << "Loaded " << room.keys.size() << " keys\n";
}
}
// Parsea los datos de una puerta individual
auto RoomLoader::parseDoorData(const fkyaml::node& door_node) -> Door::Data { // NOLINT(readability-convert-member-functions-to-static)
Door::Data door;
if (door_node.contains("animation")) {
door.animation_path = door_node["animation"].get_value<std::string>();
}
if (door_node.contains("id")) {
door.id = door_node["id"].get_value<std::string>();
}
if (door_node.contains("position")) {
const auto& pos = door_node["position"];
if (pos.contains("x")) {
door.x = pos["x"].get_value<float>() * Tile::SIZE;
}
if (pos.contains("y")) {
door.y = pos["y"].get_value<float>() * Tile::SIZE;
}
}
return door;
}
// Parsea la lista de puertas de la habitación
void RoomLoader::parseDoors(const fkyaml::node& yaml, Room::Data& room, bool verbose) { // NOLINT(readability-convert-member-functions-to-static)
if (!yaml.contains("doors") || yaml["doors"].is_null()) {
return;
}
const auto& doors_node = yaml["doors"];
for (const auto& door_node : doors_node) {
room.doors.push_back(parseDoorData(door_node));
}
if (verbose) {
std::cout << "Loaded " << room.doors.size() << " doors\n";
}
}
#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);
parsePlatforms(yaml, room, false);
parseKeys(yaml, room, false);
parseDoors(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;
// Extract filename for logging
const std::string FILE_NAME = file_path.substr(file_path.find_last_of("\\/") + 1);
try {
// Load YAML file using ResourceHelper (supports both filesystem and pack)
auto file_data = Resource::Helper::loadFile(file_path);
if (file_data.empty()) {
std::cerr << "Error: Unable to load file " << FILE_NAME << '\n';
return room;
}
// Parse YAML from string
std::string yaml_content(file_data.begin(), file_data.end());
auto yaml = fkyaml::node::deserialize(yaml_content);
// Delegación a funciones especializadas
parseRoomConfig(yaml, room, FILE_NAME);
parseTilemap(yaml, room, FILE_NAME, verbose);
parseEnemies(yaml, room, verbose);
parseItems(yaml, room, verbose);
parsePlatforms(yaml, room, verbose);
parseKeys(yaml, room, verbose);
parseDoors(yaml, room, verbose);
if (verbose) {
std::cout << "Room loaded successfully: " << FILE_NAME << '\n';
}
} 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;
}

View File

@@ -1,162 +0,0 @@
#pragma once
#include <string> // Para string
#include <vector> // Para vector
#include "external/fkyaml_node.hpp" // Para fkyaml::node
#include "game/entities/door.hpp" // Para Door::Data
#include "game/entities/enemy.hpp" // Para Enemy::Data
#include "game/entities/item.hpp" // Para Item::Data
#include "game/entities/key.hpp" // Para Key::Data
#include "game/entities/moving_platform.hpp" // Para MovingPlatform::Data
#include "game/gameplay/room.hpp" // Para Room::Data
/**
* @brief Cargador de archivos de habitaciones en formato YAML
*
* Responsabilidades:
* - Cargar archivos de room en formato YAML unificado (.yaml)
* - Parsear datos de room, tilemap, enemies e items
* - Convertir tipos de datos (tiles, posiciones, colores)
* - Validar y propagar errores de carga
*
* Esta clase contiene solo métodos estáticos y no debe instanciarse.
*/
class RoomLoader {
public:
// Constructor eliminado para prevenir instanciación
RoomLoader() = delete;
~RoomLoader() = delete;
RoomLoader(const RoomLoader&) = delete;
auto operator=(const RoomLoader&) -> RoomLoader& = delete;
RoomLoader(RoomLoader&&) = delete;
auto operator=(RoomLoader&&) -> RoomLoader& = delete;
/**
* @brief Carga un archivo de room en formato YAML
* @param file_path Ruta al archivo YAML de habitación
* @param verbose Si true, muestra información de debug
* @return Room::Data con todos los datos de la habitación incluyendo:
* - Configuración de la habitación (nombre, colores, etc.)
* - Tilemap completo (convertido de 2D a 1D)
* - Lista de enemigos con todas sus propiedades
* - Lista de items con todas sus propiedades
*
* El formato YAML esperado incluye:
* - room: configuración general
* - tilemap: array 2D de 16x32 tiles (convertido a vector 1D de 512 elementos)
* - enemies: lista de enemigos (opcional)
* - 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:
/**
* @brief Convierte room connection de YAML a formato interno
* @param value Valor del YAML (vacío, "02" o "02.yaml")
* @return "0" para sin conexión, o nombre del archivo con extensión
*/
static auto convertRoomConnection(const std::string& value) -> std::string;
/**
* @brief Convierte autoSurface de YAML a int
* @param node Nodo YAML (puede ser int o string "left"/"none"/"right")
* @return -1 para left, 0 para none, 1 para right
*/
static auto convertAutoSurface(const fkyaml::node& node) -> int;
/**
* @brief Convierte un tilemap 2D a vector 1D flat
* @param tilemap_2d Array 2D de tiles (16 rows × 32 cols)
* @return Vector 1D flat con 512 elementos
*/
static auto flattenTilemap(const std::vector<std::vector<int>>& tilemap_2d) -> std::vector<int>; // NOLINT(readability-avoid-const-params-in-decls)
/**
* @brief Parsea la configuración general de la habitación
* @param yaml Nodo raíz del YAML
* @param room Estructura de datos de la habitación a rellenar
* @param file_name Nombre del archivo para logging
*/
static void parseRoomConfig(const fkyaml::node& yaml, Room::Data& room, const std::string& file_name);
/**
* @brief Parsea las conexiones de la habitación (arriba/abajo/izq/der)
* @param conn_node Nodo YAML con las conexiones
* @param room Estructura de datos de la habitación a rellenar
*/
static void parseRoomConnections(const fkyaml::node& conn_node, Room::Data& room);
/**
* @brief Parsea el tilemap de la habitación
* @param yaml Nodo raíz del YAML
* @param room Estructura de datos de la habitación a rellenar
* @param file_name Nombre del archivo para logging
* @param verbose Si true, muestra información de debug
*/
static void parseTilemap(const fkyaml::node& yaml, Room::Data& room, const std::string& file_name, bool verbose);
/**
* @brief Parsea la lista de enemigos de la habitación
* @param yaml Nodo raíz del YAML
* @param room Estructura de datos de la habitación a rellenar
* @param verbose Si true, muestra información de debug
*/
static void parseEnemies(const fkyaml::node& yaml, Room::Data& room, bool verbose);
/**
* @brief Parsea los datos de un enemigo individual
* @param enemy_node Nodo YAML del enemigo
* @return Estructura Enemy::Data con los datos parseados
*/
static auto parseEnemyData(const fkyaml::node& enemy_node) -> Enemy::Data;
/**
* @brief Parsea los límites de movimiento de un enemigo
* @param bounds_node Nodo YAML con los límites
* @param enemy Estructura del enemigo a rellenar
*/
static void parseEnemyBoundaries(const fkyaml::node& bounds_node, Enemy::Data& enemy);
/**
* @brief Parsea la lista de items de la habitación
* @param yaml Nodo raíz del YAML
* @param room Estructura de datos de la habitación a rellenar
* @param verbose Si true, muestra información de debug
*/
static void parseItems(const fkyaml::node& yaml, Room::Data& room, bool verbose);
/**
* @brief Parsea los datos de un item individual
* @param item_node Nodo YAML del item
* @param room Datos de la habitación (para colores por defecto)
* @return Estructura Item::Data con los datos parseados
*/
static auto parseItemData(const fkyaml::node& item_node, const Room::Data& room) -> Item::Data;
static void parsePlatforms(const fkyaml::node& yaml, Room::Data& room, bool verbose);
static auto parsePlatformData(const fkyaml::node& platform_node) -> MovingPlatform::Data;
/**
* @brief Parsea la lista de llaves de la habitación
*/
static void parseKeys(const fkyaml::node& yaml, Room::Data& room, bool verbose);
/**
* @brief Parsea los datos de una llave individual
*/
static auto parseKeyData(const fkyaml::node& key_node) -> Key::Data;
/**
* @brief Parsea la lista de puertas de la habitación
*/
static void parseDoors(const fkyaml::node& yaml, Room::Data& room, bool verbose);
/**
* @brief Parsea los datos de una puerta individual
*/
static auto parseDoorData(const fkyaml::node& door_node) -> Door::Data;
};

View File

@@ -12,7 +12,7 @@
* El loader usa Resource::Helper::loadFile, que soporta tanto el resource pack
* como el filesystem (modo desarrollo). Por eso ZoneManager no depende del
* Resource::Cache y puede inicializarse en cualquier momento antes de
* RoomLoader::loadYAML.
* RoomFormat::loadYAML.
*/
class ZoneManager {
public: