- 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 {
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
|
||||
#include "game/editor/mini_map.hpp" // Para MiniMap
|
||||
#include "game/editor/tile_picker.hpp" // Para TilePicker
|
||||
#include "game/entities/door.hpp" // Para Door::Data
|
||||
#include "game/entities/enemy.hpp" // Para Enemy::Data
|
||||
#include "game/entities/item.hpp" // Para Item::Data
|
||||
#include "game/entities/key.hpp" // Para Key::Data
|
||||
#include "game/entities/moving_platform.hpp" // Para MovingPlatform::Data
|
||||
#include "game/entities/player.hpp" // Para Player::SpawnData
|
||||
#include "game/gameplay/room.hpp" // Para Room::Data
|
||||
@@ -23,7 +25,9 @@ class EditorStatusBar;
|
||||
enum class EntityType { NONE,
|
||||
ENEMY,
|
||||
ITEM,
|
||||
PLATFORM };
|
||||
PLATFORM,
|
||||
KEY,
|
||||
DOOR };
|
||||
|
||||
// Seleccion unificada: una sola entidad seleccionada a la vez
|
||||
struct Selection {
|
||||
@@ -90,6 +94,20 @@ class MapEditor {
|
||||
auto duplicatePlatform() -> std::string;
|
||||
[[nodiscard]] auto hasSelectedPlatform() const -> bool { return selection_.is(EntityType::PLATFORM); }
|
||||
|
||||
// Comandos para llaves
|
||||
auto setKeyProperty(const std::string& property, const std::string& value) -> std::string;
|
||||
auto addKey() -> std::string;
|
||||
auto deleteKey() -> std::string;
|
||||
auto duplicateKey() -> std::string;
|
||||
[[nodiscard]] auto hasSelectedKey() const -> bool { return selection_.is(EntityType::KEY); }
|
||||
|
||||
// Comandos para puertas
|
||||
auto setDoorProperty(const std::string& property, const std::string& value) -> std::string;
|
||||
auto addDoor() -> std::string;
|
||||
auto deleteDoor() -> std::string;
|
||||
auto duplicateDoor() -> std::string;
|
||||
[[nodiscard]] auto hasSelectedDoor() const -> bool { return selection_.is(EntityType::DOOR); }
|
||||
|
||||
// Seleccion unificada
|
||||
[[nodiscard]] auto getSelectionType() const -> EntityType { return selection_.type; }
|
||||
|
||||
@@ -136,6 +154,12 @@ class MapEditor {
|
||||
void renderGrid() const;
|
||||
void handleMouseDown(float game_x, float game_y);
|
||||
void handleMouseUp();
|
||||
|
||||
// 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 rebuildDoors();
|
||||
void updateDrag();
|
||||
auto commitEntityDrag() -> bool;
|
||||
void moveEntityVisual();
|
||||
|
||||
@@ -63,3 +63,13 @@ auto Door::justOpened() -> bool {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Mueve la puerta a la posición indicada (sprite + collider). NO toca el
|
||||
// CollisionMap — eso es responsabilidad del DoorManager (moveDoor/removeDoor).
|
||||
void Door::setPosition(float x, float y) {
|
||||
sprite_->setPosX(x);
|
||||
sprite_->setPosY(y);
|
||||
collider_ = sprite_->getRect();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -52,6 +52,10 @@ class Door {
|
||||
|
||||
void setPaused(bool paused) { is_paused_ = paused; } // Pausa/despausa la animación
|
||||
|
||||
#ifdef _DEBUG
|
||||
void setPosition(float x, float y); // Mueve sprite y collider en vivo (editor; NO toca CollisionMap)
|
||||
#endif
|
||||
|
||||
private:
|
||||
std::shared_ptr<AnimatedSprite> sprite_; // Sprite animado de la puerta
|
||||
SDL_FRect collider_{}; // Rectángulo de colisión
|
||||
|
||||
@@ -30,3 +30,12 @@ void Key::update(float delta_time) {
|
||||
auto Key::getPos() const -> SDL_FPoint {
|
||||
return SDL_FPoint{.x = sprite_->getX(), .y = sprite_->getY()};
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Mueve la llave a la posición indicada (sprite + collider). Solo editor.
|
||||
void Key::setPosition(float x, float y) {
|
||||
sprite_->setPosX(x);
|
||||
sprite_->setPosY(y);
|
||||
collider_ = sprite_->getRect();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -36,6 +36,10 @@ class Key {
|
||||
|
||||
void setPaused(bool paused) { is_paused_ = paused; } // Pausa/despausa la animación
|
||||
|
||||
#ifdef _DEBUG
|
||||
void setPosition(float x, float y); // Mueve sprite y collider en vivo (editor)
|
||||
#endif
|
||||
|
||||
private:
|
||||
std::shared_ptr<AnimatedSprite> sprite_; // Sprite animado de la llave
|
||||
SDL_FRect collider_{}; // Rectángulo de colisión
|
||||
|
||||
@@ -78,6 +78,40 @@ void DoorManager::tryUnlock(const SDL_FRect& player_rect) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
// Mueve una puerta del editor: limpia los WALLs viejos, reposiciona el sprite,
|
||||
// y reescribe los WALLs nuevos si la puerta sigue siendo bloqueante.
|
||||
void DoorManager::moveDoor(int index, float x, float y) {
|
||||
if (index < 0 || index >= static_cast<int>(doors_.size())) { return; }
|
||||
auto& door = doors_[index];
|
||||
|
||||
// Limpiar los WALLs viejos antes de mover
|
||||
if (door->isBlocking()) {
|
||||
writeDoorTiles(*door, static_cast<int>(TileCollider::Tile::EMPTY));
|
||||
}
|
||||
|
||||
// Reposicionar el sprite y el collider del Door
|
||||
door->setPosition(x, y);
|
||||
|
||||
// Re-escribir los WALLs nuevos en la nueva posición si sigue siendo bloqueante
|
||||
if (door->isBlocking()) {
|
||||
writeDoorTiles(*door, static_cast<int>(TileCollider::Tile::WALL));
|
||||
}
|
||||
}
|
||||
|
||||
// Elimina una puerta del editor, limpiando los WALLs antes de borrarla del vector
|
||||
void DoorManager::removeDoor(int index) {
|
||||
if (index < 0 || index >= static_cast<int>(doors_.size())) { return; }
|
||||
auto& door = doors_[index];
|
||||
|
||||
if (door->isBlocking()) {
|
||||
writeDoorTiles(*door, static_cast<int>(TileCollider::Tile::EMPTY));
|
||||
}
|
||||
|
||||
doors_.erase(doors_.begin() + index);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Setea las 4 celdas que ocupa la puerta (1 col × 4 filas) al valor indicado
|
||||
void DoorManager::writeDoorTiles(const Door& door, int tile_value) {
|
||||
// Convertir posición en píxeles a coordenadas de tile
|
||||
|
||||
@@ -58,6 +58,26 @@ class DoorManager {
|
||||
*/
|
||||
void tryUnlock(const SDL_FRect& player_rect);
|
||||
|
||||
#ifdef _DEBUG
|
||||
// --- API para el editor (debug) ---
|
||||
[[nodiscard]] auto getCount() const -> int { return static_cast<int>(doors_.size()); }
|
||||
auto getDoor(int index) -> std::shared_ptr<Door>& { return doors_.at(index); }
|
||||
|
||||
/**
|
||||
* @brief Mueve la puerta indicada a (x, y) en píxeles
|
||||
*
|
||||
* Limpia los WALLs viejos del CollisionMap y, si la puerta sigue siendo
|
||||
* bloqueante, escribe los nuevos. Encapsula el bookkeeping de tiles para
|
||||
* que el editor nunca toque el CollisionMap directamente.
|
||||
*/
|
||||
void moveDoor(int index, float x, float y);
|
||||
|
||||
/**
|
||||
* @brief Elimina la puerta indicada del vector y limpia sus WALLs del CollisionMap
|
||||
*/
|
||||
void removeDoor(int index);
|
||||
#endif
|
||||
|
||||
private:
|
||||
static constexpr int DOOR_TILES_HEIGHT = 4; // Una puerta ocupa 4 tiles verticalmente
|
||||
|
||||
|
||||
@@ -53,6 +53,12 @@ class KeyManager {
|
||||
*/
|
||||
auto checkCollision(SDL_FRect& rect) -> bool;
|
||||
|
||||
#ifdef _DEBUG
|
||||
// --- API para el editor (debug) ---
|
||||
[[nodiscard]] auto getCount() const -> int { return static_cast<int>(keys_.size()); }
|
||||
auto getKey(int index) -> std::shared_ptr<Key>& { return keys_.at(index); }
|
||||
#endif
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<Key>> keys_; // Colección de llaves
|
||||
std::string room_id_; // Identificador de la habitación
|
||||
|
||||
@@ -747,6 +747,10 @@ static auto cmdSet(const std::vector<std::string>& args) -> std::string {
|
||||
return MapEditor::get()->setItemProperty(args[0], args[1]);
|
||||
case EntityType::PLATFORM:
|
||||
return MapEditor::get()->setPlatformProperty(args[0], args[1]);
|
||||
case EntityType::KEY:
|
||||
return MapEditor::get()->setKeyProperty(args[0], args[1]);
|
||||
case EntityType::DOOR:
|
||||
return MapEditor::get()->setDoorProperty(args[0], args[1]);
|
||||
default:
|
||||
return MapEditor::get()->setRoomProperty(args[0], args[1]);
|
||||
}
|
||||
@@ -799,6 +803,38 @@ static auto cmdPlatform(const std::vector<std::string>& args) -> std::string {
|
||||
}
|
||||
return "usage: platform <add|delete|duplicate>";
|
||||
}
|
||||
|
||||
// KEY [ADD|DELETE|DUPLICATE]
|
||||
static auto cmdKey(const std::vector<std::string>& args) -> std::string {
|
||||
if ((MapEditor::get() == nullptr) || !MapEditor::get()->isActive()) { return "Editor not active"; }
|
||||
if (args.empty()) { return "usage: key <add|delete|duplicate>"; }
|
||||
if (args[0] == "ADD") { return MapEditor::get()->addKey(); }
|
||||
if (args[0] == "DELETE") {
|
||||
if (!MapEditor::get()->hasSelectedKey()) { return "No key selected"; }
|
||||
return MapEditor::get()->deleteKey();
|
||||
}
|
||||
if (args[0] == "DUPLICATE") {
|
||||
if (!MapEditor::get()->hasSelectedKey()) { return "No key selected"; }
|
||||
return MapEditor::get()->duplicateKey();
|
||||
}
|
||||
return "usage: key <add|delete|duplicate>";
|
||||
}
|
||||
|
||||
// DOOR [ADD|DELETE|DUPLICATE]
|
||||
static auto cmdDoor(const std::vector<std::string>& args) -> std::string {
|
||||
if ((MapEditor::get() == nullptr) || !MapEditor::get()->isActive()) { return "Editor not active"; }
|
||||
if (args.empty()) { return "usage: door <add|delete|duplicate>"; }
|
||||
if (args[0] == "ADD") { return MapEditor::get()->addDoor(); }
|
||||
if (args[0] == "DELETE") {
|
||||
if (!MapEditor::get()->hasSelectedDoor()) { return "No door selected"; }
|
||||
return MapEditor::get()->deleteDoor();
|
||||
}
|
||||
if (args[0] == "DUPLICATE") {
|
||||
if (!MapEditor::get()->hasSelectedDoor()) { return "No door selected"; }
|
||||
return MapEditor::get()->duplicateDoor();
|
||||
}
|
||||
return "usage: door <add|delete|duplicate>";
|
||||
}
|
||||
#endif
|
||||
|
||||
// SHOW [INFO|NOTIFICATION|CHEEVO]
|
||||
@@ -948,6 +984,8 @@ void CommandRegistry::registerHandlers() { // NOLINT(readability-function-cogni
|
||||
handlers_["cmd_enemy"] = cmdEnemy;
|
||||
handlers_["cmd_item"] = cmdItem;
|
||||
handlers_["cmd_platform"] = cmdPlatform;
|
||||
handlers_["cmd_key"] = cmdKey;
|
||||
handlers_["cmd_door"] = cmdDoor;
|
||||
#endif
|
||||
// HELP se registra en load() como lambda que captura this
|
||||
|
||||
|
||||
Reference in New Issue
Block a user