From 97c30bf9a179f7d5dc13590dd5bc6f51561eb22c Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 10 Apr 2026 09:47:48 +0200 Subject: [PATCH] treballant en sistema de portes i claus --- CMakeLists.txt | 7 ++ config/assets.yaml | 14 +++ data/doors/door1.gif | Bin 0 -> 501 bytes data/doors/door1.yaml | 16 ++++ data/keys/key1.gif | Bin 0 -> 281 bytes data/keys/key1.yaml | 8 ++ data/room/01.yaml | 28 +++--- data/room/03.yaml | 6 ++ source/core/rendering/palette_manager.hpp | 12 +-- source/game/editor/map_editor.cpp | 111 +++++++++++++--------- source/game/editor/map_editor.hpp | 22 +++-- source/game/entities/door.cpp | 65 +++++++++++++ source/game/entities/door.hpp | 62 ++++++++++++ source/game/entities/key.cpp | 32 +++++++ source/game/entities/key.hpp | 44 +++++++++ source/game/entities/moving_platform.hpp | 13 +-- source/game/gameplay/collision_map.cpp | 11 ++- source/game/gameplay/collision_map.hpp | 30 +++--- source/game/gameplay/door_manager.cpp | 92 ++++++++++++++++++ source/game/gameplay/door_manager.hpp | 69 ++++++++++++++ source/game/gameplay/door_tracker.cpp | 74 +++++++++++++++ source/game/gameplay/door_tracker.hpp | 58 +++++++++++ source/game/gameplay/inventory.cpp | 34 +++++++ source/game/gameplay/inventory.hpp | 34 +++++++ source/game/gameplay/key_manager.cpp | 69 ++++++++++++++ source/game/gameplay/key_manager.hpp | 59 ++++++++++++ source/game/gameplay/key_tracker.cpp | 74 +++++++++++++++ source/game/gameplay/key_tracker.hpp | 57 +++++++++++ source/game/gameplay/room.cpp | 66 ++++++++++++- source/game/gameplay/room.hpp | 28 ++++-- source/game/gameplay/room_loader.cpp | 84 ++++++++++++++++ source/game/gameplay/room_loader.hpp | 22 +++++ source/game/gameplay/tile_collider.cpp | 5 +- source/game/scenes/game.cpp | 23 +++++ source/game/scenes/game.hpp | 1 + source/game/ui/console_commands.cpp | 12 ++- source/utils/defines.hpp | 4 +- 37 files changed, 1236 insertions(+), 110 deletions(-) create mode 100644 data/doors/door1.gif create mode 100644 data/doors/door1.yaml create mode 100644 data/keys/key1.gif create mode 100644 data/keys/key1.yaml create mode 100644 source/game/entities/door.cpp create mode 100644 source/game/entities/door.hpp create mode 100644 source/game/entities/key.cpp create mode 100644 source/game/entities/key.hpp create mode 100644 source/game/gameplay/door_manager.cpp create mode 100644 source/game/gameplay/door_manager.hpp create mode 100644 source/game/gameplay/door_tracker.cpp create mode 100644 source/game/gameplay/door_tracker.hpp create mode 100644 source/game/gameplay/inventory.cpp create mode 100644 source/game/gameplay/inventory.hpp create mode 100644 source/game/gameplay/key_manager.cpp create mode 100644 source/game/gameplay/key_manager.hpp create mode 100644 source/game/gameplay/key_tracker.cpp create mode 100644 source/game/gameplay/key_tracker.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fffae9e..ca1c558 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,6 +69,8 @@ set(APP_SOURCES source/game/entities/enemy.cpp source/game/entities/path_enemy.cpp source/game/entities/item.cpp + source/game/entities/key.cpp + source/game/entities/door.cpp source/game/entities/moving_platform.cpp source/game/entities/player.cpp @@ -81,8 +83,13 @@ set(APP_SOURCES source/game/gameplay/tile_collider.cpp source/game/gameplay/enemy_manager.cpp source/game/gameplay/item_manager.cpp + source/game/gameplay/key_manager.cpp + source/game/gameplay/door_manager.cpp source/game/gameplay/platform_manager.cpp source/game/gameplay/item_tracker.cpp + source/game/gameplay/key_tracker.cpp + source/game/gameplay/door_tracker.cpp + source/game/gameplay/inventory.cpp source/game/gameplay/room_loader.cpp source/game/gameplay/room_tracker.cpp source/game/gameplay/room.cpp diff --git a/config/assets.yaml b/config/assets.yaml index 3f7b796..035e3b2 100644 --- a/config/assets.yaml +++ b/config/assets.yaml @@ -218,6 +218,20 @@ assets: BITMAP: - ${PREFIX}/data/items/items.gif + # KEYS + keys: + ANIMATION: + - ${PREFIX}/data/keys/key1.yaml + BITMAP: + - ${PREFIX}/data/keys/key1.gif + + # DOORS + doors: + ANIMATION: + - ${PREFIX}/data/doors/door1.yaml + BITMAP: + - ${PREFIX}/data/doors/door1.gif + # MUSIC music: MUSIC: diff --git a/data/doors/door1.gif b/data/doors/door1.gif new file mode 100644 index 0000000000000000000000000000000000000000..2c62eb030be2039499229fa3b61552ab7f07fc19 GIT binary patch literal 501 zcmVQ6THeM+t)H0V1!`)eKdmKXWLD|#?Ro<%v$Xg%hTPW!o3Og;BupU|juO08P2*sOMIJpjC51o-NoT-zltFW)4 zvACLwvbDdmw|~Q+ytBu^p2fPvtf!Hv$+o$>%*)S^(bCzh)!uv9zT4x=-`AV8U-@q>8r rpD>CWJ4S3644TM==LT{-=`ZE6l`P}ALQ6THeM+t)H0V1!`)eKdmKXWLD|#?Ro<%v$Xg%hTPW!o3Og std::string; // Nombre del modo actual ("ORIGINAL", etc.) private: - void apply(); // Aplica la paleta actual a ambas surfaces - [[nodiscard]] auto findIndex(const std::string& name) const -> size_t; // Localiza paleta por nombre en el vector - void processPathList(); // Extrae nombres de archivo de las rutas completas + void apply(); // Aplica la paleta actual a ambas surfaces + [[nodiscard]] auto findIndex(const std::string& name) const -> size_t; // Localiza paleta por nombre en el vector + void processPathList(); // Extrae nombres de archivo de las rutas completas static auto sortPalette(const Palette& palette, PaletteSortMode mode, const Palette& reference) -> Palette; std::vector palettes_; diff --git a/source/game/editor/map_editor.cpp b/source/game/editor/map_editor.cpp index faabe4c..aa95ff9 100644 --- a/source/game/editor/map_editor.cpp +++ b/source/game/editor/map_editor.cpp @@ -11,24 +11,24 @@ #include // Para cout #include // Para set -#include "core/input/mouse.hpp" // Para Mouse -#include "core/rendering/render_info.hpp" // Para RenderInfo -#include "core/rendering/screen.hpp" // Para Screen -#include "core/rendering/surface.hpp" // Para Surface -#include "core/resources/resource_cache.hpp" // Para Resource::Cache -#include "core/resources/resource_list.hpp" // Para Resource::List -#include "core/resources/resource_types.hpp" // Para RoomResource -#include "game/editor/editor_statusbar.hpp" // Para EditorStatusBar -#include "game/editor/room_saver.hpp" // Para RoomSaver -#include "game/entities/player.hpp" // Para Player -#include "game/game_control.hpp" // Para GameControl -#include "game/gameplay/enemy_manager.hpp" // Para EnemyManager -#include "game/gameplay/item_manager.hpp" // Para ItemManager -#include "game/gameplay/platform_manager.hpp" // Para PlatformManager -#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 "core/input/mouse.hpp" // Para Mouse +#include "core/rendering/render_info.hpp" // Para RenderInfo +#include "core/rendering/screen.hpp" // Para Screen +#include "core/rendering/surface.hpp" // Para Surface +#include "core/resources/resource_cache.hpp" // Para Resource::Cache +#include "core/resources/resource_list.hpp" // Para Resource::List +#include "core/resources/resource_types.hpp" // Para RoomResource +#include "game/editor/editor_statusbar.hpp" // Para EditorStatusBar +#include "game/editor/room_saver.hpp" // Para RoomSaver +#include "game/entities/player.hpp" // Para Player +#include "game/game_control.hpp" // Para GameControl +#include "game/gameplay/enemy_manager.hpp" // Para EnemyManager +#include "game/gameplay/item_manager.hpp" // Para ItemManager +#include "game/gameplay/platform_manager.hpp" // Para PlatformManager +#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" // Singleton @@ -912,19 +912,27 @@ auto MapEditor::pointInRect(float px, float py, const SDL_FRect& rect) -> bool { auto MapEditor::entityCount(EntityType type) const -> int { switch (type) { - case EntityType::ENEMY: return room_->getEnemyManager()->getCount(); - case EntityType::ITEM: return room_->getItemManager()->getCount(); - case EntityType::PLATFORM: return room_->getPlatformManager()->getCount(); - default: return 0; + case EntityType::ENEMY: + return room_->getEnemyManager()->getCount(); + case EntityType::ITEM: + return room_->getItemManager()->getCount(); + case EntityType::PLATFORM: + return room_->getPlatformManager()->getCount(); + default: + return 0; } } auto MapEditor::entityRect(EntityType type, int index) -> SDL_FRect { switch (type) { - case EntityType::ENEMY: return room_->getEnemyManager()->getEnemy(index)->getRect(); - case EntityType::ITEM: return room_->getItemManager()->getItem(index)->getCollider(); - case EntityType::PLATFORM: return room_->getPlatformManager()->getPlatform(index)->getRect(); - default: return {}; + case EntityType::ENEMY: + return room_->getEnemyManager()->getEnemy(index)->getRect(); + case EntityType::ITEM: + return room_->getItemManager()->getItem(index)->getCollider(); + case EntityType::PLATFORM: + return room_->getPlatformManager()->getPlatform(index)->getRect(); + default: + return {}; } } @@ -938,37 +946,49 @@ auto MapEditor::entityBoundaries(EntityType type, int index) const -> BoundaryDa const auto& e = room_data_.enemies[index]; return {e.x1, e.y1, e.x2, e.y2}; } - default: return {}; + default: + return {}; } } auto MapEditor::entityPosition(EntityType type, int index) const -> std::pair { switch (type) { - case EntityType::ENEMY: return {room_data_.enemies[index].x, room_data_.enemies[index].y}; - case EntityType::ITEM: return {room_data_.items[index].x, room_data_.items[index].y}; + case EntityType::ENEMY: + return {room_data_.enemies[index].x, room_data_.enemies[index].y}; + case EntityType::ITEM: + return {room_data_.items[index].x, room_data_.items[index].y}; case EntityType::PLATFORM: { const auto& path = room_data_.platforms[index].path; return path.empty() ? std::pair{0.0F, 0.0F} : std::pair{path[0].x, path[0].y}; } - default: return {0.0F, 0.0F}; + default: + return {0.0F, 0.0F}; } } auto MapEditor::entityDataCount(EntityType type) const -> int { switch (type) { - case EntityType::ENEMY: return static_cast(room_data_.enemies.size()); - case EntityType::ITEM: return static_cast(room_data_.items.size()); - case EntityType::PLATFORM: return static_cast(room_data_.platforms.size()); - default: return 0; + case EntityType::ENEMY: + return static_cast(room_data_.enemies.size()); + case EntityType::ITEM: + return static_cast(room_data_.items.size()); + case EntityType::PLATFORM: + return static_cast(room_data_.platforms.size()); + default: + return 0; } } auto MapEditor::entityLabel(EntityType type) -> const char* { switch (type) { - case EntityType::ENEMY: return "enemy"; - case EntityType::ITEM: return "item"; - case EntityType::PLATFORM: return "platform"; - default: return "none"; + case EntityType::ENEMY: + return "enemy"; + case EntityType::ITEM: + return "item"; + case EntityType::PLATFORM: + return "platform"; + default: + return "none"; } } @@ -1226,10 +1246,14 @@ void MapEditor::updateStatusBarInfo() { // NOLINT(readability-function-cognitiv // Devuelve las propiedades válidas de SET según la selección actual auto MapEditor::getSetCompletions() const -> std::vector { switch (selection_.type) { - case EntityType::ENEMY: return {"ANIMATION", "VX", "VY", "FLIP", "MIRROR"}; - case EntityType::ITEM: return {"TILE", "COUNTER"}; - case EntityType::PLATFORM: return {"ANIMATION", "SPEED", "LOOP", "EASING"}; - default: return {"ITEMCOLOR1", "ITEMCOLOR2", "CONVEYOR", "TILESET", "UP", "DOWN", "LEFT", "RIGHT"}; + case EntityType::ENEMY: + return {"ANIMATION", "VX", "VY", "FLIP", "MIRROR"}; + case EntityType::ITEM: + return {"TILE", "COUNTER"}; + case EntityType::PLATFORM: + return {"ANIMATION", "SPEED", "LOOP", "EASING"}; + default: + return {"ITEMCOLOR1", "ITEMCOLOR2", "CONVEYOR", "TILESET", "UP", "DOWN", "LEFT", "RIGHT"}; } } @@ -1935,8 +1959,7 @@ auto MapEditor::addPlatform() -> std::string { constexpr float ROUTE_HALF = 2.0F * Tile::SIZE; new_platform.path = { {CENTER_X - ROUTE_HALF, CENTER_Y, 0.0F}, - {CENTER_X + ROUTE_HALF, CENTER_Y, 0.0F} - }; + {CENTER_X + ROUTE_HALF, CENTER_Y, 0.0F}}; room_data_.platforms.push_back(new_platform); room_->getPlatformManager()->addPlatform(std::make_shared(new_platform)); diff --git a/source/game/editor/map_editor.hpp b/source/game/editor/map_editor.hpp index b02ea65..ddf306d 100644 --- a/source/game/editor/map_editor.hpp +++ b/source/game/editor/map_editor.hpp @@ -7,9 +7,9 @@ #include // Para shared_ptr, unique_ptr #include // Para string -#include "external/fkyaml_node.hpp" // Para fkyaml::node -#include "game/editor/mini_map.hpp" // Para MiniMap -#include "game/editor/tile_picker.hpp" // Para TilePicker +#include "external/fkyaml_node.hpp" // Para fkyaml::node +#include "game/editor/mini_map.hpp" // Para MiniMap +#include "game/editor/tile_picker.hpp" // Para TilePicker #include "game/entities/enemy.hpp" // Para Enemy::Data #include "game/entities/item.hpp" // Para Item::Data #include "game/entities/moving_platform.hpp" // Para MovingPlatform::Data @@ -21,13 +21,19 @@ class EditorStatusBar; // Tipo de entidad editable en el editor -enum class EntityType { NONE, ENEMY, ITEM, PLATFORM }; +enum class EntityType { NONE, + ENEMY, + ITEM, + PLATFORM }; // Seleccion unificada: una sola entidad seleccionada a la vez struct Selection { EntityType type{EntityType::NONE}; int index{-1}; - void clear() { type = EntityType::NONE; index = -1; } + void clear() { + type = EntityType::NONE; + index = -1; + } [[nodiscard]] auto isNone() const -> bool { return type == EntityType::NONE; } [[nodiscard]] auto is(EntityType t) const -> bool { return type == t && index >= 0; } }; @@ -140,7 +146,9 @@ class MapEditor { static auto pointInRect(float px, float py, const SDL_FRect& rect) -> bool; // Entity helpers: acceso abstracto a datos de entidad por tipo - struct BoundaryData { int x1, y1, x2, y2; }; + struct BoundaryData { + int x1, y1, x2, y2; + }; auto entityCount(EntityType type) const -> int; auto entityRect(EntityType type, int index) -> SDL_FRect; static auto entityHasBoundaries(EntityType type) -> bool; @@ -152,7 +160,7 @@ class MapEditor { // Estado del editor bool active_{false}; DragState drag_; - Selection selection_; // Entidad seleccionada (unificada: enemy, item o platform) + Selection selection_; // Entidad seleccionada (unificada: enemy, item o platform) static constexpr int NO_BRUSH = -2; // Sin brush activo static constexpr int ERASER_BRUSH = -1; // Brush borrador (pinta tile vacío = -1) int brush_tile_{NO_BRUSH}; // Tile activo para pintar diff --git a/source/game/entities/door.cpp b/source/game/entities/door.cpp new file mode 100644 index 0000000..fb7bb04 --- /dev/null +++ b/source/game/entities/door.cpp @@ -0,0 +1,65 @@ +#include "game/entities/door.hpp" + +#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite +#include "core/resources/resource_cache.hpp" // Para Resource + +// Constructor: carga la animación y posiciona la puerta. Si start_opened es +// true, la puerta se crea ya abierta (estado OPENED, animación "opened"); en +// caso contrario, se crea cerrada (estado CLOSED, animación "closed"). +Door::Door(const Data& data, bool start_opened) + : sprite_(std::make_shared(Resource::Cache::get()->getAnimationData(data.animation_path))), + id_(data.id), + state_(start_opened ? State::OPENED : State::CLOSED) { + sprite_->setPosX(data.x); + sprite_->setPosY(data.y); + sprite_->setCurrentAnimation(start_opened ? "opened" : "closed"); + collider_ = sprite_->getRect(); +} + +// Pinta la puerta en pantalla +void Door::render() { + sprite_->render(); +} + +// Avanza la animación. Solo OPENING anima de verdad; CLOSED y OPENED son +// frames estáticos. Cuando la animación de OPENING termina, transiciona a +// OPENED, fija el frame final y marca just_opened_ para que el DoorManager +// libere los tiles de colisión. +void Door::update(float delta_time) { + if (is_paused_) { + return; + } + + if (state_ == State::OPENING) { + sprite_->animate(delta_time); + if (sprite_->animationIsCompleted()) { + state_ = State::OPENED; + sprite_->setCurrentAnimation("opened"); + just_opened_ = true; + } + } +} + +// Posición actual (para registrar el estado abierto en DoorTracker) +auto Door::getPos() const -> SDL_FPoint { + return SDL_FPoint{.x = sprite_->getX(), .y = sprite_->getY()}; +} + +// Transición CLOSED → OPENING: cambia el estado y arranca la animación de apertura +void Door::startOpening() { + if (state_ != State::CLOSED) { + return; + } + state_ = State::OPENING; + sprite_->setCurrentAnimation("opening"); + sprite_->resetAnimation(); +} + +// Flag one-shot: devuelve true exactamente una vez después de transicionar a OPENED +auto Door::justOpened() -> bool { + if (just_opened_) { + just_opened_ = false; + return true; + } + return false; +} diff --git a/source/game/entities/door.hpp b/source/game/entities/door.hpp new file mode 100644 index 0000000..1c05773 --- /dev/null +++ b/source/game/entities/door.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include + +#include // Para shared_ptr +#include // Para string + +class AnimatedSprite; + +/** + * @brief Puerta que actúa como muro hasta que se abre con la llave correcta + * + * Entidad de 8x32 px (1 columna × 4 filas de tiles) con tres animaciones: + * - "closed": estado inicial bloqueante (frame estático) + * - "opening": animación de transición que se reproduce una sola vez + * - "opened": estado final no bloqueante (frame estático) + * + * El comportamiento de "muro" se implementa marcando los 4 tiles que ocupa + * la puerta como WALL en el CollisionMap (lo gestiona el DoorManager). Cuando + * la puerta termina de abrirse, los tiles vuelven a EMPTY y el jugador puede + * pasar. + */ +class Door { + public: + enum class State : int { + CLOSED = 0, + OPENING = 1, + OPENED = 2 + }; + + struct Data { + std::string animation_path; // Ruta al fichero de animación (ej. "door1.yaml") + std::string id; // Identificador que coincide con el id de la llave que la abre + float x{0.0F}; // Posición X en píxeles (alineada al grid de 8 px) + float y{0.0F}; // Posición Y en píxeles (alineada al grid de 8 px) + }; + + explicit Door(const Data& data, bool start_opened); + ~Door() = default; + + void render(); // Pinta la puerta en pantalla + void update(float delta_time); // Avanza la animación; si OPENING termina → OPENED + + auto getCollider() -> SDL_FRect& { return collider_; } // Rectángulo de colisión (8x32) + [[nodiscard]] auto getPos() const -> SDL_FPoint; // Posición en píxeles + [[nodiscard]] auto getId() const -> const std::string& { return id_; } // Identificador + [[nodiscard]] auto getState() const -> State { return state_; } // Estado actual + [[nodiscard]] auto isBlocking() const -> bool { return state_ != State::OPENED; } // True si bloquea al jugador + + void startOpening(); // Transición CLOSED → OPENING + auto justOpened() -> bool; // Flag one-shot consumido por el manager + + void setPaused(bool paused) { is_paused_ = paused; } // Pausa/despausa la animación + + private: + std::shared_ptr sprite_; // Sprite animado de la puerta + SDL_FRect collider_{}; // Rectángulo de colisión + std::string id_; // Identificador + State state_{State::CLOSED}; // Estado actual + bool just_opened_{false}; // Flag one-shot: la puerta acaba de transicionar a OPENED + bool is_paused_{false}; // Indica si la puerta está pausada +}; diff --git a/source/game/entities/key.cpp b/source/game/entities/key.cpp new file mode 100644 index 0000000..adca918 --- /dev/null +++ b/source/game/entities/key.cpp @@ -0,0 +1,32 @@ +#include "game/entities/key.hpp" + +#include "core/rendering/sprite/animated_sprite.hpp" // Para AnimatedSprite +#include "core/resources/resource_cache.hpp" // Para Resource + +// Constructor: carga la animación, posiciona el sprite y crea el collider +Key::Key(const Data& data) + : sprite_(std::make_shared(Resource::Cache::get()->getAnimationData(data.animation_path))), + id_(data.id) { + sprite_->setPosX(data.x); + sprite_->setPosY(data.y); + sprite_->setCurrentAnimation("default"); + collider_ = sprite_->getRect(); +} + +// Pinta la llave en pantalla +void Key::render() { + sprite_->render(); +} + +// Avanza la animación de la llave +void Key::update(float delta_time) { + if (is_paused_) { + return; + } + sprite_->animate(delta_time); +} + +// Posición actual (para registrar pickup en KeyTracker) +auto Key::getPos() const -> SDL_FPoint { + return SDL_FPoint{.x = sprite_->getX(), .y = sprite_->getY()}; +} diff --git a/source/game/entities/key.hpp b/source/game/entities/key.hpp new file mode 100644 index 0000000..53ca436 --- /dev/null +++ b/source/game/entities/key.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include + +#include // Para shared_ptr +#include // Para string + +class AnimatedSprite; + +/** + * @brief Llave coleccionable + * + * Entidad de 16x16 px con una animación "default". Al ser recogida por el + * jugador, su identificador (id_) se añade al Inventory global y la llave + * desaparece de la habitación. Una llave puede abrir todas las puertas con + * el mismo id; no se consume al usarse. + */ +class Key { + public: + struct Data { + std::string animation_path; // Ruta al fichero de animación (ej. "key1.yaml") + std::string id; // Identificador que abre las puertas con el mismo id + float x{0.0F}; // Posición X en píxeles + float y{0.0F}; // Posición Y en píxeles + }; + + explicit Key(const Data& data); + ~Key() = default; + + void render(); // Pinta la llave en pantalla + void update(float delta_time); // Avanza la animación + + auto getCollider() -> SDL_FRect& { return collider_; } // Rectángulo de colisión + [[nodiscard]] auto getPos() const -> SDL_FPoint; // Posición actual (para tracker) + [[nodiscard]] auto getId() const -> const std::string& { return id_; } // Identificador de la llave + + void setPaused(bool paused) { is_paused_ = paused; } // Pausa/despausa la animación + + private: + std::shared_ptr sprite_; // Sprite animado de la llave + SDL_FRect collider_{}; // Rectángulo de colisión + std::string id_; // Identificador + bool is_paused_{false}; // Indica si la llave está pausada +}; diff --git a/source/game/entities/moving_platform.hpp b/source/game/entities/moving_platform.hpp index 6d64f93..6dee827 100644 --- a/source/game/entities/moving_platform.hpp +++ b/source/game/entities/moving_platform.hpp @@ -10,13 +10,14 @@ class AnimatedSprite; // Punto de paso en la ruta de una plataforma struct Waypoint { - float x{0.0F}; // Posición en pixels + float x{0.0F}; // Posición en pixels float y{0.0F}; float wait{0.0F}; // Tiempo de parada en este punto (segundos, 0 = sin parada) }; // Modo de recorrido de la ruta -enum class LoopMode { PINGPONG, CIRCULAR }; +enum class LoopMode { PINGPONG, + CIRCULAR }; // Tipo de función de easing using EasingFunc = float (*)(float); @@ -25,11 +26,11 @@ class MovingPlatform { public: struct Data { std::string animation_path; - float speed{30.0F}; // px/s a lo largo del path + float speed{30.0F}; // px/s a lo largo del path LoopMode loop{LoopMode::PINGPONG}; - std::string easing{"linear"}; // Nombre del easing - int frame{0}; // Frame inicial (-1 = random) - std::vector path; // Mínimo 2 puntos + std::string easing{"linear"}; // Nombre del easing + int frame{0}; // Frame inicial (-1 = random) + std::vector path; // Mínimo 2 puntos }; explicit MovingPlatform(const Data& data); diff --git a/source/game/gameplay/collision_map.cpp b/source/game/gameplay/collision_map.cpp index 4d48036..5f91779 100644 --- a/source/game/gameplay/collision_map.cpp +++ b/source/game/gameplay/collision_map.cpp @@ -25,9 +25,12 @@ void CollisionMap::buildExtendedCenter() { // Copia una región rectangular de src (MW×MH) al mapa extendido void CollisionMap::copyRegion(const std::vector& src, - int src_col, int src_row, - int dst_col, int dst_row, - int cols, int rows) { + int src_col, + int src_row, + int dst_col, + int dst_row, + int cols, + int rows) { for (int r = 0; r < rows; ++r) { for (int c = 0; c < cols; ++c) { extended_tile_map_[((dst_row + r) * EW) + (dst_col + c)] = @@ -78,7 +81,6 @@ void CollisionMap::updateBorders(const AdjacentData& adj) { } } -#ifdef _DEBUG void CollisionMap::setCollisionTile(int index, int value) { if (index >= 0 && index < static_cast(collision_tile_map_.size())) { collision_tile_map_[index] = value; @@ -88,4 +90,3 @@ void CollisionMap::setCollisionTile(int index, int value) { extended_tile_map_[((row + B) * EW) + (col + B)] = value; } } -#endif diff --git a/source/game/gameplay/collision_map.hpp b/source/game/gameplay/collision_map.hpp index 3777739..f897ece 100644 --- a/source/game/gameplay/collision_map.hpp +++ b/source/game/gameplay/collision_map.hpp @@ -43,29 +43,35 @@ class CollisionMap { [[nodiscard]] auto getConveyorBeltDirection() const -> int { return conveyor_belt_direction_; } [[nodiscard]] auto getCollisionTileMap() const -> const std::vector& { return collision_tile_map_; } -#ifdef _DEBUG + // Modifica un tile del mapa de colisiones (original + extendido) en runtime. + // Lo usan: el editor de mapas (debug) y el DoorManager para mostrar/ocultar muros de puertas. void setCollisionTile(int index, int value); + +#ifdef _DEBUG void setConveyorBeltDirection(int direction) { conveyor_belt_direction_ = direction; } #endif private: - static constexpr int B = CollisionBorder::TILES; // Tiles de borde - static constexpr int MW = Map::WIDTH; // Ancho original (32) - static constexpr int MH = Map::HEIGHT; // Alto original (21) - static constexpr int EW = ExtendedMap::WIDTH; // Ancho extendido (38) - static constexpr int EH = ExtendedMap::HEIGHT; // Alto extendido (27) + static constexpr int B = CollisionBorder::TILES; // Tiles de borde + static constexpr int MW = Map::WIDTH; // Ancho original (32) + static constexpr int MH = Map::HEIGHT; // Alto original (21) + static constexpr int EW = ExtendedMap::WIDTH; // Ancho extendido (38) + static constexpr int EH = ExtendedMap::HEIGHT; // Alto extendido (27) - std::vector collision_tile_map_; // Original (32×21) - std::vector extended_tile_map_; // Extendido (38×27) — referenciado por TileCollider + std::vector collision_tile_map_; // Original (32×21) + std::vector extended_tile_map_; // Extendido (38×27) — referenciado por TileCollider int conveyor_belt_direction_; - TileCollider tile_collider_; // Debe ir después de extended_tile_map_ (usa referencia) + TileCollider tile_collider_; // Debe ir después de extended_tile_map_ (usa referencia) // Copia el centro (room actual) al mapa extendido. Los bordes quedan como EMPTY (0). void buildExtendedCenter(); // Copia una región rectangular de un tilemap fuente (MW×MH) al mapa extendido. void copyRegion(const std::vector& src, - int src_col, int src_row, - int dst_col, int dst_row, - int cols, int rows); + int src_col, + int src_row, + int dst_col, + int dst_row, + int cols, + int rows); }; diff --git a/source/game/gameplay/door_manager.cpp b/source/game/gameplay/door_manager.cpp new file mode 100644 index 0000000..49eb4f4 --- /dev/null +++ b/source/game/gameplay/door_manager.cpp @@ -0,0 +1,92 @@ +#include "door_manager.hpp" + +#include // Para std::move + +#include "game/entities/door.hpp" // Para Door +#include "game/gameplay/collision_map.hpp" // Para CollisionMap +#include "game/gameplay/door_tracker.hpp" // Para DoorTracker +#include "game/gameplay/inventory.hpp" // Para Inventory +#include "game/gameplay/tile_collider.hpp" // Para TileCollider::Tile +#include "utils/defines.hpp" // Para Map::WIDTH, Tile::SIZE +#include "utils/utils.hpp" // Para checkCollision + +// Constructor +DoorManager::DoorManager(std::string room_id, CollisionMap* collision_map) + : room_id_(std::move(room_id)), + collision_map_(collision_map) { +} + +// Añade una puerta. Si está bloqueante (CLOSED), pinta sus 4 tiles como WALL +// en el CollisionMap. Si ya está OPENED (porque venía persistida del +// DoorTracker), no se tocan los tiles. +void DoorManager::addDoor(std::shared_ptr door) { // NOLINT(readability-identifier-naming) + if (door->isBlocking()) { + writeDoorTiles(*door, static_cast(TileCollider::Tile::WALL)); + } + doors_.push_back(std::move(door)); +} + +// Elimina todas las puertas +void DoorManager::clear() { + doors_.clear(); +} + +// Actualiza animaciones y procesa transiciones a OPENED +void DoorManager::update(float delta_time) { + for (const auto& door : doors_) { + door->update(delta_time); + + // Si la puerta acaba de transicionar a OPENED, liberar los tiles y persistir + if (door->justOpened()) { + writeDoorTiles(*door, static_cast(TileCollider::Tile::EMPTY)); + DoorTracker::get()->addDoor(room_id_, door->getPos()); + } + } +} + +// Renderiza todas las puertas +void DoorManager::render() { + for (const auto& door : doors_) { + door->render(); + } +} + +// Pausa/despausa todas las puertas +void DoorManager::setPaused(bool paused) { + for (const auto& door : doors_) { + door->setPaused(paused); + } +} + +// Intenta abrir las puertas cercanas al jugador +void DoorManager::tryUnlock(const SDL_FRect& player_rect) { + // Infla el rect del jugador 1 px en cada lado para detectar contacto + const SDL_FRect INFLATED = { + .x = player_rect.x - 1.0F, + .y = player_rect.y - 1.0F, + .w = player_rect.w + 2.0F, + .h = player_rect.h + 2.0F, + }; + + for (const auto& door : doors_) { + if (door->getState() != Door::State::CLOSED) { + continue; + } + if (::checkCollision(INFLATED, door->getCollider()) && Inventory::get()->hasKey(door->getId())) { + door->startOpening(); + } + } +} + +// Setea las 4 celdas que ocupa la puerta (1 col × 4 filas) al valor indicado +void DoorManager::writeDoorTiles(const Door& door, int tile_value) { + // Convertir posición en píxeles a coordenadas de tile + const SDL_FPoint POS = door.getPos(); + const int COL = static_cast(POS.x) / Tile::SIZE; + const int ROW = static_cast(POS.y) / Tile::SIZE; + + for (int i = 0; i < DOOR_TILES_HEIGHT; ++i) { + const int INDEX = ((ROW + i) * Map::WIDTH) + COL; + collision_map_->setCollisionTile(INDEX, tile_value); + } +} diff --git a/source/game/gameplay/door_manager.hpp b/source/game/gameplay/door_manager.hpp new file mode 100644 index 0000000..8435d87 --- /dev/null +++ b/source/game/gameplay/door_manager.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include + +#include // Para shared_ptr +#include // Para string +#include // Para vector + +#include "game/entities/door.hpp" // Para Door, Door::Data + +class CollisionMap; + +/** + * @brief Gestor de puertas de una habitación + * + * Responsabilidades: + * - Almacenar y gestionar la colección de puertas + * - Actualizar y renderizar todas las puertas + * - Detectar contacto del jugador con puertas cerradas y disparar la apertura + * si tiene la llave correspondiente (consultando el Inventory global) + * - Sincronizar el estado bloqueante con el CollisionMap: cuando una puerta + * está CLOSED u OPENING, sus 4 tiles son WALL; cuando pasa a OPENED, se + * ponen a EMPTY + * - Persistir el estado abierto en DoorTracker + */ +class DoorManager { + public: + DoorManager(std::string room_id, CollisionMap* collision_map); + ~DoorManager() = default; + + // Prohibir copia y movimiento para evitar duplicación accidental + DoorManager(const DoorManager&) = delete; + auto operator=(const DoorManager&) -> DoorManager& = delete; + DoorManager(DoorManager&&) = delete; + auto operator=(DoorManager&&) -> DoorManager& = delete; + + // Gestión de puertas + void addDoor(std::shared_ptr door); // Añade una puerta y aplica WALLs si está cerrada + void clear(); // Elimina todas las puertas + + // Actualización y renderizado + void update(float delta_time); // Actualiza animaciones y procesa transiciones a OPENED + void render(); // Renderiza todas las puertas + + // Estado + void setPaused(bool paused); // Pausa/despausa todas las puertas + + /** + * @brief Intenta abrir las puertas cercanas al jugador si tiene la llave + * + * Infla el rectángulo del jugador en 1 px para detectar contacto + * (las puertas cerradas son muros sólidos, así que el jugador queda + * adyacente sin solapar). Para cada puerta CLOSED que solape el rect + * inflado, si el Inventory contiene la llave correspondiente, llama a + * door->startOpening(). + * + * @param player_rect Rectángulo de colisión del jugador + */ + void tryUnlock(const SDL_FRect& player_rect); + + private: + static constexpr int DOOR_TILES_HEIGHT = 4; // Una puerta ocupa 4 tiles verticalmente + + void writeDoorTiles(const Door& door, int tile_value); // Setea las 4 celdas en el CollisionMap + + std::vector> doors_; // Colección de puertas + std::string room_id_; // Identificador de la habitación + CollisionMap* collision_map_; // Referencia no propietaria al CollisionMap de la Room +}; diff --git a/source/game/gameplay/door_tracker.cpp b/source/game/gameplay/door_tracker.cpp new file mode 100644 index 0000000..2e34a7d --- /dev/null +++ b/source/game/gameplay/door_tracker.cpp @@ -0,0 +1,74 @@ +#include "game/gameplay/door_tracker.hpp" + +// [SINGLETON] +DoorTracker* DoorTracker::door_tracker = nullptr; + +// [SINGLETON] Crearemos el objeto con esta función estática +void DoorTracker::init() { + DoorTracker::door_tracker = new DoorTracker(); +} + +// [SINGLETON] Destruiremos el objeto con esta función estática +void DoorTracker::destroy() { + delete DoorTracker::door_tracker; +} + +// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él +auto DoorTracker::get() -> DoorTracker* { + return DoorTracker::door_tracker; +} + +// Comprueba si la puerta ya está abierta +auto DoorTracker::hasBeenOpened(const std::string& name, SDL_FPoint pos) -> bool { + if (const int INDEX = findByName(name); INDEX != NOT_FOUND) { + if (findByPos(INDEX, pos) != NOT_FOUND) { + return true; + } + } + + return false; +} + +// Marca la puerta como abierta +void DoorTracker::addDoor(const std::string& name, SDL_FPoint pos) { + if (!hasBeenOpened(name, pos)) { + if (const int INDEX = findByName(name); INDEX != NOT_FOUND) { + doors_.at(INDEX).pos.push_back(pos); + } else { + doors_.emplace_back(name, pos); + } + } +} + +// Vacía el tracker +void DoorTracker::clear() { + doors_.clear(); +} + +// Busca una entrada en la lista por nombre +auto DoorTracker::findByName(const std::string& name) -> int { + int i = 0; + + for (const auto& door : doors_) { + if (door.name == name) { + return i; + } + i++; + } + + return NOT_FOUND; +} + +// Busca una entrada en la lista por posición +auto DoorTracker::findByPos(int index, SDL_FPoint pos) -> int { + int i = 0; + + for (const auto& door : doors_[index].pos) { + if ((door.x == pos.x) && (door.y == pos.y)) { + return i; + } + i++; + } + + return NOT_FOUND; +} diff --git a/source/game/gameplay/door_tracker.hpp b/source/game/gameplay/door_tracker.hpp new file mode 100644 index 0000000..80ce5b1 --- /dev/null +++ b/source/game/gameplay/door_tracker.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include // Para string, basic_string +#include +#include // Para vector + +/** + * @brief Tracker global de puertas abiertas (singleton) + * + * Mantiene un registro por habitación de las posiciones de las puertas que + * ya han sido abiertas, de modo que al volver a entrar a una habitación las + * puertas ya abiertas se reconstruyen directamente en estado OPENED. + * Mismo patrón que ItemTracker. + */ +class DoorTracker { + public: + // Gestión singleton + static void init(); // Inicialización + static void destroy(); // Destrucción + static auto get() -> DoorTracker*; // Acceso al singleton + + // Gestión de puertas abiertas + auto hasBeenOpened(const std::string& name, SDL_FPoint pos) -> bool; // Comprueba si la puerta ya está abierta + void addDoor(const std::string& name, SDL_FPoint pos); // Marca la puerta como abierta + void clear(); // Vacía el tracker (reset de partida) + + private: + // Tipos anidados privados + struct Data { + std::string name; // Nombre de la habitación + std::vector pos; // Lista de posiciones de puertas abiertas + + // Constructor para facilitar creación con posición inicial + Data(std::string name, const SDL_FPoint& position) + : name(std::move(name)) { + pos.push_back(position); + } + }; + + // Constantes privadas + static constexpr int NOT_FOUND = -1; // Valor de retorno cuando no se encuentra un elemento + + // Constantes singleton + static DoorTracker* door_tracker; // [SINGLETON] Objeto privado + + // Métodos privados + auto findByName(const std::string& name) -> int; // Busca una entrada en la lista por nombre + auto findByPos(int index, SDL_FPoint pos) -> int; // Busca una entrada en la lista por posición + + // Constructor y destructor privados [SINGLETON] + DoorTracker() = default; + ~DoorTracker() = default; + + // Variables miembro + std::vector doors_; // Lista de puertas abiertas por habitación +}; diff --git a/source/game/gameplay/inventory.cpp b/source/game/gameplay/inventory.cpp new file mode 100644 index 0000000..eeaa40d --- /dev/null +++ b/source/game/gameplay/inventory.cpp @@ -0,0 +1,34 @@ +#include "game/gameplay/inventory.hpp" + +// [SINGLETON] +Inventory* Inventory::inventory = nullptr; + +// [SINGLETON] Crearemos el objeto con esta función estática +void Inventory::init() { + Inventory::inventory = new Inventory(); +} + +// [SINGLETON] Destruiremos el objeto con esta función estática +void Inventory::destroy() { + delete Inventory::inventory; +} + +// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él +auto Inventory::get() -> Inventory* { + return Inventory::inventory; +} + +// Añade una llave al inventario +void Inventory::addKey(const std::string& key_id) { + keys_.insert(key_id); +} + +// Comprueba si el inventario contiene una llave +auto Inventory::hasKey(const std::string& key_id) const -> bool { + return keys_.contains(key_id); +} + +// Vacía el inventario +void Inventory::clear() { + keys_.clear(); +} diff --git a/source/game/gameplay/inventory.hpp b/source/game/gameplay/inventory.hpp new file mode 100644 index 0000000..f3b2e54 --- /dev/null +++ b/source/game/gameplay/inventory.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include // Para set +#include // Para string + +/** + * @brief Inventario global del jugador (singleton) + * + * Mantiene el conjunto de identificadores de llaves recogidas durante la partida. + * Persiste entre habitaciones y entre muertes (igual que ItemTracker). + */ +class Inventory { + public: + // Gestión singleton + static void init(); // Inicialización + static void destroy(); // Destrucción + static auto get() -> Inventory*; // Acceso al singleton + + // Gestión de llaves + void addKey(const std::string& key_id); // Añade una llave al inventario + [[nodiscard]] auto hasKey(const std::string& key_id) const -> bool; // Comprueba si tiene una llave + void clear(); // Vacía el inventario (reset de partida) + + private: + // Constantes singleton + static Inventory* inventory; // [SINGLETON] Objeto privado + + // Constructor y destructor privados [SINGLETON] + Inventory() = default; + ~Inventory() = default; + + // Variables miembro + std::set keys_; // Conjunto de identificadores de llaves recogidas +}; diff --git a/source/game/gameplay/key_manager.cpp b/source/game/gameplay/key_manager.cpp new file mode 100644 index 0000000..e287d5c --- /dev/null +++ b/source/game/gameplay/key_manager.cpp @@ -0,0 +1,69 @@ +#include "key_manager.hpp" + +#include // Para std::move + +#include "core/audio/audio.hpp" // Para Audio +#include "game/defaults.hpp" // Para Defaults::Sound::Files +#include "game/entities/key.hpp" // Para Key +#include "game/gameplay/inventory.hpp" // Para Inventory +#include "game/gameplay/key_tracker.hpp" // Para KeyTracker +#include "utils/utils.hpp" // Para checkCollision + +// Constructor +KeyManager::KeyManager(std::string room_id) + : room_id_(std::move(room_id)) { +} + +// Añade una llave a la colección +void KeyManager::addKey(std::shared_ptr key) { // NOLINT(readability-identifier-naming) + keys_.push_back(std::move(key)); +} + +// Elimina todas las llaves +void KeyManager::clear() { + keys_.clear(); +} + +// Actualiza todas las llaves +void KeyManager::update(float delta_time) { + for (const auto& key : keys_) { + key->update(delta_time); + } +} + +// Renderiza todas las llaves +void KeyManager::render() { + for (const auto& key : keys_) { + key->render(); + } +} + +// Pausa/despausa todas las llaves +void KeyManager::setPaused(bool paused) { + for (const auto& key : keys_) { + key->setPaused(paused); + } +} + +// Comprueba si hay colisión con alguna llave +auto KeyManager::checkCollision(SDL_FRect& rect) -> bool { + for (int i = 0; i < static_cast(keys_.size()); ++i) { + if (::checkCollision(rect, keys_.at(i)->getCollider())) { + // Añade el id de la llave al Inventory global + Inventory::get()->addKey(keys_.at(i)->getId()); + + // Registra la posición en KeyTracker (para no respawnear) + KeyTracker::get()->addKey(room_id_, keys_.at(i)->getPos()); + + // Elimina la llave de la colección + keys_.erase(keys_.begin() + i); + + // Reproduce el sonido de pickup (reutiliza el sonido de item) + Audio::get()->playSound(Defaults::Sound::Files::ITEM, Audio::Group::GAME); + + return true; + } + } + + return false; +} diff --git a/source/game/gameplay/key_manager.hpp b/source/game/gameplay/key_manager.hpp new file mode 100644 index 0000000..58112ee --- /dev/null +++ b/source/game/gameplay/key_manager.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include + +#include // Para shared_ptr +#include // Para string +#include // Para vector + +#include "game/entities/key.hpp" // Para Key, Key::Data + +/** + * @brief Gestor de llaves de una habitación + * + * Responsabilidades: + * - Almacenar y gestionar la colección de llaves + * - Actualizar y renderizar todas las llaves + * - Detectar colisiones con el jugador y gestionar pickup + * - Integración con Inventory (singleton global) y KeyTracker (persistencia por room) + */ +class KeyManager { + public: + explicit KeyManager(std::string room_id); + ~KeyManager() = default; + + // Prohibir copia y movimiento para evitar duplicación accidental + KeyManager(const KeyManager&) = delete; + auto operator=(const KeyManager&) -> KeyManager& = delete; + KeyManager(KeyManager&&) = delete; + auto operator=(KeyManager&&) -> KeyManager& = delete; + + // Gestión de llaves + void addKey(std::shared_ptr key); // Añade una llave a la colección + void clear(); // Elimina todas las llaves + + // Actualización y renderizado + void update(float delta_time); // Actualiza todas las llaves + void render(); // Renderiza todas las llaves + + // Estado + void setPaused(bool paused); // Pausa/despausa todas las llaves + + /** + * @brief Comprueba si hay colisión con alguna llave + * + * Si hay colisión: + * - Añade el id de la llave al Inventory global + * - Registra la posición en KeyTracker (para no respawnear) + * - Elimina la llave de la colección + * - Reproduce el sonido de pickup + * + * @param rect Rectángulo de colisión + * @return true si hubo colisión, false en caso contrario + */ + auto checkCollision(SDL_FRect& rect) -> bool; + + private: + std::vector> keys_; // Colección de llaves + std::string room_id_; // Identificador de la habitación +}; diff --git a/source/game/gameplay/key_tracker.cpp b/source/game/gameplay/key_tracker.cpp new file mode 100644 index 0000000..66dc50f --- /dev/null +++ b/source/game/gameplay/key_tracker.cpp @@ -0,0 +1,74 @@ +#include "game/gameplay/key_tracker.hpp" + +// [SINGLETON] +KeyTracker* KeyTracker::key_tracker = nullptr; + +// [SINGLETON] Crearemos el objeto con esta función estática +void KeyTracker::init() { + KeyTracker::key_tracker = new KeyTracker(); +} + +// [SINGLETON] Destruiremos el objeto con esta función estática +void KeyTracker::destroy() { + delete KeyTracker::key_tracker; +} + +// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él +auto KeyTracker::get() -> KeyTracker* { + return KeyTracker::key_tracker; +} + +// Comprueba si la llave ya ha sido cogida +auto KeyTracker::hasBeenPicked(const std::string& name, SDL_FPoint pos) -> bool { + if (const int INDEX = findByName(name); INDEX != NOT_FOUND) { + if (findByPos(INDEX, pos) != NOT_FOUND) { + return true; + } + } + + return false; +} + +// Añade la llave a la lista de llaves recogidas +void KeyTracker::addKey(const std::string& name, SDL_FPoint pos) { + if (!hasBeenPicked(name, pos)) { + if (const int INDEX = findByName(name); INDEX != NOT_FOUND) { + keys_.at(INDEX).pos.push_back(pos); + } else { + keys_.emplace_back(name, pos); + } + } +} + +// Vacía el tracker +void KeyTracker::clear() { + keys_.clear(); +} + +// Busca una entrada en la lista por nombre +auto KeyTracker::findByName(const std::string& name) -> int { + int i = 0; + + for (const auto& key : keys_) { + if (key.name == name) { + return i; + } + i++; + } + + return NOT_FOUND; +} + +// Busca una entrada en la lista por posición +auto KeyTracker::findByPos(int index, SDL_FPoint pos) -> int { + int i = 0; + + for (const auto& key : keys_[index].pos) { + if ((key.x == pos.x) && (key.y == pos.y)) { + return i; + } + i++; + } + + return NOT_FOUND; +} diff --git a/source/game/gameplay/key_tracker.hpp b/source/game/gameplay/key_tracker.hpp new file mode 100644 index 0000000..3444264 --- /dev/null +++ b/source/game/gameplay/key_tracker.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include + +#include // Para string, basic_string +#include +#include // Para vector + +/** + * @brief Tracker global de llaves recogidas (singleton) + * + * Mantiene un registro por habitación de las posiciones de las llaves recogidas, + * de modo que al volver a entrar a una habitación las llaves ya cogidas no + * vuelvan a aparecer. Mismo patrón que ItemTracker. + */ +class KeyTracker { + public: + // Gestión singleton + static void init(); // Inicialización + static void destroy(); // Destrucción + static auto get() -> KeyTracker*; // Acceso al singleton + + // Gestión de llaves recogidas + auto hasBeenPicked(const std::string& name, SDL_FPoint pos) -> bool; // Comprueba si la llave ya ha sido cogida + void addKey(const std::string& name, SDL_FPoint pos); // Añade la llave a la lista + void clear(); // Vacía el tracker (reset de partida) + + private: + // Tipos anidados privados + struct Data { + std::string name; // Nombre de la habitación + std::vector pos; // Lista de posiciones de llaves cogidas + + // Constructor para facilitar creación con posición inicial + Data(std::string name, const SDL_FPoint& position) + : name(std::move(name)) { + pos.push_back(position); + } + }; + + // Constantes privadas + static constexpr int NOT_FOUND = -1; // Valor de retorno cuando no se encuentra un elemento + + // Constantes singleton + static KeyTracker* key_tracker; // [SINGLETON] Objeto privado + + // Métodos privados + auto findByName(const std::string& name) -> int; // Busca una entrada en la lista por nombre + auto findByPos(int index, SDL_FPoint pos) -> int; // Busca una entrada en la lista por posición + + // Constructor y destructor privados [SINGLETON] + KeyTracker() = default; + ~KeyTracker() = default; + + // Variables miembro + std::vector keys_; // Lista de llaves recogidas por habitación +}; diff --git a/source/game/gameplay/room.cpp b/source/game/gameplay/room.cpp index c22894d..86d0aae 100644 --- a/source/game/gameplay/room.cpp +++ b/source/game/gameplay/room.cpp @@ -6,9 +6,13 @@ #include "core/resources/resource_cache.hpp" // Para Resource #include "game/defaults.hpp" // Para Defaults::Game #include "game/gameplay/collision_map.hpp" // Para CollisionMap +#include "game/gameplay/door_manager.hpp" // Para DoorManager +#include "game/gameplay/door_tracker.hpp" // Para DoorTracker #include "game/gameplay/enemy_manager.hpp" // Para EnemyManager #include "game/gameplay/item_manager.hpp" // Para ItemManager #include "game/gameplay/item_tracker.hpp" // Para ItemTracker +#include "game/gameplay/key_manager.hpp" // Para KeyManager +#include "game/gameplay/key_tracker.hpp" // Para KeyTracker #include "game/gameplay/platform_manager.hpp" // Para PlatformManager #include "game/gameplay/room_loader.hpp" // Para RoomLoader #include "game/gameplay/scoreboard.hpp" // Para Scoreboard::Data @@ -21,18 +25,27 @@ Room::Room(const std::string& room_path, std::shared_ptr data) : data_(std::move(data)) { auto room = Resource::Cache::get()->getRoom(room_path); - // Crea los managers de enemigos, items y plataformas + // Crea los managers de enemigos, items, plataformas y llaves enemy_manager_ = std::make_unique(); item_manager_ = std::make_unique(room->number, data_); platform_manager_ = std::make_unique(); + key_manager_ = std::make_unique(room->number); - initializeRoom(*room); - // Crea el mapa de colisiones desde el collision_tile_map + // Crea el mapa de colisiones desde el collision_tile_map (debe ir antes + // del DoorManager porque éste lo necesita para mutar tiles dinámicamente) collision_map_ = std::make_unique(room->collision_tile_map, conveyor_belt_direction_); - // Crea el renderizador del tilemap (necesita tile_map_, tile_set_width_, surface_, bg_color_, conveyor_belt_direction_) + // Crea el manager de puertas (necesita el CollisionMap para sincronizar muros) + door_manager_ = std::make_unique(room->number, collision_map_.get()); + + initializeRoom(*room); + + // Crea el renderizador del tilemap (necesita tile_map_, tile_set_width_, surface_, bg_color_, conveyor_belt_direction_). + // Se inicializa con el collision tile map ya modificado por las puertas (que han marcado sus + // celdas como WALL en el collision_map_, pero el renderer solo lo usa para detectar superficies + // de dibujo, no para colisión, así que pasamos la versión actualizada del collision_map_). tilemap_renderer_ = std::make_unique(tile_map_, tile_set_width_, surface_, bg_color_, conveyor_belt_direction_); - tilemap_renderer_->initialize(room->collision_tile_map); + tilemap_renderer_->initialize(collision_map_->getCollisionTileMap()); } // Destructor @@ -79,6 +92,23 @@ void Room::initializeRoom(const Data& room) { item_manager_->addItem(std::make_shared(item_copy)); } } + + // Crear las llaves usando el manager (saltar las ya recogidas) + for (const auto& key_data : room.keys) { + const SDL_FPoint KEY_POS{.x = key_data.x, .y = key_data.y}; + if (!KeyTracker::get()->hasBeenPicked(room.number, KEY_POS)) { + key_manager_->addKey(std::make_shared(key_data)); + } + } + + // Crear las puertas usando el manager. Las que ya hayan sido abiertas + // antes (DoorTracker) se construyen directamente en estado OPENED y no + // pintan WALLs en el collision_map_. + for (const auto& door_data : room.doors) { + const SDL_FPoint DOOR_POS{.x = door_data.x, .y = door_data.y}; + const bool ALREADY_OPENED = DoorTracker::get()->hasBeenOpened(room.number, DOOR_POS); + door_manager_->addDoor(std::make_shared(door_data, ALREADY_OPENED)); + } } // Dibuja el mapa en pantalla @@ -101,6 +131,16 @@ void Room::renderItems() { item_manager_->render(); } +// Dibuja las llaves en pantalla +void Room::renderKeys() { + key_manager_->render(); +} + +// Dibuja las puertas en pantalla +void Room::renderDoors() { + door_manager_->render(); +} + #ifdef _DEBUG // Redibuja el mapa (para actualizar modo debug) void Room::redrawMap() { @@ -202,6 +242,12 @@ void Room::update(float delta_time) { // NOLINT(readability-make-member-functio // Actualiza los items usando el manager item_manager_->update(delta_time); + + // Actualiza las llaves usando el manager + key_manager_->update(delta_time); + + // Actualiza las puertas usando el manager (procesa transiciones a OPENED) + door_manager_->update(delta_time); } // Pone el mapa en modo pausa @@ -209,6 +255,8 @@ void Room::setPaused(bool value) { is_paused_ = value; tilemap_renderer_->setPaused(value); item_manager_->setPaused(value); + key_manager_->setPaused(value); + door_manager_->setPaused(value); } auto Room::getTileCollider() const -> const TileCollider& { @@ -247,6 +295,14 @@ auto Room::itemCollision(SDL_FRect& rect) -> bool { return item_manager_->checkCollision(rect); } +auto Room::keyCollision(SDL_FRect& rect) -> bool { + return key_manager_->checkCollision(rect); +} + +void Room::tryUnlockDoors(const SDL_FRect& player_rect) { + door_manager_->tryUnlock(player_rect); +} + auto Room::checkPlayerOnPlatform(const SDL_FRect& player_collider, float player_vy) -> MovingPlatform* { return platform_manager_->checkPlayerOnPlatform(player_collider, player_vy); } diff --git a/source/game/gameplay/room.hpp b/source/game/gameplay/room.hpp index ff2cb3e..a31b30b 100644 --- a/source/game/gameplay/room.hpp +++ b/source/game/gameplay/room.hpp @@ -6,17 +6,21 @@ #include // Para string #include // Para vector -#include "game/defaults.hpp" // Para Defaults::Game::Room -#include "game/entities/enemy.hpp" // Para EnemyData -#include "game/entities/item.hpp" // Para ItemData -#include "game/entities/moving_platform.hpp" // Para MovingPlatform::Data -#include "game/gameplay/collision_map.hpp" // Para CollisionMap::AdjacentData -#include "game/gameplay/scoreboard.hpp" // Para Scoreboard::Data -#include "utils/defines.hpp" // Para Tile::SIZE, Map::WIDTH, Map::HEIGHT +#include "game/defaults.hpp" // Para Defaults::Game::Room +#include "game/entities/door.hpp" // Para Door::Data +#include "game/entities/enemy.hpp" // Para EnemyData +#include "game/entities/item.hpp" // Para ItemData +#include "game/entities/key.hpp" // Para Key::Data +#include "game/entities/moving_platform.hpp" // Para MovingPlatform::Data +#include "game/gameplay/collision_map.hpp" // Para CollisionMap::AdjacentData +#include "game/gameplay/scoreboard.hpp" // Para Scoreboard::Data +#include "utils/defines.hpp" // Para Tile::SIZE, Map::WIDTH, Map::HEIGHT class Surface; class EnemyManager; class ItemManager; class PlatformManager; +class KeyManager; +class DoorManager; class TileCollider; class TilemapRenderer; @@ -47,6 +51,8 @@ class Room { std::vector enemies; std::vector items; std::vector platforms; + std::vector keys; + std::vector doors; }; // Constructor y destructor @@ -60,6 +66,8 @@ class Room { void renderEnemies(); void renderPlatforms(); void renderItems(); + void renderKeys(); + void renderDoors(); #ifdef _DEBUG void redrawMap(); void updateEditorMode(float delta_time); @@ -67,6 +75,8 @@ class Room { auto getEnemyManager() -> EnemyManager* { return enemy_manager_.get(); } auto getItemManager() -> ItemManager* { return item_manager_.get(); } auto getPlatformManager() -> PlatformManager* { return platform_manager_.get(); } + auto getKeyManager() -> KeyManager* { return key_manager_.get(); } + auto getDoorManager() -> DoorManager* { return door_manager_.get(); } void setItemColors(Uint8 color1, Uint8 color2); void setTile(int index, int tile_value); void setCollisionTile(int index, int value); @@ -80,6 +90,8 @@ class Room { auto getRoom(Border border) -> std::string; auto enemyCollision(SDL_FRect& rect) -> bool; auto itemCollision(SDL_FRect& rect) -> bool; + auto keyCollision(SDL_FRect& rect) -> bool; + void tryUnlockDoors(const SDL_FRect& player_rect); auto checkPlayerOnPlatform(const SDL_FRect& player_collider, float player_vy) -> MovingPlatform*; void setPaused(bool value); [[nodiscard]] auto getConveyorBeltDirection() const -> int { return conveyor_belt_direction_; } @@ -98,6 +110,8 @@ class Room { std::unique_ptr enemy_manager_; std::unique_ptr item_manager_; std::unique_ptr platform_manager_; + std::unique_ptr key_manager_; + std::unique_ptr door_manager_; std::unique_ptr collision_map_; std::unique_ptr tilemap_renderer_; std::shared_ptr surface_; diff --git a/source/game/gameplay/room_loader.cpp b/source/game/gameplay/room_loader.cpp index b4e0d1e..65186c1 100644 --- a/source/game/gameplay/room_loader.cpp +++ b/source/game/gameplay/room_loader.cpp @@ -382,6 +382,86 @@ void RoomLoader::parsePlatforms(const fkyaml::node& yaml, Room::Data& room, bool } } +// Parsea los datos de una llave individual +auto RoomLoader::parseKeyData(const fkyaml::node& key_node) -> Key::Data { // NOLINT(readability-convert-member-functions-to-static) + Key::Data key; + + if (key_node.contains("animation")) { + key.animation_path = key_node["animation"].get_value(); + } + if (key_node.contains("id")) { + key.id = key_node["id"].get_value(); + } + if (key_node.contains("position")) { + const auto& pos = key_node["position"]; + if (pos.contains("x")) { + key.x = pos["x"].get_value() * Tile::SIZE; + } + if (pos.contains("y")) { + key.y = pos["y"].get_value() * Tile::SIZE; + } + } + + return key; +} + +// Parsea la lista de llaves de la habitación +void RoomLoader::parseKeys(const fkyaml::node& yaml, Room::Data& room, bool verbose) { // NOLINT(readability-convert-member-functions-to-static) + if (!yaml.contains("keys") || yaml["keys"].is_null()) { + return; + } + + const auto& keys_node = yaml["keys"]; + + for (const auto& key_node : keys_node) { + room.keys.push_back(parseKeyData(key_node)); + } + + if (verbose) { + std::cout << "Loaded " << room.keys.size() << " keys\n"; + } +} + +// Parsea los datos de una puerta individual +auto RoomLoader::parseDoorData(const fkyaml::node& door_node) -> Door::Data { // NOLINT(readability-convert-member-functions-to-static) + Door::Data door; + + if (door_node.contains("animation")) { + door.animation_path = door_node["animation"].get_value(); + } + if (door_node.contains("id")) { + door.id = door_node["id"].get_value(); + } + if (door_node.contains("position")) { + const auto& pos = door_node["position"]; + if (pos.contains("x")) { + door.x = pos["x"].get_value() * Tile::SIZE; + } + if (pos.contains("y")) { + door.y = pos["y"].get_value() * Tile::SIZE; + } + } + + return door; +} + +// Parsea la lista de puertas de la habitación +void RoomLoader::parseDoors(const fkyaml::node& yaml, Room::Data& room, bool verbose) { // NOLINT(readability-convert-member-functions-to-static) + if (!yaml.contains("doors") || yaml["doors"].is_null()) { + return; + } + + const auto& doors_node = yaml["doors"]; + + for (const auto& door_node : doors_node) { + room.doors.push_back(parseDoorData(door_node)); + } + + if (verbose) { + std::cout << "Loaded " << room.doors.size() << " doors\n"; + } +} + #ifdef _DEBUG // Carga una habitación desde un string YAML (para el editor de mapas, evita el resource pack) auto RoomLoader::loadFromString(const std::string& yaml_content, const std::string& file_name) -> Room::Data { @@ -393,6 +473,8 @@ auto RoomLoader::loadFromString(const std::string& yaml_content, const std::stri parseEnemies(yaml, room, false); parseItems(yaml, room, false); parsePlatforms(yaml, room, false); + parseKeys(yaml, room, false); + parseDoors(yaml, room, false); } catch (const fkyaml::exception& e) { std::cerr << "YAML parsing error in " << file_name << ": " << e.what() << '\n'; } catch (const std::exception& e) { @@ -428,6 +510,8 @@ auto RoomLoader::loadYAML(const std::string& file_path, bool verbose) -> Room::D parseEnemies(yaml, room, verbose); parseItems(yaml, room, verbose); parsePlatforms(yaml, room, verbose); + parseKeys(yaml, room, verbose); + parseDoors(yaml, room, verbose); if (verbose) { std::cout << "Room loaded successfully: " << FILE_NAME << '\n'; diff --git a/source/game/gameplay/room_loader.hpp b/source/game/gameplay/room_loader.hpp index 5fb4837..8b332aa 100644 --- a/source/game/gameplay/room_loader.hpp +++ b/source/game/gameplay/room_loader.hpp @@ -4,8 +4,10 @@ #include // Para vector #include "external/fkyaml_node.hpp" // Para fkyaml::node +#include "game/entities/door.hpp" // Para Door::Data #include "game/entities/enemy.hpp" // Para Enemy::Data #include "game/entities/item.hpp" // Para Item::Data +#include "game/entities/key.hpp" // Para Key::Data #include "game/entities/moving_platform.hpp" // Para MovingPlatform::Data #include "game/gameplay/room.hpp" // Para Room::Data @@ -137,4 +139,24 @@ class RoomLoader { static void parsePlatforms(const fkyaml::node& yaml, Room::Data& room, bool verbose); static auto parsePlatformData(const fkyaml::node& platform_node) -> MovingPlatform::Data; + + /** + * @brief Parsea la lista de llaves de la habitación + */ + static void parseKeys(const fkyaml::node& yaml, Room::Data& room, bool verbose); + + /** + * @brief Parsea los datos de una llave individual + */ + static auto parseKeyData(const fkyaml::node& key_node) -> Key::Data; + + /** + * @brief Parsea la lista de puertas de la habitación + */ + static void parseDoors(const fkyaml::node& yaml, Room::Data& room, bool verbose); + + /** + * @brief Parsea los datos de una puerta individual + */ + static auto parseDoorData(const fkyaml::node& door_node) -> Door::Data; }; diff --git a/source/game/gameplay/tile_collider.cpp b/source/game/gameplay/tile_collider.cpp index 11bee07..fcfbe2e 100644 --- a/source/game/gameplay/tile_collider.cpp +++ b/source/game/gameplay/tile_collider.cpp @@ -5,7 +5,10 @@ #include "utils/defines.hpp" TileCollider::TileCollider(const std::vector& extended_tile_map, int width, int height, int border_px) - : width_(width), height_(height), border_px_(border_px), tile_map_(extended_tile_map) {} + : width_(width), + height_(height), + border_px_(border_px), + tile_map_(extended_tile_map) {} // --- Queries básicas --- diff --git a/source/game/scenes/game.cpp b/source/game/scenes/game.cpp index 32494a2..6503504 100644 --- a/source/game/scenes/game.cpp +++ b/source/game/scenes/game.cpp @@ -20,7 +20,10 @@ #include "game/defaults.hpp" // Para Defaults::Game #include "game/entities/moving_platform.hpp" // Para MovingPlatform #include "game/game_control.hpp" // Para GameControl +#include "game/gameplay/door_tracker.hpp" // Para DoorTracker +#include "game/gameplay/inventory.hpp" // Para Inventory #include "game/gameplay/item_tracker.hpp" // Para ItemTracker +#include "game/gameplay/key_tracker.hpp" // Para KeyTracker #include "game/gameplay/room.hpp" // Para Room, RoomData #include "game/gameplay/room_tracker.hpp" // Para RoomTracker #include "game/gameplay/scoreboard.hpp" // Para Scoreboard::Data, Scoreboard @@ -66,6 +69,9 @@ Game::Game(Mode mode) // Crea objetos e inicializa variables ItemTracker::init(); + KeyTracker::init(); + DoorTracker::init(); + Inventory::init(); demoInit(); room_ = getOrCreateRoom(current_room_); initPlayer(spawn_data_, room_); @@ -164,6 +170,9 @@ Game::~Game() { } ItemTracker::destroy(); + KeyTracker::destroy(); + DoorTracker::destroy(); + Inventory::destroy(); if (Console::get() != nullptr) { Console::get()->on_toggle = nullptr; } @@ -325,6 +334,8 @@ void Game::updatePlaying(float delta_time) { #endif checkPlayerIsOnBorder(); checkPlayerAndItems(); + checkPlayerAndKeys(); + room_->tryUnlockDoors(player_->getCollider()); checkPlayerAndEnemies(); checkIfPlayerIsAlive(); @@ -507,6 +518,8 @@ void Game::renderPlaying() { transition_old_room_->renderPlatforms(); transition_old_room_->renderEnemies(); transition_old_room_->renderItems(); + transition_old_room_->renderKeys(); + transition_old_room_->renderDoors(); // Renderizar habitación entrante + jugador con su offset Screen::get()->setRenderOffset(new_ox, new_oy); @@ -514,6 +527,8 @@ void Game::renderPlaying() { room_->renderPlatforms(); room_->renderEnemies(); room_->renderItems(); + room_->renderKeys(); + room_->renderDoors(); if (mode_ == Mode::GAME) { player_->render(); } @@ -527,6 +542,8 @@ void Game::renderPlaying() { room_->renderPlatforms(); room_->renderEnemies(); room_->renderItems(); + room_->renderKeys(); + room_->renderDoors(); if (mode_ == Mode::GAME) { player_->render(); } @@ -569,6 +586,8 @@ void Game::renderFadeToEnding() { room_->renderMap(); room_->renderEnemies(); room_->renderItems(); + room_->renderKeys(); + room_->renderDoors(); player_->render(); // Player congelado pero visible scoreboard_->render(); @@ -935,6 +954,10 @@ void Game::checkPlayerAndPlatforms() { } // Comprueba las colisiones del jugador con los objetos +void Game::checkPlayerAndKeys() { + room_->keyCollision(player_->getCollider()); +} + void Game::checkPlayerAndItems() { room_->itemCollision(player_->getCollider()); } diff --git a/source/game/scenes/game.hpp b/source/game/scenes/game.hpp index 435216c..b01c22f 100644 --- a/source/game/scenes/game.hpp +++ b/source/game/scenes/game.hpp @@ -77,6 +77,7 @@ class Game { auto checkPlayerAndEnemies() -> bool; // Comprueba las colisiones del jugador con los enemigos void checkPlayerAndPlatforms(); // Comprueba si el jugador está sobre una plataforma móvil void checkPlayerAndItems(); // Comprueba las colisiones del jugador con los objetos + void checkPlayerAndKeys(); // Comprueba las colisiones del jugador con las llaves void checkIfPlayerIsAlive(); // Comprueba si el jugador esta vivo void killPlayer(); // Mata al jugador void togglePause(); // Pone el juego en pausa diff --git a/source/game/ui/console_commands.cpp b/source/game/ui/console_commands.cpp index 1ea48e9..7979403 100644 --- a/source/game/ui/console_commands.cpp +++ b/source/game/ui/console_commands.cpp @@ -741,10 +741,14 @@ static auto cmdSet(const std::vector& args) -> std::string { if (args.size() < 2) { return "usage: set "; } switch (MapEditor::get()->getSelectionType()) { - case EntityType::ENEMY: return MapEditor::get()->setEnemyProperty(args[0], args[1]); - case EntityType::ITEM: return MapEditor::get()->setItemProperty(args[0], args[1]); - case EntityType::PLATFORM: return MapEditor::get()->setPlatformProperty(args[0], args[1]); - default: return MapEditor::get()->setRoomProperty(args[0], args[1]); + case EntityType::ENEMY: + return MapEditor::get()->setEnemyProperty(args[0], args[1]); + case EntityType::ITEM: + return MapEditor::get()->setItemProperty(args[0], args[1]); + case EntityType::PLATFORM: + return MapEditor::get()->setPlatformProperty(args[0], args[1]); + default: + return MapEditor::get()->setRoomProperty(args[0], args[1]); } } diff --git a/source/utils/defines.hpp b/source/utils/defines.hpp index d20276c..f29cf34 100644 --- a/source/utils/defines.hpp +++ b/source/utils/defines.hpp @@ -24,8 +24,8 @@ namespace Map { // Borde extra alrededor del tilemap de colisión para cross-room collision. // Debe ser >= ceil(max(Player::WIDTH, Player::HEIGHT) / Tile::SIZE). namespace CollisionBorder { - constexpr int TILES = 3; // Tiles de borde por lado - constexpr int PX = TILES * Tile::SIZE; // Píxeles de borde por lado + constexpr int TILES = 3; // Tiles de borde por lado + constexpr int PX = TILES * Tile::SIZE; // Píxeles de borde por lado } // namespace CollisionBorder // Tilemap de colisión extendido (room + bordes de las adyacentes)