segon commit
This commit is contained in:
89
source/game/editor/editor_statusbar.cpp
Normal file
89
source/game/editor/editor_statusbar.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include "game/editor/editor_statusbar.hpp"
|
||||
|
||||
#include <string> // Para to_string
|
||||
#include <utility>
|
||||
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
#include "core/rendering/text.hpp" // Para Text
|
||||
#include "core/resources/resource_cache.hpp" // Para Resource::Cache
|
||||
#include "game/options.hpp" // Para Options::game
|
||||
#include "utils/defines.hpp" // Para Tile::SIZE
|
||||
#include "utils/utils.hpp" // Para stringToColor, toLower
|
||||
|
||||
// Constructor
|
||||
EditorStatusBar::EditorStatusBar(std::string room_number, std::string room_name)
|
||||
: room_number_(std::move(room_number)),
|
||||
room_name_(std::move(room_name)) {
|
||||
const float SURFACE_WIDTH = Options::game.width;
|
||||
constexpr float SURFACE_HEIGHT = 6.0F * Tile::SIZE; // 48 pixels, igual que el scoreboard
|
||||
|
||||
surface_ = std::make_shared<Surface>(SURFACE_WIDTH, SURFACE_HEIGHT);
|
||||
surface_dest_ = {.x = 0, .y = Options::game.height - SURFACE_HEIGHT, .w = SURFACE_WIDTH, .h = SURFACE_HEIGHT};
|
||||
}
|
||||
|
||||
// Pinta la barra de estado en pantalla
|
||||
void EditorStatusBar::render() {
|
||||
surface_->render(nullptr, &surface_dest_);
|
||||
}
|
||||
|
||||
// Actualiza la barra de estado
|
||||
void EditorStatusBar::update([[maybe_unused]] float delta_time) {
|
||||
fillTexture();
|
||||
}
|
||||
|
||||
void EditorStatusBar::setMouseTile(int tile_x, int tile_y) {
|
||||
mouse_tile_x_ = tile_x;
|
||||
mouse_tile_y_ = tile_y;
|
||||
}
|
||||
|
||||
void EditorStatusBar::setLine2(const std::string& text) { line2_ = text; }
|
||||
void EditorStatusBar::setLine3(const std::string& text) { line3_ = text; }
|
||||
void EditorStatusBar::setLine4(const std::string& text) { line4_ = text; }
|
||||
void EditorStatusBar::setLine5(const std::string& text) { line5_ = text; }
|
||||
|
||||
// Dibuja los elementos en la surface
|
||||
void EditorStatusBar::fillTexture() {
|
||||
auto previous_renderer = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(surface_);
|
||||
|
||||
surface_->clear(stringToColor("black"));
|
||||
|
||||
auto text = Resource::Cache::get()->getText("8bithud");
|
||||
const Uint8 LABEL_COLOR = stringToColor("bright_cyan");
|
||||
const Uint8 VALUE_COLOR = stringToColor("white");
|
||||
const Uint8 DETAIL_COLOR = stringToColor("bright_yellow");
|
||||
|
||||
// Línea 1: Nombre de la habitación
|
||||
text->writeColored(LEFT_X, LINE1_Y, toLower(room_number_ + " " + room_name_), LABEL_COLOR);
|
||||
|
||||
// Línea 2: Propiedades de room o info de enemigo
|
||||
if (!line2_.empty()) {
|
||||
text->writeColored(LEFT_X, LINE2_Y, toLower(line2_), DETAIL_COLOR);
|
||||
}
|
||||
|
||||
// Línea 3: Conexiones+items o propiedades del enemigo
|
||||
if (!line3_.empty()) {
|
||||
text->writeColored(LEFT_X, LINE3_Y, toLower(line3_), VALUE_COLOR);
|
||||
}
|
||||
|
||||
// Línea 4: Extra
|
||||
if (!line4_.empty()) {
|
||||
text->writeColored(LEFT_X, LINE4_Y, toLower(line4_), DETAIL_COLOR);
|
||||
}
|
||||
|
||||
// Línea 5: Tile coords + drag info
|
||||
const std::string TILE_X_STR = (mouse_tile_x_ < 10 ? "0" : "") + std::to_string(mouse_tile_x_);
|
||||
const std::string TILE_Y_STR = (mouse_tile_y_ < 10 ? "0" : "") + std::to_string(mouse_tile_y_);
|
||||
std::string line5 = "tile:" + TILE_X_STR + "," + TILE_Y_STR;
|
||||
if (!line5_.empty()) {
|
||||
line5 += " " + line5_;
|
||||
}
|
||||
text->writeColored(LEFT_X, LINE5_Y, toLower(line5), stringToColor("bright_green"));
|
||||
|
||||
Screen::get()->setRendererSurface(previous_renderer);
|
||||
}
|
||||
|
||||
#endif // _DEBUG
|
||||
52
source/game/editor/editor_statusbar.hpp
Normal file
52
source/game/editor/editor_statusbar.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
|
||||
class Surface;
|
||||
|
||||
class EditorStatusBar {
|
||||
public:
|
||||
EditorStatusBar(std::string room_number, std::string room_name);
|
||||
~EditorStatusBar() = default;
|
||||
|
||||
void render();
|
||||
void update(float delta_time);
|
||||
void setMouseTile(int tile_x, int tile_y);
|
||||
void setLine2(const std::string& text);
|
||||
void setLine3(const std::string& text);
|
||||
void setLine4(const std::string& text);
|
||||
void setLine5(const std::string& text);
|
||||
|
||||
private:
|
||||
void fillTexture(); // Dibuja los elementos en la surface
|
||||
|
||||
// Constantes de posición (en pixels dentro de la surface de 256x48)
|
||||
// Font 8bithud lowercase = 6px alto → 5 líneas con 8px de separación
|
||||
static constexpr int LINE1_Y = 2; // Nombre de la habitación
|
||||
static constexpr int LINE2_Y = 10; // Propiedades de room / enemy info
|
||||
static constexpr int LINE3_Y = 18; // Conexiones+items / enemy detail
|
||||
static constexpr int LINE4_Y = 26; // Extra
|
||||
static constexpr int LINE5_Y = 34; // Tile coords + drag info
|
||||
static constexpr int LEFT_X = 4; // Margen izquierdo
|
||||
|
||||
// Objetos
|
||||
std::shared_ptr<Surface> surface_; // Surface donde dibujar la barra
|
||||
SDL_FRect surface_dest_{}; // Rectángulo destino en pantalla
|
||||
|
||||
// Variables
|
||||
std::string room_number_; // Número de la habitación
|
||||
std::string room_name_; // Nombre de la habitación
|
||||
int mouse_tile_x_{0}; // Coordenada X del ratón en tiles
|
||||
int mouse_tile_y_{0}; // Coordenada Y del ratón en tiles
|
||||
std::string line2_; // Contenido de la línea 2
|
||||
std::string line3_; // Contenido de la línea 3
|
||||
std::string line4_; // Contenido de la línea 4
|
||||
std::string line5_; // Contenido de la línea 5
|
||||
};
|
||||
|
||||
#endif // _DEBUG
|
||||
1633
source/game/editor/map_editor.cpp
Normal file
1633
source/game/editor/map_editor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
160
source/game/editor/map_editor.hpp
Normal file
160
source/game/editor/map_editor.hpp
Normal file
@@ -0,0 +1,160 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <memory> // Para shared_ptr, unique_ptr
|
||||
#include <string> // Para string
|
||||
|
||||
#include "external/fkyaml_node.hpp" // Para fkyaml::node
|
||||
#include "game/editor/mini_map.hpp" // Para MiniMap
|
||||
#include "game/editor/tile_picker.hpp" // Para TilePicker
|
||||
#include "game/entities/enemy.hpp" // Para Enemy::Data
|
||||
#include "game/entities/item.hpp" // Para Item::Data
|
||||
#include "game/entities/player.hpp" // Para Player::SpawnData
|
||||
#include "game/gameplay/room.hpp" // Para Room::Data
|
||||
#include "game/gameplay/scoreboard.hpp" // Para Scoreboard::Data
|
||||
#include "game/options.hpp" // Para Options::Cheat
|
||||
|
||||
class EditorStatusBar;
|
||||
|
||||
class MapEditor {
|
||||
public:
|
||||
static void init(); // [SINGLETON] Crea el objeto
|
||||
static void destroy(); // [SINGLETON] Destruye el objeto
|
||||
static auto get() -> MapEditor*; // [SINGLETON] Obtiene el objeto
|
||||
|
||||
void enter(std::shared_ptr<Room> room, std::shared_ptr<Player> player, const std::string& room_path, std::shared_ptr<Scoreboard::Data> scoreboard_data);
|
||||
void exit();
|
||||
[[nodiscard]] auto isActive() const -> bool { return active_; }
|
||||
|
||||
void update(float delta_time);
|
||||
void render();
|
||||
void handleEvent(const SDL_Event& event);
|
||||
auto revert() -> std::string;
|
||||
|
||||
// Comandos para enemigos (llamados desde console_commands)
|
||||
auto setEnemyProperty(const std::string& property, const std::string& value) -> std::string;
|
||||
auto addEnemy() -> std::string;
|
||||
auto deleteEnemy() -> std::string;
|
||||
auto duplicateEnemy() -> std::string;
|
||||
[[nodiscard]] auto hasSelectedEnemy() const -> bool;
|
||||
[[nodiscard]] auto getSetCompletions() const -> std::vector<std::string>;
|
||||
|
||||
// Comandos para propiedades de la habitación
|
||||
auto setRoomProperty(const std::string& property, const std::string& value) -> std::string;
|
||||
auto createNewRoom(const std::string& direction = "") -> std::string;
|
||||
auto deleteRoom() -> std::string;
|
||||
|
||||
// Opciones del editor (llamados desde console_commands / teclas)
|
||||
auto showInfo(bool show) -> std::string;
|
||||
auto showGrid(bool show) -> std::string;
|
||||
[[nodiscard]] auto isGridEnabled() const -> bool { return settings_.grid; }
|
||||
void toggleMiniMap();
|
||||
void setReenter(bool value) { reenter_ = value; }
|
||||
auto setMiniMapBg(const std::string& color) -> std::string;
|
||||
auto setMiniMapConn(const std::string& color) -> std::string;
|
||||
|
||||
// Comandos para items
|
||||
auto setItemProperty(const std::string& property, const std::string& value) -> std::string;
|
||||
auto addItem() -> std::string;
|
||||
auto deleteItem() -> std::string;
|
||||
auto duplicateItem() -> std::string;
|
||||
[[nodiscard]] auto hasSelectedItem() const -> bool;
|
||||
void openTilePicker(const std::string& tileset_name, int current_tile);
|
||||
|
||||
private:
|
||||
static MapEditor* instance_; // NOLINT(readability-identifier-naming) [SINGLETON] Objeto privado
|
||||
|
||||
MapEditor(); // Constructor
|
||||
~MapEditor(); // Destructor
|
||||
|
||||
// Opciones persistentes del editor
|
||||
struct Settings {
|
||||
bool grid{false};
|
||||
bool show_render_info{false};
|
||||
std::string minimap_bg{"blue"};
|
||||
std::string minimap_conn{"white"};
|
||||
};
|
||||
Settings settings_;
|
||||
void loadSettings();
|
||||
void saveSettings() const;
|
||||
|
||||
// Tipos para drag & drop y selección
|
||||
enum class DragTarget { NONE,
|
||||
PLAYER,
|
||||
ENEMY_INITIAL,
|
||||
ENEMY_BOUND1,
|
||||
ENEMY_BOUND2,
|
||||
ITEM };
|
||||
|
||||
struct DragState {
|
||||
DragTarget target{DragTarget::NONE};
|
||||
int index{-1};
|
||||
float offset_x{0.0F};
|
||||
float offset_y{0.0F};
|
||||
float snap_x{0.0F};
|
||||
float snap_y{0.0F};
|
||||
bool moved{false}; // true si el ratón se movió durante el drag
|
||||
};
|
||||
|
||||
// Métodos internos
|
||||
void updateMousePosition();
|
||||
void renderEnemyBoundaries();
|
||||
static void renderBoundaryMarker(float x, float y, Uint8 color);
|
||||
void renderSelectionHighlight();
|
||||
void renderGrid() const;
|
||||
void handleMouseDown(float game_x, float game_y);
|
||||
void handleMouseUp();
|
||||
void updateDrag();
|
||||
void autosave();
|
||||
void updateStatusBarInfo();
|
||||
static auto snapToGrid(float value) -> float;
|
||||
static auto pointInRect(float px, float py, const SDL_FRect& rect) -> bool;
|
||||
|
||||
// Estado del editor
|
||||
bool active_{false};
|
||||
DragState drag_;
|
||||
int selected_enemy_{-1}; // Índice del enemigo seleccionado (-1 = ninguno)
|
||||
int selected_item_{-1}; // Índice del item seleccionado (-1 = ninguno)
|
||||
static constexpr int NO_BRUSH = -2; // Sin brush activo
|
||||
static constexpr int ERASER_BRUSH = -1; // Brush borrador (pinta tile vacío = -1)
|
||||
int brush_tile_{NO_BRUSH}; // Tile activo para pintar
|
||||
bool painting_{false}; // true mientras se está pintando con click izquierdo mantenido
|
||||
|
||||
// Datos de la habitación
|
||||
Room::Data room_data_;
|
||||
std::string room_path_;
|
||||
std::string file_path_;
|
||||
|
||||
// YAML: nodo original (para campos que no se editan: name_ca, etc.)
|
||||
fkyaml::node yaml_;
|
||||
fkyaml::node yaml_backup_;
|
||||
|
||||
// Referencias a objetos vivos
|
||||
std::shared_ptr<Room> room_;
|
||||
std::shared_ptr<Player> player_;
|
||||
std::shared_ptr<Scoreboard::Data> scoreboard_data_;
|
||||
|
||||
// Barra de estado del editor
|
||||
std::unique_ptr<EditorStatusBar> statusbar_;
|
||||
|
||||
// Tile picker y mini mapa
|
||||
TilePicker tile_picker_;
|
||||
std::unique_ptr<MiniMap> mini_map_;
|
||||
bool mini_map_visible_{false};
|
||||
|
||||
// Estado del ratón
|
||||
float mouse_game_x_{0.0F};
|
||||
float mouse_game_y_{0.0F};
|
||||
int mouse_tile_x_{0};
|
||||
int mouse_tile_y_{0};
|
||||
|
||||
// Estado previo (para restaurar al salir)
|
||||
Options::Cheat::State invincible_before_editor_{Options::Cheat::State::DISABLED};
|
||||
bool render_info_before_editor_{false};
|
||||
bool reenter_{false}; // true cuando es un re-enter tras cambio de room (no tocar render_info)
|
||||
};
|
||||
|
||||
#endif // _DEBUG
|
||||
391
source/game/editor/mini_map.cpp
Normal file
391
source/game/editor/mini_map.cpp
Normal file
@@ -0,0 +1,391 @@
|
||||
#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, {.x = 0, .y = 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 = {{
|
||||
{.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<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;
|
||||
|
||||
auto room_cx = static_cast<float>(cellPixelX(pos.x) + (CELL_W / 2));
|
||||
auto 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_) {
|
||||
auto rx = static_cast<float>(cellPixelX(mini.pos.x));
|
||||
auto 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;
|
||||
auto cur_w = static_cast<float>(CELL_W + 2);
|
||||
auto 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
|
||||
103
source/game/editor/mini_map.hpp
Normal file
103
source/game/editor/mini_map.hpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <functional> // Para function
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
#include <unordered_map> // Para unordered_map
|
||||
#include <vector> // Para vector
|
||||
|
||||
class Surface;
|
||||
|
||||
/**
|
||||
* @brief Minimapa global del juego para el editor
|
||||
*
|
||||
* Genera una vista en miniatura de todas las habitaciones del juego,
|
||||
* posicionadas según sus conexiones.
|
||||
* Cada tile del mapa se representa como 1 pixel del color predominante de ese tile.
|
||||
* Resultado: cada room = 32x16 pixels.
|
||||
*/
|
||||
class MiniMap {
|
||||
public:
|
||||
explicit MiniMap(Uint8 bg_color = 2, Uint8 conn_color = 14);
|
||||
~MiniMap() = default;
|
||||
|
||||
void render(const std::string& current_room);
|
||||
void handleEvent(const SDL_Event& event, const std::string& current_room);
|
||||
void rebuild(Uint8 bg_color, Uint8 conn_color);
|
||||
void centerOnRoom(const std::string& room_name);
|
||||
[[nodiscard]] auto isReady() const -> bool { return !room_positions_.empty(); }
|
||||
|
||||
// Callback al hacer click en una minihabitación (nombre del room)
|
||||
std::function<void(const std::string&)> on_navigate;
|
||||
|
||||
private:
|
||||
// Posición de una room en el grid del minimapa
|
||||
struct GridPos {
|
||||
int x{0};
|
||||
int y{0};
|
||||
};
|
||||
|
||||
// Una room renderizada
|
||||
struct RoomMini {
|
||||
std::shared_ptr<Surface> surface; // 32x16 pixels
|
||||
GridPos pos; // Posición en el grid
|
||||
};
|
||||
|
||||
void buildTileColorTable(const std::string& tileset_name);
|
||||
void buildRoomSurfaces();
|
||||
void layoutRooms();
|
||||
void composeFinalSurface();
|
||||
auto getRoomMiniSurface(const std::string& room_name) -> std::shared_ptr<Surface>;
|
||||
void drawConnections();
|
||||
auto roomAtScreen(float screen_x, float screen_y) -> std::string;
|
||||
auto cellPixelX(int grid_x) const -> int { return PADDING + ((grid_x - min_grid_x_) * (CELL_W + GAP)); }
|
||||
auto cellPixelY(int grid_y) const -> int { return PADDING + ((grid_y - min_grid_y_) * (CELL_H + GAP)); }
|
||||
|
||||
// Tabla de color predominante por tile index
|
||||
std::vector<Uint8> tile_colors_; // tile_index → palette color index
|
||||
int tileset_width_{0}; // Ancho del tileset en tiles
|
||||
Uint8 tileset_transparent_{16}; // Color transparente del tileset
|
||||
|
||||
// Rooms renderizadas y posicionadas
|
||||
std::unordered_map<std::string, RoomMini> room_positions_;
|
||||
|
||||
// Surface final compuesta
|
||||
std::shared_ptr<Surface> map_surface_;
|
||||
int map_width_{0}; // Ancho en pixels
|
||||
int map_height_{0}; // Alto en pixels
|
||||
|
||||
// Offset para normalizar coordenadas
|
||||
int min_grid_x_{0};
|
||||
int min_grid_y_{0};
|
||||
|
||||
// Viewport: offset de la surface del minimapa respecto al play area
|
||||
float view_x_{0.0F}; // Offset X actual
|
||||
float view_y_{0.0F}; // Offset Y actual
|
||||
bool dragging_{false};
|
||||
float drag_start_x_{0.0F}; // Posición del ratón al inicio del drag
|
||||
float drag_start_y_{0.0F};
|
||||
float view_start_x_{0.0F}; // Viewport al inicio del drag
|
||||
float view_start_y_{0.0F};
|
||||
|
||||
// Constantes
|
||||
static constexpr int ROOM_W = 32; // Ancho de una room en pixels del minimapa
|
||||
static constexpr int ROOM_H = 16; // Alto de una room en pixels del minimapa
|
||||
static constexpr int BORDER = 1; // Borde alrededor de cada room
|
||||
static constexpr int CELL_W = ROOM_W + (BORDER * 2); // Room + borde
|
||||
static constexpr int CELL_H = ROOM_H + (BORDER * 2);
|
||||
static constexpr int GAP = 4; // Separación entre celdas
|
||||
static constexpr int SHADOW_OFFSET = 1; // Desplazamiento de la sombra
|
||||
static constexpr int PADDING = 4; // Padding alrededor del minimapa
|
||||
|
||||
// Colores del minimapa (índices de paleta)
|
||||
Uint8 bg_color_{2}; // Fondo general (configurable)
|
||||
Uint8 conn_color_{14}; // Líneas de conexión (configurable)
|
||||
static constexpr Uint8 COLOR_ROOM_BORDER = 0; // Borde de cada miniroom
|
||||
static constexpr Uint8 COLOR_SHADOW = 1; // Sombra de cada miniroom
|
||||
};
|
||||
|
||||
#endif // _DEBUG
|
||||
178
source/game/editor/room_saver.cpp
Normal file
178
source/game/editor/room_saver.cpp
Normal file
@@ -0,0 +1,178 @@
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include "game/editor/room_saver.hpp"
|
||||
|
||||
#include <cmath> // Para std::round
|
||||
#include <fstream> // Para ifstream, ofstream, istreambuf_iterator
|
||||
#include <iostream> // Para cout, cerr
|
||||
#include <sstream> // Para ostringstream
|
||||
|
||||
#include "utils/defines.hpp" // Para Tile::SIZE
|
||||
|
||||
// Carga el YAML original directamente del filesystem (no del resource pack)
|
||||
auto RoomSaver::loadYAML(const std::string& file_path) -> fkyaml::node {
|
||||
std::ifstream file(file_path);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "RoomSaver: Cannot open " << file_path << "\n";
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
file.close();
|
||||
return fkyaml::node::deserialize(content);
|
||||
}
|
||||
|
||||
// Convierte una room connection al formato YAML
|
||||
auto RoomSaver::roomConnectionToYAML(const std::string& connection) -> std::string {
|
||||
if (connection == "0" || connection.empty()) { return "null"; }
|
||||
return connection;
|
||||
}
|
||||
|
||||
// Convierte la dirección del conveyor belt a string
|
||||
auto RoomSaver::conveyorBeltToString(int direction) -> std::string {
|
||||
if (direction < 0) { return "left"; }
|
||||
if (direction > 0) { return "right"; }
|
||||
return "none";
|
||||
}
|
||||
|
||||
// Genera el YAML completo como texto con formato compacto
|
||||
auto RoomSaver::buildYAML(const fkyaml::node& original_yaml, const Room::Data& room_data) -> std::string { // NOLINT(readability-function-cognitive-complexity)
|
||||
std::ostringstream out;
|
||||
|
||||
// --- Cabecera: nombre como comentario ---
|
||||
out << "# " << room_data.name << "\n";
|
||||
|
||||
// --- Sección room ---
|
||||
out << "room:\n";
|
||||
|
||||
// Escribir todos los campos name_* del YAML original (preserva name_ca, name_en, etc.)
|
||||
if (original_yaml.contains("room")) {
|
||||
const auto& room_node = original_yaml["room"];
|
||||
for (auto it = room_node.begin(); it != room_node.end(); ++it) {
|
||||
const auto KEY = it.key().get_value<std::string>();
|
||||
if (KEY.substr(0, 5) == "name_") {
|
||||
out << " " << KEY << ": \"" << it.value().get_value<std::string>() << "\"\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out << " bgColor: " << room_data.bg_color << "\n";
|
||||
out << " border: " << room_data.border_color << "\n";
|
||||
out << " tileSetFile: " << room_data.tile_set_file << "\n";
|
||||
|
||||
// Conexiones
|
||||
out << "\n";
|
||||
out << " # Conexiones de la habitación (null = sin conexión)\n";
|
||||
out << " connections:\n";
|
||||
out << " up: " << roomConnectionToYAML(room_data.upper_room) << "\n";
|
||||
out << " down: " << roomConnectionToYAML(room_data.lower_room) << "\n";
|
||||
out << " left: " << roomConnectionToYAML(room_data.left_room) << "\n";
|
||||
out << " right: " << roomConnectionToYAML(room_data.right_room) << "\n";
|
||||
|
||||
// Colores de items
|
||||
out << "\n";
|
||||
out << " # Colores de los objetos\n";
|
||||
out << " itemColor1: " << (room_data.item_color1.empty() ? "yellow" : room_data.item_color1) << "\n";
|
||||
out << " itemColor2: " << (room_data.item_color2.empty() ? "magenta" : room_data.item_color2) << "\n";
|
||||
|
||||
// Conveyor belt
|
||||
out << "\n";
|
||||
out << " # Dirección de la cinta transportadora: left, none, right\n";
|
||||
out << " conveyorBelt: " << conveyorBeltToString(room_data.conveyor_belt_direction) << "\n";
|
||||
|
||||
// --- Tilemap (16 filas × 32 columnas, formato flow) ---
|
||||
out << "\n";
|
||||
out << "# Tilemap: 16 filas × 32 columnas (256×192 píxeles @ 8px/tile)\n";
|
||||
out << "# Índices de tiles (-1 = vacío)\n";
|
||||
out << "tilemap:\n";
|
||||
constexpr int MAP_WIDTH = 32;
|
||||
constexpr int MAP_HEIGHT = 16;
|
||||
for (int row = 0; row < MAP_HEIGHT; ++row) {
|
||||
out << " - [";
|
||||
for (int col = 0; col < MAP_WIDTH; ++col) {
|
||||
int index = (row * MAP_WIDTH) + col;
|
||||
if (index < static_cast<int>(room_data.tile_map.size())) {
|
||||
out << room_data.tile_map[index];
|
||||
} else {
|
||||
out << -1;
|
||||
}
|
||||
if (col < MAP_WIDTH - 1) { out << ", "; }
|
||||
}
|
||||
out << "]\n";
|
||||
}
|
||||
|
||||
// --- Enemigos ---
|
||||
if (!room_data.enemies.empty()) {
|
||||
out << "\n";
|
||||
out << "# Enemigos en esta habitación\n";
|
||||
out << "enemies:\n";
|
||||
for (const auto& enemy : room_data.enemies) {
|
||||
out << " - animation: " << enemy.animation_path << "\n";
|
||||
|
||||
int pos_x = static_cast<int>(std::round(enemy.x / Tile::SIZE));
|
||||
int pos_y = static_cast<int>(std::round(enemy.y / Tile::SIZE));
|
||||
out << " position: {x: " << pos_x << ", y: " << pos_y << "}\n";
|
||||
|
||||
out << " velocity: {x: " << enemy.vx << ", y: " << enemy.vy << "}\n";
|
||||
|
||||
int b1_x = enemy.x1 / Tile::SIZE;
|
||||
int b1_y = enemy.y1 / Tile::SIZE;
|
||||
int b2_x = enemy.x2 / Tile::SIZE;
|
||||
int b2_y = enemy.y2 / Tile::SIZE;
|
||||
out << " boundaries:\n";
|
||||
out << " position1: {x: " << b1_x << ", y: " << b1_y << "}\n";
|
||||
out << " position2: {x: " << b2_x << ", y: " << b2_y << "}\n";
|
||||
|
||||
if (!enemy.color.empty() && enemy.color != "white") {
|
||||
out << " color: " << enemy.color << "\n";
|
||||
}
|
||||
if (enemy.flip) { out << " flip: true\n"; }
|
||||
if (enemy.mirror) { out << " mirror: true\n"; }
|
||||
if (enemy.frame != -1) { out << " frame: " << enemy.frame << "\n"; }
|
||||
|
||||
out << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// --- Items ---
|
||||
if (!room_data.items.empty()) {
|
||||
out << "# Objetos en esta habitación\n";
|
||||
out << "items:\n";
|
||||
for (const auto& item : room_data.items) {
|
||||
out << " - tileSetFile: " << item.tile_set_file << "\n";
|
||||
out << " tile: " << item.tile << "\n";
|
||||
|
||||
int item_x = static_cast<int>(std::round(item.x / Tile::SIZE));
|
||||
int item_y = static_cast<int>(std::round(item.y / Tile::SIZE));
|
||||
out << " position: {x: " << item_x << ", y: " << item_y << "}\n";
|
||||
|
||||
if (item.counter != 0) {
|
||||
out << " counter: " << item.counter << "\n";
|
||||
}
|
||||
|
||||
out << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
// Guarda el YAML a disco
|
||||
auto RoomSaver::saveYAML(const std::string& file_path, const fkyaml::node& original_yaml, const Room::Data& room_data) -> std::string {
|
||||
std::string content = buildYAML(original_yaml, room_data);
|
||||
|
||||
std::ofstream file(file_path);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "RoomSaver: Cannot write to " << file_path << "\n";
|
||||
return "Error: Cannot write to " + file_path;
|
||||
}
|
||||
|
||||
file << content;
|
||||
file.close();
|
||||
|
||||
const std::string FILE_NAME = file_path.substr(file_path.find_last_of("\\/") + 1);
|
||||
std::cout << "RoomSaver: Saved " << FILE_NAME << "\n";
|
||||
return "Saved " + FILE_NAME;
|
||||
}
|
||||
|
||||
#endif // _DEBUG
|
||||
35
source/game/editor/room_saver.hpp
Normal file
35
source/game/editor/room_saver.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include <string> // Para string
|
||||
|
||||
#include "external/fkyaml_node.hpp" // Para fkyaml::node
|
||||
#include "game/gameplay/room.hpp" // Para Room::Data
|
||||
|
||||
/**
|
||||
* @brief Guardado de archivos YAML de habitaciones para el editor de mapas
|
||||
*
|
||||
* Lee el YAML original con fkyaml (para acceder a todos los campos: name_ca, name_en, etc.)
|
||||
* Genera el YAML como texto formateado compacto (idéntico al formato original de los ficheros).
|
||||
* Solo se usa en builds de debug.
|
||||
*/
|
||||
class RoomSaver {
|
||||
public:
|
||||
RoomSaver() = delete;
|
||||
|
||||
// Carga el YAML original desde disco como nodo fkyaml (lee del filesystem, no del pack)
|
||||
static auto loadYAML(const std::string& file_path) -> fkyaml::node;
|
||||
|
||||
// Genera y guarda el YAML completo a disco
|
||||
// original_yaml: nodo fkyaml con los datos originales (para campos que no se editan: name_ca, etc.)
|
||||
// room_data: datos editados (posiciones de enemigos, items, etc.)
|
||||
static auto saveYAML(const std::string& file_path, const fkyaml::node& original_yaml, const Room::Data& room_data) -> std::string;
|
||||
|
||||
private:
|
||||
static auto buildYAML(const fkyaml::node& original_yaml, const Room::Data& room_data) -> std::string;
|
||||
static auto roomConnectionToYAML(const std::string& connection) -> std::string;
|
||||
static auto conveyorBeltToString(int direction) -> std::string;
|
||||
};
|
||||
|
||||
#endif // _DEBUG
|
||||
240
source/game/editor/tile_picker.cpp
Normal file
240
source/game/editor/tile_picker.cpp
Normal file
@@ -0,0 +1,240 @@
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include "game/editor/tile_picker.hpp"
|
||||
|
||||
#include <algorithm> // Para std::clamp, std::min
|
||||
|
||||
#include "core/rendering/screen.hpp" // Para Screen
|
||||
#include "core/rendering/surface.hpp" // Para Surface
|
||||
#include "core/resources/resource_cache.hpp" // Para Resource::Cache
|
||||
#include "utils/defines.hpp" // Para Tile::SIZE, PlayArea
|
||||
#include "utils/utils.hpp" // Para stringToColor
|
||||
|
||||
// Margen del borde alrededor del tileset (en pixels)
|
||||
static constexpr int BORDER_PAD = 3;
|
||||
|
||||
// Abre el picker con un tileset
|
||||
void TilePicker::open(const std::string& tileset_name, int current_tile, int bg_color, int source_color, int target_color, int tile_spacing_in, int tile_spacing_out) {
|
||||
tileset_ = Resource::Cache::get()->getSurface(tileset_name);
|
||||
if (!tileset_) {
|
||||
open_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
spacing_in_ = tile_spacing_in;
|
||||
spacing_out_ = tile_spacing_out;
|
||||
|
||||
// Calcular dimensiones del tileset en tiles (teniendo en cuenta spacing de entrada)
|
||||
int src_cell = Tile::SIZE + spacing_in_;
|
||||
tileset_width_ = static_cast<int>(tileset_->getWidth()) / src_cell;
|
||||
tileset_height_ = static_cast<int>(tileset_->getHeight()) / src_cell;
|
||||
// Corregir si el último tile cabe sin spacing
|
||||
if (tileset_width_ == 0 && tileset_->getWidth() >= Tile::SIZE) { tileset_width_ = 1; }
|
||||
if (tileset_height_ == 0 && tileset_->getHeight() >= Tile::SIZE) { tileset_height_ = 1; }
|
||||
|
||||
current_tile_ = current_tile;
|
||||
hover_tile_ = -1;
|
||||
scroll_y_ = 0;
|
||||
|
||||
// Dimensiones de salida (con spacing visual entre tiles)
|
||||
int out_cell = Tile::SIZE + spacing_out_;
|
||||
int display_w = (tileset_width_ * out_cell) - spacing_out_; // Sin trailing
|
||||
int display_h = (tileset_height_ * out_cell) - spacing_out_;
|
||||
|
||||
// Frame: display + borde
|
||||
int frame_w = display_w + (BORDER_PAD * 2);
|
||||
int frame_h = display_h + (BORDER_PAD * 2);
|
||||
frame_surface_ = std::make_shared<Surface>(frame_w, frame_h);
|
||||
|
||||
// Componer: fondo + borde + tiles uno a uno
|
||||
{
|
||||
auto prev = Screen::get()->getRendererSurface();
|
||||
Screen::get()->setRendererSurface(frame_surface_);
|
||||
|
||||
Uint8 fill_color = (bg_color >= 0) ? static_cast<Uint8>(bg_color) : stringToColor("black");
|
||||
frame_surface_->clear(fill_color);
|
||||
|
||||
// Borde doble
|
||||
SDL_FRect outer = {.x = 0, .y = 0, .w = static_cast<float>(frame_w), .h = static_cast<float>(frame_h)};
|
||||
frame_surface_->drawRectBorder(&outer, stringToColor("bright_white"));
|
||||
SDL_FRect inner = {.x = 1, .y = 1, .w = static_cast<float>(frame_w - 2), .h = static_cast<float>(frame_h - 2)};
|
||||
frame_surface_->drawRectBorder(&inner, stringToColor("white"));
|
||||
|
||||
// Renderizar cada tile individualmente
|
||||
constexpr auto TS = static_cast<float>(Tile::SIZE);
|
||||
for (int row = 0; row < tileset_height_; ++row) {
|
||||
for (int col = 0; col < tileset_width_; ++col) {
|
||||
// Fuente: posición en el tileset original
|
||||
SDL_FRect src = {
|
||||
.x = static_cast<float>(col * src_cell),
|
||||
.y = static_cast<float>(row * src_cell),
|
||||
.w = TS,
|
||||
.h = TS};
|
||||
|
||||
// Destino: posición en el frame con spacing de salida
|
||||
int dst_x = BORDER_PAD + (col * out_cell);
|
||||
int dst_y = BORDER_PAD + (row * out_cell);
|
||||
|
||||
if (source_color >= 0 && target_color >= 0) {
|
||||
tileset_->renderWithColorReplace(dst_x, dst_y, static_cast<Uint8>(source_color), static_cast<Uint8>(target_color), &src);
|
||||
} else {
|
||||
SDL_FRect dst = {.x = static_cast<float>(dst_x), .y = static_cast<float>(dst_y), .w = TS, .h = TS};
|
||||
tileset_->render(&src, &dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Screen::get()->setRendererSurface(prev);
|
||||
}
|
||||
|
||||
// Centrar en el play area
|
||||
offset_x_ = (PlayArea::WIDTH - frame_w) / 2;
|
||||
int offset_y = (PlayArea::HEIGHT - frame_h) / 2;
|
||||
offset_y = std::max(offset_y, 0);
|
||||
|
||||
visible_height_ = PlayArea::HEIGHT;
|
||||
|
||||
frame_dst_ = {.x = static_cast<float>(offset_x_), .y = static_cast<float>(offset_y), .w = static_cast<float>(frame_w), .h = static_cast<float>(frame_h)};
|
||||
|
||||
// Si el frame es más alto que el play area, necesitará scroll
|
||||
if (frame_h > visible_height_) {
|
||||
frame_dst_.y = 0;
|
||||
if (current_tile_ >= 0) {
|
||||
int tile_row = current_tile_ / tileset_width_;
|
||||
int tile_y_px = tile_row * out_cell;
|
||||
if (tile_y_px > visible_height_ / 2) {
|
||||
scroll_y_ = tile_y_px - visible_height_ / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open_ = true;
|
||||
updateMousePosition();
|
||||
}
|
||||
|
||||
// Cierra el picker
|
||||
void TilePicker::close() {
|
||||
open_ = false;
|
||||
tileset_.reset();
|
||||
frame_surface_.reset();
|
||||
}
|
||||
|
||||
// Renderiza el picker
|
||||
void TilePicker::render() {
|
||||
if (!open_ || !frame_surface_) { return; }
|
||||
|
||||
auto game_surface = Screen::get()->getRendererSurface();
|
||||
if (!game_surface) { return; }
|
||||
|
||||
int frame_h = static_cast<int>(frame_dst_.h);
|
||||
|
||||
if (frame_h <= visible_height_) {
|
||||
frame_surface_->render(nullptr, &frame_dst_);
|
||||
} else {
|
||||
int max_scroll = frame_h - visible_height_;
|
||||
scroll_y_ = std::clamp(scroll_y_, 0, max_scroll);
|
||||
|
||||
SDL_FRect src = {.x = 0, .y = static_cast<float>(scroll_y_), .w = frame_dst_.w, .h = static_cast<float>(visible_height_)};
|
||||
SDL_FRect dst = {.x = frame_dst_.x, .y = 0, .w = frame_dst_.w, .h = static_cast<float>(visible_height_)};
|
||||
frame_surface_->render(&src, &dst);
|
||||
}
|
||||
|
||||
// Highlights (en game_surface, encima del frame)
|
||||
int out_cell = Tile::SIZE + spacing_out_;
|
||||
float tileset_screen_x = frame_dst_.x + BORDER_PAD;
|
||||
float tileset_screen_y = frame_dst_.y + BORDER_PAD - static_cast<float>(scroll_y_);
|
||||
constexpr auto TS = static_cast<float>(Tile::SIZE);
|
||||
|
||||
// Highlight del tile bajo el cursor (blanco)
|
||||
if (hover_tile_ >= 0) {
|
||||
int col = hover_tile_ % tileset_width_;
|
||||
int row = hover_tile_ / tileset_width_;
|
||||
float hx = tileset_screen_x + static_cast<float>(col * out_cell);
|
||||
float hy = tileset_screen_y + static_cast<float>(row * out_cell);
|
||||
if (hy >= 0 && hy + TS <= visible_height_) {
|
||||
SDL_FRect highlight = {.x = hx, .y = hy, .w = TS, .h = TS};
|
||||
game_surface->drawRectBorder(&highlight, stringToColor("bright_white"));
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight del tile actual (verde)
|
||||
if (current_tile_ >= 0) {
|
||||
int col = current_tile_ % tileset_width_;
|
||||
int row = current_tile_ / tileset_width_;
|
||||
float cx = tileset_screen_x + static_cast<float>(col * out_cell);
|
||||
float cy = tileset_screen_y + static_cast<float>(row * out_cell);
|
||||
if (cy >= 0 && cy + TS <= visible_height_) {
|
||||
SDL_FRect cur_rect = {.x = cx, .y = cy, .w = TS, .h = TS};
|
||||
game_surface->drawRectBorder(&cur_rect, stringToColor("bright_green"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Maneja eventos del picker
|
||||
void TilePicker::handleEvent(const SDL_Event& event) {
|
||||
if (!open_) { return; }
|
||||
|
||||
if (event.type == SDL_EVENT_MOUSE_MOTION) {
|
||||
updateMousePosition();
|
||||
}
|
||||
|
||||
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
|
||||
if (event.button.button == SDL_BUTTON_LEFT && hover_tile_ >= 0) {
|
||||
if (on_select) { on_select(hover_tile_); }
|
||||
close();
|
||||
} else if (event.button.button == SDL_BUTTON_RIGHT) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
if (event.type == SDL_EVENT_MOUSE_WHEEL) {
|
||||
scroll_y_ -= static_cast<int>(event.wheel.y) * Tile::SIZE * 2;
|
||||
int max_scroll = static_cast<int>(frame_dst_.h) - visible_height_;
|
||||
max_scroll = std::max(max_scroll, 0);
|
||||
scroll_y_ = std::clamp(scroll_y_, 0, max_scroll);
|
||||
updateMousePosition();
|
||||
}
|
||||
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
// Calcula qué tile está bajo el cursor
|
||||
void TilePicker::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();
|
||||
float game_x = render_x - dst_rect.x;
|
||||
float game_y = render_y - dst_rect.y;
|
||||
|
||||
// Coordenada relativa al contenido del frame (con scroll)
|
||||
float rel_x = game_x - frame_dst_.x - BORDER_PAD;
|
||||
float rel_y = game_y - frame_dst_.y - BORDER_PAD + static_cast<float>(scroll_y_);
|
||||
|
||||
// Convertir a tile teniendo en cuenta el spacing de salida
|
||||
int out_cell = Tile::SIZE + spacing_out_;
|
||||
int tile_x = static_cast<int>(rel_x) / out_cell;
|
||||
int tile_y = static_cast<int>(rel_y) / out_cell;
|
||||
|
||||
// Verificar que estamos sobre un tile y no sobre el spacing
|
||||
int local_x = static_cast<int>(rel_x) % out_cell;
|
||||
int local_y = static_cast<int>(rel_y) % out_cell;
|
||||
bool on_tile = (local_x < Tile::SIZE && local_y < Tile::SIZE);
|
||||
|
||||
if (on_tile && rel_x >= 0 && rel_y >= 0 &&
|
||||
tile_x >= 0 && tile_x < tileset_width_ &&
|
||||
tile_y >= 0 && tile_y < tileset_height_) {
|
||||
hover_tile_ = tile_y * tileset_width_ + tile_x;
|
||||
} else {
|
||||
hover_tile_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // _DEBUG
|
||||
64
source/game/editor/tile_picker.hpp
Normal file
64
source/game/editor/tile_picker.hpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <functional> // Para function
|
||||
#include <memory> // Para shared_ptr
|
||||
#include <string> // Para string
|
||||
|
||||
class Surface;
|
||||
|
||||
/**
|
||||
* @brief Selector visual de tiles de un tileset
|
||||
*
|
||||
* Muestra el tileset centrado en el play area.
|
||||
* Hover ilumina el tile bajo el cursor.
|
||||
* Click selecciona el tile y cierra el picker.
|
||||
* Mouse wheel para scroll si el tileset es más alto que el play area.
|
||||
* ESC o click derecho para cancelar.
|
||||
*/
|
||||
class TilePicker {
|
||||
public:
|
||||
TilePicker() = default;
|
||||
~TilePicker() = default;
|
||||
|
||||
// Abre el picker con un tileset
|
||||
// bg_color: color de fondo del panel (-1 = negro)
|
||||
// source_color/target_color: sustitución de color (-1 = sin sustitución)
|
||||
// tile_spacing_in: pixels de separación entre tiles en el fichero fuente
|
||||
// tile_spacing_out: pixels de separación visual entre tiles al mostrar
|
||||
void open(const std::string& tileset_name, int current_tile = -1, int bg_color = -1, int source_color = -1, int target_color = -1, int tile_spacing_in = 0, int tile_spacing_out = 1);
|
||||
void close();
|
||||
[[nodiscard]] auto isOpen() const -> bool { return open_; }
|
||||
|
||||
void render();
|
||||
void handleEvent(const SDL_Event& event);
|
||||
|
||||
// Callback al seleccionar un tile (índice del tile)
|
||||
std::function<void(int)> on_select;
|
||||
|
||||
private:
|
||||
void updateMousePosition();
|
||||
|
||||
bool open_{false};
|
||||
std::shared_ptr<Surface> tileset_; // Surface del tileset original
|
||||
std::shared_ptr<Surface> frame_surface_; // Surface compuesta: borde + tileset
|
||||
SDL_FRect frame_dst_{}; // Posición del frame en pantalla
|
||||
int tileset_width_{0}; // Ancho del tileset en tiles
|
||||
int tileset_height_{0}; // Alto del tileset en tiles
|
||||
int current_tile_{-1}; // Tile actualmente seleccionado (highlight)
|
||||
int hover_tile_{-1}; // Tile bajo el cursor
|
||||
|
||||
// Spacing
|
||||
int spacing_in_{0}; // Spacing en el fichero fuente
|
||||
int spacing_out_{1}; // Spacing visual al mostrar
|
||||
|
||||
// Scroll y posicionamiento
|
||||
int scroll_y_{0}; // Scroll vertical en pixels
|
||||
int offset_x_{0}; // Offset X para centrar en pantalla
|
||||
int visible_height_{0}; // Altura visible en pixels
|
||||
};
|
||||
|
||||
#endif // _DEBUG
|
||||
Reference in New Issue
Block a user