392 lines
15 KiB
C++
392 lines
15 KiB
C++
#ifdef _DEBUG
|
|
|
|
#include "game/editor/mini_map.hpp"
|
|
|
|
#include <algorithm> // Para std::max, std::min
|
|
#include <array> // Para std::array
|
|
#include <cmath> // Para std::floor
|
|
#include <iostream> // Para cout
|
|
#include <map> // Para std::map
|
|
#include <queue> // Para queue (BFS)
|
|
#include <set> // Para set
|
|
|
|
#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/gameplay/room.hpp" // Para Room::Data
|
|
#include "utils/defines.hpp" // Para Tile::SIZE, PlayArea
|
|
#include "utils/utils.hpp" // Para stringToColor
|
|
|
|
// Constructor: construye todo el minimapa
|
|
MiniMap::MiniMap(Uint8 bg_color, Uint8 conn_color)
|
|
: bg_color_(bg_color),
|
|
conn_color_(conn_color) {
|
|
buildTileColorTable("standard.gif");
|
|
layoutRooms();
|
|
buildRoomSurfaces();
|
|
composeFinalSurface();
|
|
}
|
|
|
|
// Regenera la surface final con nuevo color de fondo
|
|
void MiniMap::rebuild(Uint8 bg_color, Uint8 conn_color) {
|
|
bg_color_ = bg_color;
|
|
conn_color_ = conn_color;
|
|
composeFinalSurface();
|
|
}
|
|
|
|
// Analiza el tileset y crea tabla: tile_index → color predominante
|
|
void MiniMap::buildTileColorTable(const std::string& tileset_name) {
|
|
auto tileset = Resource::Cache::get()->getSurface(tileset_name);
|
|
if (!tileset) { return; }
|
|
|
|
tileset_width_ = static_cast<int>(tileset->getWidth()) / Tile::SIZE;
|
|
tileset_transparent_ = tileset->getTransparentColor();
|
|
int tileset_height = static_cast<int>(tileset->getHeight()) / Tile::SIZE;
|
|
int total_tiles = tileset_width_ * tileset_height;
|
|
|
|
tile_colors_.resize(total_tiles, 0);
|
|
|
|
for (int tile = 0; tile < total_tiles; ++tile) {
|
|
int tile_x = (tile % tileset_width_) * Tile::SIZE;
|
|
int tile_y = (tile / tileset_width_) * Tile::SIZE;
|
|
|
|
// Contar frecuencia de cada color en el tile (ignorar el color transparente del tileset)
|
|
Uint8 transparent = tileset->getTransparentColor();
|
|
std::array<int, 256> freq{};
|
|
for (int y = 0; y < Tile::SIZE; ++y) {
|
|
for (int x = 0; x < Tile::SIZE; ++x) {
|
|
Uint8 pixel = tileset->getPixel(tile_x + x, tile_y + y);
|
|
if (pixel != transparent) {
|
|
freq[pixel]++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Encontrar el color más frecuente (transparent = tile vacío, no se pinta)
|
|
Uint8 best_color = transparent;
|
|
int best_count = 0;
|
|
for (int c = 0; c < 256; ++c) {
|
|
if (c == transparent) { continue; }
|
|
if (freq[c] > best_count) {
|
|
best_count = freq[c];
|
|
best_color = static_cast<Uint8>(c);
|
|
}
|
|
}
|
|
|
|
tile_colors_[tile] = best_color;
|
|
}
|
|
}
|
|
|
|
// Posiciona las rooms en un grid usando BFS desde las conexiones
|
|
void MiniMap::layoutRooms() {
|
|
auto& rooms = Resource::Cache::get()->getRooms();
|
|
if (rooms.empty()) { return; }
|
|
|
|
// Mapa de nombre → Room::Data
|
|
std::unordered_map<std::string, std::shared_ptr<Room::Data>> room_map;
|
|
for (const auto& r : rooms) {
|
|
room_map[r.name] = r.room;
|
|
}
|
|
|
|
// BFS para posicionar rooms
|
|
std::set<std::string> visited;
|
|
std::queue<std::pair<std::string, GridPos>> bfs;
|
|
|
|
// Empezar por la primera room
|
|
const std::string& start = rooms[0].name;
|
|
bfs.push({start, {0, 0}});
|
|
visited.insert(start);
|
|
|
|
// Grid ocupado: posición → nombre de room
|
|
std::map<std::pair<int, int>, std::string> grid_occupied;
|
|
|
|
while (!bfs.empty()) {
|
|
auto [name, pos] = bfs.front();
|
|
bfs.pop();
|
|
|
|
auto key = std::make_pair(pos.x, pos.y);
|
|
if (grid_occupied.contains(key)) { continue; }
|
|
|
|
grid_occupied[key] = name;
|
|
room_positions_[name] = RoomMini{.surface = nullptr, .pos = pos};
|
|
|
|
auto it = room_map.find(name);
|
|
if (it == room_map.end()) { continue; }
|
|
const auto& data = it->second;
|
|
|
|
// Vecinos: up, down, left, right
|
|
struct Neighbor {
|
|
std::string room;
|
|
int dx, dy;
|
|
};
|
|
std::array<Neighbor, 4> neighbors = {{
|
|
{data->upper_room, 0, -1},
|
|
{data->lower_room, 0, 1},
|
|
{data->left_room, -1, 0},
|
|
{data->right_room, 1, 0},
|
|
}};
|
|
|
|
for (const auto& [neighbor_name, dx, dy] : neighbors) {
|
|
if (neighbor_name == "0" || neighbor_name.empty()) { continue; }
|
|
if (visited.contains(neighbor_name)) { continue; }
|
|
|
|
GridPos neighbor_pos = {pos.x + dx, pos.y + dy};
|
|
auto nkey = std::make_pair(neighbor_pos.x, neighbor_pos.y);
|
|
if (!grid_occupied.contains(nkey)) {
|
|
visited.insert(neighbor_name);
|
|
bfs.push({neighbor_name, neighbor_pos});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calcular bounds del grid
|
|
min_grid_x_ = 0;
|
|
min_grid_y_ = 0;
|
|
int max_grid_x = 0;
|
|
int max_grid_y = 0;
|
|
for (const auto& [name, mini] : room_positions_) {
|
|
min_grid_x_ = std::min(min_grid_x_, mini.pos.x);
|
|
min_grid_y_ = std::min(min_grid_y_, mini.pos.y);
|
|
max_grid_x = std::max(max_grid_x, mini.pos.x);
|
|
max_grid_y = std::max(max_grid_y, mini.pos.y);
|
|
}
|
|
|
|
int cols = max_grid_x - min_grid_x_ + 1;
|
|
int rows = max_grid_y - min_grid_y_ + 1;
|
|
map_width_ = cols * (CELL_W + GAP) - GAP + PADDING * 2;
|
|
map_height_ = rows * (CELL_H + GAP) - GAP + PADDING * 2;
|
|
|
|
std::cout << "MiniMap: " << room_positions_.size() << " rooms, grid " << cols << "x" << rows
|
|
<< " → " << map_width_ << "x" << map_height_ << " px\n";
|
|
}
|
|
|
|
// Genera una mini-surface de 32x16 por room
|
|
void MiniMap::buildRoomSurfaces() {
|
|
for (auto& [name, mini] : room_positions_) {
|
|
mini.surface = getRoomMiniSurface(name);
|
|
}
|
|
}
|
|
|
|
// Genera la mini-surface de una room: 1 pixel por tile, color predominante
|
|
auto MiniMap::getRoomMiniSurface(const std::string& room_name) -> std::shared_ptr<Surface> {
|
|
auto room_data = Resource::Cache::get()->getRoom(room_name);
|
|
if (!room_data) { return nullptr; }
|
|
|
|
auto surface = std::make_shared<Surface>(ROOM_W, ROOM_H);
|
|
|
|
auto prev = Screen::get()->getRendererSurface();
|
|
Screen::get()->setRendererSurface(surface);
|
|
surface->clear(stringToColor(room_data->bg_color));
|
|
|
|
const auto& tile_map = room_data->tile_map;
|
|
for (int y = 0; y < ROOM_H; ++y) {
|
|
for (int x = 0; x < ROOM_W; ++x) {
|
|
int index = y * ROOM_W + x;
|
|
if (index >= static_cast<int>(tile_map.size())) { continue; }
|
|
|
|
int tile = tile_map[index];
|
|
if (tile < 0 || tile >= static_cast<int>(tile_colors_.size())) { continue; }
|
|
|
|
Uint8 color = tile_colors_[tile];
|
|
if (color != tileset_transparent_) {
|
|
surface->putPixel(x, y, color);
|
|
}
|
|
}
|
|
}
|
|
|
|
Screen::get()->setRendererSurface(prev);
|
|
return surface;
|
|
}
|
|
|
|
// Compone la surface final con todas las rooms posicionadas
|
|
void MiniMap::composeFinalSurface() {
|
|
if (map_width_ <= 0 || map_height_ <= 0) { return; }
|
|
|
|
// Surface un poco más grande para la sombra del borde inferior/derecho
|
|
map_surface_ = std::make_shared<Surface>(map_width_ + SHADOW_OFFSET, map_height_ + SHADOW_OFFSET);
|
|
|
|
auto prev = Screen::get()->getRendererSurface();
|
|
Screen::get()->setRendererSurface(map_surface_);
|
|
|
|
// 1. Fondo general
|
|
map_surface_->clear(bg_color_);
|
|
|
|
// 2. Líneas de conexión entre rooms (debajo de todo)
|
|
drawConnections();
|
|
|
|
// 3. Sombras de las rooms (desplazadas 1px abajo-derecha)
|
|
for (const auto& [name, mini] : room_positions_) {
|
|
if (!mini.surface) { continue; }
|
|
int px = cellPixelX(mini.pos.x) + SHADOW_OFFSET;
|
|
int py = cellPixelY(mini.pos.y) + SHADOW_OFFSET;
|
|
SDL_FRect shadow = {.x = static_cast<float>(px), .y = static_cast<float>(py), .w = static_cast<float>(CELL_W), .h = static_cast<float>(CELL_H)};
|
|
map_surface_->fillRect(&shadow, COLOR_SHADOW);
|
|
}
|
|
|
|
// 4. Borde negro de cada room + contenido de la room
|
|
for (const auto& [name, mini] : room_positions_) {
|
|
if (!mini.surface) { continue; }
|
|
int px = cellPixelX(mini.pos.x);
|
|
int py = cellPixelY(mini.pos.y);
|
|
|
|
// Borde negro (la celda entera)
|
|
SDL_FRect cell = {.x = static_cast<float>(px), .y = static_cast<float>(py), .w = static_cast<float>(CELL_W), .h = static_cast<float>(CELL_H)};
|
|
map_surface_->fillRect(&cell, COLOR_ROOM_BORDER);
|
|
|
|
// Miniroom dentro del borde
|
|
SDL_FRect dst = {.x = static_cast<float>(px + BORDER), .y = static_cast<float>(py + BORDER), .w = static_cast<float>(ROOM_W), .h = static_cast<float>(ROOM_H)};
|
|
mini.surface->render(nullptr, &dst);
|
|
}
|
|
|
|
Screen::get()->setRendererSurface(prev);
|
|
}
|
|
|
|
// Dibuja las líneas de conexión entre rooms vecinas
|
|
void MiniMap::drawConnections() {
|
|
for (const auto& [name, mini] : room_positions_) {
|
|
auto room_data = Resource::Cache::get()->getRoom(name);
|
|
if (!room_data) { continue; }
|
|
|
|
int px = cellPixelX(mini.pos.x);
|
|
int py = cellPixelY(mini.pos.y);
|
|
|
|
// Conexión derecha
|
|
if (room_data->right_room != "0" && !room_data->right_room.empty() && room_positions_.contains(room_data->right_room)) {
|
|
int x1 = px + CELL_W;
|
|
int y_mid = py + CELL_H / 2 - 1;
|
|
SDL_FRect line = {.x = static_cast<float>(x1), .y = static_cast<float>(y_mid), .w = static_cast<float>(GAP), .h = 3.0F};
|
|
map_surface_->fillRect(&line, conn_color_);
|
|
}
|
|
|
|
// Conexión abajo
|
|
if (room_data->lower_room != "0" && !room_data->lower_room.empty() && room_positions_.contains(room_data->lower_room)) {
|
|
int x_mid = px + CELL_W / 2 - 1;
|
|
int y1 = py + CELL_H;
|
|
SDL_FRect line = {.x = static_cast<float>(x_mid), .y = static_cast<float>(y1), .w = 3.0F, .h = static_cast<float>(GAP)};
|
|
map_surface_->fillRect(&line, conn_color_);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Centra el viewport en una room
|
|
void MiniMap::centerOnRoom(const std::string& room_name) {
|
|
auto it = room_positions_.find(room_name);
|
|
if (it == room_positions_.end()) { return; }
|
|
const auto& pos = it->second.pos;
|
|
|
|
float room_cx = static_cast<float>(cellPixelX(pos.x) + CELL_W / 2);
|
|
float room_cy = static_cast<float>(cellPixelY(pos.y) + CELL_H / 2);
|
|
view_x_ = static_cast<float>(PlayArea::WIDTH) / 2.0F - room_cx;
|
|
view_y_ = static_cast<float>(PlayArea::HEIGHT) / 2.0F - room_cy;
|
|
}
|
|
|
|
// Devuelve el nombre de la room en una posición de pantalla, o vacío si no hay ninguna
|
|
auto MiniMap::roomAtScreen(float screen_x, float screen_y) -> std::string {
|
|
// Convertir coordenada de pantalla a coordenada dentro del minimapa
|
|
float map_x = screen_x - view_x_;
|
|
float map_y = screen_y - view_y_;
|
|
|
|
for (const auto& [name, mini] : room_positions_) {
|
|
float rx = static_cast<float>(cellPixelX(mini.pos.x));
|
|
float ry = static_cast<float>(cellPixelY(mini.pos.y));
|
|
if (map_x >= rx && map_x < rx + CELL_W && map_y >= ry && map_y < ry + CELL_H) {
|
|
return name;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
// Renderiza el minimapa
|
|
void MiniMap::render(const std::string& current_room) {
|
|
if (!map_surface_) { return; }
|
|
|
|
auto game_surface = Screen::get()->getRendererSurface();
|
|
if (!game_surface) { return; }
|
|
|
|
// Renderizar la surface del minimapa con el viewport actual (alineado a pixel)
|
|
float vx = std::floor(view_x_);
|
|
float vy = std::floor(view_y_);
|
|
SDL_FRect dst = {.x = vx, .y = vy, .w = static_cast<float>(map_width_ + SHADOW_OFFSET), .h = static_cast<float>(map_height_ + SHADOW_OFFSET)};
|
|
map_surface_->render(nullptr, &dst);
|
|
|
|
// Highlight de la room actual (solo si está completamente visible en el play area)
|
|
auto it = room_positions_.find(current_room);
|
|
if (it != room_positions_.end()) {
|
|
float cur_x = vx + static_cast<float>(cellPixelX(it->second.pos.x)) - 1;
|
|
float cur_y = vy + static_cast<float>(cellPixelY(it->second.pos.y)) - 1;
|
|
float cur_w = static_cast<float>(CELL_W + 2);
|
|
float cur_h = static_cast<float>(CELL_H + 2);
|
|
if (cur_x >= 0 && cur_y >= 0 && cur_x + cur_w <= PlayArea::WIDTH && cur_y + cur_h <= PlayArea::HEIGHT) {
|
|
SDL_FRect highlight = {.x = cur_x, .y = cur_y, .w = cur_w, .h = cur_h};
|
|
game_surface->drawRectBorder(&highlight, stringToColor("bright_white"));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Maneja eventos del minimapa (drag para explorar, click para navegar)
|
|
void MiniMap::handleEvent(const SDL_Event& event, const std::string& current_room) {
|
|
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && event.button.button == SDL_BUTTON_LEFT) {
|
|
// Guardar posición inicial para detectar si es click o drag
|
|
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();
|
|
|
|
dragging_ = true;
|
|
drag_start_x_ = render_x - dst_rect.x;
|
|
drag_start_y_ = render_y - dst_rect.y;
|
|
view_start_x_ = view_x_;
|
|
view_start_y_ = view_y_;
|
|
}
|
|
|
|
if (event.type == SDL_EVENT_MOUSE_MOTION && dragging_) {
|
|
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();
|
|
|
|
float game_x = render_x - dst_rect.x;
|
|
float game_y = render_y - dst_rect.y;
|
|
view_x_ = view_start_x_ + (game_x - drag_start_x_);
|
|
view_y_ = view_start_y_ + (game_y - drag_start_y_);
|
|
}
|
|
|
|
if (event.type == SDL_EVENT_MOUSE_BUTTON_UP && event.button.button == SDL_BUTTON_LEFT) {
|
|
if (dragging_) {
|
|
// Comprobar si fue click (sin mover) o drag
|
|
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();
|
|
|
|
float game_x = render_x - dst_rect.x;
|
|
float game_y = render_y - dst_rect.y;
|
|
|
|
float dx = game_x - drag_start_x_;
|
|
float dy = game_y - drag_start_y_;
|
|
bool was_click = (dx * dx + dy * dy) < 4.0F; // Menos de 2px de movimiento = click
|
|
|
|
if (was_click) {
|
|
// Click: navegar a la room bajo el cursor
|
|
std::string room = roomAtScreen(game_x, game_y);
|
|
if (!room.empty() && room != current_room && on_navigate) {
|
|
on_navigate(room);
|
|
}
|
|
}
|
|
|
|
dragging_ = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // _DEBUG
|