Files
jaildoctors_dilemma/source/game/editor/map_editor.cpp
2026-04-02 10:39:57 +02:00

303 lines
9.8 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 "game/editor/editor_statusbar.hpp" // Para EditorStatusBar
#include "game/entities/player.hpp" // Para Player
#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 futura)
auto room_data_ptr = Resource::Cache::get()->getRoom(room_path);
if (room_data_ptr) {
room_data_ = *room_data_ptr;
}
// Guardar estado de invencibilidad y forzarla
invincible_before_editor_ = Options::cheats.invincible;
Options::cheats.invincible = Options::Cheat::State::ENABLED;
player_->setColor();
// Crear la barra de estado
statusbar_ = std::make_unique<EditorStatusBar>(room_->getNumber(), room_->getName());
// Resetear estado de drag
drag_ = {};
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();
// Liberar recursos
drag_ = {};
statusbar_.reset();
room_.reset();
player_.reset();
scoreboard_data_.reset();
std::cout << "MapEditor: OFF\n";
}
// 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 con las coordenadas del ratón
if (statusbar_) {
statusbar_->setMouseTile(mouse_tile_x_, mouse_tile_y_);
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 → items
// 1. Hit test sobre el jugador
SDL_FRect player_rect = player_->getRect();
if (pointInRect(game_x, game_y, player_rect)) {
drag_.target = DragTarget::PLAYER;
drag_.index = -1;
drag_.offset_x = game_x - player_rect.x;
drag_.offset_y = game_y - player_rect.y;
drag_.snap_x = player_rect.x;
drag_.snap_y = player_rect.y;
return;
}
// (Fases 4+: hit test sobre enemigos e items)
}
// Procesa soltar el ratón: commit del drag
void MapEditor::handleMouseUp() {
if (drag_.target == DragTarget::NONE) { return; }
switch (drag_.target) {
case DragTarget::PLAYER:
// Mover el jugador a la posición snapped
player_->setDebugPosition(drag_.snap_x, drag_.snap_y);
player_->finalizeDebugTeleport();
break;
default:
break;
}
// Resetear estado de drag
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;
drag_.snap_x = snapToGrid(raw_x);
drag_.snap_y = snapToGrid(raw_y);
// Mientras arrastramos, mover la entidad visualmente a la posición snapped
if (drag_.target == DragTarget::PLAYER) {
player_->setDebugPosition(drag_.snap_x, drag_.snap_y);
}
}
// Dibuja highlight del elemento seleccionado/arrastrado
void MapEditor::renderSelectionHighlight() {
if (drag_.target == DragTarget::NONE) { return; }
auto game_surface = Screen::get()->getRendererSurface();
if (!game_surface) { return; }
const Uint8 HIGHLIGHT_COLOR = stringToColor("bright_white");
SDL_FRect highlight_rect{};
switch (drag_.target) {
case DragTarget::PLAYER:
highlight_rect = player_->getRect();
break;
default:
return;
}
// Dibujar rectángulo de highlight alrededor de la entidad
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, HIGHLIGHT_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 (const auto& enemy : room_data_.enemies) {
// Dibujar línea de ruta: boundary1 → posición inicial → boundary2
constexpr float HALF = Tile::SIZE / 2.0F;
float init_cx = enemy.x + HALF;
float init_cy = enemy.y + HALF;
float b1_cx = static_cast<float>(enemy.x1) + HALF;
float b1_cy = static_cast<float>(enemy.y1) + HALF;
float b2_cx = static_cast<float>(enemy.x2) + HALF;
float b2_cy = static_cast<float>(enemy.y2) + HALF;
// Línea de boundary1 a posición inicial
game_surface->drawLine(b1_cx, b1_cy, init_cx, init_cy, COLOR_ROUTE);
// Línea de posición inicial a boundary2
game_surface->drawLine(init_cx, init_cy, b2_cx, b2_cy, COLOR_ROUTE);
// Marcadores en las boundaries
renderBoundaryMarker(static_cast<float>(enemy.x1), static_cast<float>(enemy.y1), COLOR_BOUND1);
renderBoundaryMarker(static_cast<float>(enemy.x2), static_cast<float>(enemy.y2), 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; }
}
#endif // _DEBUG