#ifdef _DEBUG #include "game/editor/mini_map.hpp" #include // Para std::max, std::min #include // Para std::array #include // Para std::floor #include // Para cout #include // Para std::map #include // Para queue (BFS) #include // 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(tileset->getWidth()) / Tile::SIZE; tileset_transparent_ = tileset->getTransparentColor(); int tileset_height = static_cast(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 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(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> room_map; for (const auto& r : rooms) { room_map[r.name] = r.room; } // BFS para posicionar rooms std::set visited; std::queue> bfs; // Empezar por la primera room const std::string& start = rooms[0].name; bfs.push({start, {.x = 0, .y = 0}}); visited.insert(start); // Grid ocupado: posición → nombre de room std::map, 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 neighbors = {{ {.room = data->upper_room, .dx = 0, .dy = -1}, {.room = data->lower_room, .dx = 0, .dy = 1}, {.room = data->left_room, .dx = -1, .dy = 0}, {.room = data->right_room, .dx = 1, .dy = 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 = {.x = pos.x + dx, .y = pos.y + dy}; auto nkey = std::make_pair(neighbor_pos.x, neighbor_pos.y); if (!grid_occupied.contains(nkey)) { visited.insert(neighbor_name); bfs.emplace(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 { auto room_data = Resource::Cache::get()->getRoom(room_name); if (!room_data) { return nullptr; } auto surface = std::make_shared(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(tile_map.size())) { continue; } int tile = tile_map[index]; if (tile < 0 || tile >= static_cast(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(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(px), .y = static_cast(py), .w = static_cast(CELL_W), .h = static_cast(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(px), .y = static_cast(py), .w = static_cast(CELL_W), .h = static_cast(CELL_H)}; map_surface_->fillRect(&cell, COLOR_ROOM_BORDER); // Miniroom dentro del borde SDL_FRect dst = {.x = static_cast(px + BORDER), .y = static_cast(py + BORDER), .w = static_cast(ROOM_W), .h = static_cast(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(x1), .y = static_cast(y_mid), .w = static_cast(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(x_mid), .y = static_cast(y1), .w = 3.0F, .h = static_cast(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; auto room_cx = static_cast(cellPixelX(pos.x) + (CELL_W / 2)); auto room_cy = static_cast(cellPixelY(pos.y) + (CELL_H / 2)); view_x_ = static_cast(PlayArea::WIDTH) / 2.0F - room_cx; view_y_ = static_cast(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_) { auto rx = static_cast(cellPixelX(mini.pos.x)); auto ry = static_cast(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(map_width_ + SHADOW_OFFSET), .h = static_cast(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(cellPixelX(it->second.pos.x)) - 1; float cur_y = vy + static_cast(cellPixelY(it->second.pos.y)) - 1; auto cur_w = static_cast(CELL_W + 2); auto cur_h = static_cast(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