Files
projecte_2026/source/game/gameplay/room_format.cpp
Sergio Valor 234ae82f56 - afegides les habitacions de tot el joc (buides)
- minimapa mostra els numeros d'habitacio
- tecla per a fer captures de pantalla
2026-04-12 10:25:12 +02:00

755 lines
27 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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(static_cast<size_t>(Map::WIDTH) * static_cast<size_t>(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., "001.yaml" → "001")
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;
}
// bgColor: zona, override si está en el yaml
if (room_node.contains("bgColor")) {
room.bg_color = readColorNode(room_node["bgColor"]);
room.bg_color_overridden = true;
} else if (zone != nullptr) {
room.bg_color = zone->bg_color;
}
// Room connections
if (room_node.contains("connections")) {
parseRoomConnections(room_node["connections"], room);
}
}
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) -> 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;
// Colores: defaults de Item::Data, override si están en el yaml
if (item_node.contains("color1")) {
item.color1 = readColorNode(item_node["color1"]);
item.color1_overridden = true;
}
if (item_node.contains("color2")) {
item.color2 = readColorNode(item_node["color2"]);
item.color2_overridden = true;
}
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));
}
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, music y bg_color 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.bg_color = zone->bg_color;
}
data.tile_set_overridden = false;
data.music_overridden = false;
data.bg_color_overridden = false;
// Conexiones a cero
data.upper_room = "0";
data.lower_room = "0";
data.left_room = "0";
data.right_room = "0";
// Tilemaps del tamaño correcto, vacíos
data.tile_map.resize(static_cast<size_t>(Map::WIDTH) * static_cast<size_t>(Map::HEIGHT), -1);
data.collision_tile_map.resize(static_cast<size_t>(Map::WIDTH) * static_cast<size_t>(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::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";
}
// bgColor solo si es override explícito del valor heredado de la zona
if (room_data.bg_color_overridden) {
out << " bgColor: " << static_cast<int>(room_data.bg_color) << "\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";
// --- 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";
}
// color1/color2 solo si son override explícito del default
if (item.color1_overridden) {
out << " color1: " << static_cast<int>(item.color1) << "\n";
}
if (item.color2_overridden) {
out << " color2: " << static_cast<int>(item.color2) << "\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