treballant en editor de items i tile_picker
This commit is contained in:
@@ -41,8 +41,8 @@ class EditorStatusBar {
|
||||
// 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
|
||||
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
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
#include "core/resources/resource_cache.hpp" // Para Resource::Cache
|
||||
#include "core/resources/resource_list.hpp" // Para Resource::List
|
||||
#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/entities/player.hpp" // Para Player
|
||||
#include "game/gameplay/enemy_manager.hpp" // Para EnemyManager
|
||||
#include "game/gameplay/item_manager.hpp" // Para ItemManager
|
||||
#include "game/gameplay/room.hpp" // Para Room
|
||||
#include "game/options.hpp" // Para Options
|
||||
#include "game/ui/console.hpp" // Para Console
|
||||
#include "utils/defines.hpp" // Para Tile::SIZE, PlayArea
|
||||
#include "utils/utils.hpp" // Para stringToColor
|
||||
|
||||
@@ -193,6 +193,11 @@ void MapEditor::render() {
|
||||
// Renderizar highlight de selección (encima de los sprites)
|
||||
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)
|
||||
if (statusbar_) {
|
||||
statusbar_->render();
|
||||
@@ -201,6 +206,12 @@ void MapEditor::render() {
|
||||
|
||||
// Maneja eventos del editor
|
||||
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) {
|
||||
handleMouseDown(mouse_game_x_, mouse_game_y_);
|
||||
} 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_item_ = -1;
|
||||
}
|
||||
|
||||
// Procesa soltar el ratón: commit del drag
|
||||
@@ -281,11 +293,14 @@ void MapEditor::handleMouseUp() {
|
||||
// Si no se movió: fue un click → seleccionar/deseleccionar
|
||||
if (!drag_.moved) {
|
||||
if (drag_.target == DragTarget::ENEMY_INITIAL) {
|
||||
// Toggle selección: si ya estaba seleccionado, deseleccionar
|
||||
selected_enemy_ = (selected_enemy_ == IDX) ? -1 : IDX;
|
||||
} else {
|
||||
// Click en otro tipo de entidad: deseleccionar enemigo
|
||||
selected_item_ = -1;
|
||||
} else if (drag_.target == DragTarget::ITEM) {
|
||||
selected_item_ = (selected_item_ == IDX) ? -1 : IDX;
|
||||
selected_enemy_ = -1;
|
||||
} else {
|
||||
selected_enemy_ = -1;
|
||||
selected_item_ = -1;
|
||||
}
|
||||
drag_ = {};
|
||||
return;
|
||||
@@ -333,6 +348,7 @@ void MapEditor::handleMouseUp() {
|
||||
case DragTarget::ITEM:
|
||||
if (IDX >= 0 && IDX < room_->getItemManager()->getCount()) {
|
||||
room_->getItemManager()->getItem(IDX)->setPosition(drag_.snap_x, drag_.snap_y);
|
||||
selected_item_ = IDX;
|
||||
selected_enemy_ = -1;
|
||||
changed = true;
|
||||
}
|
||||
@@ -415,6 +431,17 @@ void MapEditor::renderSelectionHighlight() {
|
||||
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)
|
||||
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)
|
||||
if (drag_.target != DragTarget::NONE && drag_.moved) {
|
||||
switch (drag_.target) {
|
||||
case DragTarget::PLAYER: line5 = "dragging: player"; break;
|
||||
case DragTarget::ENEMY_INITIAL: line5 = "dragging: enemy " + std::to_string(drag_.index); 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;
|
||||
case DragTarget::PLAYER:
|
||||
line5 = "dragging: player";
|
||||
break;
|
||||
case DragTarget::ENEMY_INITIAL:
|
||||
line5 = "dragging: enemy " + std::to_string(drag_.index);
|
||||
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;
|
||||
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.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 {
|
||||
// Propiedades de la habitación
|
||||
std::string conv = "none";
|
||||
if (room_data_.conveyor_belt_direction < 0) { conv = "left"; }
|
||||
else if (room_data_.conveyor_belt_direction > 0) { conv = "right"; }
|
||||
if (room_data_.conveyor_belt_direction < 0) {
|
||||
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;
|
||||
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) +
|
||||
" itm:" + room_data_.item_color1 + "/" + room_data_.item_color2;
|
||||
" l:" + conn(room_data_.left_room) + " r:" + conn(room_data_.right_room) +
|
||||
" itm:" + room_data_.item_color1 + "/" + room_data_.item_color2;
|
||||
}
|
||||
|
||||
statusbar_->setLine2(line2);
|
||||
statusbar_->setLine3(line3);
|
||||
statusbar_->setLine4(""); // Disponible para uso futuro
|
||||
statusbar_->setLine4("");
|
||||
statusbar_->setLine5(line5);
|
||||
|
||||
// Actualizar el prompt de la consola según la selección
|
||||
if (selected_enemy_ >= 0) {
|
||||
Console::get()->setPrompt("enemy " + std::to_string(selected_enemy_) + "> ");
|
||||
} else if (selected_item_ >= 0) {
|
||||
Console::get()->setPrompt("item " + std::to_string(selected_item_) + "> ");
|
||||
} else {
|
||||
Console::get()->setPrompt("room> ");
|
||||
}
|
||||
@@ -826,9 +875,14 @@ auto MapEditor::setRoomProperty(const std::string& property, const std::string&
|
||||
}
|
||||
|
||||
if (property == "CONVEYOR") {
|
||||
if (val == "left") { room_data_.conveyor_belt_direction = -1; }
|
||||
else if (val == "right") { room_data_.conveyor_belt_direction = 1; }
|
||||
else { room_data_.conveyor_belt_direction = 0; val = "none"; }
|
||||
if (val == "left") {
|
||||
room_data_.conveyor_belt_direction = -1;
|
||||
} else if (val == "right") {
|
||||
room_data_.conveyor_belt_direction = 1;
|
||||
} else {
|
||||
room_data_.conveyor_belt_direction = 0;
|
||||
val = "none";
|
||||
}
|
||||
autosave();
|
||||
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; }
|
||||
else if (property == "DOWN") { room_data_.lower_room = connection; }
|
||||
else if (property == "LEFT") { room_data_.left_room = connection; }
|
||||
else { room_data_.right_room = connection; }
|
||||
if (property == "UP") {
|
||||
room_data_.upper_room = connection;
|
||||
} else if (property == "DOWN") {
|
||||
room_data_.lower_room = connection;
|
||||
} else if (property == "LEFT") {
|
||||
room_data_.left_room = connection;
|
||||
} else {
|
||||
room_data_.right_room = connection;
|
||||
}
|
||||
|
||||
autosave();
|
||||
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)";
|
||||
}
|
||||
|
||||
// ¿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
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <string> // Para string
|
||||
|
||||
#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/item.hpp" // Para Item::Data
|
||||
#include "game/entities/player.hpp" // Para Player::SpawnData
|
||||
@@ -42,6 +43,14 @@ class MapEditor {
|
||||
// Comandos para propiedades de la habitación
|
||||
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:
|
||||
static MapEditor* instance_; // [SINGLETON] Objeto privado
|
||||
|
||||
@@ -49,7 +58,12 @@ class MapEditor {
|
||||
~MapEditor(); // Destructor
|
||||
|
||||
// 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 {
|
||||
DragTarget target{DragTarget::NONE};
|
||||
@@ -78,6 +92,7 @@ class MapEditor {
|
||||
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)
|
||||
|
||||
// Datos de la habitación
|
||||
Room::Data room_data_;
|
||||
@@ -96,6 +111,9 @@ class MapEditor {
|
||||
// Barra de estado del editor
|
||||
std::unique_ptr<EditorStatusBar> statusbar_;
|
||||
|
||||
// Tile picker (para seleccionar tiles de un tileset)
|
||||
TilePicker tile_picker_;
|
||||
|
||||
// Estado del ratón
|
||||
float mouse_game_x_{0.0F};
|
||||
float mouse_game_y_{0.0F};
|
||||
|
||||
208
source/game/editor/tile_picker.cpp
Normal file
208
source/game/editor/tile_picker.cpp
Normal 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
|
||||
58
source/game/editor/tile_picker.hpp
Normal file
58
source/game/editor/tile_picker.hpp
Normal 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
|
||||
@@ -49,6 +49,13 @@ void Item::setPosition(float x, float y) {
|
||||
}
|
||||
#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
|
||||
void Item::setColors(Uint8 col1, Uint8 col2) {
|
||||
// Reinicializa el vector de colores
|
||||
|
||||
@@ -31,6 +31,7 @@ class Item {
|
||||
void setColors(Uint8 col1, Uint8 col2); // Asigna los colores del objeto
|
||||
#ifdef _DEBUG
|
||||
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
|
||||
|
||||
private:
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace GameControl {
|
||||
inline std::function<void()> enter_editor;
|
||||
inline std::function<void()> exit_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)
|
||||
} // namespace GameControl
|
||||
#endif
|
||||
|
||||
@@ -69,13 +69,13 @@ class Room {
|
||||
void renderEnemies(); // Dibuja los enemigos en pantalla
|
||||
void renderItems(); // Dibuja los objetos en pantalla
|
||||
#ifdef _DEBUG
|
||||
void redrawMap(); // Redibuja el mapa (para actualizar modo debug)
|
||||
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
|
||||
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)
|
||||
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 redrawMap(); // Redibuja el mapa (para actualizar modo debug)
|
||||
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
|
||||
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)
|
||||
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)
|
||||
#endif
|
||||
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
|
||||
|
||||
@@ -87,7 +87,7 @@ class Console {
|
||||
// Estado de la entrada de texto
|
||||
std::vector<std::string> msg_lines_; // Líneas de mensaje (1 o más)
|
||||
std::string input_line_;
|
||||
std::string prompt_{"> "}; // Prompt configurable
|
||||
std::string prompt_{"> "}; // Prompt configurable
|
||||
float cursor_timer_{0.0F};
|
||||
bool cursor_visible_{true};
|
||||
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
#include "utils/utils.hpp" // Para toUpper, prettyName
|
||||
|
||||
#ifdef _DEBUG
|
||||
#include "core/system/debug.hpp" // Para Debug
|
||||
#include "game/editor/map_editor.hpp" // Para MapEditor
|
||||
#include "core/system/debug.hpp" // Para Debug
|
||||
#include "game/editor/map_editor.hpp" // Para MapEditor
|
||||
#endif
|
||||
|
||||
// ── 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
|
||||
static auto cmd_set(const std::vector<std::string>& args) -> std::string {
|
||||
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>"; }
|
||||
|
||||
// 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]);
|
||||
}
|
||||
|
||||
// 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
|
||||
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>";
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// SHOW [INFO|NOTIFICATION|CHEEVO]
|
||||
@@ -937,6 +965,7 @@ void CommandRegistry::registerHandlers() {
|
||||
handlers_["cmd_edit"] = cmd_edit;
|
||||
handlers_["cmd_set"] = cmd_set;
|
||||
handlers_["cmd_enemy"] = cmd_enemy;
|
||||
handlers_["cmd_item"] = cmd_item;
|
||||
#endif
|
||||
// HELP se registra en load() como lambda que captura this
|
||||
|
||||
@@ -967,9 +996,7 @@ void CommandRegistry::registerHandlers() {
|
||||
#ifdef _DEBUG
|
||||
// Colores de la paleta (compartido por SET COLOR, BGCOLOR, BORDER, ITEMCOLOR1, ITEMCOLOR2)
|
||||
auto color_provider = []() -> std::vector<std::string> {
|
||||
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"};
|
||||
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"};
|
||||
};
|
||||
dynamic_providers_["SET COLOR"] = color_provider;
|
||||
dynamic_providers_["SET BGCOLOR"] = color_provider;
|
||||
|
||||
Reference in New Issue
Block a user