treballant en editor de items i tile_picker

This commit is contained in:
2026-04-02 14:49:26 +02:00
parent acaf434e5c
commit 22d6ac2fbf
15 changed files with 540 additions and 45 deletions

View File

@@ -100,6 +100,7 @@ set(APP_SOURCES
source/game/editor/map_editor.cpp source/game/editor/map_editor.cpp
source/game/editor/editor_statusbar.cpp source/game/editor/editor_statusbar.cpp
source/game/editor/room_saver.cpp source/game/editor/room_saver.cpp
source/game/editor/tile_picker.cpp
# Game - UI # Game - UI
source/game/ui/console.cpp source/game/ui/console.cpp

View File

@@ -226,13 +226,20 @@ categories:
completions: completions:
ENEMY: [ADD, DELETE, DUPLICATE] ENEMY: [ADD, DELETE, DUPLICATE]
- keyword: ITEM
handler: cmd_item
description: "Add, delete or duplicate item (editor)"
usage: "ITEM <ADD|DELETE|DUPLICATE>"
completions:
ITEM: [ADD, DELETE, DUPLICATE]
- keyword: SET - keyword: SET
handler: cmd_set handler: cmd_set
description: "Set property (enemy or room, editor mode)" description: "Set property (enemy or room, editor mode)"
usage: "SET <property> <value>" usage: "SET <property> <value>"
dynamic_completions: true dynamic_completions: true
completions: completions:
SET: [ANIMATION, COLOR, VX, VY, FLIP, MIRROR, BGCOLOR, BORDER, ITEMCOLOR1, ITEMCOLOR2, CONVEYOR, TILESET, UP, DOWN, LEFT, RIGHT] SET: [ANIMATION, COLOR, VX, VY, FLIP, MIRROR, BGCOLOR, BORDER, ITEMCOLOR1, ITEMCOLOR2, CONVEYOR, TILESET, UP, DOWN, LEFT, RIGHT, TILE, COUNTER]
SET FLIP: [ON, OFF] SET FLIP: [ON, OFF]
SET MIRROR: [ON, OFF] SET MIRROR: [ON, OFF]
SET CONVEYOR: [LEFT, NONE, RIGHT] SET CONVEYOR: [LEFT, NONE, RIGHT]

View File

@@ -16,7 +16,7 @@
#include "core/resources/resource_list.hpp" // Para List, List::Type #include "core/resources/resource_list.hpp" // Para List, List::Type
#include "game/defaults.hpp" // Para Defaults namespace #include "game/defaults.hpp" // Para Defaults namespace
#include "game/gameplay/room.hpp" // Para RoomData, loadRoomFile, loadRoomTileFile #include "game/gameplay/room.hpp" // Para RoomData, loadRoomFile, loadRoomTileFile
#include "game/gameplay/room_loader.hpp" // Para RoomLoader::loadFromString #include "game/gameplay/room_loader.hpp" // Para RoomLoader::loadFromString
#include "game/options.hpp" // Para Options, OptionsGame, options #include "game/options.hpp" // Para Options, OptionsGame, options
#include "utils/defines.hpp" // Para WINDOW_CAPTION #include "utils/defines.hpp" // Para WINDOW_CAPTION
#include "utils/utils.hpp" // Para getFileName, printWithDots, PaletteColor #include "utils/utils.hpp" // Para getFileName, printWithDots, PaletteColor

View File

@@ -25,9 +25,9 @@ namespace Resource {
auto getRoom(const std::string& name) -> std::shared_ptr<Room::Data>; auto getRoom(const std::string& name) -> std::shared_ptr<Room::Data>;
auto getRooms() -> std::vector<RoomResource>&; auto getRooms() -> std::vector<RoomResource>&;
void reload(); // Recarga todos los recursos void reload(); // Recarga todos los recursos
#ifdef _DEBUG #ifdef _DEBUG
void reloadRoom(const std::string& name); // Recarga una habitación desde disco void reloadRoom(const std::string& name); // Recarga una habitación desde disco
#endif #endif
private: private:

View File

@@ -41,8 +41,8 @@ class EditorStatusBar {
// Variables // Variables
std::string room_number_; // Número de la habitación std::string room_number_; // Número de la habitación
std::string room_name_; // Nombre 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_x_{0}; // Coordenada X del ratón en tiles
int mouse_tile_y_{0}; // Coordenada Y 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 line2_; // Contenido de la línea 2
std::string line3_; // Contenido de la línea 3 std::string line3_; // Contenido de la línea 3
std::string line4_; // Contenido de la línea 4 std::string line4_; // Contenido de la línea 4

View File

@@ -13,13 +13,13 @@
#include "core/resources/resource_cache.hpp" // Para Resource::Cache #include "core/resources/resource_cache.hpp" // Para Resource::Cache
#include "core/resources/resource_list.hpp" // Para Resource::List #include "core/resources/resource_list.hpp" // Para Resource::List
#include "game/editor/editor_statusbar.hpp" // Para EditorStatusBar #include "game/editor/editor_statusbar.hpp" // Para EditorStatusBar
#include "game/ui/console.hpp" // Para Console
#include "game/editor/room_saver.hpp" // Para RoomSaver #include "game/editor/room_saver.hpp" // Para RoomSaver
#include "game/entities/player.hpp" // Para Player #include "game/entities/player.hpp" // Para Player
#include "game/gameplay/enemy_manager.hpp" // Para EnemyManager #include "game/gameplay/enemy_manager.hpp" // Para EnemyManager
#include "game/gameplay/item_manager.hpp" // Para ItemManager #include "game/gameplay/item_manager.hpp" // Para ItemManager
#include "game/gameplay/room.hpp" // Para Room #include "game/gameplay/room.hpp" // Para Room
#include "game/options.hpp" // Para Options #include "game/options.hpp" // Para Options
#include "game/ui/console.hpp" // Para Console
#include "utils/defines.hpp" // Para Tile::SIZE, PlayArea #include "utils/defines.hpp" // Para Tile::SIZE, PlayArea
#include "utils/utils.hpp" // Para stringToColor #include "utils/utils.hpp" // Para stringToColor
@@ -193,6 +193,11 @@ void MapEditor::render() {
// Renderizar highlight de selección (encima de los sprites) // Renderizar highlight de selección (encima de los sprites)
renderSelectionHighlight(); renderSelectionHighlight();
// Tile picker (encima de todo en el play area)
if (tile_picker_.isOpen()) {
tile_picker_.render();
}
// Renderizar barra de estado del editor (reemplaza al scoreboard) // Renderizar barra de estado del editor (reemplaza al scoreboard)
if (statusbar_) { if (statusbar_) {
statusbar_->render(); statusbar_->render();
@@ -201,6 +206,12 @@ void MapEditor::render() {
// Maneja eventos del editor // Maneja eventos del editor
void MapEditor::handleEvent(const SDL_Event& event) { void MapEditor::handleEvent(const SDL_Event& event) {
// Si el tile picker está abierto, los eventos van a él
if (tile_picker_.isOpen()) {
tile_picker_.handleEvent(event);
return;
}
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && event.button.button == SDL_BUTTON_LEFT) { if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && event.button.button == SDL_BUTTON_LEFT) {
handleMouseDown(mouse_game_x_, mouse_game_y_); handleMouseDown(mouse_game_x_, mouse_game_y_);
} else if (event.type == SDL_EVENT_MOUSE_BUTTON_UP && event.button.button == SDL_BUTTON_LEFT) { } else if (event.type == SDL_EVENT_MOUSE_BUTTON_UP && event.button.button == SDL_BUTTON_LEFT) {
@@ -268,8 +279,9 @@ void MapEditor::handleMouseDown(float game_x, float game_y) {
} }
} }
// Click en el fondo: deseleccionar // Click en el fondo: deseleccionar todo
selected_enemy_ = -1; selected_enemy_ = -1;
selected_item_ = -1;
} }
// Procesa soltar el ratón: commit del drag // Procesa soltar el ratón: commit del drag
@@ -281,11 +293,14 @@ void MapEditor::handleMouseUp() {
// Si no se movió: fue un click → seleccionar/deseleccionar // Si no se movió: fue un click → seleccionar/deseleccionar
if (!drag_.moved) { if (!drag_.moved) {
if (drag_.target == DragTarget::ENEMY_INITIAL) { if (drag_.target == DragTarget::ENEMY_INITIAL) {
// Toggle selección: si ya estaba seleccionado, deseleccionar
selected_enemy_ = (selected_enemy_ == IDX) ? -1 : IDX; selected_enemy_ = (selected_enemy_ == IDX) ? -1 : IDX;
} else { selected_item_ = -1;
// Click en otro tipo de entidad: deseleccionar enemigo } else if (drag_.target == DragTarget::ITEM) {
selected_item_ = (selected_item_ == IDX) ? -1 : IDX;
selected_enemy_ = -1; selected_enemy_ = -1;
} else {
selected_enemy_ = -1;
selected_item_ = -1;
} }
drag_ = {}; drag_ = {};
return; return;
@@ -333,6 +348,7 @@ void MapEditor::handleMouseUp() {
case DragTarget::ITEM: case DragTarget::ITEM:
if (IDX >= 0 && IDX < room_->getItemManager()->getCount()) { if (IDX >= 0 && IDX < room_->getItemManager()->getCount()) {
room_->getItemManager()->getItem(IDX)->setPosition(drag_.snap_x, drag_.snap_y); room_->getItemManager()->getItem(IDX)->setPosition(drag_.snap_x, drag_.snap_y);
selected_item_ = IDX;
selected_enemy_ = -1; selected_enemy_ = -1;
changed = true; changed = true;
} }
@@ -415,6 +431,17 @@ void MapEditor::renderSelectionHighlight() {
game_surface->drawRectBorder(&border, stringToColor("bright_green")); game_surface->drawRectBorder(&border, stringToColor("bright_green"));
} }
// Highlight del item seleccionado (persistente, color bright_green)
if (selected_item_ >= 0 && selected_item_ < room_->getItemManager()->getCount()) {
SDL_FRect item_rect = room_->getItemManager()->getItem(selected_item_)->getCollider();
SDL_FRect border = {
.x = item_rect.x - 1,
.y = item_rect.y - 1,
.w = item_rect.w + 2,
.h = item_rect.h + 2};
game_surface->drawRectBorder(&border, stringToColor("bright_green"));
}
// Highlight del drag activo (temporal, color bright_white) // Highlight del drag activo (temporal, color bright_white)
if (drag_.target == DragTarget::NONE || !drag_.moved) { return; } if (drag_.target == DragTarget::NONE || !drag_.moved) { return; }
@@ -559,12 +586,23 @@ void MapEditor::updateStatusBarInfo() {
// Info de drag activo (línea 5, junto a tile coords) // Info de drag activo (línea 5, junto a tile coords)
if (drag_.target != DragTarget::NONE && drag_.moved) { if (drag_.target != DragTarget::NONE && drag_.moved) {
switch (drag_.target) { switch (drag_.target) {
case DragTarget::PLAYER: line5 = "dragging: player"; break; case DragTarget::PLAYER:
case DragTarget::ENEMY_INITIAL: line5 = "dragging: enemy " + std::to_string(drag_.index); break; line5 = "dragging: player";
case DragTarget::ENEMY_BOUND1: line5 = "dragging: e" + std::to_string(drag_.index) + " bound1"; break; break;
case DragTarget::ENEMY_BOUND2: line5 = "dragging: e" + std::to_string(drag_.index) + " bound2"; break; case DragTarget::ENEMY_INITIAL:
case DragTarget::ITEM: line5 = "dragging: item " + std::to_string(drag_.index); break; line5 = "dragging: enemy " + std::to_string(drag_.index);
case DragTarget::NONE: break; break;
case DragTarget::ENEMY_BOUND1:
line5 = "dragging: e" + std::to_string(drag_.index) + " bound1";
break;
case DragTarget::ENEMY_BOUND2:
line5 = "dragging: e" + std::to_string(drag_.index) + " bound2";
break;
case DragTarget::ITEM:
line5 = "dragging: item " + std::to_string(drag_.index);
break;
case DragTarget::NONE:
break;
} }
} }
@@ -578,29 +616,40 @@ void MapEditor::updateStatusBarInfo() {
line2 = "enemy " + std::to_string(selected_enemy_) + ": " + anim + " " + e.color; line2 = "enemy " + std::to_string(selected_enemy_) + ": " + anim + " " + e.color;
line3 = "vx:" + std::to_string(static_cast<int>(e.vx)) + line3 = "vx:" + std::to_string(static_cast<int>(e.vx)) +
" vy:" + std::to_string(static_cast<int>(e.vy)); " vy:" + std::to_string(static_cast<int>(e.vy));
if (e.flip) { line3 += " flip"; } if (e.flip) { line3 += " flip"; }
if (e.mirror) { line3 += " mirror"; } if (e.mirror) { line3 += " mirror"; }
} else if (selected_item_ >= 0 && selected_item_ < static_cast<int>(room_data_.items.size())) {
// Item seleccionado
const auto& it = room_data_.items[selected_item_];
line2 = "item " + std::to_string(selected_item_) + ": tile=" + std::to_string(it.tile) +
" counter=" + std::to_string(it.counter);
line3 = "tileset: " + it.tile_set_file;
} else { } else {
// Propiedades de la habitación // Propiedades de la habitación
std::string conv = "none"; std::string conv = "none";
if (room_data_.conveyor_belt_direction < 0) { conv = "left"; } if (room_data_.conveyor_belt_direction < 0) {
else if (room_data_.conveyor_belt_direction > 0) { conv = "right"; } conv = "left";
} else if (room_data_.conveyor_belt_direction > 0) {
conv = "right";
}
line2 = "bg:" + room_data_.bg_color + " brd:" + room_data_.border_color + " conv:" + conv; line2 = "bg:" + room_data_.bg_color + " brd:" + room_data_.border_color + " conv:" + conv;
line3 = "u:" + conn(room_data_.upper_room) + " d:" + conn(room_data_.lower_room) + line3 = "u:" + conn(room_data_.upper_room) + " d:" + conn(room_data_.lower_room) +
" l:" + conn(room_data_.left_room) + " r:" + conn(room_data_.right_room) + " l:" + conn(room_data_.left_room) + " r:" + conn(room_data_.right_room) +
" itm:" + room_data_.item_color1 + "/" + room_data_.item_color2; " itm:" + room_data_.item_color1 + "/" + room_data_.item_color2;
} }
statusbar_->setLine2(line2); statusbar_->setLine2(line2);
statusbar_->setLine3(line3); statusbar_->setLine3(line3);
statusbar_->setLine4(""); // Disponible para uso futuro statusbar_->setLine4("");
statusbar_->setLine5(line5); statusbar_->setLine5(line5);
// Actualizar el prompt de la consola según la selección // Actualizar el prompt de la consola según la selección
if (selected_enemy_ >= 0) { if (selected_enemy_ >= 0) {
Console::get()->setPrompt("enemy " + std::to_string(selected_enemy_) + "> "); Console::get()->setPrompt("enemy " + std::to_string(selected_enemy_) + "> ");
} else if (selected_item_ >= 0) {
Console::get()->setPrompt("item " + std::to_string(selected_item_) + "> ");
} else { } else {
Console::get()->setPrompt("room> "); Console::get()->setPrompt("room> ");
} }
@@ -826,9 +875,14 @@ auto MapEditor::setRoomProperty(const std::string& property, const std::string&
} }
if (property == "CONVEYOR") { if (property == "CONVEYOR") {
if (val == "left") { room_data_.conveyor_belt_direction = -1; } if (val == "left") {
else if (val == "right") { room_data_.conveyor_belt_direction = 1; } room_data_.conveyor_belt_direction = -1;
else { room_data_.conveyor_belt_direction = 0; val = "none"; } } else if (val == "right") {
room_data_.conveyor_belt_direction = 1;
} else {
room_data_.conveyor_belt_direction = 0;
val = "none";
}
autosave(); autosave();
return "conveyor: " + val; return "conveyor: " + val;
} }
@@ -858,10 +912,15 @@ auto MapEditor::setRoomProperty(const std::string& property, const std::string&
} }
} }
if (property == "UP") { room_data_.upper_room = connection; } if (property == "UP") {
else if (property == "DOWN") { room_data_.lower_room = connection; } room_data_.upper_room = connection;
else if (property == "LEFT") { room_data_.left_room = connection; } } else if (property == "DOWN") {
else { room_data_.right_room = connection; } room_data_.lower_room = connection;
} else if (property == "LEFT") {
room_data_.left_room = connection;
} else {
room_data_.right_room = connection;
}
autosave(); autosave();
return toLower(property) + ": " + connection; return toLower(property) + ": " + connection;
@@ -870,4 +929,113 @@ auto MapEditor::setRoomProperty(const std::string& property, const std::string&
return "Unknown property: " + property + " (use: bgcolor, border, itemcolor1, itemcolor2, conveyor, tileset, up, down, left, right)"; return "Unknown property: " + property + " (use: bgcolor, border, itemcolor1, itemcolor2, conveyor, tileset, up, down, left, right)";
} }
// ¿Hay un item seleccionado?
auto MapEditor::hasSelectedItem() const -> bool {
return selected_item_ >= 0 && selected_item_ < static_cast<int>(room_data_.items.size());
}
// Modifica una propiedad del item seleccionado
auto MapEditor::setItemProperty(const std::string& property, const std::string& value) -> std::string {
if (!active_) { return "Editor not active"; }
if (!hasSelectedItem()) { return "No item selected"; }
auto& item = room_data_.items[selected_item_];
if (property == "TILE") {
// Abrir el tile picker visual
openTilePicker(item.tile_set_file, item.tile);
return "Select tile...";
}
if (property == "COUNTER") {
try {
item.counter = std::stoi(value);
} catch (...) { return "Invalid value: " + value; }
autosave();
return "counter: " + std::to_string(item.counter);
}
return "Unknown property: " + property + " (use: tile, counter)";
}
// Abre el tile picker para seleccionar un tile
void MapEditor::openTilePicker(const std::string& tileset_name, int current_tile) {
// Cerrar la consola si está abierta (para que el primer click vaya al picker)
if (Console::get()->isActive()) {
Console::get()->toggle();
}
tile_picker_.on_select = [this](int tile) {
if (!hasSelectedItem()) { return; }
room_data_.items[selected_item_].tile = tile;
room_->getItemManager()->getItem(selected_item_)->setTile(tile);
autosave();
};
// Pasar color de fondo de la habitación + color de sustitución del item
int bg = stringToColor(room_data_.bg_color);
int item_color = stringToColor(room_data_.item_color1);
tile_picker_.open(tileset_name, current_tile, bg, 1, item_color);
}
// Crea un nuevo item con valores por defecto, centrado en la habitación
auto MapEditor::addItem() -> std::string {
if (!active_) { return "Editor not active"; }
Item::Data new_item;
new_item.tile_set_file = "items.gif";
new_item.tile = 42; // Tile por defecto
new_item.x = PlayArea::CENTER_X;
new_item.y = PlayArea::CENTER_Y;
new_item.counter = 0;
new_item.color1 = stringToColor(room_data_.item_color1);
new_item.color2 = stringToColor(room_data_.item_color2);
room_data_.items.push_back(new_item);
room_->getItemManager()->addItem(std::make_shared<Item>(new_item));
selected_item_ = static_cast<int>(room_data_.items.size()) - 1;
selected_enemy_ = -1;
autosave();
return "Added item " + std::to_string(selected_item_);
}
// Elimina el item seleccionado
auto MapEditor::deleteItem() -> std::string {
if (!active_) { return "Editor not active"; }
if (!hasSelectedItem()) { return "No item selected"; }
const int IDX = selected_item_;
room_data_.items.erase(room_data_.items.begin() + IDX);
// Recrear todos los items (los índices cambian al borrar)
auto* item_mgr = room_->getItemManager();
item_mgr->clear();
for (const auto& item_data : room_data_.items) {
item_mgr->addItem(std::make_shared<Item>(item_data));
}
selected_item_ = -1;
autosave();
return "Deleted item " + std::to_string(IDX);
}
// Duplica el item seleccionado (lo pone un tile a la derecha)
auto MapEditor::duplicateItem() -> std::string {
if (!active_) { return "Editor not active"; }
if (!hasSelectedItem()) { return "No item selected"; }
Item::Data copy = room_data_.items[selected_item_];
copy.x += Tile::SIZE;
room_data_.items.push_back(copy);
room_->getItemManager()->addItem(std::make_shared<Item>(copy));
selected_item_ = static_cast<int>(room_data_.items.size()) - 1;
selected_enemy_ = -1;
autosave();
return "Duplicated as item " + std::to_string(selected_item_);
}
#endif // _DEBUG #endif // _DEBUG

View File

@@ -8,6 +8,7 @@
#include <string> // Para string #include <string> // Para string
#include "external/fkyaml_node.hpp" // Para fkyaml::node #include "external/fkyaml_node.hpp" // Para fkyaml::node
#include "game/editor/tile_picker.hpp" // Para TilePicker
#include "game/entities/enemy.hpp" // Para Enemy::Data #include "game/entities/enemy.hpp" // Para Enemy::Data
#include "game/entities/item.hpp" // Para Item::Data #include "game/entities/item.hpp" // Para Item::Data
#include "game/entities/player.hpp" // Para Player::SpawnData #include "game/entities/player.hpp" // Para Player::SpawnData
@@ -42,6 +43,14 @@ class MapEditor {
// Comandos para propiedades de la habitación // Comandos para propiedades de la habitación
auto setRoomProperty(const std::string& property, const std::string& value) -> std::string; auto setRoomProperty(const std::string& property, const std::string& value) -> 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: private:
static MapEditor* instance_; // [SINGLETON] Objeto privado static MapEditor* instance_; // [SINGLETON] Objeto privado
@@ -49,7 +58,12 @@ class MapEditor {
~MapEditor(); // Destructor ~MapEditor(); // Destructor
// Tipos para drag & drop y selección // Tipos para drag & drop y selección
enum class DragTarget { NONE, PLAYER, ENEMY_INITIAL, ENEMY_BOUND1, ENEMY_BOUND2, ITEM }; enum class DragTarget { NONE,
PLAYER,
ENEMY_INITIAL,
ENEMY_BOUND1,
ENEMY_BOUND2,
ITEM };
struct DragState { struct DragState {
DragTarget target{DragTarget::NONE}; DragTarget target{DragTarget::NONE};
@@ -78,6 +92,7 @@ class MapEditor {
bool active_{false}; bool active_{false};
DragState drag_; DragState drag_;
int selected_enemy_{-1}; // Índice del enemigo seleccionado (-1 = ninguno) int selected_enemy_{-1}; // Índice del enemigo seleccionado (-1 = ninguno)
int selected_item_{-1}; // Índice del item seleccionado (-1 = ninguno)
// Datos de la habitación // Datos de la habitación
Room::Data room_data_; Room::Data room_data_;
@@ -96,6 +111,9 @@ class MapEditor {
// Barra de estado del editor // Barra de estado del editor
std::unique_ptr<EditorStatusBar> statusbar_; std::unique_ptr<EditorStatusBar> statusbar_;
// Tile picker (para seleccionar tiles de un tileset)
TilePicker tile_picker_;
// Estado del ratón // Estado del ratón
float mouse_game_x_{0.0F}; float mouse_game_x_{0.0F};
float mouse_game_y_{0.0F}; float mouse_game_y_{0.0F};

View File

@@ -0,0 +1,208 @@
#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) {
tileset_ = Resource::Cache::get()->getSurface(tileset_name);
if (!tileset_) {
open_ = false;
return;
}
tileset_width_ = static_cast<int>(tileset_->getWidth()) / Tile::SIZE;
tileset_height_ = static_cast<int>(tileset_->getHeight()) / Tile::SIZE;
current_tile_ = current_tile;
hover_tile_ = -1;
scroll_y_ = 0;
int tileset_px_w = tileset_width_ * Tile::SIZE;
int tileset_px_h = tileset_height_ * Tile::SIZE;
// Crear surface con borde: tileset + BORDER_PAD pixels a cada lado
int frame_w = tileset_px_w + BORDER_PAD * 2;
int frame_h = tileset_px_h + BORDER_PAD * 2;
frame_surface_ = std::make_shared<Surface>(frame_w, frame_h);
// Componer: fondo + borde + tileset (con sustitución de color opcional)
{
auto prev = Screen::get()->getRendererSurface();
Screen::get()->setRendererSurface(frame_surface_);
// Fondo: color personalizado o negro por defecto
Uint8 fill_color = (bg_color >= 0) ? static_cast<Uint8>(bg_color) : stringToColor("black");
frame_surface_->clear(fill_color);
// Borde doble (bright_white + white)
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"));
// Tileset dentro del borde (con o sin sustitución de color)
if (source_color >= 0 && target_color >= 0) {
tileset_->renderWithColorReplace(BORDER_PAD, BORDER_PAD, static_cast<Uint8>(source_color), static_cast<Uint8>(target_color));
} else {
SDL_FRect dst_rect = {.x = static_cast<float>(BORDER_PAD), .y = static_cast<float>(BORDER_PAD), .w = static_cast<float>(tileset_px_w), .h = static_cast<float>(tileset_px_h)};
tileset_->render(nullptr, &dst_rect);
}
Screen::get()->setRendererSurface(prev);
}
// Centrar en el play area
offset_x_ = (PlayArea::WIDTH - frame_w) / 2;
int offset_y = (PlayArea::HEIGHT - frame_h) / 2;
if (offset_y < 0) { offset_y = 0; }
// Área visible
visible_height_ = PlayArea::HEIGHT;
// Posición de destino del frame
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;
// Scroll hasta el tile actual si está fuera de vista
if (current_tile_ >= 0) {
int tile_row = current_tile_ / tileset_width_;
int tile_y_px = tile_row * Tile::SIZE;
if (tile_y_px > visible_height_ / 2) {
scroll_y_ = tile_y_px - visible_height_ / 2;
}
}
}
open_ = true;
}
// 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_) {
// El frame cabe entero: renderizar tal cual (flotando sobre el mapa)
frame_surface_->render(nullptr, &frame_dst_);
} else {
// El frame no cabe: renderizar porción visible con scroll
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);
}
// Los highlights se dibujan directamente en game_surface (encima del frame)
float tileset_screen_x = frame_dst_.x + BORDER_PAD;
float tileset_screen_y = frame_dst_.y + BORDER_PAD - static_cast<float>(scroll_y_);
// 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 * Tile::SIZE);
float hy = tileset_screen_y + static_cast<float>(row * Tile::SIZE);
if (hy >= 0 && hy + Tile::SIZE <= visible_height_) {
SDL_FRect highlight = {.x = hx, .y = hy, .w = static_cast<float>(Tile::SIZE), .h = static_cast<float>(Tile::SIZE)};
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 * Tile::SIZE);
float cy = tileset_screen_y + static_cast<float>(row * Tile::SIZE);
if (cy >= 0 && cy + Tile::SIZE <= visible_height_) {
SDL_FRect cur_rect = {.x = cx, .y = cy, .w = static_cast<float>(Tile::SIZE), .h = static_cast<float>(Tile::SIZE)};
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_;
if (max_scroll < 0) { 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 tileset (dentro del frame, con scroll)
float tileset_x = game_x - frame_dst_.x - BORDER_PAD;
float tileset_y = game_y - frame_dst_.y - BORDER_PAD + static_cast<float>(scroll_y_);
int tile_x = static_cast<int>(tileset_x) / Tile::SIZE;
int tile_y = static_cast<int>(tileset_y) / Tile::SIZE;
if (tileset_x >= 0 && tile_x >= 0 && tile_x < tileset_width_ &&
tileset_y >= 0 && tile_y >= 0 && tile_y < tileset_height_) {
hover_tile_ = tile_y * tileset_width_ + tile_x;
} else {
hover_tile_ = -1;
}
}
#endif // _DEBUG

View File

@@ -0,0 +1,58 @@
#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 al renderizar el tileset (-1 = sin sustitución)
void open(const std::string& tileset_name, int current_tile = -1, int bg_color = -1, int source_color = -1, int target_color = -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
// 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

View File

@@ -49,6 +49,13 @@ void Item::setPosition(float x, float y) {
} }
#endif #endif
#ifdef _DEBUG
// Cambia el tile del item (para editor)
void Item::setTile(int tile) {
sprite_->setClip((tile % 10) * ITEM_SIZE, (tile / 10) * ITEM_SIZE, ITEM_SIZE, ITEM_SIZE);
}
#endif
// Asigna los colores del objeto // Asigna los colores del objeto
void Item::setColors(Uint8 col1, Uint8 col2) { void Item::setColors(Uint8 col1, Uint8 col2) {
// Reinicializa el vector de colores // Reinicializa el vector de colores

View File

@@ -31,6 +31,7 @@ class Item {
void setColors(Uint8 col1, Uint8 col2); // Asigna los colores del objeto void setColors(Uint8 col1, Uint8 col2); // Asigna los colores del objeto
#ifdef _DEBUG #ifdef _DEBUG
void setPosition(float x, float y); // Establece la posición del item (para editor) void setPosition(float x, float y); // Establece la posición del item (para editor)
void setTile(int tile); // Cambia el tile del item (para editor)
#endif #endif
private: private:

View File

@@ -27,7 +27,7 @@ namespace GameControl {
inline std::function<void()> enter_editor; inline std::function<void()> enter_editor;
inline std::function<void()> exit_editor; inline std::function<void()> exit_editor;
inline std::function<std::string()> revert_editor; inline std::function<std::string()> revert_editor;
inline std::function<void()> reload_current_room; // Recarga la habitación actual desde disco inline std::function<void()> reload_current_room; // Recarga la habitación actual desde disco
inline std::function<std::string(const std::string&)> get_adjacent_room; // Obtiene la room adyacente (UP/DOWN/LEFT/RIGHT) inline std::function<std::string(const std::string&)> get_adjacent_room; // Obtiene la room adyacente (UP/DOWN/LEFT/RIGHT)
} // namespace GameControl } // namespace GameControl
#endif #endif

View File

@@ -69,13 +69,13 @@ class Room {
void renderEnemies(); // Dibuja los enemigos en pantalla void renderEnemies(); // Dibuja los enemigos en pantalla
void renderItems(); // Dibuja los objetos en pantalla void renderItems(); // Dibuja los objetos en pantalla
#ifdef _DEBUG #ifdef _DEBUG
void redrawMap(); // Redibuja el mapa (para actualizar modo debug) void redrawMap(); // Redibuja el mapa (para actualizar modo debug)
void updateEditorMode(float delta_time); // Actualiza animaciones sin mover enemigos (para editor) void updateEditorMode(float delta_time); // Actualiza animaciones sin mover enemigos (para editor)
void resetEnemyPositions(const std::vector<Enemy::Data>& enemy_data); // Resetea enemigos a posiciones iniciales void resetEnemyPositions(const std::vector<Enemy::Data>& enemy_data); // Resetea enemigos a posiciones iniciales
auto getEnemyManager() -> EnemyManager* { return enemy_manager_.get(); } // Acceso al gestor de enemigos (para editor) auto getEnemyManager() -> EnemyManager* { return enemy_manager_.get(); } // Acceso al gestor de enemigos (para editor)
auto getItemManager() -> ItemManager* { return item_manager_.get(); } // Acceso al gestor de items (para editor) auto getItemManager() -> ItemManager* { return item_manager_.get(); } // Acceso al gestor de items (para editor)
void setBgColor(const std::string& color); // Cambia color de fondo y redibuja (para editor) void setBgColor(const std::string& color); // Cambia color de fondo y redibuja (para editor)
void setItemColors(const std::string& color1, const std::string& color2); // Cambia colores de items (para editor) void setItemColors(const std::string& color1, const std::string& color2); // Cambia colores de items (para editor)
#endif #endif
void update(float delta_time); // Actualiza las variables y objetos de la habitación void update(float delta_time); // Actualiza las variables y objetos de la habitación
auto getRoom(Border border) -> std::string; // Devuelve la cadena del fichero de la habitación contigua segun el borde auto getRoom(Border border) -> std::string; // Devuelve la cadena del fichero de la habitación contigua segun el borde

View File

@@ -87,7 +87,7 @@ class Console {
// Estado de la entrada de texto // Estado de la entrada de texto
std::vector<std::string> msg_lines_; // Líneas de mensaje (1 o más) std::vector<std::string> msg_lines_; // Líneas de mensaje (1 o más)
std::string input_line_; std::string input_line_;
std::string prompt_{"> "}; // Prompt configurable std::string prompt_{"> "}; // Prompt configurable
float cursor_timer_{0.0F}; float cursor_timer_{0.0F};
bool cursor_visible_{true}; bool cursor_visible_{true};

View File

@@ -23,8 +23,8 @@
#include "utils/utils.hpp" // Para toUpper, prettyName #include "utils/utils.hpp" // Para toUpper, prettyName
#ifdef _DEBUG #ifdef _DEBUG
#include "core/system/debug.hpp" // Para Debug #include "core/system/debug.hpp" // Para Debug
#include "game/editor/map_editor.hpp" // Para MapEditor #include "game/editor/map_editor.hpp" // Para MapEditor
#endif #endif
// ── Helpers ────────────────────────────────────────────────────────────────── // ── Helpers ──────────────────────────────────────────────────────────────────
@@ -708,6 +708,13 @@ static auto cmd_edit(const std::vector<std::string>& args) -> std::string {
// SET <property> <value> — modifica propiedad del enemigo seleccionado o de la habitación // SET <property> <value> — modifica propiedad del enemigo seleccionado o de la habitación
static auto cmd_set(const std::vector<std::string>& args) -> std::string { static auto cmd_set(const std::vector<std::string>& args) -> std::string {
if (!MapEditor::get() || !MapEditor::get()->isActive()) { return "Editor not active"; } if (!MapEditor::get() || !MapEditor::get()->isActive()) { return "Editor not active"; }
if (args.empty()) { return "usage: set <property> <value>"; }
// SET TILE no necesita argumento (abre el tile picker visual)
if (args[0] == "TILE" && MapEditor::get()->hasSelectedItem()) {
return MapEditor::get()->setItemProperty("TILE", "");
}
if (args.size() < 2) { return "usage: set <property> <value>"; } if (args.size() < 2) { return "usage: set <property> <value>"; }
// Si hay enemigo seleccionado, aplicar a enemigo // Si hay enemigo seleccionado, aplicar a enemigo
@@ -715,6 +722,11 @@ static auto cmd_set(const std::vector<std::string>& args) -> std::string {
return MapEditor::get()->setEnemyProperty(args[0], args[1]); return MapEditor::get()->setEnemyProperty(args[0], args[1]);
} }
// Si hay item seleccionado, aplicar a item
if (MapEditor::get()->hasSelectedItem()) {
return MapEditor::get()->setItemProperty(args[0], args[1]);
}
// Si no, aplicar a la habitación // Si no, aplicar a la habitación
return MapEditor::get()->setRoomProperty(args[0], args[1]); return MapEditor::get()->setRoomProperty(args[0], args[1]);
} }
@@ -734,6 +746,22 @@ static auto cmd_enemy(const std::vector<std::string>& args) -> std::string {
} }
return "usage: enemy <add|delete|duplicate>"; return "usage: enemy <add|delete|duplicate>";
} }
// ITEM [ADD|DELETE|DUPLICATE]
static auto cmd_item(const std::vector<std::string>& args) -> std::string {
if (!MapEditor::get() || !MapEditor::get()->isActive()) { return "Editor not active"; }
if (args.empty()) { return "usage: item <add|delete|duplicate>"; }
if (args[0] == "ADD") { return MapEditor::get()->addItem(); }
if (args[0] == "DELETE") {
if (!MapEditor::get()->hasSelectedItem()) { return "No item selected"; }
return MapEditor::get()->deleteItem();
}
if (args[0] == "DUPLICATE") {
if (!MapEditor::get()->hasSelectedItem()) { return "No item selected"; }
return MapEditor::get()->duplicateItem();
}
return "usage: item <add|delete|duplicate>";
}
#endif #endif
// SHOW [INFO|NOTIFICATION|CHEEVO] // SHOW [INFO|NOTIFICATION|CHEEVO]
@@ -937,6 +965,7 @@ void CommandRegistry::registerHandlers() {
handlers_["cmd_edit"] = cmd_edit; handlers_["cmd_edit"] = cmd_edit;
handlers_["cmd_set"] = cmd_set; handlers_["cmd_set"] = cmd_set;
handlers_["cmd_enemy"] = cmd_enemy; handlers_["cmd_enemy"] = cmd_enemy;
handlers_["cmd_item"] = cmd_item;
#endif #endif
// HELP se registra en load() como lambda que captura this // HELP se registra en load() como lambda que captura this
@@ -967,9 +996,7 @@ void CommandRegistry::registerHandlers() {
#ifdef _DEBUG #ifdef _DEBUG
// Colores de la paleta (compartido por SET COLOR, BGCOLOR, BORDER, ITEMCOLOR1, ITEMCOLOR2) // Colores de la paleta (compartido por SET COLOR, BGCOLOR, BORDER, ITEMCOLOR1, ITEMCOLOR2)
auto color_provider = []() -> std::vector<std::string> { auto color_provider = []() -> std::vector<std::string> {
return {"BLACK", "BRIGHT_BLACK", "BLUE", "BRIGHT_BLUE", "RED", "BRIGHT_RED", return {"BLACK", "BRIGHT_BLACK", "BLUE", "BRIGHT_BLUE", "RED", "BRIGHT_RED", "MAGENTA", "BRIGHT_MAGENTA", "GREEN", "BRIGHT_GREEN", "CYAN", "BRIGHT_CYAN", "YELLOW", "BRIGHT_YELLOW", "WHITE", "BRIGHT_WHITE"};
"MAGENTA", "BRIGHT_MAGENTA", "GREEN", "BRIGHT_GREEN", "CYAN", "BRIGHT_CYAN",
"YELLOW", "BRIGHT_YELLOW", "WHITE", "BRIGHT_WHITE"};
}; };
dynamic_providers_["SET COLOR"] = color_provider; dynamic_providers_["SET COLOR"] = color_provider;
dynamic_providers_["SET BGCOLOR"] = color_provider; dynamic_providers_["SET BGCOLOR"] = color_provider;