From 22d6ac2fbf18dacedb5a62d8016b15b2305a399d Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Thu, 2 Apr 2026 14:49:26 +0200 Subject: [PATCH] treballant en editor de items i tile_picker --- CMakeLists.txt | 1 + data/console/commands.yaml | 9 +- source/core/resources/resource_cache.cpp | 2 +- source/core/resources/resource_cache.hpp | 4 +- source/game/editor/editor_statusbar.hpp | 4 +- source/game/editor/map_editor.cpp | 216 ++++++++++++++++++++--- source/game/editor/map_editor.hpp | 20 ++- source/game/editor/tile_picker.cpp | 208 ++++++++++++++++++++++ source/game/editor/tile_picker.hpp | 58 ++++++ source/game/entities/item.cpp | 7 + source/game/entities/item.hpp | 1 + source/game/game_control.hpp | 2 +- source/game/gameplay/room.hpp | 14 +- source/game/ui/console.hpp | 2 +- source/game/ui/console_commands.cpp | 37 +++- 15 files changed, 540 insertions(+), 45 deletions(-) create mode 100644 source/game/editor/tile_picker.cpp create mode 100644 source/game/editor/tile_picker.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a898255..39f87ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,6 +100,7 @@ set(APP_SOURCES source/game/editor/map_editor.cpp source/game/editor/editor_statusbar.cpp source/game/editor/room_saver.cpp + source/game/editor/tile_picker.cpp # Game - UI source/game/ui/console.cpp diff --git a/data/console/commands.yaml b/data/console/commands.yaml index 255f6df..2e97df4 100644 --- a/data/console/commands.yaml +++ b/data/console/commands.yaml @@ -226,13 +226,20 @@ categories: completions: ENEMY: [ADD, DELETE, DUPLICATE] + - keyword: ITEM + handler: cmd_item + description: "Add, delete or duplicate item (editor)" + usage: "ITEM " + completions: + ITEM: [ADD, DELETE, DUPLICATE] + - keyword: SET handler: cmd_set description: "Set property (enemy or room, editor mode)" usage: "SET " dynamic_completions: true 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 MIRROR: [ON, OFF] SET CONVEYOR: [LEFT, NONE, RIGHT] diff --git a/source/core/resources/resource_cache.cpp b/source/core/resources/resource_cache.cpp index 830d313..93eda5c 100644 --- a/source/core/resources/resource_cache.cpp +++ b/source/core/resources/resource_cache.cpp @@ -16,7 +16,7 @@ #include "core/resources/resource_list.hpp" // Para List, List::Type #include "game/defaults.hpp" // Para Defaults namespace #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 "utils/defines.hpp" // Para WINDOW_CAPTION #include "utils/utils.hpp" // Para getFileName, printWithDots, PaletteColor diff --git a/source/core/resources/resource_cache.hpp b/source/core/resources/resource_cache.hpp index 83d628d..d64f2e5 100644 --- a/source/core/resources/resource_cache.hpp +++ b/source/core/resources/resource_cache.hpp @@ -25,9 +25,9 @@ namespace Resource { auto getRoom(const std::string& name) -> std::shared_ptr; auto getRooms() -> std::vector&; - void reload(); // Recarga todos los recursos + void reload(); // Recarga todos los recursos #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 private: diff --git a/source/game/editor/editor_statusbar.hpp b/source/game/editor/editor_statusbar.hpp index 9959cb3..9ca054b 100644 --- a/source/game/editor/editor_statusbar.hpp +++ b/source/game/editor/editor_statusbar.hpp @@ -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 diff --git a/source/game/editor/map_editor.cpp b/source/game/editor/map_editor.cpp index 632550e..e766589 100644 --- a/source/game/editor/map_editor.cpp +++ b/source/game/editor/map_editor.cpp @@ -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(e.vx)) + - " vy:" + std::to_string(static_cast(e.vy)); + " vy:" + std::to_string(static_cast(e.vy)); if (e.flip) { line3 += " flip"; } if (e.mirror) { line3 += " mirror"; } + } else if (selected_item_ >= 0 && selected_item_ < static_cast(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(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(new_item)); + + selected_item_ = static_cast(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_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(copy)); + + selected_item_ = static_cast(room_data_.items.size()) - 1; + selected_enemy_ = -1; + + autosave(); + return "Duplicated as item " + std::to_string(selected_item_); +} + #endif // _DEBUG diff --git a/source/game/editor/map_editor.hpp b/source/game/editor/map_editor.hpp index 22af2ef..699e975 100644 --- a/source/game/editor/map_editor.hpp +++ b/source/game/editor/map_editor.hpp @@ -8,6 +8,7 @@ #include // 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 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}; diff --git a/source/game/editor/tile_picker.cpp b/source/game/editor/tile_picker.cpp new file mode 100644 index 0000000..2a21d47 --- /dev/null +++ b/source/game/editor/tile_picker.cpp @@ -0,0 +1,208 @@ +#ifdef _DEBUG + +#include "game/editor/tile_picker.hpp" + +#include // 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(tileset_->getWidth()) / Tile::SIZE; + tileset_height_ = static_cast(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(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(bg_color) : stringToColor("black"); + frame_surface_->clear(fill_color); + + // Borde doble (bright_white + white) + SDL_FRect outer = {.x = 0, .y = 0, .w = static_cast(frame_w), .h = static_cast(frame_h)}; + frame_surface_->drawRectBorder(&outer, stringToColor("bright_white")); + SDL_FRect inner = {.x = 1, .y = 1, .w = static_cast(frame_w - 2), .h = static_cast(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(source_color), static_cast(target_color)); + } else { + SDL_FRect dst_rect = {.x = static_cast(BORDER_PAD), .y = static_cast(BORDER_PAD), .w = static_cast(tileset_px_w), .h = static_cast(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(offset_x_), .y = static_cast(offset_y), .w = static_cast(frame_w), .h = static_cast(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(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(scroll_y_), .w = frame_dst_.w, .h = static_cast(visible_height_)}; + SDL_FRect dst = {.x = frame_dst_.x, .y = 0, .w = frame_dst_.w, .h = static_cast(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(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(col * Tile::SIZE); + float hy = tileset_screen_y + static_cast(row * Tile::SIZE); + if (hy >= 0 && hy + Tile::SIZE <= visible_height_) { + SDL_FRect highlight = {.x = hx, .y = hy, .w = static_cast(Tile::SIZE), .h = static_cast(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(col * Tile::SIZE); + float cy = tileset_screen_y + static_cast(row * Tile::SIZE); + if (cy >= 0 && cy + Tile::SIZE <= visible_height_) { + SDL_FRect cur_rect = {.x = cx, .y = cy, .w = static_cast(Tile::SIZE), .h = static_cast(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(event.wheel.y) * Tile::SIZE * 2; + int max_scroll = static_cast(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(scroll_y_); + + int tile_x = static_cast(tileset_x) / Tile::SIZE; + int tile_y = static_cast(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 diff --git a/source/game/editor/tile_picker.hpp b/source/game/editor/tile_picker.hpp new file mode 100644 index 0000000..62c35c4 --- /dev/null +++ b/source/game/editor/tile_picker.hpp @@ -0,0 +1,58 @@ +#pragma once + +#ifdef _DEBUG + +#include + +#include // Para function +#include // Para shared_ptr +#include // 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 on_select; + + private: + void updateMousePosition(); + + bool open_{false}; + std::shared_ptr tileset_; // Surface del tileset original + std::shared_ptr 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 diff --git a/source/game/entities/item.cpp b/source/game/entities/item.cpp index b7b5c67..ce31205 100644 --- a/source/game/entities/item.cpp +++ b/source/game/entities/item.cpp @@ -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 diff --git a/source/game/entities/item.hpp b/source/game/entities/item.hpp index 804fb4f..2ec933c 100644 --- a/source/game/entities/item.hpp +++ b/source/game/entities/item.hpp @@ -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: diff --git a/source/game/game_control.hpp b/source/game/game_control.hpp index 5a193d7..5da6911 100644 --- a/source/game/game_control.hpp +++ b/source/game/game_control.hpp @@ -27,7 +27,7 @@ namespace GameControl { inline std::function enter_editor; inline std::function exit_editor; inline std::function revert_editor; - inline std::function reload_current_room; // Recarga la habitación actual desde disco + inline std::function reload_current_room; // Recarga la habitación actual desde disco inline std::function get_adjacent_room; // Obtiene la room adyacente (UP/DOWN/LEFT/RIGHT) } // namespace GameControl #endif diff --git a/source/game/gameplay/room.hpp b/source/game/gameplay/room.hpp index d598a6a..fe22d3a 100644 --- a/source/game/gameplay/room.hpp +++ b/source/game/gameplay/room.hpp @@ -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); // 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); // 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 diff --git a/source/game/ui/console.hpp b/source/game/ui/console.hpp index aa76e04..234b8fa 100644 --- a/source/game/ui/console.hpp +++ b/source/game/ui/console.hpp @@ -87,7 +87,7 @@ class Console { // Estado de la entrada de texto std::vector 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}; diff --git a/source/game/ui/console_commands.cpp b/source/game/ui/console_commands.cpp index 3afb794..b3093ab 100644 --- a/source/game/ui/console_commands.cpp +++ b/source/game/ui/console_commands.cpp @@ -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& args) -> std::string { // SET — modifica propiedad del enemigo seleccionado o de la habitación static auto cmd_set(const std::vector& args) -> std::string { if (!MapEditor::get() || !MapEditor::get()->isActive()) { return "Editor not active"; } + if (args.empty()) { return "usage: set "; } + + // 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 "; } // Si hay enemigo seleccionado, aplicar a enemigo @@ -715,6 +722,11 @@ static auto cmd_set(const std::vector& 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& args) -> std::string { } return "usage: enemy "; } + +// ITEM [ADD|DELETE|DUPLICATE] +static auto cmd_item(const std::vector& args) -> std::string { + if (!MapEditor::get() || !MapEditor::get()->isActive()) { return "Editor not active"; } + if (args.empty()) { return "usage: item "; } + 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 "; +} #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 { - 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;