- afegides claus i portes al editor

- fix: crear una nova habitació no modificava la memoria, soles els fitxers
This commit is contained in:
2026-04-10 18:34:04 +02:00
parent 077b86ea4a
commit 8ebf7894f2
15 changed files with 633 additions and 17 deletions

View File

@@ -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 {

View File

@@ -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();

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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