#include "room_loader.hpp" #include // Para exception #include // Para cout, cerr #include "external/fkyaml_node.hpp" // Para fkyaml::node #include "core/resources/resource_helper.hpp" // Para Resource::Helper #include "utils/defines.hpp" // Para TILE_SIZE #include "utils/utils.hpp" // Para stringToColor // Convierte room connection de YAML a formato interno auto RoomLoader::convertRoomConnection(const std::string& value) -> std::string { 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"; } // Convierte string de autoSurface a int auto RoomLoader::convertAutoSurface(const fkyaml::node& node) -> int { if (node.is_integer()) { return node.get_value(); } if (node.is_string()) { const auto VALUE = node.get_value(); 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>& tilemap_2d) -> std::vector { std::vector tilemap_flat; tilemap_flat.reserve(512); // 16 rows × 32 cols 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) { 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 if (room_node.contains("name")) { room.name = room_node["name"].get_value(); } if (room_node.contains("bgColor")) { room.bg_color = room_node["bgColor"].get_value(); } if (room_node.contains("border")) { room.border_color = room_node["border"].get_value(); } if (room_node.contains("tileSetFile")) { room.tile_set_file = room_node["tileSetFile"].get_value(); } // Room connections if (room_node.contains("connections")) { parseRoomConnections(room_node["connections"], room); } // Item colors room.item_color1 = room_node.contains("itemColor1") ? room_node["itemColor1"].get_value_or("yellow") : "yellow"; room.item_color2 = room_node.contains("itemColor2") ? room_node["itemColor2"].get_value_or("magenta") : "magenta"; // 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("null")) : "0"; room.lower_room = conn_node.contains("down") ? convertRoomConnection(conn_node["down"].get_value_or("null")) : "0"; room.left_room = conn_node.contains("left") ? convertRoomConnection(conn_node["left"].get_value_or("null")) : "0"; room.right_room = conn_node.contains("right") ? convertRoomConnection(conn_node["right"].get_value_or("null")) : "0"; } // Parsea el tilemap de la habitación void RoomLoader::parseTilemap(const fkyaml::node& yaml, Room::Data& room, const std::string& file_name, bool verbose) { if (!yaml.contains("tilemap")) { std::cerr << "Warning: No tilemap found in " << file_name << '\n'; return; } const auto& tilemap_node = yaml["tilemap"]; // Read 2D array std::vector> tilemap_2d; tilemap_2d.reserve(16); for (const auto& row_node : tilemap_node) { std::vector row; row.reserve(32); for (const auto& tile_node : row_node) { row.push_back(tile_node.get_value()); } tilemap_2d.push_back(row); } // Convert to 1D flat array room.tile_map = flattenTilemap(tilemap_2d); if (verbose) { std::cout << "Loaded tilemap: " << room.tile_map.size() << " tiles\n"; } } // Parsea los límites de movimiento de un enemigo void RoomLoader::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() * TILE_SIZE; } if (pos1.contains("y")) { enemy.y1 = pos1["y"].get_value() * TILE_SIZE; } } if (bounds_node.contains("position2")) { const auto& pos2 = bounds_node["position2"]; if (pos2.contains("x")) { enemy.x2 = pos2["x"].get_value() * TILE_SIZE; } if (pos2.contains("y")) { enemy.y2 = pos2["y"].get_value() * TILE_SIZE; } } // Formato antiguo: x1/y1/x2/y2 (compatibilidad) if (bounds_node.contains("x1")) { enemy.x1 = bounds_node["x1"].get_value() * TILE_SIZE; } if (bounds_node.contains("y1")) { enemy.y1 = bounds_node["y1"].get_value() * TILE_SIZE; } if (bounds_node.contains("x2")) { enemy.x2 = bounds_node["x2"].get_value() * TILE_SIZE; } if (bounds_node.contains("y2")) { enemy.y2 = bounds_node["y2"].get_value() * TILE_SIZE; } } // Parsea los datos de un enemigo individual auto RoomLoader::parseEnemyData(const fkyaml::node& enemy_node) -> Enemy::Data { Enemy::Data enemy; // Animation path if (enemy_node.contains("animation")) { enemy.animation_path = enemy_node["animation"].get_value(); } // 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() * TILE_SIZE; } if (pos.contains("y")) { enemy.y = pos["y"].get_value() * 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(); } if (vel.contains("y")) { enemy.vy = vel["y"].get_value(); } } // Boundaries (in tiles, convert to pixels) if (enemy_node.contains("boundaries")) { parseEnemyBoundaries(enemy_node["boundaries"], enemy); } // Color enemy.color = enemy_node.contains("color") ? enemy_node["color"].get_value_or("white") : "white"; // Optional fields enemy.flip = enemy_node.contains("flip") ? enemy_node["flip"].get_value_or(false) : false; enemy.mirror = enemy_node.contains("mirror") ? enemy_node["mirror"].get_value_or(false) : false; enemy.frame = enemy_node.contains("frame") ? enemy_node["frame"].get_value_or(-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) { 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 { Item::Data item; // Tileset file if (item_node.contains("tileSetFile")) { item.tile_set_file = item_node["tileSetFile"].get_value(); } // Tile index if (item_node.contains("tile")) { item.tile = item_node["tile"].get_value(); } // 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() * TILE_SIZE; } if (pos.contains("y")) { item.y = pos["y"].get_value() * TILE_SIZE; } } // Counter item.counter = item_node.contains("counter") ? item_node["counter"].get_value_or(0) : 0; // Colors (assigned from room defaults) item.color1 = stringToColor(room.item_color1); item.color2 = stringToColor(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) { 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"; } } // Carga un archivo de room en formato YAML auto RoomLoader::loadYAML(const std::string& file_path, bool verbose) -> Room::Data { 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); 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; }