- afegides claus i portes al editor
- fix: crear una nova habitació no modificava la memoria, soles els fitxers
This commit is contained in:
@@ -23,8 +23,10 @@
|
||||
#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/door_manager.hpp" // Para DoorManager
|
||||
#include "game/gameplay/enemy_manager.hpp" // Para EnemyManager
|
||||
#include "game/gameplay/item_manager.hpp" // Para ItemManager
|
||||
#include "game/gameplay/key_manager.hpp" // Para KeyManager
|
||||
#include "game/gameplay/platform_manager.hpp" // Para PlatformManager
|
||||
#include "game/gameplay/room.hpp" // Para Room
|
||||
#include "game/gameplay/zone.hpp" // Para Zone::Data
|
||||
@@ -328,7 +330,17 @@ void MapEditor::autosave() {
|
||||
room_data_.items[i].y = pos.y;
|
||||
}
|
||||
|
||||
// Sincronizar posiciones de llaves desde los sprites vivos a room_data_
|
||||
// (mismo patrón que items: el sprite es la fuente de verdad durante el drag)
|
||||
auto* key_mgr = room_->getKeyManager();
|
||||
for (int i = 0; i < key_mgr->getCount() && i < static_cast<int>(room_data_.keys.size()); ++i) {
|
||||
SDL_FPoint pos = key_mgr->getKey(i)->getPos();
|
||||
room_data_.keys[i].x = pos.x;
|
||||
room_data_.keys[i].y = pos.y;
|
||||
}
|
||||
|
||||
// Platforms are already synced via resetToInitialPosition during drag commit
|
||||
// Doors are already synced via DoorManager::moveDoor in commitEntityDrag
|
||||
|
||||
RoomFormat::saveYAML(file_path_, room_data_);
|
||||
}
|
||||
@@ -413,10 +425,13 @@ void MapEditor::render() {
|
||||
// Renderizar los marcadores de boundaries y líneas de ruta (debajo de los sprites)
|
||||
renderEntityBoundaries();
|
||||
|
||||
// Renderizar entidades normales: enemigos (animados en posición inicial), items, plataformas, jugador
|
||||
// Renderizar entidades normales: enemigos (animados en posición inicial), items,
|
||||
// plataformas, llaves, puertas, jugador
|
||||
room_->renderEnemies();
|
||||
room_->renderItems();
|
||||
room_->renderPlatforms();
|
||||
room_->renderKeys();
|
||||
room_->renderDoors();
|
||||
player_->render();
|
||||
|
||||
// Renderizar highlight de selección (encima de los sprites)
|
||||
@@ -608,8 +623,10 @@ void MapEditor::handleMouseDown(float game_x, float game_y) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Hit test on entity initials
|
||||
for (auto type : {EntityType::ENEMY, EntityType::PLATFORM, EntityType::ITEM}) {
|
||||
// 2. Hit test on entity initials. DOOR antes que KEY/ITEM porque ocupa más
|
||||
// espacio (1×4 tiles); ENEMY/PLATFORM van primero porque pueden estar
|
||||
// dibujados encima de items pequeños.
|
||||
for (auto type : {EntityType::ENEMY, EntityType::PLATFORM, EntityType::DOOR, EntityType::KEY, EntityType::ITEM}) {
|
||||
for (int i = 0; i < entityCount(type); ++i) {
|
||||
SDL_FRect rect = entityRect(type, i);
|
||||
if (pointInRect(game_x, game_y, rect)) {
|
||||
@@ -724,6 +741,33 @@ auto MapEditor::commitEntityDrag() -> bool {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case EntityType::KEY:
|
||||
if (IDX >= 0 && IDX < static_cast<int>(room_data_.keys.size())) {
|
||||
// El sprite ya está en su posición visual final desde moveEntityVisual.
|
||||
// Solo hay que actualizar room_data_; el autosave hará el sync inverso
|
||||
// sprite→data igual que con items.
|
||||
room_data_.keys[IDX].x = drag_.snap_x;
|
||||
room_data_.keys[IDX].y = drag_.snap_y;
|
||||
selection_ = {EntityType::KEY, IDX};
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case EntityType::DOOR:
|
||||
if (IDX >= 0 && IDX < static_cast<int>(room_data_.doors.size())) {
|
||||
// Truco crítico: durante el drag, moveEntityVisual movió el sprite
|
||||
// pero los WALLs del CollisionMap siguen en la posición antigua. Antes
|
||||
// de llamar a moveDoor (que limpia los tiles "actuales" y escribe los
|
||||
// nuevos), restauramos el sprite a su posición vieja para que coincida
|
||||
// con los tiles. moveDoor luego hace el ciclo limpio y completo.
|
||||
auto* door_mgr = room_->getDoorManager();
|
||||
door_mgr->getDoor(IDX)->setPosition(room_data_.doors[IDX].x, room_data_.doors[IDX].y);
|
||||
room_data_.doors[IDX].x = drag_.snap_x;
|
||||
room_data_.doors[IDX].y = drag_.snap_y;
|
||||
door_mgr->moveDoor(IDX, drag_.snap_x, drag_.snap_y);
|
||||
selection_ = {EntityType::DOOR, IDX};
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -799,6 +843,20 @@ void MapEditor::moveEntityVisual() {
|
||||
room_->getPlatformManager()->getPlatform(drag_.index)->resetToInitialPosition(temp_data);
|
||||
}
|
||||
break;
|
||||
case EntityType::KEY:
|
||||
if (drag_.index >= 0 && drag_.index < room_->getKeyManager()->getCount()) {
|
||||
room_->getKeyManager()->getKey(drag_.index)->setPosition(drag_.snap_x, drag_.snap_y);
|
||||
}
|
||||
break;
|
||||
case EntityType::DOOR:
|
||||
// Solo movemos el sprite visualmente. Los WALLs del CollisionMap NO
|
||||
// se tocan durante el drag (la puerta arrastrada no debería bloquear
|
||||
// su nueva posición todavía). El bookkeeping completo se hace en
|
||||
// commitEntityDrag al soltar.
|
||||
if (drag_.index >= 0 && drag_.index < room_->getDoorManager()->getCount()) {
|
||||
room_->getDoorManager()->getDoor(drag_.index)->setPosition(drag_.snap_x, drag_.snap_y);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -916,6 +974,10 @@ auto MapEditor::entityCount(EntityType type) const -> int {
|
||||
return room_->getItemManager()->getCount();
|
||||
case EntityType::PLATFORM:
|
||||
return room_->getPlatformManager()->getCount();
|
||||
case EntityType::KEY:
|
||||
return room_->getKeyManager()->getCount();
|
||||
case EntityType::DOOR:
|
||||
return room_->getDoorManager()->getCount();
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -929,6 +991,10 @@ auto MapEditor::entityRect(EntityType type, int index) -> SDL_FRect {
|
||||
return room_->getItemManager()->getItem(index)->getCollider();
|
||||
case EntityType::PLATFORM:
|
||||
return room_->getPlatformManager()->getPlatform(index)->getRect();
|
||||
case EntityType::KEY:
|
||||
return room_->getKeyManager()->getKey(index)->getCollider();
|
||||
case EntityType::DOOR:
|
||||
return room_->getDoorManager()->getDoor(index)->getCollider();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
@@ -959,6 +1025,10 @@ auto MapEditor::entityPosition(EntityType type, int index) const -> std::pair<fl
|
||||
const auto& path = room_data_.platforms[index].path;
|
||||
return path.empty() ? std::pair{0.0F, 0.0F} : std::pair{path[0].x, path[0].y};
|
||||
}
|
||||
case EntityType::KEY:
|
||||
return {room_data_.keys[index].x, room_data_.keys[index].y};
|
||||
case EntityType::DOOR:
|
||||
return {room_data_.doors[index].x, room_data_.doors[index].y};
|
||||
default:
|
||||
return {0.0F, 0.0F};
|
||||
}
|
||||
@@ -972,6 +1042,10 @@ auto MapEditor::entityDataCount(EntityType type) const -> int {
|
||||
return static_cast<int>(room_data_.items.size());
|
||||
case EntityType::PLATFORM:
|
||||
return static_cast<int>(room_data_.platforms.size());
|
||||
case EntityType::KEY:
|
||||
return static_cast<int>(room_data_.keys.size());
|
||||
case EntityType::DOOR:
|
||||
return static_cast<int>(room_data_.doors.size());
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -985,6 +1059,10 @@ auto MapEditor::entityLabel(EntityType type) -> const char* {
|
||||
return "item";
|
||||
case EntityType::PLATFORM:
|
||||
return "platform";
|
||||
case EntityType::KEY:
|
||||
return "key";
|
||||
case EntityType::DOOR:
|
||||
return "door";
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
@@ -1203,6 +1281,28 @@ void MapEditor::updateStatusBarInfo() { // NOLINT(readability-function-cognitiv
|
||||
}
|
||||
break;
|
||||
|
||||
case EntityType::KEY:
|
||||
if (selection_.index < static_cast<int>(room_data_.keys.size())) {
|
||||
const auto& k = room_data_.keys[selection_.index];
|
||||
std::string anim = k.animation_path;
|
||||
auto dot = anim.rfind('.');
|
||||
if (dot != std::string::npos) { anim = anim.substr(0, dot); }
|
||||
line2 = "key " + std::to_string(selection_.index) + ": " + anim;
|
||||
line3 = "id: " + k.id;
|
||||
}
|
||||
break;
|
||||
|
||||
case EntityType::DOOR:
|
||||
if (selection_.index < static_cast<int>(room_data_.doors.size())) {
|
||||
const auto& d = room_data_.doors[selection_.index];
|
||||
std::string anim = d.animation_path;
|
||||
auto dot = anim.rfind('.');
|
||||
if (dot != std::string::npos) { anim = anim.substr(0, dot); }
|
||||
line2 = "door " + std::to_string(selection_.index) + ": " + anim;
|
||||
line3 = "id: " + d.id;
|
||||
}
|
||||
break;
|
||||
|
||||
case EntityType::NONE: {
|
||||
// Propiedades de la habitación
|
||||
std::string conv = "none";
|
||||
@@ -1252,8 +1352,12 @@ auto MapEditor::getSetCompletions() const -> std::vector<std::string> {
|
||||
return {"TILE", "COUNTER"};
|
||||
case EntityType::PLATFORM:
|
||||
return {"ANIMATION", "SPEED", "LOOP", "EASING"};
|
||||
case EntityType::KEY:
|
||||
return {"ID", "ANIMATION"};
|
||||
case EntityType::DOOR:
|
||||
return {"ID", "ANIMATION"};
|
||||
default:
|
||||
return {"ITEMCOLOR1", "ITEMCOLOR2", "CONVEYOR", "TILESET", "UP", "DOWN", "LEFT", "RIGHT"};
|
||||
return {"ZONE", "ITEMCOLOR1", "ITEMCOLOR2", "CONVEYOR", "TILESET", "MUSIC", "UP", "DOWN", "LEFT", "RIGHT"};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1667,6 +1771,17 @@ auto MapEditor::createNewRoom(const std::string& direction) -> std::string { //
|
||||
Room::Data new_room = RoomFormat::createDefault();
|
||||
new_room.number = std::string(name_buf).substr(0, std::string(name_buf).find('.'));
|
||||
|
||||
// Heredar la zona de la room actual (y sus tileset/música resueltos), para
|
||||
// que el usuario no tenga que asignarla manualmente al expandir un nivel.
|
||||
// No marcamos los overrides: si la room actual tenía overrides explícitos,
|
||||
// la nueva se queda con los valores resueltos pero como heredados de zona.
|
||||
new_room.zone = room_data_.zone;
|
||||
const Zone::Data* zone = ZoneManager::get()->getZone(new_room.zone);
|
||||
if (zone != nullptr) {
|
||||
new_room.tile_set_file = zone->tile_set_file;
|
||||
new_room.music = zone->music;
|
||||
}
|
||||
|
||||
// Conexión recíproca: la nueva room conecta de vuelta a la actual
|
||||
if (direction == "UP") {
|
||||
new_room.lower_room = room_path_;
|
||||
@@ -1687,15 +1802,39 @@ auto MapEditor::createNewRoom(const std::string& direction) -> std::string { //
|
||||
Resource::Cache::get()->getRooms().emplace_back(
|
||||
RoomResource{.name = new_name, .room = std::make_shared<Room::Data>(new_room)});
|
||||
|
||||
// Conectar la room actual con la nueva (recíproco: ya hecho arriba para la nueva)
|
||||
// Conectar la room actual con la nueva (recíproco: ya hecho arriba para la nueva).
|
||||
// Actualizamos tres sitios para que la conexión sea visible inmediatamente:
|
||||
// 1. room_data_ (la copia del editor) → será autosaveada al yaml
|
||||
// 2. la Room viva del juego (room_) → para que el navigation funcione sin reload
|
||||
// 3. la Room::Data cacheada en Resource::Cache → para que adjacent rooms y
|
||||
// futuros reloads vean la conexión nueva
|
||||
Room::Border border = Room::Border::NONE;
|
||||
if (direction == "UP") {
|
||||
room_data_.upper_room = new_name;
|
||||
border = Room::Border::TOP;
|
||||
} else if (direction == "DOWN") {
|
||||
room_data_.lower_room = new_name;
|
||||
border = Room::Border::BOTTOM;
|
||||
} else if (direction == "LEFT") {
|
||||
room_data_.left_room = new_name;
|
||||
border = Room::Border::LEFT;
|
||||
} else if (direction == "RIGHT") {
|
||||
room_data_.right_room = new_name;
|
||||
border = Room::Border::RIGHT;
|
||||
}
|
||||
|
||||
if (border != Room::Border::NONE) {
|
||||
// Sincronizar con la Room viva (Game::room_cache_ apunta a este shared_ptr)
|
||||
room_->setConnection(border, new_name);
|
||||
|
||||
// Sincronizar con Resource::Cache::rooms_ (datos crudos)
|
||||
auto cached = Resource::Cache::get()->getRoom(room_path_);
|
||||
if (cached) {
|
||||
if (direction == "UP") { cached->upper_room = new_name; }
|
||||
else if (direction == "DOWN") { cached->lower_room = new_name; }
|
||||
else if (direction == "LEFT") { cached->left_room = new_name; }
|
||||
else if (direction == "RIGHT") { cached->right_room = new_name; }
|
||||
}
|
||||
}
|
||||
|
||||
if (!direction.empty()) { autosave(); }
|
||||
@@ -2016,6 +2155,238 @@ auto MapEditor::duplicatePlatform() -> std::string {
|
||||
return "Duplicated as platform " + std::to_string(new_index);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// LLAVES
|
||||
// ============================================================================
|
||||
|
||||
// Modifica una propiedad de la llave seleccionada
|
||||
auto MapEditor::setKeyProperty(const std::string& property, const std::string& value) -> std::string {
|
||||
if (!active_) { return "Editor not active"; }
|
||||
if (!hasSelectedKey()) { return "No key selected"; }
|
||||
|
||||
auto& key = room_data_.keys[selection_.index];
|
||||
|
||||
if (property == "ID") {
|
||||
key.id = value;
|
||||
// Recrear el Key (el id se pasa al constructor)
|
||||
try {
|
||||
room_->getKeyManager()->getKey(selection_.index) = std::make_shared<Key>(key);
|
||||
} catch (const std::exception& e) {
|
||||
return std::string("Error: ") + e.what();
|
||||
}
|
||||
autosave();
|
||||
return "id: " + value;
|
||||
}
|
||||
|
||||
if (property == "ANIMATION") {
|
||||
std::string anim = toLower(value);
|
||||
if (anim.find('.') == std::string::npos) { anim += ".yaml"; }
|
||||
std::string old_anim = key.animation_path;
|
||||
key.animation_path = anim;
|
||||
try {
|
||||
room_->getKeyManager()->getKey(selection_.index) = std::make_shared<Key>(key);
|
||||
} catch (const std::exception& e) {
|
||||
key.animation_path = old_anim;
|
||||
return std::string("Error: ") + e.what();
|
||||
}
|
||||
autosave();
|
||||
return "animation: " + anim;
|
||||
}
|
||||
|
||||
return "Unknown property: " + property + " (use: id, animation)";
|
||||
}
|
||||
|
||||
// Crea una nueva llave centrada en la habitación
|
||||
auto MapEditor::addKey() -> std::string {
|
||||
if (!active_) { return "Editor not active"; }
|
||||
|
||||
Key::Data new_key;
|
||||
new_key.animation_path = "key1.yaml";
|
||||
new_key.id = "1";
|
||||
new_key.x = static_cast<float>(PlayArea::CENTER_X);
|
||||
new_key.y = static_cast<float>(PlayArea::CENTER_Y);
|
||||
|
||||
room_data_.keys.push_back(new_key);
|
||||
try {
|
||||
room_->getKeyManager()->addKey(std::make_shared<Key>(new_key));
|
||||
} catch (const std::exception& e) {
|
||||
room_data_.keys.pop_back();
|
||||
return std::string("Error: ") + e.what();
|
||||
}
|
||||
|
||||
int new_index = static_cast<int>(room_data_.keys.size()) - 1;
|
||||
selection_ = {EntityType::KEY, new_index};
|
||||
|
||||
autosave();
|
||||
return "Added key " + std::to_string(new_index);
|
||||
}
|
||||
|
||||
// Elimina la llave seleccionada
|
||||
auto MapEditor::deleteKey() -> std::string {
|
||||
if (!active_) { return "Editor not active"; }
|
||||
if (!hasSelectedKey()) { return "No key selected"; }
|
||||
|
||||
const int IDX = selection_.index;
|
||||
room_data_.keys.erase(room_data_.keys.begin() + IDX);
|
||||
|
||||
// Recrear todas las llaves (los índices cambian al borrar)
|
||||
auto* key_mgr = room_->getKeyManager();
|
||||
key_mgr->clear();
|
||||
for (const auto& key_data : room_data_.keys) {
|
||||
key_mgr->addKey(std::make_shared<Key>(key_data));
|
||||
}
|
||||
|
||||
selection_.clear();
|
||||
autosave();
|
||||
return "Deleted key " + std::to_string(IDX);
|
||||
}
|
||||
|
||||
// Duplica la llave seleccionada (la pone un tile a la derecha)
|
||||
auto MapEditor::duplicateKey() -> std::string {
|
||||
if (!active_) { return "Editor not active"; }
|
||||
if (!hasSelectedKey()) { return "No key selected"; }
|
||||
|
||||
Key::Data copy = room_data_.keys[selection_.index];
|
||||
copy.x += Tile::SIZE;
|
||||
|
||||
room_data_.keys.push_back(copy);
|
||||
try {
|
||||
room_->getKeyManager()->addKey(std::make_shared<Key>(copy));
|
||||
} catch (const std::exception& e) {
|
||||
room_data_.keys.pop_back();
|
||||
return std::string("Error: ") + e.what();
|
||||
}
|
||||
|
||||
int new_index = static_cast<int>(room_data_.keys.size()) - 1;
|
||||
selection_ = {EntityType::KEY, new_index};
|
||||
|
||||
autosave();
|
||||
return "Duplicated as key " + std::to_string(new_index);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PUERTAS
|
||||
// ============================================================================
|
||||
|
||||
// Reconstruye todas las puertas vivas desde room_data_, limpiando primero los
|
||||
// WALLs antiguos del CollisionMap. Lo usa setDoorProperty cuando un cambio
|
||||
// (id, animation) requiere recrear el Door y mantener los tiles sincronizados.
|
||||
void MapEditor::rebuildDoors() {
|
||||
auto* door_mgr = room_->getDoorManager();
|
||||
// Borrar una a una desde el principio: cada removeDoor limpia sus WALLs
|
||||
while (door_mgr->getCount() > 0) {
|
||||
door_mgr->removeDoor(0);
|
||||
}
|
||||
// Re-añadir desde room_data_; addDoor reescribe los WALLs si bloquean
|
||||
for (const auto& d : room_data_.doors) {
|
||||
door_mgr->addDoor(std::make_shared<Door>(d, /*start_opened=*/false));
|
||||
}
|
||||
}
|
||||
|
||||
// Modifica una propiedad de la puerta seleccionada
|
||||
auto MapEditor::setDoorProperty(const std::string& property, const std::string& value) -> std::string {
|
||||
if (!active_) { return "Editor not active"; }
|
||||
if (!hasSelectedDoor()) { return "No door selected"; }
|
||||
|
||||
auto& door = room_data_.doors[selection_.index];
|
||||
|
||||
if (property == "ID") {
|
||||
door.id = value;
|
||||
// Recrear preserva el id pero pasa por el constructor → rebuild completo
|
||||
// para mantener los tiles del CollisionMap sincronizados.
|
||||
rebuildDoors();
|
||||
autosave();
|
||||
return "id: " + value;
|
||||
}
|
||||
|
||||
if (property == "ANIMATION") {
|
||||
std::string anim = toLower(value);
|
||||
if (anim.find('.') == std::string::npos) { anim += ".yaml"; }
|
||||
std::string old_anim = door.animation_path;
|
||||
door.animation_path = anim;
|
||||
try {
|
||||
rebuildDoors();
|
||||
} catch (const std::exception& e) {
|
||||
door.animation_path = old_anim;
|
||||
// Reconstruir con los datos viejos para dejar el manager coherente
|
||||
rebuildDoors();
|
||||
return std::string("Error: ") + e.what();
|
||||
}
|
||||
autosave();
|
||||
return "animation: " + anim;
|
||||
}
|
||||
|
||||
return "Unknown property: " + property + " (use: id, animation)";
|
||||
}
|
||||
|
||||
// Crea una nueva puerta centrada en la habitación, alineada al grid de 8 px
|
||||
auto MapEditor::addDoor() -> std::string {
|
||||
if (!active_) { return "Editor not active"; }
|
||||
|
||||
Door::Data new_door;
|
||||
new_door.animation_path = "door1.yaml";
|
||||
new_door.id = "1";
|
||||
// Centrar y alinear al grid (la puerta ocupa 1×4 tiles)
|
||||
new_door.x = static_cast<float>((PlayArea::CENTER_X / Tile::SIZE) * Tile::SIZE);
|
||||
new_door.y = static_cast<float>((PlayArea::CENTER_Y / Tile::SIZE) * Tile::SIZE);
|
||||
|
||||
room_data_.doors.push_back(new_door);
|
||||
try {
|
||||
// addDoor del manager ya escribe los WALLs si la puerta es bloqueante
|
||||
// (lo es por defecto, porque pasamos start_opened=false).
|
||||
room_->getDoorManager()->addDoor(std::make_shared<Door>(new_door, /*start_opened=*/false));
|
||||
} catch (const std::exception& e) {
|
||||
room_data_.doors.pop_back();
|
||||
return std::string("Error: ") + e.what();
|
||||
}
|
||||
|
||||
int new_index = static_cast<int>(room_data_.doors.size()) - 1;
|
||||
selection_ = {EntityType::DOOR, new_index};
|
||||
|
||||
autosave();
|
||||
return "Added door " + std::to_string(new_index);
|
||||
}
|
||||
|
||||
// Elimina la puerta seleccionada (limpia los WALLs del CollisionMap)
|
||||
auto MapEditor::deleteDoor() -> std::string {
|
||||
if (!active_) { return "Editor not active"; }
|
||||
if (!hasSelectedDoor()) { return "No door selected"; }
|
||||
|
||||
const int IDX = selection_.index;
|
||||
|
||||
// Importante: usar removeDoor del manager (limpia los WALLs antes de borrar)
|
||||
room_->getDoorManager()->removeDoor(IDX);
|
||||
room_data_.doors.erase(room_data_.doors.begin() + IDX);
|
||||
|
||||
selection_.clear();
|
||||
autosave();
|
||||
return "Deleted door " + std::to_string(IDX);
|
||||
}
|
||||
|
||||
// Duplica la puerta seleccionada (la pone una altura entera abajo, 4 tiles,
|
||||
// para no solapar visualmente)
|
||||
auto MapEditor::duplicateDoor() -> std::string {
|
||||
if (!active_) { return "Editor not active"; }
|
||||
if (!hasSelectedDoor()) { return "No door selected"; }
|
||||
|
||||
Door::Data copy = room_data_.doors[selection_.index];
|
||||
copy.y += 4 * Tile::SIZE; // 4 tiles = altura completa de una puerta
|
||||
|
||||
room_data_.doors.push_back(copy);
|
||||
try {
|
||||
room_->getDoorManager()->addDoor(std::make_shared<Door>(copy, /*start_opened=*/false));
|
||||
} catch (const std::exception& e) {
|
||||
room_data_.doors.pop_back();
|
||||
return std::string("Error: ") + e.what();
|
||||
}
|
||||
|
||||
int new_index = static_cast<int>(room_data_.doors.size()) - 1;
|
||||
selection_ = {EntityType::DOOR, new_index};
|
||||
|
||||
autosave();
|
||||
return "Duplicated as door " + std::to_string(new_index);
|
||||
}
|
||||
|
||||
// Elige un color de grid que contraste con el fondo
|
||||
// Empieza con bright_black (1), si coincide con el bg en la paleta activa, sube índices
|
||||
static auto pickGridColor(Uint8 bg, const std::shared_ptr<Surface>& surface) -> Uint8 {
|
||||
|
||||
Reference in New Issue
Block a user