diff --git a/data/console/commands.yaml b/data/console/commands.yaml index bdfd3b0..c2900a5 100644 --- a/data/console/commands.yaml +++ b/data/console/commands.yaml @@ -203,7 +203,8 @@ categories: description: "Change to room number (GAME only)" usage: "ROOM <1-60>|NEXT|PREV|LEFT|RIGHT|UP|DOWN" completions: - ROOM: [NEXT, PREV, LEFT, RIGHT, UP, DOWN] + ROOM: [NEXT, PREV, LEFT, RIGHT, UP, DOWN, NEW, DELETE] + ROOM NEW: [LEFT, RIGHT, UP, DOWN] - keyword: SCENE handler: cmd_scene diff --git a/source/game/editor/map_editor.cpp b/source/game/editor/map_editor.cpp index 9a64d18..9e6d0ef 100644 --- a/source/game/editor/map_editor.cpp +++ b/source/game/editor/map_editor.cpp @@ -5,8 +5,10 @@ #include #include // Para std::round +#include // Para std::remove (borrar fichero) #include // Para ifstream, ofstream #include // Para cout +#include // Para set #include "core/input/mouse.hpp" // Para Mouse #include "core/rendering/render_info.hpp" // Para RenderInfo @@ -14,6 +16,7 @@ #include "core/rendering/surface.hpp" // Para Surface #include "core/resources/resource_cache.hpp" // Para Resource::Cache #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/game_control.hpp" // Para GameControl #include "game/editor/room_saver.hpp" // Para RoomSaver @@ -131,6 +134,16 @@ void MapEditor::toggleMiniMap() { } mini_map_visible_ = !mini_map_visible_; if (mini_map_visible_) { + // Reconstruir el minimapa (pueden haber cambiado rooms, conexiones, tiles) + mini_map_ = std::make_unique(parseColor(settings_.minimap_bg), parseColor(settings_.minimap_conn)); + mini_map_->on_navigate = [this](const std::string& room_name) { + mini_map_visible_ = false; + reenter_ = true; + if (GameControl::exit_editor) { GameControl::exit_editor(); } + if (GameControl::change_room && GameControl::change_room(room_name)) { + if (GameControl::enter_editor) { GameControl::enter_editor(); } + } + }; mini_map_->centerOnRoom(room_path_); } } @@ -1147,27 +1160,59 @@ auto MapEditor::setRoomProperty(const std::string& property, const std::string& if (property == "UP" || property == "DOWN" || property == "LEFT" || property == "RIGHT") { std::string connection = "0"; if (val != "0" && val != "null" && val != "none") { - // Convertir número a fichero: "6" → "06.yaml" try { int num = std::stoi(val); char buf[16]; std::snprintf(buf, sizeof(buf), "%02d.yaml", num); connection = buf; } catch (...) { - // Si no es número, asumir que es un nombre de fichero connection = val; if (connection.find('.') == std::string::npos) { connection += ".yaml"; } } } - if (property == "UP") { - room_data_.upper_room = connection; - } else if (property == "DOWN") { - room_data_.lower_room = connection; - } else if (property == "LEFT") { - room_data_.left_room = connection; - } else { - room_data_.right_room = connection; + // Dirección opuesta para la conexión recíproca + std::string opposite; + std::string* our_field = nullptr; + if (property == "UP") { opposite = "lower_room"; our_field = &room_data_.upper_room; } + if (property == "DOWN") { opposite = "upper_room"; our_field = &room_data_.lower_room; } + if (property == "LEFT") { opposite = "right_room"; our_field = &room_data_.left_room; } + if (property == "RIGHT") { opposite = "left_room"; our_field = &room_data_.right_room; } + + // Si había una conexión anterior, romper la recíproca de la otra room + if (our_field != nullptr && *our_field != "0" && !our_field->empty()) { + auto old_other = Resource::Cache::get()->getRoom(*our_field); + if (old_other) { + if (opposite == "upper_room") { old_other->upper_room = "0"; } + else if (opposite == "lower_room") { old_other->lower_room = "0"; } + else if (opposite == "left_room") { old_other->left_room = "0"; } + else if (opposite == "right_room") { old_other->right_room = "0"; } + // 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); + } + } + } + + // Aplicar la nueva conexión + if (our_field != nullptr) { *our_field = connection; } + + // Crear la conexión recíproca en la otra room + if (connection != "0") { + auto other = Resource::Cache::get()->getRoom(connection); + if (other) { + if (opposite == "upper_room") { other->upper_room = room_path_; } + else if (opposite == "lower_room") { other->lower_room = room_path_; } + else if (opposite == "left_room") { other->left_room = room_path_; } + else if (opposite == "right_room") { other->right_room = room_path_; } + 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); + } + } } autosave(); @@ -1177,6 +1222,267 @@ auto MapEditor::setRoomProperty(const std::string& property, const std::string& return "Unknown property: " + property + " (use: bgcolor, border, itemcolor1, itemcolor2, conveyor, tileset, up, down, left, right)"; } +// Obtiene la ruta de assets.yaml derivada de la ruta de una room +auto MapEditor::getAssetsYamlPath() -> std::string { + std::string ref_path = Resource::List::get()->get(room_path_); + if (ref_path.empty()) { return ""; } + // ref_path es algo como .../data/room/03.yaml → queremos .../config/assets.yaml + auto pos = ref_path.find("/data/room/"); + if (pos == std::string::npos) { return ""; } + return ref_path.substr(0, pos) + "/config/assets.yaml"; +} + +// Añade una room a assets.yaml (bajo la sección rooms:) +void MapEditor::addRoomToAssetsYaml(const std::string& room_name) { + std::string assets_path = getAssetsYamlPath(); + if (assets_path.empty()) { return; } + + // Leer el fichero + std::ifstream in(assets_path); + if (!in.is_open()) { return; } + std::string content((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + in.close(); + + // Buscar la última entrada de room y añadir después + std::string entry = " - type: ROOM\n path: ${PREFIX}/data/room/" + room_name + "\n"; + auto last_room = content.rfind("path: ${PREFIX}/data/room/"); + if (last_room != std::string::npos) { + auto end_of_line = content.find('\n', last_room); + if (end_of_line != std::string::npos) { + content.insert(end_of_line + 1, entry); + } + } + + std::ofstream out(assets_path); + if (out.is_open()) { + out << content; + out.close(); + } +} + +// Quita una room de assets.yaml +void MapEditor::removeRoomFromAssetsYaml(const std::string& room_name) { + std::string assets_path = getAssetsYamlPath(); + if (assets_path.empty()) { return; } + + std::ifstream in(assets_path); + if (!in.is_open()) { return; } + std::string content((std::istreambuf_iterator(in)), std::istreambuf_iterator()); + in.close(); + + // Buscar "path: ${PREFIX}/data/room/XX.yaml" y eliminar esa entry (2 líneas: type + path) + std::string search = "path: ${PREFIX}/data/room/" + room_name; + auto pos = content.find(search); + if (pos != std::string::npos) { + // Retroceder hasta " - type: ROOM\n" + auto line_start = content.rfind(" - type: ROOM", pos); + // Avanzar hasta el fin de la línea del path + auto line_end = content.find('\n', pos); + if (line_start != std::string::npos && line_end != std::string::npos) { + content.erase(line_start, line_end - line_start + 1); + } + } + + std::ofstream out(assets_path); + if (out.is_open()) { + out << content; + out.close(); + } +} + +// Crea una nueva habitación +auto MapEditor::createNewRoom(const std::string& direction) -> std::string { + if (!active_) { return "Editor not active"; } + + // Validar dirección si se proporcionó + if (!direction.empty() && direction != "LEFT" && direction != "RIGHT" && direction != "UP" && direction != "DOWN") { + return "usage: room new [left|right|up|down]"; + } + + // Comprobar que no hay ya una room en esa dirección + if (!direction.empty()) { + std::string* existing = nullptr; + if (direction == "UP") { existing = &room_data_.upper_room; } + else if (direction == "DOWN") { existing = &room_data_.lower_room; } + else if (direction == "LEFT") { existing = &room_data_.left_room; } + else if (direction == "RIGHT") { existing = &room_data_.right_room; } + if (existing != nullptr && *existing != "0" && !existing->empty()) { + return "Already connected " + toLower(direction) + ": " + *existing; + } + } + + // Encontrar el primer número libre (reutiliza huecos) + auto& rooms = Resource::Cache::get()->getRooms(); + std::set used; + for (const auto& r : rooms) { + try { used.insert(std::stoi(r.name.substr(0, r.name.find('.')))); } + catch (...) {} + } + int new_num = 1; + while (used.contains(new_num)) { ++new_num; } + char name_buf[16]; + std::snprintf(name_buf, sizeof(name_buf), "%02d.yaml", new_num); + std::string new_name = name_buf; + + // Derivar la ruta de la carpeta de rooms + std::string ref_path = Resource::List::get()->get(room_path_); + if (ref_path.empty()) { return "Error: cannot resolve room path"; } + 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; + new_room.number = std::string(name_buf).substr(0, std::string(name_buf).find('.')); + new_room.name = "NO_NAME"; + new_room.bg_color = "black"; + new_room.border_color = "magenta"; + new_room.tile_set_file = "standard.gif"; + new_room.item_color1 = "bright_cyan"; + new_room.item_color2 = "yellow"; + 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(32 * 16, -1); + + // Conexión recíproca: la nueva room conecta de vuelta a la actual + if (direction == "UP") { new_room.lower_room = room_path_; } + else if (direction == "DOWN") { new_room.upper_room = room_path_; } + else if (direction == "LEFT") { new_room.right_room = room_path_; } + else if (direction == "RIGHT") { new_room.left_room = room_path_; } + + // Conexiones del YAML + auto connStr = [](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 << " bgColor: black\n"; + file << " border: magenta\n"; + file << " tileSetFile: standard.gif\n"; + file << "\n"; + file << " connections:\n"; + file << " up: " << connStr(new_room.upper_room) << "\n"; + file << " down: " << connStr(new_room.lower_room) << "\n"; + file << " left: " << connStr(new_room.left_room) << "\n"; + file << " right: " << connStr(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 < 16; ++row) { + file << " - ["; + for (int col = 0; col < 32; ++col) { + file << "-1"; + if (col < 31) { file << ", "; } + } + file << "]\n"; + } + file.close(); + + // Registrar en Resource::List, cache y assets.yaml + Resource::List::get()->add(new_path, Resource::List::Type::ROOM, true, true); + Resource::Cache::get()->getRooms().emplace_back( + RoomResource{.name = new_name, .room = std::make_shared(new_room)}); + addRoomToAssetsYaml(new_name); + + // Conectar la room actual con la nueva (recíproco: ya hecho arriba para la nueva) + if (direction == "UP") { room_data_.upper_room = new_name; } + else if (direction == "DOWN") { room_data_.lower_room = new_name; } + else if (direction == "LEFT") { room_data_.left_room = new_name; } + else if (direction == "RIGHT") { room_data_.right_room = new_name; } + + if (!direction.empty()) { autosave(); } + + // Navegar a la nueva room + reenter_ = true; + if (GameControl::exit_editor) { GameControl::exit_editor(); } + if (GameControl::change_room && GameControl::change_room(new_name)) { + if (GameControl::enter_editor) { GameControl::enter_editor(); } + } + + return "Created room " + new_name + (direction.empty() ? "" : " (" + toLower(direction) + ")"); +} + +// Elimina la habitación actual +auto MapEditor::deleteRoom() -> std::string { + if (!active_) { return "Editor not active"; } + + std::string deleted_name = room_path_; + + // Buscar una room vecina a la que navegar después de borrar + std::string target = "0"; + if (room_data_.upper_room != "0" && !room_data_.upper_room.empty()) { target = room_data_.upper_room; } + else if (room_data_.lower_room != "0" && !room_data_.lower_room.empty()) { target = room_data_.lower_room; } + else if (room_data_.left_room != "0" && !room_data_.left_room.empty()) { target = room_data_.left_room; } + else if (room_data_.right_room != "0" && !room_data_.right_room.empty()) { target = room_data_.right_room; } + + if (target == "0") { + // Buscar la primera room que no sea esta + for (const auto& r : Resource::Cache::get()->getRooms()) { + if (r.name != deleted_name) { target = r.name; break; } + } + } + if (target == "0") { return "Cannot delete: no other room to navigate to"; } + + // Desenlazar todas las conexiones recíprocas + auto unlinkReciprocal = [&](const std::string& neighbor, const std::string& field_name) { + if (neighbor == "0" || neighbor.empty()) { return; } + auto other = Resource::Cache::get()->getRoom(neighbor); + if (!other) { return; } + if (field_name == "upper_room") { other->upper_room = "0"; } + else if (field_name == "lower_room") { other->lower_room = "0"; } + else if (field_name == "left_room") { other->left_room = "0"; } + else if (field_name == "right_room") { other->right_room = "0"; } + // 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); + } + }; + + unlinkReciprocal(room_data_.upper_room, "lower_room"); // Si nosotros somos su lower + unlinkReciprocal(room_data_.lower_room, "upper_room"); // Si nosotros somos su upper + unlinkReciprocal(room_data_.left_room, "right_room"); // Si nosotros somos su right + unlinkReciprocal(room_data_.right_room, "left_room"); // Si nosotros somos su left + + // Navegar a la room destino antes de borrar + reenter_ = true; + if (GameControl::exit_editor) { GameControl::exit_editor(); } + if (GameControl::change_room) { GameControl::change_room(target); } + + // Borrar el YAML del disco + std::string yaml_path = Resource::List::get()->get(deleted_name); + if (!yaml_path.empty()) { + std::remove(yaml_path.c_str()); + } + + // Quitar del cache + auto& cache_rooms = Resource::Cache::get()->getRooms(); + cache_rooms.erase( + std::remove_if(cache_rooms.begin(), cache_rooms.end(), + [&](const RoomResource& r) { return r.name == deleted_name; }), + cache_rooms.end()); + + // Quitar de assets.yaml + removeRoomFromAssetsYaml(deleted_name); + + // Re-entrar al editor en la room destino + if (GameControl::enter_editor) { GameControl::enter_editor(); } + + return "Deleted room " + deleted_name + ", moved to " + target; +} + // ¿Hay un item seleccionado? auto MapEditor::hasSelectedItem() const -> bool { return selected_item_ >= 0 && selected_item_ < static_cast(room_data_.items.size()); diff --git a/source/game/editor/map_editor.hpp b/source/game/editor/map_editor.hpp index 29bd0cb..83ed6cd 100644 --- a/source/game/editor/map_editor.hpp +++ b/source/game/editor/map_editor.hpp @@ -43,6 +43,8 @@ class MapEditor { // Comandos para propiedades de la habitación auto setRoomProperty(const std::string& property, const std::string& value) -> std::string; + auto createNewRoom(const std::string& direction = "") -> std::string; + auto deleteRoom() -> std::string; // Opciones del editor (llamados desde console_commands / teclas) auto showInfo(bool show) -> std::string; @@ -106,6 +108,9 @@ class MapEditor { void handleMouseUp(); void updateDrag(); void autosave(); + auto getAssetsYamlPath() -> std::string; + void addRoomToAssetsYaml(const std::string& room_name); + void removeRoomFromAssetsYaml(const std::string& room_name); void updateStatusBarInfo(); static auto snapToGrid(float value) -> float; static auto pointInRect(float px, float py, const SDL_FRect& rect) -> bool; diff --git a/source/game/ui/console_commands.cpp b/source/game/ui/console_commands.cpp index 0e1a9b4..0776d1e 100644 --- a/source/game/ui/console_commands.cpp +++ b/source/game/ui/console_commands.cpp @@ -595,6 +595,19 @@ static auto cmd_room(const std::vector& args) -> std::string { if (SceneManager::current != SceneManager::Scene::GAME) { return "Only available in GAME scene"; } if (args.empty()) { return "usage: room <1-60>|next|prev|left|right|up|down"; } + // DELETE: borrar la habitación actual + if (args[0] == "DELETE") { + if (!MapEditor::get() || !MapEditor::get()->isActive()) { return "Editor not active"; } + return MapEditor::get()->deleteRoom(); + } + + // NEW [LEFT|RIGHT|UP|DOWN]: crear habitación nueva (opcionalmente conectada) + if (args[0] == "NEW") { + if (!MapEditor::get() || !MapEditor::get()->isActive()) { return "Editor not active"; } + std::string direction = (args.size() >= 2) ? args[1] : ""; + return MapEditor::get()->createNewRoom(direction); + } + // Direcciones: LEFT, RIGHT, UP, DOWN if (args[0] == "LEFT" || args[0] == "RIGHT" || args[0] == "UP" || args[0] == "DOWN") { if (!GameControl::get_adjacent_room) { return "Game not initialized"; }