#ifdef _DEBUG #include "game/editor/map_editor.hpp" #include #include // Para std::round #include // 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, std::shared_ptr player, const std::string& room_path, std::shared_ptr 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(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(Tile::SIZE)) * static_cast(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(enemy.x1) + HALF; float b1_cy = static_cast(enemy.y1) + HALF; float b2_cx = static_cast(enemy.x2) + HALF; float b2_cy = static_cast(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(enemy.x1), static_cast(enemy.y1), COLOR_BOUND1); renderBoundaryMarker(static_cast(enemy.x2), static_cast(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(Tile::SIZE), .h = static_cast(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(mouse_game_x_) / Tile::SIZE; mouse_tile_y_ = static_cast(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