740 lines
26 KiB
C++
740 lines
26 KiB
C++
#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);
|
||
}
|
||
}
|
||
|
||
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 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";
|
||
|
||
// 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::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";
|
||
|
||
// --- 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
|