diff --git a/CMakeLists.txt b/CMakeLists.txt index dcdd82e..1bf5bbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,6 +96,10 @@ set(APP_SOURCES source/game/scenes/logo.cpp source/game/scenes/title.cpp + # Game - Editor (debug only, guarded by #ifdef _DEBUG in source) + source/game/editor/map_editor.cpp + source/game/editor/editor_statusbar.cpp + # Game - UI source/game/ui/console.cpp source/game/ui/console_commands.cpp diff --git a/data/console/commands.yaml b/data/console/commands.yaml index 3ab21b5..3da10a8 100644 --- a/data/console/commands.yaml +++ b/data/console/commands.yaml @@ -212,6 +212,13 @@ categories: completions: SCENE: [LOGO, LOADING, TITLE, CREDITS, GAME, ENDING, ENDING2, RESTART] + - keyword: EDIT + handler: cmd_edit + description: "Map editor mode (GAME only)" + usage: "EDIT [ON|OFF|SAVE]" + completions: + EDIT: [ON, OFF, SAVE] + - name: CHEATS commands: - keyword: CHEAT diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index 217609a..9064d77 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -36,7 +36,8 @@ #include "utils/defines.hpp" // Para WINDOW_CAPTION #ifdef _DEBUG -#include "core/system/debug.hpp" // Para Debug +#include "core/system/debug.hpp" // Para Debug +#include "game/editor/map_editor.hpp" // Para MapEditor #endif #ifndef _WIN32 @@ -183,6 +184,7 @@ Director::Director() { Debug::get()->setDebugFile(Resource::List::get()->get("debug.yaml")); Debug::get()->loadFromFile(); SceneManager::current = Debug::get()->getInitialScene(); + MapEditor::init(); #endif std::cout << "\n"; // Fin de inicialización de sistemas @@ -217,6 +219,7 @@ Director::~Director() { Cheevos::destroy(); Locale::destroy(); #ifdef _DEBUG + MapEditor::destroy(); Debug::destroy(); #endif Input::destroy(); diff --git a/source/game/editor/editor_statusbar.cpp b/source/game/editor/editor_statusbar.cpp new file mode 100644 index 0000000..a250a80 --- /dev/null +++ b/source/game/editor/editor_statusbar.cpp @@ -0,0 +1,69 @@ +#ifdef _DEBUG + +#include "game/editor/editor_statusbar.hpp" + +#include // Para to_string + +#include "core/rendering/screen.hpp" // Para Screen +#include "core/rendering/surface.hpp" // Para Surface +#include "core/rendering/text.hpp" // Para Text +#include "core/resources/resource_cache.hpp" // Para Resource::Cache +#include "game/options.hpp" // Para Options::game +#include "utils/defines.hpp" // Para Tile::SIZE +#include "utils/utils.hpp" // Para stringToColor + +// Constructor +EditorStatusBar::EditorStatusBar(const std::string& room_number, const std::string& room_name) + : room_number_(room_number), + room_name_(room_name) { + const float SURFACE_WIDTH = Options::game.width; + constexpr float SURFACE_HEIGHT = 6.0F * Tile::SIZE; // 48 pixels, igual que el scoreboard + + surface_ = std::make_shared(SURFACE_WIDTH, SURFACE_HEIGHT); + surface_dest_ = {.x = 0, .y = Options::game.height - SURFACE_HEIGHT, .w = SURFACE_WIDTH, .h = SURFACE_HEIGHT}; +} + +// Pinta la barra de estado en pantalla +void EditorStatusBar::render() { + surface_->render(nullptr, &surface_dest_); +} + +// Actualiza la barra de estado +void EditorStatusBar::update([[maybe_unused]] float delta_time) { + fillTexture(); +} + +// Establece las coordenadas del ratón en tiles +void EditorStatusBar::setMouseTile(int tile_x, int tile_y) { + mouse_tile_x_ = tile_x; + mouse_tile_y_ = tile_y; +} + +// Dibuja los elementos en la surface +void EditorStatusBar::fillTexture() { + auto previous_renderer = Screen::get()->getRendererSurface(); + Screen::get()->setRendererSurface(surface_); + + surface_->clear(stringToColor("black")); + + auto text = Resource::Cache::get()->getText("smb2"); + const Uint8 LABEL_COLOR = stringToColor("bright_cyan"); + const Uint8 VALUE_COLOR = stringToColor("white"); + + // Línea 1: Número y nombre de la habitación + const std::string ROOM_TEXT = room_number_ + " " + room_name_; + text->writeColored(LEFT_X, LINE1_Y, ROOM_TEXT, LABEL_COLOR); + + // Línea 2: Coordenadas del ratón en tiles + const std::string TILE_X_STR = (mouse_tile_x_ < 10 ? "0" : "") + std::to_string(mouse_tile_x_); + const std::string TILE_Y_STR = (mouse_tile_y_ < 10 ? "0" : "") + std::to_string(mouse_tile_y_); + text->writeColored(LEFT_X, LINE2_Y, "TILE:", LABEL_COLOR); + text->writeColored(LEFT_X + 48, LINE2_Y, TILE_X_STR + "," + TILE_Y_STR, VALUE_COLOR); + + // Indicador de modo editor + text->writeColored(176, LINE2_Y, "EDITOR", stringToColor("bright_green")); + + Screen::get()->setRendererSurface(previous_renderer); +} + +#endif // _DEBUG diff --git a/source/game/editor/editor_statusbar.hpp b/source/game/editor/editor_statusbar.hpp new file mode 100644 index 0000000..48d4051 --- /dev/null +++ b/source/game/editor/editor_statusbar.hpp @@ -0,0 +1,40 @@ +#pragma once + +#ifdef _DEBUG + +#include + +#include // Para shared_ptr +#include // Para string + +class Surface; + +class EditorStatusBar { + public: + EditorStatusBar(const std::string& room_number, const std::string& room_name); + ~EditorStatusBar() = default; + + void render(); + void update(float delta_time); + void setMouseTile(int tile_x, int tile_y); + + private: + void fillTexture(); // Dibuja los elementos en la surface + + // Constantes de posición (en pixels dentro de la surface de 256x48) + static constexpr int LINE1_Y = 8; // Nombre de la habitación + static constexpr int LINE2_Y = 24; // Coordenadas del ratón + static constexpr int LEFT_X = 8; // Margen izquierdo + + // Objetos + std::shared_ptr surface_; // Surface donde dibujar la barra + SDL_FRect surface_dest_{}; // Rectángulo destino en pantalla + + // Variables + std::string room_number_; // Número de la habitación + std::string room_name_; // Nombre de la habitación + int mouse_tile_x_{0}; // Coordenada X del ratón en tiles + int mouse_tile_y_{0}; // Coordenada Y del ratón en tiles +}; + +#endif // _DEBUG diff --git a/source/game/editor/map_editor.cpp b/source/game/editor/map_editor.cpp new file mode 100644 index 0000000..793c1e9 --- /dev/null +++ b/source/game/editor/map_editor.cpp @@ -0,0 +1,147 @@ +#ifdef _DEBUG + +#include "game/editor/map_editor.hpp" + +#include + +#include // Para cout + +#include "core/input/mouse.hpp" // Para Mouse +#include "core/rendering/screen.hpp" // Para Screen +#include "game/editor/editor_statusbar.hpp" // Para EditorStatusBar +#include "game/entities/player.hpp" // Para Player +#include "game/gameplay/room.hpp" // Para Room +#include "game/options.hpp" // Para Options +#include "utils/defines.hpp" // Para Tile::SIZE, PlayArea + +// Singleton +MapEditor* MapEditor::instance_ = nullptr; + +void MapEditor::init() { + instance_ = new MapEditor(); +} + +void MapEditor::destroy() { + delete instance_; + instance_ = nullptr; +} + +auto MapEditor::get() -> MapEditor* { + return instance_; +} + +// Constructor +MapEditor::MapEditor() = default; + +// Destructor +MapEditor::~MapEditor() = default; + +// Entra en modo editor +void MapEditor::enter(std::shared_ptr room, std::shared_ptr player, + const std::string& room_path, std::shared_ptr scoreboard_data) { + if (active_) { return; } + + room_ = std::move(room); + player_ = std::move(player); + room_path_ = room_path; + scoreboard_data_ = std::move(scoreboard_data); + + // Guardar estado de invencibilidad y forzarla + invincible_before_editor_ = Options::cheats.invincible; + Options::cheats.invincible = Options::Cheat::State::ENABLED; + player_->setColor(); + + // Crear la barra de estado + statusbar_ = std::make_unique(room_->getNumber(), room_->getName()); + + // Pausar enemigos e items + room_->setPaused(true); + + active_ = true; + std::cout << "MapEditor: ON (room " << room_path_ << ")\n"; +} + +// Sale del modo editor +void MapEditor::exit() { + if (!active_) { return; } + + active_ = false; + + // Restaurar invencibilidad + Options::cheats.invincible = invincible_before_editor_; + player_->setColor(); + + // Despausar enemigos e items + room_->setPaused(false); + + // Liberar recursos + statusbar_.reset(); + room_.reset(); + player_.reset(); + scoreboard_data_.reset(); + + std::cout << "MapEditor: OFF\n"; +} + +// Actualiza el editor +void MapEditor::update([[maybe_unused]] float delta_time) { + // Mantener el ratón siempre visible + SDL_ShowCursor(); + Mouse::last_mouse_move_time = SDL_GetTicks(); + + // Actualizar posición del ratón + updateMousePosition(); + + // Actualizar la barra de estado con las coordenadas del ratón + if (statusbar_) { + statusbar_->setMouseTile(mouse_tile_x_, mouse_tile_y_); + statusbar_->update(delta_time); + } +} + +// Renderiza el editor +void MapEditor::render() { + // El tilemap ya ha sido renderizado por Game::renderPlaying() antes de llamar aquí + // Renderizar entidades (por ahora: enemigos, items, jugador normales) + room_->renderEnemies(); + room_->renderItems(); + player_->render(); + + // Renderizar barra de estado del editor (reemplaza al scoreboard) + if (statusbar_) { + statusbar_->render(); + } +} + +// Maneja eventos del editor +void MapEditor::handleEvent([[maybe_unused]] const SDL_Event& event) { + // Por ahora no procesamos eventos específicos del editor + // En fases posteriores: drag & drop +} + +// Convierte coordenadas de ventana a coordenadas de juego y tile +void MapEditor::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(); + mouse_game_x_ = render_x - dst_rect.x; + mouse_game_y_ = render_y - dst_rect.y; + + // Convertir a coordenadas de tile (clampeadas al área de juego) + mouse_tile_x_ = static_cast(mouse_game_x_) / Tile::SIZE; + mouse_tile_y_ = static_cast(mouse_game_y_) / Tile::SIZE; + + // Clampear a los límites del mapa + if (mouse_tile_x_ < 0) { mouse_tile_x_ = 0; } + if (mouse_tile_x_ >= PlayArea::WIDTH / Tile::SIZE) { mouse_tile_x_ = PlayArea::WIDTH / Tile::SIZE - 1; } + if (mouse_tile_y_ < 0) { mouse_tile_y_ = 0; } + if (mouse_tile_y_ >= PlayArea::HEIGHT / Tile::SIZE) { mouse_tile_y_ = PlayArea::HEIGHT / Tile::SIZE - 1; } +} + +#endif // _DEBUG diff --git a/source/game/editor/map_editor.hpp b/source/game/editor/map_editor.hpp new file mode 100644 index 0000000..01dbcb1 --- /dev/null +++ b/source/game/editor/map_editor.hpp @@ -0,0 +1,68 @@ +#pragma once + +#ifdef _DEBUG + +#include + +#include // Para shared_ptr, unique_ptr +#include // Para string + +#include "game/entities/enemy.hpp" // Para Enemy::Data +#include "game/entities/item.hpp" // Para Item::Data +#include "game/entities/player.hpp" // Para Player::SpawnData +#include "game/gameplay/room.hpp" // Para Room::Data +#include "game/gameplay/scoreboard.hpp" // Para Scoreboard::Data +#include "game/options.hpp" // Para Options::Cheat + +class EditorStatusBar; + +class MapEditor { + public: + static void init(); // [SINGLETON] Crea el objeto + static void destroy(); // [SINGLETON] Destruye el objeto + static auto get() -> MapEditor*; // [SINGLETON] Obtiene el objeto + + void enter(std::shared_ptr room, std::shared_ptr player, + const std::string& room_path, std::shared_ptr scoreboard_data); + void exit(); + [[nodiscard]] auto isActive() const -> bool { return active_; } + + void update(float delta_time); + void render(); + void handleEvent(const SDL_Event& event); + + private: + static MapEditor* instance_; // [SINGLETON] Objeto privado + + MapEditor(); // Constructor + ~MapEditor(); // Destructor + + void updateMousePosition(); // Convierte coordenadas de ventana a coordenadas de juego y tile + + // Estado del editor + bool active_{false}; + + // Copia mutable de los datos de la habitación (para edición futura) + Room::Data room_data_; + Player::SpawnData player_spawn_; + std::string room_path_; + + // Referencias a objetos vivos (para rendering) + std::shared_ptr room_; + std::shared_ptr player_; + std::shared_ptr scoreboard_data_; + + // Barra de estado del editor + std::unique_ptr statusbar_; + + // Estado del ratón en coordenadas de juego + float mouse_game_x_{0.0F}; + float mouse_game_y_{0.0F}; + int mouse_tile_x_{0}; + int mouse_tile_y_{0}; + + // Estado previo de invencibilidad (para restaurar al salir) + Options::Cheat::State invincible_before_editor_{Options::Cheat::State::DISABLED}; +}; + +#endif // _DEBUG diff --git a/source/game/game_control.hpp b/source/game/game_control.hpp index 7d82471..ecefc45 100644 --- a/source/game/game_control.hpp +++ b/source/game/game_control.hpp @@ -23,5 +23,9 @@ namespace GameControl { inline std::function set_initial_room; // Registrada por Game::Game() — guarda la posición/flip actuales del jugador como posición de inicio en debug.yaml inline std::function set_initial_pos; + // Registradas por Game::Game() — control del editor de mapas + inline std::function enter_editor; + inline std::function exit_editor; + inline std::function save_editor; } // namespace GameControl #endif diff --git a/source/game/scenes/game.cpp b/source/game/scenes/game.cpp index d2cbc04..3093864 100644 --- a/source/game/scenes/game.cpp +++ b/source/game/scenes/game.cpp @@ -32,7 +32,8 @@ #include "utils/utils.hpp" // Para PaletteColor, stringToColor #ifdef _DEBUG -#include "core/system/debug.hpp" // Para Debug +#include "core/system/debug.hpp" // Para Debug +#include "game/editor/map_editor.hpp" // Para MapEditor #endif // Constructor @@ -119,6 +120,13 @@ Game::Game(Mode mode) Debug::get()->saveToFile(); return "Pos:" + std::to_string(tile_x) + "," + std::to_string(tile_y); }; + GameControl::enter_editor = [this]() -> void { + MapEditor::get()->enter(room_, player_, current_room_, scoreboard_data_); + }; + GameControl::exit_editor = []() -> void { + MapEditor::get()->exit(); + }; + GameControl::save_editor = nullptr; // Se implementará en la fase 5 #endif SceneManager::current = (mode_ == Mode::GAME) ? SceneManager::Scene::GAME : SceneManager::Scene::DEMO; @@ -139,6 +147,10 @@ Game::~Game() { GameControl::toggle_debug_mode = nullptr; GameControl::set_initial_room = nullptr; GameControl::set_initial_pos = nullptr; + if (MapEditor::get()->isActive()) { MapEditor::get()->exit(); } + GameControl::enter_editor = nullptr; + GameControl::exit_editor = nullptr; + GameControl::save_editor = nullptr; #endif } @@ -149,7 +161,11 @@ void Game::handleEvents() { GlobalEvents::handle(event); #ifdef _DEBUG if (!Console::get()->isActive()) { - handleDebugEvents(event); + if (MapEditor::get()->isActive()) { + MapEditor::get()->handleEvent(event); + } else { + handleDebugEvents(event); + } } #endif } @@ -172,6 +188,14 @@ void Game::handleInput() { return; } +#ifdef _DEBUG + // Si el editor de mapas está activo, no procesar inputs del juego + if (MapEditor::get()->isActive()) { + GlobalInputs::handle(); + return; + } +#endif + // Durante fade/postfade, solo procesar inputs globales if (state_ != State::PLAYING) { GlobalInputs::handle(); @@ -240,6 +264,14 @@ void Game::update() { // Actualiza el juego en estado PLAYING void Game::updatePlaying(float delta_time) { +#ifdef _DEBUG + // Si el editor de mapas está activo, delegar en él y no ejecutar gameplay + if (MapEditor::get()->isActive()) { + MapEditor::get()->update(delta_time); + return; + } +#endif + // Actualiza los objetos room_->update(delta_time); switch (mode_) { @@ -379,8 +411,19 @@ void Game::renderPlaying() { // Prepara para dibujar el frame Screen::get()->start(); - // Dibuja los elementos del juego en orden + // Dibuja el mapa de tiles (siempre) room_->renderMap(); + +#ifdef _DEBUG + // Si el editor está activo, delegar el renderizado de entidades y statusbar + if (MapEditor::get()->isActive()) { + MapEditor::get()->render(); + Screen::get()->render(); + return; + } +#endif + + // Dibuja los elementos del juego en orden room_->renderEnemies(); room_->renderItems(); if (mode_ == Mode::GAME) { diff --git a/source/game/ui/console_commands.cpp b/source/game/ui/console_commands.cpp index 0563a8c..1e43f67 100644 --- a/source/game/ui/console_commands.cpp +++ b/source/game/ui/console_commands.cpp @@ -629,6 +629,31 @@ static auto cmd_scene(const std::vector& args) -> std::string { if (args[0] == "ENDING2") { return GO_TO(SceneManager::Scene::ENDING2, "Ending 2"); } return "Unknown scene: " + args[0]; } + +// EDIT [ON|OFF|SAVE] +static auto cmd_edit(const std::vector& args) -> std::string { + if (args.empty() || args[0] == "ON") { + if (GameControl::enter_editor) { + GameControl::enter_editor(); + return "Editor ON"; + } + return "Not in game"; + } + if (args[0] == "OFF") { + if (GameControl::exit_editor) { + GameControl::exit_editor(); + return "Editor OFF"; + } + return "Not in game"; + } + if (args[0] == "SAVE") { + if (GameControl::save_editor) { + return GameControl::save_editor(); + } + return "Editor not active"; + } + return "usage: edit [on|off|save]"; +} #endif // SHOW [INFO|NOTIFICATION|CHEEVO] @@ -829,6 +854,7 @@ void CommandRegistry::registerHandlers() { handlers_["cmd_items"] = cmd_items; handlers_["cmd_room"] = cmd_room; handlers_["cmd_scene"] = cmd_scene; + handlers_["cmd_edit"] = cmd_edit; #endif // HELP se registra en load() como lambda que captura this