665 lines
24 KiB
C++
665 lines
24 KiB
C++
#ifdef _DEBUG
|
|
|
|
#include "game/editor/map_editor.hpp"
|
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
#include <cmath> // Para std::round
|
|
#include <iostream> // Para cout
|
|
|
|
#include "core/input/mouse.hpp" // Para Mouse
|
|
#include "core/rendering/screen.hpp" // Para Screen
|
|
#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 "game/editor/editor_statusbar.hpp" // Para EditorStatusBar
|
|
#include "game/ui/console.hpp" // Para Console
|
|
#include "game/editor/room_saver.hpp" // Para RoomSaver
|
|
#include "game/entities/player.hpp" // Para Player
|
|
#include "game/gameplay/enemy_manager.hpp" // Para EnemyManager
|
|
#include "game/gameplay/item_manager.hpp" // Para ItemManager
|
|
#include "game/gameplay/room.hpp" // Para Room
|
|
#include "game/options.hpp" // Para Options
|
|
#include "utils/defines.hpp" // Para Tile::SIZE, PlayArea
|
|
#include "utils/utils.hpp" // Para stringToColor
|
|
|
|
// Singleton
|
|
MapEditor* MapEditor::instance_ = nullptr;
|
|
|
|
void MapEditor::init() {
|
|
instance_ = new MapEditor();
|
|
}
|
|
|
|
void MapEditor::destroy() {
|
|
delete instance_;
|
|
instance_ = nullptr;
|
|
}
|
|
|
|
auto MapEditor::get() -> MapEditor* {
|
|
return instance_;
|
|
}
|
|
|
|
// Constructor
|
|
MapEditor::MapEditor() = default;
|
|
|
|
// Destructor
|
|
MapEditor::~MapEditor() = default;
|
|
|
|
// Entra en modo editor
|
|
void MapEditor::enter(std::shared_ptr<Room> room, std::shared_ptr<Player> player, const std::string& room_path, std::shared_ptr<Scoreboard::Data> scoreboard_data) {
|
|
if (active_) { return; }
|
|
|
|
room_ = std::move(room);
|
|
player_ = std::move(player);
|
|
room_path_ = room_path;
|
|
scoreboard_data_ = std::move(scoreboard_data);
|
|
|
|
// Cargar una copia de los datos de la habitación (para boundaries y edición)
|
|
auto room_data_ptr = Resource::Cache::get()->getRoom(room_path);
|
|
if (room_data_ptr) {
|
|
room_data_ = *room_data_ptr;
|
|
}
|
|
|
|
// Obtener la ruta completa y cargar el YAML original (para edición parcial y backup)
|
|
file_path_ = Resource::List::get()->get(room_path_);
|
|
if (!file_path_.empty()) {
|
|
yaml_ = RoomSaver::loadYAML(file_path_);
|
|
yaml_backup_ = yaml_; // Copia profunda para revert
|
|
}
|
|
|
|
// Guardar estado de invencibilidad y forzarla
|
|
invincible_before_editor_ = Options::cheats.invincible;
|
|
Options::cheats.invincible = Options::Cheat::State::ENABLED;
|
|
player_->setColor();
|
|
|
|
// Resetear enemigos a su posición inicial (pueden haberse movido durante el gameplay)
|
|
room_->resetEnemyPositions(room_data_.enemies);
|
|
|
|
// Crear la barra de estado
|
|
statusbar_ = std::make_unique<EditorStatusBar>(room_->getNumber(), room_->getName());
|
|
|
|
// Resetear estado
|
|
drag_ = {};
|
|
selected_enemy_ = -1;
|
|
|
|
active_ = true;
|
|
std::cout << "MapEditor: ON (room " << room_path_ << ")\n";
|
|
}
|
|
|
|
// Sale del modo editor
|
|
void MapEditor::exit() {
|
|
if (!active_) { return; }
|
|
|
|
active_ = false;
|
|
|
|
// Restaurar invencibilidad
|
|
Options::cheats.invincible = invincible_before_editor_;
|
|
player_->setColor();
|
|
|
|
// Restaurar prompt de la consola y limpiar estado
|
|
selected_enemy_ = -1;
|
|
Console::get()->setPrompt("> ");
|
|
drag_ = {};
|
|
statusbar_.reset();
|
|
room_.reset();
|
|
player_.reset();
|
|
scoreboard_data_.reset();
|
|
|
|
std::cout << "MapEditor: OFF\n";
|
|
}
|
|
|
|
// Revierte todos los cambios al estado original
|
|
auto MapEditor::revert() -> std::string {
|
|
if (!active_) { return "Editor not active"; }
|
|
if (file_path_.empty()) { return "Error: No file path"; }
|
|
|
|
// Restaurar el YAML original y reescribir el fichero
|
|
yaml_ = yaml_backup_;
|
|
auto room_data_ptr = Resource::Cache::get()->getRoom(room_path_);
|
|
if (room_data_ptr) {
|
|
room_data_ = *room_data_ptr;
|
|
}
|
|
RoomSaver::saveYAML(file_path_, yaml_, room_data_);
|
|
|
|
// Resetear los sprites vivos a las posiciones originales
|
|
room_->resetEnemyPositions(room_data_.enemies);
|
|
|
|
// Resetear items (recargar posiciones)
|
|
auto* item_mgr = room_->getItemManager();
|
|
for (int i = 0; i < item_mgr->getCount() && i < static_cast<int>(room_data_.items.size()); ++i) {
|
|
item_mgr->getItem(i)->setPosition(room_data_.items[i].x, room_data_.items[i].y);
|
|
}
|
|
|
|
return "Reverted to original";
|
|
}
|
|
|
|
// Auto-guarda al YAML tras soltar una entidad
|
|
void MapEditor::autosave() {
|
|
if (file_path_.empty()) { return; }
|
|
|
|
// Sincronizar posiciones de items desde los sprites vivos a room_data_
|
|
auto* item_mgr = room_->getItemManager();
|
|
for (int i = 0; i < item_mgr->getCount() && i < static_cast<int>(room_data_.items.size()); ++i) {
|
|
SDL_FPoint pos = item_mgr->getItem(i)->getPos();
|
|
room_data_.items[i].x = pos.x;
|
|
room_data_.items[i].y = pos.y;
|
|
}
|
|
|
|
RoomSaver::saveYAML(file_path_, yaml_, room_data_);
|
|
}
|
|
|
|
// Actualiza el editor
|
|
void MapEditor::update(float delta_time) {
|
|
// Mantener el ratón siempre visible
|
|
SDL_ShowCursor();
|
|
Mouse::last_mouse_move_time = SDL_GetTicks();
|
|
|
|
// Actualizar animaciones de enemigos e items (sin mover enemigos)
|
|
room_->updateEditorMode(delta_time);
|
|
|
|
// Actualizar posición del ratón
|
|
updateMousePosition();
|
|
|
|
// Si estamos arrastrando, actualizar la posición snapped
|
|
if (drag_.target != DragTarget::NONE) {
|
|
updateDrag();
|
|
}
|
|
|
|
// Actualizar la barra de estado
|
|
updateStatusBarInfo();
|
|
if (statusbar_) {
|
|
statusbar_->update(delta_time);
|
|
}
|
|
}
|
|
|
|
// Renderiza el editor
|
|
void MapEditor::render() {
|
|
// El tilemap ya ha sido renderizado por Game::renderPlaying() antes de llamar aquí
|
|
|
|
// Renderizar los marcadores de boundaries y líneas de ruta (debajo de los sprites)
|
|
renderEnemyBoundaries();
|
|
|
|
// Renderizar entidades normales: enemigos (animados en posición inicial), items, jugador
|
|
room_->renderEnemies();
|
|
room_->renderItems();
|
|
player_->render();
|
|
|
|
// Renderizar highlight de selección (encima de los sprites)
|
|
renderSelectionHighlight();
|
|
|
|
// Renderizar barra de estado del editor (reemplaza al scoreboard)
|
|
if (statusbar_) {
|
|
statusbar_->render();
|
|
}
|
|
}
|
|
|
|
// Maneja eventos del editor
|
|
void MapEditor::handleEvent(const SDL_Event& event) {
|
|
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && event.button.button == SDL_BUTTON_LEFT) {
|
|
handleMouseDown(mouse_game_x_, mouse_game_y_);
|
|
} else if (event.type == SDL_EVENT_MOUSE_BUTTON_UP && event.button.button == SDL_BUTTON_LEFT) {
|
|
handleMouseUp();
|
|
}
|
|
}
|
|
|
|
// Procesa click del ratón: hit test + inicio de drag
|
|
void MapEditor::handleMouseDown(float game_x, float game_y) {
|
|
// Prioridad de hit test: jugador → enemigos (initial) → enemigos (boundaries) → items
|
|
|
|
// Helper para iniciar drag
|
|
auto startDrag = [&](DragTarget target, int index, float entity_x, float entity_y) {
|
|
drag_.target = target;
|
|
drag_.index = index;
|
|
drag_.offset_x = game_x - entity_x;
|
|
drag_.offset_y = game_y - entity_y;
|
|
drag_.snap_x = entity_x;
|
|
drag_.snap_y = entity_y;
|
|
drag_.moved = false;
|
|
};
|
|
|
|
// 1. Hit test sobre el jugador (8x16)
|
|
SDL_FRect player_rect = player_->getRect();
|
|
if (pointInRect(game_x, game_y, player_rect)) {
|
|
startDrag(DragTarget::PLAYER, -1, player_rect.x, player_rect.y);
|
|
return;
|
|
}
|
|
|
|
// 2. Hit test sobre enemigos: posición inicial (usan el rect del sprite vivo)
|
|
auto* enemy_mgr = room_->getEnemyManager();
|
|
for (int i = 0; i < enemy_mgr->getCount(); ++i) {
|
|
SDL_FRect enemy_rect = enemy_mgr->getEnemy(i)->getRect();
|
|
if (pointInRect(game_x, game_y, enemy_rect)) {
|
|
startDrag(DragTarget::ENEMY_INITIAL, i, enemy_rect.x, enemy_rect.y);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 3. Hit test sobre boundaries de enemigos (rectángulos 8x8 en las posiciones de room_data_)
|
|
for (int i = 0; i < static_cast<int>(room_data_.enemies.size()); ++i) {
|
|
const auto& ed = room_data_.enemies[i];
|
|
constexpr float SZ = static_cast<float>(Tile::SIZE);
|
|
|
|
SDL_FRect b1_rect = {.x = static_cast<float>(ed.x1), .y = static_cast<float>(ed.y1), .w = SZ, .h = SZ};
|
|
if (pointInRect(game_x, game_y, b1_rect)) {
|
|
startDrag(DragTarget::ENEMY_BOUND1, i, b1_rect.x, b1_rect.y);
|
|
return;
|
|
}
|
|
|
|
SDL_FRect b2_rect = {.x = static_cast<float>(ed.x2), .y = static_cast<float>(ed.y2), .w = SZ, .h = SZ};
|
|
if (pointInRect(game_x, game_y, b2_rect)) {
|
|
startDrag(DragTarget::ENEMY_BOUND2, i, b2_rect.x, b2_rect.y);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 4. Hit test sobre items (8x8)
|
|
auto* item_mgr = room_->getItemManager();
|
|
for (int i = 0; i < item_mgr->getCount(); ++i) {
|
|
SDL_FRect item_rect = item_mgr->getItem(i)->getCollider();
|
|
if (pointInRect(game_x, game_y, item_rect)) {
|
|
startDrag(DragTarget::ITEM, i, item_rect.x, item_rect.y);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Click en el fondo: deseleccionar
|
|
selected_enemy_ = -1;
|
|
}
|
|
|
|
// Procesa soltar el ratón: commit del drag
|
|
void MapEditor::handleMouseUp() {
|
|
if (drag_.target == DragTarget::NONE) { return; }
|
|
|
|
const int IDX = drag_.index;
|
|
|
|
// Si no se movió: fue un click → seleccionar/deseleccionar
|
|
if (!drag_.moved) {
|
|
if (drag_.target == DragTarget::ENEMY_INITIAL) {
|
|
// Toggle selección: si ya estaba seleccionado, deseleccionar
|
|
selected_enemy_ = (selected_enemy_ == IDX) ? -1 : IDX;
|
|
} else {
|
|
// Click en otro tipo de entidad: deseleccionar enemigo
|
|
selected_enemy_ = -1;
|
|
}
|
|
drag_ = {};
|
|
return;
|
|
}
|
|
|
|
// Hubo movimiento: commit del drag
|
|
const int SNAP_X = static_cast<int>(drag_.snap_x);
|
|
const int SNAP_Y = static_cast<int>(drag_.snap_y);
|
|
bool changed = false;
|
|
|
|
switch (drag_.target) {
|
|
case DragTarget::PLAYER:
|
|
player_->setDebugPosition(drag_.snap_x, drag_.snap_y);
|
|
player_->finalizeDebugTeleport();
|
|
break;
|
|
|
|
case DragTarget::ENEMY_INITIAL:
|
|
if (IDX >= 0 && IDX < static_cast<int>(room_data_.enemies.size())) {
|
|
room_data_.enemies[IDX].x = drag_.snap_x;
|
|
room_data_.enemies[IDX].y = drag_.snap_y;
|
|
room_->getEnemyManager()->getEnemy(IDX)->resetToInitialPosition(room_data_.enemies[IDX]);
|
|
selected_enemy_ = IDX; // Seleccionar el enemigo arrastrado
|
|
changed = true;
|
|
}
|
|
break;
|
|
|
|
case DragTarget::ENEMY_BOUND1:
|
|
if (IDX >= 0 && IDX < static_cast<int>(room_data_.enemies.size())) {
|
|
room_data_.enemies[IDX].x1 = SNAP_X;
|
|
room_data_.enemies[IDX].y1 = SNAP_Y;
|
|
selected_enemy_ = IDX;
|
|
changed = true;
|
|
}
|
|
break;
|
|
|
|
case DragTarget::ENEMY_BOUND2:
|
|
if (IDX >= 0 && IDX < static_cast<int>(room_data_.enemies.size())) {
|
|
room_data_.enemies[IDX].x2 = SNAP_X;
|
|
room_data_.enemies[IDX].y2 = SNAP_Y;
|
|
selected_enemy_ = IDX;
|
|
changed = true;
|
|
}
|
|
break;
|
|
|
|
case DragTarget::ITEM:
|
|
if (IDX >= 0 && IDX < room_->getItemManager()->getCount()) {
|
|
room_->getItemManager()->getItem(IDX)->setPosition(drag_.snap_x, drag_.snap_y);
|
|
selected_enemy_ = -1;
|
|
changed = true;
|
|
}
|
|
break;
|
|
|
|
case DragTarget::NONE:
|
|
break;
|
|
}
|
|
|
|
if (changed) { autosave(); }
|
|
drag_ = {};
|
|
}
|
|
|
|
// Actualiza la posición snapped durante el drag
|
|
void MapEditor::updateDrag() {
|
|
float raw_x = mouse_game_x_ - drag_.offset_x;
|
|
float raw_y = mouse_game_y_ - drag_.offset_y;
|
|
|
|
float new_snap_x = snapToGrid(raw_x);
|
|
float new_snap_y = snapToGrid(raw_y);
|
|
|
|
// Detectar si hubo movimiento real (el snap cambió respecto al inicio)
|
|
if (new_snap_x != drag_.snap_x || new_snap_y != drag_.snap_y) {
|
|
drag_.moved = true;
|
|
}
|
|
|
|
drag_.snap_x = new_snap_x;
|
|
drag_.snap_y = new_snap_y;
|
|
|
|
// Mientras arrastramos, mover la entidad visualmente a la posición snapped
|
|
switch (drag_.target) {
|
|
case DragTarget::PLAYER:
|
|
player_->setDebugPosition(drag_.snap_x, drag_.snap_y);
|
|
break;
|
|
|
|
case DragTarget::ENEMY_INITIAL:
|
|
if (drag_.index >= 0 && drag_.index < room_->getEnemyManager()->getCount()) {
|
|
// Mover el sprite vivo del enemigo durante el arrastre
|
|
auto& enemy = room_->getEnemyManager()->getEnemy(drag_.index);
|
|
Enemy::Data temp_data = room_data_.enemies[drag_.index];
|
|
temp_data.x = drag_.snap_x;
|
|
temp_data.y = drag_.snap_y;
|
|
enemy->resetToInitialPosition(temp_data);
|
|
}
|
|
break;
|
|
|
|
case DragTarget::ENEMY_BOUND1:
|
|
// Los boundaries se actualizan visualmente en renderEnemyBoundaries() via drag_.snap
|
|
break;
|
|
|
|
case DragTarget::ENEMY_BOUND2:
|
|
break;
|
|
|
|
case DragTarget::ITEM:
|
|
if (drag_.index >= 0 && drag_.index < room_->getItemManager()->getCount()) {
|
|
room_->getItemManager()->getItem(drag_.index)->setPosition(drag_.snap_x, drag_.snap_y);
|
|
}
|
|
break;
|
|
|
|
case DragTarget::NONE:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Dibuja highlight del elemento seleccionado/arrastrado
|
|
void MapEditor::renderSelectionHighlight() {
|
|
auto game_surface = Screen::get()->getRendererSurface();
|
|
if (!game_surface) { return; }
|
|
|
|
constexpr float SZ = static_cast<float>(Tile::SIZE);
|
|
|
|
// Highlight del enemigo seleccionado (persistente, color bright_green)
|
|
if (selected_enemy_ >= 0 && selected_enemy_ < room_->getEnemyManager()->getCount()) {
|
|
SDL_FRect enemy_rect = room_->getEnemyManager()->getEnemy(selected_enemy_)->getRect();
|
|
SDL_FRect border = {
|
|
.x = enemy_rect.x - 1,
|
|
.y = enemy_rect.y - 1,
|
|
.w = enemy_rect.w + 2,
|
|
.h = enemy_rect.h + 2};
|
|
game_surface->drawRectBorder(&border, stringToColor("bright_green"));
|
|
}
|
|
|
|
// Highlight del drag activo (temporal, color bright_white)
|
|
if (drag_.target == DragTarget::NONE || !drag_.moved) { return; }
|
|
|
|
const Uint8 DRAG_COLOR = stringToColor("bright_white");
|
|
SDL_FRect highlight_rect{};
|
|
|
|
switch (drag_.target) {
|
|
case DragTarget::PLAYER:
|
|
highlight_rect = player_->getRect();
|
|
break;
|
|
case DragTarget::ENEMY_INITIAL:
|
|
if (drag_.index >= 0 && drag_.index < room_->getEnemyManager()->getCount()) {
|
|
highlight_rect = room_->getEnemyManager()->getEnemy(drag_.index)->getRect();
|
|
}
|
|
break;
|
|
case DragTarget::ENEMY_BOUND1:
|
|
case DragTarget::ENEMY_BOUND2:
|
|
highlight_rect = {.x = drag_.snap_x, .y = drag_.snap_y, .w = SZ, .h = SZ};
|
|
break;
|
|
case DragTarget::ITEM:
|
|
if (drag_.index >= 0 && drag_.index < room_->getItemManager()->getCount()) {
|
|
highlight_rect = room_->getItemManager()->getItem(drag_.index)->getCollider();
|
|
}
|
|
break;
|
|
case DragTarget::NONE:
|
|
return;
|
|
}
|
|
|
|
SDL_FRect border = {
|
|
.x = highlight_rect.x - 1,
|
|
.y = highlight_rect.y - 1,
|
|
.w = highlight_rect.w + 2,
|
|
.h = highlight_rect.h + 2};
|
|
game_surface->drawRectBorder(&border, DRAG_COLOR);
|
|
}
|
|
|
|
// Alinea un valor a la cuadrícula de 8x8
|
|
auto MapEditor::snapToGrid(float value) -> float {
|
|
return std::round(value / static_cast<float>(Tile::SIZE)) * static_cast<float>(Tile::SIZE);
|
|
}
|
|
|
|
// Hit test: punto dentro de rectángulo
|
|
auto MapEditor::pointInRect(float px, float py, const SDL_FRect& rect) -> bool {
|
|
return px >= rect.x && px < rect.x + rect.w && py >= rect.y && py < rect.y + rect.h;
|
|
}
|
|
|
|
// Dibuja marcadores de boundaries y líneas de ruta para los enemigos
|
|
void MapEditor::renderEnemyBoundaries() {
|
|
auto game_surface = Screen::get()->getRendererSurface();
|
|
if (!game_surface) { return; }
|
|
|
|
const Uint8 COLOR_BOUND1 = stringToColor("bright_cyan");
|
|
const Uint8 COLOR_BOUND2 = stringToColor("bright_yellow");
|
|
const Uint8 COLOR_ROUTE = stringToColor("bright_white");
|
|
|
|
for (int i = 0; i < static_cast<int>(room_data_.enemies.size()); ++i) {
|
|
const auto& enemy = room_data_.enemies[i];
|
|
constexpr float HALF = Tile::SIZE / 2.0F;
|
|
|
|
// Posiciones base (pueden estar siendo arrastradas)
|
|
float init_x = enemy.x;
|
|
float init_y = enemy.y;
|
|
float b1_x = static_cast<float>(enemy.x1);
|
|
float b1_y = static_cast<float>(enemy.y1);
|
|
float b2_x = static_cast<float>(enemy.x2);
|
|
float b2_y = static_cast<float>(enemy.y2);
|
|
|
|
// Si estamos arrastrando una boundary de este enemigo, usar la posición snapped
|
|
if (drag_.index == i) {
|
|
if (drag_.target == DragTarget::ENEMY_BOUND1) {
|
|
b1_x = drag_.snap_x;
|
|
b1_y = drag_.snap_y;
|
|
} else if (drag_.target == DragTarget::ENEMY_BOUND2) {
|
|
b2_x = drag_.snap_x;
|
|
b2_y = drag_.snap_y;
|
|
} else if (drag_.target == DragTarget::ENEMY_INITIAL) {
|
|
init_x = drag_.snap_x;
|
|
init_y = drag_.snap_y;
|
|
}
|
|
}
|
|
|
|
// Dibujar líneas de ruta
|
|
game_surface->drawLine(b1_x + HALF, b1_y + HALF, init_x + HALF, init_y + HALF, COLOR_ROUTE);
|
|
game_surface->drawLine(init_x + HALF, init_y + HALF, b2_x + HALF, b2_y + HALF, COLOR_ROUTE);
|
|
|
|
// Marcadores en las boundaries
|
|
renderBoundaryMarker(b1_x, b1_y, COLOR_BOUND1);
|
|
renderBoundaryMarker(b2_x, b2_y, COLOR_BOUND2);
|
|
}
|
|
}
|
|
|
|
// Dibuja un marcador de boundary (rectángulo pequeño) en una posición
|
|
void MapEditor::renderBoundaryMarker(float x, float y, Uint8 color) {
|
|
auto game_surface = Screen::get()->getRendererSurface();
|
|
if (!game_surface) { return; }
|
|
|
|
SDL_FRect marker = {.x = x, .y = y, .w = static_cast<float>(Tile::SIZE), .h = static_cast<float>(Tile::SIZE)};
|
|
game_surface->drawRectBorder(&marker, color);
|
|
}
|
|
|
|
// Convierte coordenadas de ventana a coordenadas de juego y tile
|
|
void MapEditor::updateMousePosition() {
|
|
float mouse_x = 0.0F;
|
|
float mouse_y = 0.0F;
|
|
SDL_GetMouseState(&mouse_x, &mouse_y);
|
|
|
|
float render_x = 0.0F;
|
|
float render_y = 0.0F;
|
|
SDL_RenderCoordinatesFromWindow(Screen::get()->getRenderer(), mouse_x, mouse_y, &render_x, &render_y);
|
|
|
|
SDL_FRect dst_rect = Screen::get()->getGameSurfaceDstRect();
|
|
mouse_game_x_ = render_x - dst_rect.x;
|
|
mouse_game_y_ = render_y - dst_rect.y;
|
|
|
|
// Convertir a coordenadas de tile (clampeadas al área de juego)
|
|
mouse_tile_x_ = static_cast<int>(mouse_game_x_) / Tile::SIZE;
|
|
mouse_tile_y_ = static_cast<int>(mouse_game_y_) / Tile::SIZE;
|
|
|
|
// Clampear a los límites del mapa
|
|
if (mouse_tile_x_ < 0) { mouse_tile_x_ = 0; }
|
|
if (mouse_tile_x_ >= PlayArea::WIDTH / Tile::SIZE) { mouse_tile_x_ = PlayArea::WIDTH / Tile::SIZE - 1; }
|
|
if (mouse_tile_y_ < 0) { mouse_tile_y_ = 0; }
|
|
if (mouse_tile_y_ >= PlayArea::HEIGHT / Tile::SIZE) { mouse_tile_y_ = PlayArea::HEIGHT / Tile::SIZE - 1; }
|
|
}
|
|
|
|
// Actualiza la información de la barra de estado
|
|
void MapEditor::updateStatusBarInfo() {
|
|
if (!statusbar_) { return; }
|
|
|
|
statusbar_->setMouseTile(mouse_tile_x_, mouse_tile_y_);
|
|
|
|
std::string sel_info;
|
|
std::string sel_detail;
|
|
|
|
// Info de drag activo (prioridad)
|
|
if (drag_.target != DragTarget::NONE && drag_.moved) {
|
|
switch (drag_.target) {
|
|
case DragTarget::PLAYER: sel_info = "player"; break;
|
|
case DragTarget::ENEMY_INITIAL: sel_info = "enemy " + std::to_string(drag_.index); break;
|
|
case DragTarget::ENEMY_BOUND1: sel_info = "e" + std::to_string(drag_.index) + " bound1"; break;
|
|
case DragTarget::ENEMY_BOUND2: sel_info = "e" + std::to_string(drag_.index) + " bound2"; break;
|
|
case DragTarget::ITEM: sel_info = "item " + std::to_string(drag_.index); break;
|
|
case DragTarget::NONE: break;
|
|
}
|
|
}
|
|
// Info de enemigo seleccionado (persistente)
|
|
else if (selected_enemy_ >= 0 && selected_enemy_ < static_cast<int>(room_data_.enemies.size())) {
|
|
const auto& e = room_data_.enemies[selected_enemy_];
|
|
// Extraer nombre corto de la animación (sin .yaml)
|
|
std::string anim = e.animation_path;
|
|
auto dot = anim.rfind('.');
|
|
if (dot != std::string::npos) { anim = anim.substr(0, dot); }
|
|
|
|
sel_info = "enemy " + std::to_string(selected_enemy_) + ": " + anim;
|
|
sel_detail = e.color + " vx:" + std::to_string(static_cast<int>(e.vx)) +
|
|
" vy:" + std::to_string(static_cast<int>(e.vy));
|
|
if (e.flip) { sel_detail += " flip"; }
|
|
}
|
|
|
|
statusbar_->setSelectionInfo(sel_info);
|
|
statusbar_->setSelectionDetail(sel_detail);
|
|
|
|
// Actualizar el prompt de la consola según la selección
|
|
if (selected_enemy_ >= 0) {
|
|
Console::get()->setPrompt("enemy " + std::to_string(selected_enemy_) + "> ");
|
|
} else {
|
|
Console::get()->setPrompt("> ");
|
|
}
|
|
}
|
|
|
|
// ¿Hay un enemigo seleccionado?
|
|
auto MapEditor::hasSelectedEnemy() const -> bool {
|
|
return selected_enemy_ >= 0 && selected_enemy_ < static_cast<int>(room_data_.enemies.size());
|
|
}
|
|
|
|
// Modifica una propiedad del enemigo seleccionado
|
|
auto MapEditor::setEnemyProperty(const std::string& property, const std::string& value) -> std::string {
|
|
if (!active_) { return "Editor not active"; }
|
|
if (!hasSelectedEnemy()) { return "No enemy selected"; }
|
|
|
|
auto& enemy = room_data_.enemies[selected_enemy_];
|
|
|
|
if (property == "ANIMATION") {
|
|
std::string anim = toLower(value);
|
|
if (anim.find('.') == std::string::npos) { anim += ".yaml"; }
|
|
|
|
// Intentar recrear el enemigo con la nueva animación
|
|
std::string old_anim = enemy.animation_path;
|
|
enemy.animation_path = anim;
|
|
try {
|
|
auto* enemy_mgr = room_->getEnemyManager();
|
|
enemy_mgr->getEnemy(selected_enemy_) = std::make_shared<Enemy>(enemy);
|
|
} catch (const std::exception& e) {
|
|
enemy.animation_path = old_anim; // Restaurar si falla
|
|
return std::string("Error: ") + e.what();
|
|
}
|
|
|
|
autosave();
|
|
return "animation: " + anim;
|
|
}
|
|
|
|
if (property == "COLOR") {
|
|
std::string color = toLower(value);
|
|
|
|
// Intentar recrear el enemigo con el nuevo color
|
|
std::string old_color = enemy.color;
|
|
enemy.color = color;
|
|
try {
|
|
auto* enemy_mgr = room_->getEnemyManager();
|
|
enemy_mgr->getEnemy(selected_enemy_) = std::make_shared<Enemy>(enemy);
|
|
} catch (const std::exception& e) {
|
|
enemy.color = old_color;
|
|
return std::string("Error: ") + e.what();
|
|
}
|
|
|
|
autosave();
|
|
return "color: " + color;
|
|
}
|
|
|
|
if (property == "VX") {
|
|
try {
|
|
enemy.vx = std::stof(value);
|
|
} catch (...) { return "Invalid value: " + value; }
|
|
enemy.vy = 0.0F; // No se permiten velocidades en los dos ejes
|
|
|
|
room_->getEnemyManager()->getEnemy(selected_enemy_)->resetToInitialPosition(enemy);
|
|
autosave();
|
|
return "vx: " + std::to_string(static_cast<int>(enemy.vx)) + " vy: 0";
|
|
}
|
|
|
|
if (property == "VY") {
|
|
try {
|
|
enemy.vy = std::stof(value);
|
|
} catch (...) { return "Invalid value: " + value; }
|
|
enemy.vx = 0.0F; // No se permiten velocidades en los dos ejes
|
|
|
|
room_->getEnemyManager()->getEnemy(selected_enemy_)->resetToInitialPosition(enemy);
|
|
autosave();
|
|
return "vy: " + std::to_string(static_cast<int>(enemy.vy)) + " vx: 0";
|
|
}
|
|
|
|
if (property == "FLIP") {
|
|
enemy.flip = (value == "ON" || value == "TRUE" || value == "1");
|
|
|
|
room_->getEnemyManager()->getEnemy(selected_enemy_)->resetToInitialPosition(enemy);
|
|
autosave();
|
|
return std::string("flip: ") + (enemy.flip ? "on" : "off");
|
|
}
|
|
|
|
return "Unknown property: " + property + " (use: animation, color, vx, vy, flip)";
|
|
}
|
|
|
|
#endif // _DEBUG
|