diff --git a/.claude/settings.local.json b/.claude/settings.local.json index deb7a8de..5c7216e0 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -6,7 +6,9 @@ "Bash(tools/linter/run_clang-tidy.sh:*)", "Bash(make resources.pack:*)", "Bash(tools/linter/run_cppcheck.sh:*)", - "Bash(cat:*)" + "Bash(cat:*)", + "Bash(git add:*)", + "Bash(git commit:*)" ], "deny": [], "ask": [] diff --git a/CMakeLists.txt b/CMakeLists.txt index ee31c959..a79fc9c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,11 +77,16 @@ set(APP_SOURCES # Game - Gameplay source/game/gameplay/cheevos.cpp + source/game/gameplay/collision_map.cpp + source/game/gameplay/enemy_manager.cpp + source/game/gameplay/item_manager.cpp source/game/gameplay/item_tracker.cpp source/game/gameplay/room.cpp + source/game/gameplay/room_loader.cpp source/game/gameplay/room_tracker.cpp source/game/gameplay/scoreboard.cpp source/game/gameplay/stats.cpp + source/game/gameplay/tilemap_renderer.cpp # Game - Scenes source/game/scenes/credits.cpp diff --git a/source/game/gameplay/collision_map.cpp b/source/game/gameplay/collision_map.cpp new file mode 100644 index 00000000..2998a6d1 --- /dev/null +++ b/source/game/gameplay/collision_map.cpp @@ -0,0 +1,486 @@ +#include "collision_map.hpp" + +#include // Para std::ranges::any_of + +#include "core/system/debug.hpp" // Para Debug +#include "utils/defines.hpp" // Para Collision + +// Constructor +CollisionMap::CollisionMap(std::vector tile_map, int tile_set_width, int conveyor_belt_direction) + : tile_map_(std::move(tile_map)) + , tile_set_width_(tile_set_width) + , conveyor_belt_direction_(conveyor_belt_direction) { + // Inicializa todas las superficies de colisión + initializeSurfaces(); +} + +// Inicializa todas las superficies de colisión +void CollisionMap::initializeSurfaces() { + setBottomSurfaces(); + setTopSurfaces(); + setLeftSurfaces(); + setRightSurfaces(); + setLeftSlopes(); + setRightSlopes(); + setAutoSurfaces(); +} + +// Devuelve el tipo de tile que hay en ese pixel +auto CollisionMap::getTile(SDL_FPoint point) const -> Tile { + const int POS = ((point.y / TILE_SIZE) * MAP_WIDTH) + (point.x / TILE_SIZE); + return getTile(POS); +} + +// Devuelve el tipo de tile que hay en ese indice +auto CollisionMap::getTile(int index) const -> Tile { + const bool ON_RANGE = (index > -1) && (index < (int)tile_map_.size()); + + if (ON_RANGE) { + // Las filas 0-8 son de tiles t_wall + if ((tile_map_[index] >= 0) && (tile_map_[index] < 9 * tile_set_width_)) { + return Tile::WALL; + } + + // Las filas 9-17 son de tiles t_passable + if ((tile_map_[index] >= 9 * tile_set_width_) && (tile_map_[index] < 18 * tile_set_width_)) { + return Tile::PASSABLE; + } + + // Las filas 18-20 es de tiles t_animated + if ((tile_map_[index] >= 18 * tile_set_width_) && (tile_map_[index] < 21 * tile_set_width_)) { + return Tile::ANIMATED; + } + + // La fila 21 es de tiles t_slope_r + if ((tile_map_[index] >= 21 * tile_set_width_) && (tile_map_[index] < 22 * tile_set_width_)) { + return Tile::SLOPE_R; + } + + // La fila 22 es de tiles t_slope_l + if ((tile_map_[index] >= 22 * tile_set_width_) && (tile_map_[index] < 23 * tile_set_width_)) { + return Tile::SLOPE_L; + } + + // La fila 23 es de tiles t_kill + if ((tile_map_[index] >= 23 * tile_set_width_) && (tile_map_[index] < 24 * tile_set_width_)) { + return Tile::KILL; + } + } + + return Tile::EMPTY; +} + +// Obten la coordenada de la cuesta a partir de un punto perteneciente a ese tile +auto CollisionMap::getSlopeHeight(SDL_FPoint p, Tile slope) -> int { + // Calcula la base del tile + int base = ((p.y / TILE_SIZE) * TILE_SIZE) + TILE_SIZE; +#ifdef _DEBUG + Debug::get()->add("BASE = " + std::to_string(base)); +#endif + + // Calcula cuanto se ha entrado en el tile horizontalmente + const int POS = (static_cast(p.x) % TILE_SIZE); // Esto da un valor entre 0 y 7 +#ifdef _DEBUG + Debug::get()->add("POS = " + std::to_string(POS)); +#endif + + // Se resta a la base la cantidad de pixeles pos en funcion de la rampa + if (slope == Tile::SLOPE_R) { + base -= POS + 1; +#ifdef _DEBUG + Debug::get()->add("BASE_R = " + std::to_string(base)); +#endif + } else { + base -= (TILE_SIZE - POS); +#ifdef _DEBUG + Debug::get()->add("BASE_L = " + std::to_string(base)); +#endif + } + + return base; +} + +// === Queries de colisión === + +// Comprueba las colisiones con paredes derechas +auto CollisionMap::checkRightSurfaces(SDL_FRect& rect) -> int { + for (const auto& s : right_walls_) { + if (checkCollision(s, rect)) { + return s.x; + } + } + return Collision::NONE; +} + +// Comprueba las colisiones con paredes izquierdas +auto CollisionMap::checkLeftSurfaces(SDL_FRect& rect) -> int { + for (const auto& s : left_walls_) { + if (checkCollision(s, rect)) { + return s.x; + } + } + return Collision::NONE; +} + +// Comprueba las colisiones con techos +auto CollisionMap::checkTopSurfaces(SDL_FRect& rect) -> int { + for (const auto& s : top_floors_) { + if (checkCollision(s, rect)) { + return s.y; + } + } + return Collision::NONE; +} + +// Comprueba las colisiones punto con techos +auto CollisionMap::checkTopSurfaces(SDL_FPoint& p) -> bool { + return std::ranges::any_of(top_floors_, [&](const auto& s) { + return checkCollision(s, p); + }); +} + +// Comprueba las colisiones con suelos +auto CollisionMap::checkBottomSurfaces(SDL_FRect& rect) -> int { + for (const auto& s : bottom_floors_) { + if (checkCollision(s, rect)) { + return s.y; + } + } + return Collision::NONE; +} + +// Comprueba las colisiones con conveyor belts +auto CollisionMap::checkAutoSurfaces(SDL_FRect& rect) -> int { + for (const auto& s : conveyor_belt_floors_) { + if (checkCollision(s, rect)) { + return s.y; + } + } + return Collision::NONE; +} + +// Comprueba las colisiones punto con conveyor belts +auto CollisionMap::checkConveyorBelts(SDL_FPoint& p) -> bool { + return std::ranges::any_of(conveyor_belt_floors_, [&](const auto& s) { + return checkCollision(s, p); + }); +} + +// Comprueba las colisiones línea con rampas izquierdas +auto CollisionMap::checkLeftSlopes(const LineVertical& line) -> int { + for (const auto& slope : left_slopes_) { + const auto P = checkCollision(slope, line); + if (P.x != -1) { + return P.y; + } + } + return Collision::NONE; +} + +// Comprueba las colisiones punto con rampas izquierdas +auto CollisionMap::checkLeftSlopes(SDL_FPoint& p) -> bool { + return std::ranges::any_of(left_slopes_, [&](const auto& slope) { + return checkCollision(p, slope); + }); +} + +// Comprueba las colisiones línea con rampas derechas +auto CollisionMap::checkRightSlopes(const LineVertical& line) -> int { + for (const auto& slope : right_slopes_) { + const auto P = checkCollision(slope, line); + if (P.x != -1) { + return P.y; + } + } + return Collision::NONE; +} + +// Comprueba las colisiones punto con rampas derechas +auto CollisionMap::checkRightSlopes(SDL_FPoint& p) -> bool { + return std::ranges::any_of(right_slopes_, [&](const auto& slope) { + return checkCollision(p, slope); + }); +} + +// === Helpers para recopilar tiles === + +// Helper: recopila tiles inferiores (muros sin muro debajo) +auto CollisionMap::collectBottomTiles() -> std::vector { + std::vector tile; + + // Busca todos los tiles de tipo muro que no tengan debajo otro muro + // Hay que recorrer la habitación por filas (excepto los de la última fila) + for (int i = 0; i < (int)tile_map_.size() - MAP_WIDTH; ++i) { + if (getTile(i) == Tile::WALL && getTile(i + MAP_WIDTH) != Tile::WALL) { + tile.push_back(i); + + // Si llega al final de la fila, introduce un separador + if (i % MAP_WIDTH == MAP_WIDTH - 1) { + tile.push_back(-1); + } + } + } + + // Añade un terminador + tile.push_back(-1); + return tile; +} + +// Helper: recopila tiles superiores (muros o pasables sin muro encima) +auto CollisionMap::collectTopTiles() -> std::vector { + std::vector tile; + + // Busca todos los tiles de tipo muro o pasable que no tengan encima un muro + // Hay que recorrer la habitación por filas (excepto los de la primera fila) + for (int i = MAP_WIDTH; i < (int)tile_map_.size(); ++i) { + if ((getTile(i) == Tile::WALL || getTile(i) == Tile::PASSABLE) && getTile(i - MAP_WIDTH) != Tile::WALL) { + tile.push_back(i); + + // Si llega al final de la fila, introduce un separador + if (i % MAP_WIDTH == MAP_WIDTH - 1) { + tile.push_back(-1); + } + } + } + + // Añade un terminador + tile.push_back(-1); + return tile; +} + +// Helper: recopila tiles animados (para superficies automaticas/conveyor belts) +auto CollisionMap::collectAnimatedTiles() -> std::vector { + std::vector tile; + + // Busca todos los tiles de tipo animado + // Hay que recorrer la habitación por filas (excepto los de la primera fila) + for (int i = MAP_WIDTH; i < (int)tile_map_.size(); ++i) { + if (getTile(i) == Tile::ANIMATED) { + tile.push_back(i); + + // Si llega al final de la fila, introduce un separador + if (i % MAP_WIDTH == MAP_WIDTH - 1) { + tile.push_back(-1); + } + } + } + + // Añade un terminador si hay tiles + if (!tile.empty()) { + tile.push_back(-1); + } + + return tile; +} + +// Helper: construye lineas horizontales a partir de tiles consecutivos +void CollisionMap::buildHorizontalLines(const std::vector& tiles, std::vector& lines, bool is_bottom_surface) { + if (tiles.size() <= 1) { + return; + } + + int i = 0; + while (i < (int)tiles.size() - 1) { + LineHorizontal line; + line.x1 = (tiles[i] % MAP_WIDTH) * TILE_SIZE; + + // Calcula Y segun si es superficie inferior o superior + if (is_bottom_surface) { + line.y = ((tiles[i] / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; + } else { + line.y = (tiles[i] / MAP_WIDTH) * TILE_SIZE; + } + + int last_one = i; + i++; + + // Encuentra tiles consecutivos + if (i < (int)tiles.size()) { + while (tiles[i] == tiles[i - 1] + 1) { + last_one = i; + i++; + if (i >= (int)tiles.size()) { + break; + } + } + } + + line.x2 = ((tiles[last_one] % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; + lines.push_back(line); + + // Salta separadores + if (i < (int)tiles.size() && tiles[i] == -1) { + i++; + } + } +} + +// === Métodos de generación de geometría === + +// Calcula las superficies inferiores +void CollisionMap::setBottomSurfaces() { + std::vector tile = collectBottomTiles(); + buildHorizontalLines(tile, bottom_floors_, true); +} + +// Calcula las superficies superiores +void CollisionMap::setTopSurfaces() { + std::vector tile = collectTopTiles(); + buildHorizontalLines(tile, top_floors_, false); +} + +// Calcula las superficies laterales izquierdas +void CollisionMap::setLeftSurfaces() { + std::vector tile; + + // Busca todos los tiles de tipo muro que no tienen a su izquierda un tile de tipo muro + // Hay que recorrer la habitación por columnas (excepto los de la primera columna) + for (int i = 1; i < MAP_WIDTH; ++i) { + for (int j = 0; j < MAP_HEIGHT; ++j) { + const int POS = ((j * MAP_WIDTH) + i); + if (getTile(POS) == Tile::WALL && getTile(POS - 1) != Tile::WALL) { + tile.push_back(POS); + } + } + } + + // Añade un terminador + tile.push_back(-1); + + // Recorre el vector de tiles buscando tiles consecutivos + // (Los tiles de la misma columna, la diferencia entre ellos es de mapWidth) + // para localizar las superficies + if ((int)tile.size() > 1) { + int i = 0; + do { + LineVertical line; + line.x = (tile[i] % MAP_WIDTH) * TILE_SIZE; + line.y1 = ((tile[i] / MAP_WIDTH) * TILE_SIZE); + while (tile[i] + MAP_WIDTH == tile[i + 1]) { + if (i == (int)tile.size() - 1) { + break; + } + i++; + } + line.y2 = ((tile[i] / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; + left_walls_.push_back(line); + i++; + } while (i < (int)tile.size() - 1); + } +} + +// Calcula las superficies laterales derechas +void CollisionMap::setRightSurfaces() { + std::vector tile; + + // Busca todos los tiles de tipo muro que no tienen a su derecha un tile de tipo muro + // Hay que recorrer la habitación por columnas (excepto los de la última columna) + for (int i = 0; i < MAP_WIDTH - 1; ++i) { + for (int j = 0; j < MAP_HEIGHT; ++j) { + const int POS = ((j * MAP_WIDTH) + i); + if (getTile(POS) == Tile::WALL && getTile(POS + 1) != Tile::WALL) { + tile.push_back(POS); + } + } + } + + // Añade un terminador + tile.push_back(-1); + + // Recorre el vector de tiles buscando tiles consecutivos + // (Los tiles de la misma columna, la diferencia entre ellos es de mapWidth) + // para localizar las superficies + if ((int)tile.size() > 1) { + int i = 0; + do { + LineVertical line; + line.x = ((tile[i] % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; + line.y1 = ((tile[i] / MAP_WIDTH) * TILE_SIZE); + while (tile[i] + MAP_WIDTH == tile[i + 1]) { + if (i == (int)tile.size() - 1) { + break; + } + i++; + } + line.y2 = ((tile[i] / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; + right_walls_.push_back(line); + i++; + } while (i < (int)tile.size() - 1); + } +} + +// Encuentra todas las rampas que suben hacia la izquierda +void CollisionMap::setLeftSlopes() { + // Recorre la habitación entera por filas buscando tiles de tipo t_slope_l + std::vector found; + for (int i = 0; i < (int)tile_map_.size(); ++i) { + if (getTile(i) == Tile::SLOPE_L) { + found.push_back(i); + } + } + + // El primer elemento es el inicio de una rampa. Se añade ese elemento y se buscan los siguientes, + // que seran i + mapWidth + 1. Conforme se añaden se eliminan y se vuelve a escudriñar el vector de + // tiles encontrados hasta que esté vacío + + while (!found.empty()) { + LineDiagonal line; + line.x1 = (found[0] % MAP_WIDTH) * TILE_SIZE; + line.y1 = (found[0] / MAP_WIDTH) * TILE_SIZE; + int looking_for = found[0] + MAP_WIDTH + 1; + int last_one_found = found[0]; + found.erase(found.begin()); + for (int i = 0; i < (int)found.size(); ++i) { + if (found[i] == looking_for) { + last_one_found = looking_for; + looking_for += MAP_WIDTH + 1; + found.erase(found.begin() + i); + i--; + } + } + line.x2 = ((last_one_found % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; + line.y2 = ((last_one_found / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; + left_slopes_.push_back(line); + } +} + +// Encuentra todas las rampas que suben hacia la derecha +void CollisionMap::setRightSlopes() { + // Recorre la habitación entera por filas buscando tiles de tipo t_slope_r + std::vector found; + for (int i = 0; i < (int)tile_map_.size(); ++i) { + if (getTile(i) == Tile::SLOPE_R) { + found.push_back(i); + } + } + + // El primer elemento es el inicio de una rampa. Se añade ese elemento y se buscan los siguientes, + // que seran i + mapWidth - 1. Conforme se añaden se eliminan y se vuelve a escudriñar el vector de + // tiles encontrados hasta que esté vacío + + while (!found.empty()) { + LineDiagonal line; + line.x1 = ((found[0] % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; + line.y1 = (found[0] / MAP_WIDTH) * TILE_SIZE; + int looking_for = found[0] + MAP_WIDTH - 1; + int last_one_found = found[0]; + found.erase(found.begin()); + for (int i = 0; i < (int)found.size(); ++i) { + if (found[i] == looking_for) { + last_one_found = looking_for; + looking_for += MAP_WIDTH - 1; + found.erase(found.begin() + i); + i--; + } + } + line.x2 = (last_one_found % MAP_WIDTH) * TILE_SIZE; + line.y2 = ((last_one_found / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; + right_slopes_.push_back(line); + } +} + +// Calcula las superficies automaticas (conveyor belts) +void CollisionMap::setAutoSurfaces() { + std::vector tile = collectAnimatedTiles(); + buildHorizontalLines(tile, conveyor_belt_floors_, false); +} diff --git a/source/game/gameplay/collision_map.hpp b/source/game/gameplay/collision_map.hpp new file mode 100644 index 00000000..4866a094 --- /dev/null +++ b/source/game/gameplay/collision_map.hpp @@ -0,0 +1,120 @@ +#pragma once + +#include + +#include // Para vector + +#include "utils/utils.hpp" // Para LineHorizontal, LineDiagonal, LineVertical + +/** + * @brief Mapa de colisiones de una habitación + * + * Responsabilidades: + * - Almacenar la geometría de colisión (superficies, rampas, conveyor belts) + * - Generar geometría a partir del tilemap + * - Proporcionar queries de colisión para Player y otras entidades + * - Determinar tipo de tile en posiciones específicas + */ +class CollisionMap { + public: + // Enumeración de tipos de tile (para colisiones) + enum class Tile { + EMPTY, + WALL, + PASSABLE, + SLOPE_L, + SLOPE_R, + KILL, + ANIMATED + }; + + /** + * @brief Constructor + * @param tile_map Vector con índices de tiles de la habitación + * @param tile_set_width Ancho del tileset en tiles (para calcular tipo de tile) + * @param conveyor_belt_direction Dirección de las cintas transportadoras (-1, 0, +1) + */ + CollisionMap(std::vector tile_map, int tile_set_width, int conveyor_belt_direction); + ~CollisionMap() = default; + + // Prohibir copia y movimiento + CollisionMap(const CollisionMap&) = delete; + auto operator=(const CollisionMap&) -> CollisionMap& = delete; + CollisionMap(CollisionMap&&) = delete; + auto operator=(CollisionMap&&) -> CollisionMap& = delete; + + // --- Queries de tipo de tile --- + auto getTile(SDL_FPoint point) const -> Tile; // Devuelve el tipo de tile en un punto (pixel) + auto getTile(int index) const -> Tile; // Devuelve el tipo de tile en un índice del tilemap + + // --- Queries de colisión con superficies --- + auto checkRightSurfaces(SDL_FRect& rect) -> int; // Colisión con paredes derechas (retorna X) + auto checkLeftSurfaces(SDL_FRect& rect) -> int; // Colisión con paredes izquierdas (retorna X) + auto checkTopSurfaces(SDL_FRect& rect) -> int; // Colisión con techos (retorna Y) + auto checkTopSurfaces(SDL_FPoint& p) -> bool; // Colisión punto con techos + auto checkBottomSurfaces(SDL_FRect& rect) -> int; // Colisión con suelos (retorna Y) + + // --- Queries de colisión con superficies automáticas (conveyor belts) --- + auto checkAutoSurfaces(SDL_FRect& rect) -> int; // Colisión con conveyor belts (retorna Y) + auto checkConveyorBelts(SDL_FPoint& p) -> bool; // Colisión punto con conveyor belts + + // --- Queries de colisión con rampas --- + auto checkLeftSlopes(const LineVertical& line) -> int; // Colisión línea con rampas izquierdas (retorna Y) + auto checkLeftSlopes(SDL_FPoint& p) -> bool; // Colisión punto con rampas izquierdas + auto checkRightSlopes(const LineVertical& line) -> int; // Colisión línea con rampas derechas (retorna Y) + auto checkRightSlopes(SDL_FPoint& p) -> bool; // Colisión punto con rampas derechas + + // --- Métodos estáticos --- + static auto getTileSize() -> int { return TILE_SIZE; } // Tamaño del tile en pixels + static auto getSlopeHeight(SDL_FPoint p, Tile slope) -> int; // Altura de rampa en un punto + + // --- Getters --- + [[nodiscard]] auto getConveyorBeltDirection() const -> int { return conveyor_belt_direction_; } + + // Getters para debug visualization + [[nodiscard]] auto getBottomFloors() const -> const std::vector& { return bottom_floors_; } + [[nodiscard]] auto getTopFloors() const -> const std::vector& { return top_floors_; } + [[nodiscard]] auto getLeftWalls() const -> const std::vector& { return left_walls_; } + [[nodiscard]] auto getRightWalls() const -> const std::vector& { return right_walls_; } + [[nodiscard]] auto getLeftSlopes() const -> const std::vector& { return left_slopes_; } + [[nodiscard]] auto getRightSlopes() const -> const std::vector& { return right_slopes_; } + [[nodiscard]] auto getConveyorBeltFloors() const -> const std::vector& { return conveyor_belt_floors_; } + + private: + // --- Constantes --- + static constexpr int TILE_SIZE = 8; // Tamaño del tile en pixels + static constexpr int MAP_WIDTH = 32; // Ancho del mapa en tiles + static constexpr int MAP_HEIGHT = 16; // Alto del mapa en tiles + + // --- Datos de la habitación --- + std::vector tile_map_; // Índices de tiles de la habitación + int tile_set_width_; // Ancho del tileset en tiles + int conveyor_belt_direction_; // Dirección de conveyor belts + + // --- Geometría de colisión --- + std::vector bottom_floors_; // Superficies inferiores (suelos) + std::vector top_floors_; // Superficies superiores (techos) + std::vector left_walls_; // Paredes izquierdas + std::vector right_walls_; // Paredes derechas + std::vector left_slopes_; // Rampas que suben hacia la izquierda + std::vector right_slopes_; // Rampas que suben hacia la derecha + std::vector conveyor_belt_floors_; // Superficies automáticas (conveyor belts) + + // --- Métodos privados de generación de geometría --- + void initializeSurfaces(); // Inicializa todas las superficies de colisión + + // Helpers para recopilar tiles + auto collectBottomTiles() -> std::vector; // Tiles con superficie inferior + auto collectTopTiles() -> std::vector; // Tiles con superficie superior + auto collectAnimatedTiles() -> std::vector; // Tiles animados (conveyor belts) + + // Construcción de geometría + static void buildHorizontalLines(const std::vector& tiles, std::vector& lines, bool is_bottom_surface); + void setBottomSurfaces(); // Calcula superficies inferiores + void setTopSurfaces(); // Calcula superficies superiores + void setLeftSurfaces(); // Calcula paredes izquierdas + void setRightSurfaces(); // Calcula paredes derechas + void setLeftSlopes(); // Calcula rampas izquierdas + void setRightSlopes(); // Calcula rampas derechas + void setAutoSurfaces(); // Calcula conveyor belts +}; diff --git a/source/game/gameplay/enemy_manager.cpp b/source/game/gameplay/enemy_manager.cpp new file mode 100644 index 00000000..a1ed5aee --- /dev/null +++ b/source/game/gameplay/enemy_manager.cpp @@ -0,0 +1,49 @@ +#include "enemy_manager.hpp" + +#include // Para std::ranges::any_of + +#include "utils/utils.hpp" // Para checkCollision +#include "game/entities/enemy.hpp" // Para Enemy + +// Añade un enemigo a la colección +void EnemyManager::addEnemy(std::shared_ptr enemy) { + enemies_.push_back(std::move(enemy)); +} + +// Elimina todos los enemigos +void EnemyManager::clear() { + enemies_.clear(); +} + +// Elimina el último enemigo de la colección +void EnemyManager::removeLastEnemy() { + if (!enemies_.empty()) { + enemies_.pop_back(); + } +} + +// Comprueba si no hay enemigos +auto EnemyManager::isEmpty() const -> bool { + return enemies_.empty(); +} + +// Actualiza todos los enemigos +void EnemyManager::update(float delta_time) { + for (const auto& enemy : enemies_) { + enemy->update(delta_time); + } +} + +// Renderiza todos los enemigos +void EnemyManager::render() { + for (const auto& enemy : enemies_) { + enemy->render(); + } +} + +// Comprueba si hay colisión con algún enemigo +auto EnemyManager::checkCollision(SDL_FRect& rect) -> bool { + return std::ranges::any_of(enemies_, [&rect](const auto& enemy) { + return ::checkCollision(rect, enemy->getCollider()); + }); +} diff --git a/source/game/gameplay/enemy_manager.hpp b/source/game/gameplay/enemy_manager.hpp new file mode 100644 index 00000000..ce83747b --- /dev/null +++ b/source/game/gameplay/enemy_manager.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include + +#include // Para shared_ptr +#include // Para vector + +class Enemy; + +/** + * @brief Gestor de enemigos de una habitación + * + * Responsabilidades: + * - Almacenar y gestionar la colección de enemigos + * - Actualizar todos los enemigos + * - Renderizar todos los enemigos + * - Detectar colisiones con enemigos + */ +class EnemyManager { + public: + EnemyManager() = default; + ~EnemyManager() = default; + + // Prohibir copia y movimiento para evitar duplicación accidental + EnemyManager(const EnemyManager&) = delete; + EnemyManager& operator=(const EnemyManager&) = delete; + EnemyManager(EnemyManager&&) = delete; + EnemyManager& operator=(EnemyManager&&) = delete; + + // Gestión de enemigos + void addEnemy(std::shared_ptr enemy); // Añade un enemigo a la colección + void clear(); // Elimina todos los enemigos + void removeLastEnemy(); // Elimina el último enemigo de la colección + auto isEmpty() const -> bool; // Comprueba si no hay enemigos + + // Actualización y renderizado + void update(float delta_time); // Actualiza todos los enemigos + void render(); // Renderiza todos los enemigos + + // Detección de colisiones + auto checkCollision(SDL_FRect& rect) -> bool; // Comprueba si hay colisión con algún enemigo + + private: + std::vector> enemies_; // Colección de enemigos +}; diff --git a/source/game/gameplay/item_manager.cpp b/source/game/gameplay/item_manager.cpp new file mode 100644 index 00000000..45d067ce --- /dev/null +++ b/source/game/gameplay/item_manager.cpp @@ -0,0 +1,69 @@ +#include "item_manager.hpp" + +#include "core/audio/audio.hpp" // Para Audio +#include "utils/utils.hpp" // Para checkCollision +#include "game/entities/item.hpp" // Para Item +#include "item_tracker.hpp" // Para ItemTracker +#include "scoreboard.hpp" // Para Scoreboard::Data +#include "game/options.hpp" // Para Options + +// Constructor +ItemManager::ItemManager(std::string room_name, std::shared_ptr scoreboard_data) + : room_name_(std::move(room_name)) + , data_(std::move(scoreboard_data)) { +} + +// Añade un item a la colección +void ItemManager::addItem(std::shared_ptr item) { + items_.push_back(std::move(item)); +} + +// Elimina todos los items +void ItemManager::clear() { + items_.clear(); +} + +// Actualiza todos los items +void ItemManager::update(float delta_time) { + for (const auto& item : items_) { + item->update(delta_time); + } +} + +// Renderiza todos los items +void ItemManager::render() { + for (const auto& item : items_) { + item->render(); + } +} + +// Pausa/despausa todos los items +void ItemManager::setPaused(bool paused) { + for (const auto& item : items_) { + item->setPaused(paused); + } +} + +// Comprueba si hay colisión con algún item +auto ItemManager::checkCollision(SDL_FRect& rect) -> bool { + for (int i = 0; i < static_cast(items_.size()); ++i) { + if (::checkCollision(rect, items_.at(i)->getCollider())) { + // Registra el item como recogido + ItemTracker::get()->addItem(room_name_, items_.at(i)->getPos()); + + // Elimina el item de la colección + items_.erase(items_.begin() + i); + + // Reproduce el sonido de pickup + Audio::get()->playSound("item.wav", Audio::Group::GAME); + + // Actualiza el scoreboard y estadísticas + data_->items++; + Options::stats.items = data_->items; + + return true; + } + } + + return false; +} diff --git a/source/game/gameplay/item_manager.hpp b/source/game/gameplay/item_manager.hpp new file mode 100644 index 00000000..2e59e286 --- /dev/null +++ b/source/game/gameplay/item_manager.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include + +#include // Para shared_ptr +#include // Para string +#include // Para vector + +#include "scoreboard.hpp" // Para Scoreboard::Data + +class Item; + +/** + * @brief Gestor de items de una habitación + * + * Responsabilidades: + * - Almacenar y gestionar la colección de items + * - Actualizar todos los items + * - Renderizar todos los items + * - Detectar colisiones con items y gestionar pickup + * - Integración con ItemTracker, Scoreboard y Audio + */ +class ItemManager { + public: + /** + * @brief Constructor + * @param room_name Nombre de la habitación (para ItemTracker) + * @param scoreboard_data Puntero compartido a los datos del scoreboard + */ + ItemManager(std::string room_name, std::shared_ptr scoreboard_data); + ~ItemManager() = default; + + // Prohibir copia y movimiento para evitar duplicación accidental + ItemManager(const ItemManager&) = delete; + ItemManager& operator=(const ItemManager&) = delete; + ItemManager(ItemManager&&) = delete; + ItemManager& operator=(ItemManager&&) = delete; + + // Gestión de items + void addItem(std::shared_ptr item); // Añade un item a la colección + void clear(); // Elimina todos los items + + // Actualización y renderizado + void update(float delta_time); // Actualiza todos los items + void render(); // Renderiza todos los items + + // Estado + void setPaused(bool paused); // Pausa/despausa todos los items + + // Detección de colisiones + /** + * @brief Comprueba si hay colisión con algún item + * + * Si hay colisión: + * - Añade el item a ItemTracker + * - Elimina el item de la colección + * - Reproduce el sonido de pickup + * - Actualiza el scoreboard y estadísticas + * + * @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> items_; // Colección de items + std::string room_name_; // Nombre de la habitación + std::shared_ptr data_; // Datos del scoreboard +}; diff --git a/source/game/gameplay/room.cpp b/source/game/gameplay/room.cpp index c774e3c6..4df865bf 100644 --- a/source/game/gameplay/room.cpp +++ b/source/game/gameplay/room.cpp @@ -1,39 +1,47 @@ #include "game/gameplay/room.hpp" -#include // Para std::ranges::any_of -#include // Para exception -#include // Para basic_ostream, operator<<, basic_istream -#include // Para cout, cerr -#include // Para basic_stringstream -#include +#include // Para std::move -#include "core/audio/audio.hpp" // Para Audio #include "core/rendering/screen.hpp" // Para Screen #include "core/rendering/surface.hpp" // Para Surface -#include "core/rendering/surface_sprite.hpp" // Para SSprite #include "core/resources/resource_cache.hpp" // Para Resource -#include "core/resources/resource_helper.hpp" // Para ResourceHelper -#include "core/system/debug.hpp" // Para Debug +#include "game/gameplay/collision_map.hpp" // Para CollisionMap +#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/room_loader.hpp" // Para RoomLoader +#include "game/gameplay/tilemap_renderer.hpp" // Para TilemapRenderer #include "game/gameplay/scoreboard.hpp" // Para Scoreboard::Data -#include "game/options.hpp" // Para Options, OptionsStats, options -#include "utils/defines.hpp" // Para BLOCK, PLAY_AREA_HEIGHT, PLAY_AREA_WIDTH -#include "utils/utils.hpp" // Para LineHorizontal, LineDiagonal, LineVertical +#include "utils/defines.hpp" // Para TILE_SIZE +#include "utils/utils.hpp" // Para stringToColor // Constructor Room::Room(const std::string& room_path, std::shared_ptr data) - : data_(std::move(std::move(data))) { + : data_(std::move(data)) { auto room = Resource::Cache::get()->getRoom(room_path); + + // Crea los managers de enemigos e items + enemy_manager_ = std::make_unique(); + item_manager_ = std::make_unique(room->name, data_); + initializeRoom(*room); - openTheJail(); // Abre la Jail si se da el caso - initRoomSurfaces(); // Inicializa las superficies de colision - setAnimatedTiles(); // Busca los tiles animados - map_surface_ = std::make_shared(PLAY_AREA_WIDTH, PLAY_AREA_HEIGHT); // Crea la textura para el mapa de tiles de la habitación - fillMapTexture(); // Pinta el mapa de la habitación en la textura - Screen::get()->setBorderColor(stringToColor(border_color_)); // Establece el color del borde + // Crea el mapa de colisiones (necesita tile_map_, tile_set_width_, conveyor_belt_direction_) + collision_map_ = std::make_unique(tile_map_, tile_set_width_, conveyor_belt_direction_); + + openTheJail(); // Abre la Jail si se da el caso + + // Crea el renderizador del tilemap (necesita tile_map_, tile_set_width_, surface_, bg_color_, conveyor_belt_direction_) + tilemap_renderer_ = std::make_unique(tile_map_, tile_set_width_, surface_, bg_color_, + conveyor_belt_direction_); + tilemap_renderer_->initialize(collision_map_.get()); // Inicializa (crea map_surface, pinta tiles, busca animados) + + Screen::get()->setBorderColor(stringToColor(border_color_)); // Establece el color del borde } +// Destructor +Room::~Room() = default; + void Room::initializeRoom(const Data& room) { // Asignar valores a las variables miembro number_ = room.number; @@ -53,14 +61,13 @@ void Room::initializeRoom(const Data& room) { surface_ = Resource::Cache::get()->getSurface(room.tile_set_file); tile_set_width_ = surface_->getWidth() / TILE_SIZE; is_paused_ = false; - time_accumulator_ = 0.0F; - // Crear los enemigos + // Crear los enemigos usando el manager for (const auto& enemy_data : room.enemies) { - enemies_.emplace_back(std::make_shared(enemy_data)); + enemy_manager_->addEnemy(std::make_shared(enemy_data)); } - // Crear los items + // Crear los items usando el manager for (const auto& item : room.items) { const SDL_FPoint ITEM_POS = {item.x, item.y}; @@ -71,717 +78,17 @@ void Room::initializeRoom(const Data& room) { item_copy.color2 = stringToColor(item_color2_); // Crear el objeto Item usando la copia modificada - items_.emplace_back(std::make_shared(item_copy)); + item_manager_->addItem(std::make_shared(item_copy)); } } } -// Crea la textura con el mapeado de la habitación -void Room::fillMapTexture() { - const Uint8 COLOR = stringToColor(bg_color_); - auto previuos_renderer = Screen::get()->getRendererSurface(); - Screen::get()->setRendererSurface(map_surface_); - map_surface_->clear(COLOR); - - // Los tileSetFiles son de 20x20 tiles. El primer tile es el 0. Cuentan hacia la derecha y hacia abajo - - SDL_FRect clip = {0, 0, TILE_SIZE, TILE_SIZE}; - for (int y = 0; y < MAP_HEIGHT; ++y) { - for (int x = 0; x < MAP_WIDTH; ++x) { - // Tiled pone los tiles vacios del mapa como cero y empieza a contar de 1 a n. - // Al cargar el mapa en memoria, se resta uno, por tanto los tiles vacios son -1 - // Tampoco hay que dibujar los tiles animados que estan en la fila 19 (indices) - const int INDEX = (y * MAP_WIDTH) + x; - const bool A = (tile_map_[INDEX] >= 18 * tile_set_width_) && (tile_map_[INDEX] < 19 * tile_set_width_); - const bool B = tile_map_[INDEX] > -1; - - if (B && !A) { - clip.x = (tile_map_[INDEX] % tile_set_width_) * TILE_SIZE; - clip.y = (tile_map_[INDEX] / tile_set_width_) * TILE_SIZE; - surface_->render(x * TILE_SIZE, y * TILE_SIZE, &clip); - } - } - } - -#ifdef _DEBUG - if (Debug::get()->getEnabled()) { - auto surface = Screen::get()->getRendererSurface(); - - // BottomSurfaces - { - for (auto l : bottom_floors_) { - surface->drawLine(l.x1, l.y, l.x2, l.y, static_cast(PaletteColor::BLUE)); - } - } - - // TopSurfaces - { - for (auto l : top_floors_) { - surface->drawLine(l.x1, l.y, l.x2, l.y, static_cast(PaletteColor::RED)); - } - } - - // LeftSurfaces - { - for (auto l : left_walls_) { - surface->drawLine(l.x, l.y1, l.x, l.y2, static_cast(PaletteColor::GREEN)); - } - } - - // RightSurfaces - { - for (auto l : right_walls_) { - surface->drawLine(l.x, l.y1, l.x, l.y2, static_cast(PaletteColor::MAGENTA)); - } - } - - // LeftSlopes - { - for (auto l : left_slopes_) { - surface->drawLine(l.x1, l.y1, l.x2, l.y2, static_cast(PaletteColor::CYAN)); - } - } - - // RightSlopes - { - for (auto l : right_slopes_) { - surface->drawLine(l.x1, l.y1, l.x2, l.y2, static_cast(PaletteColor::YELLOW)); - } - } - - // AutoSurfaces - { - for (auto l : conveyor_belt_floors_) { - surface->drawLine(l.x1, l.y, l.x2, l.y, static_cast(PaletteColor::WHITE)); - } - } - } - -#endif // _DEBUG - Screen::get()->setRendererSurface(previuos_renderer); -} - -// Dibuja el mapa en pantalla -void Room::renderMap() { - // Dibuja la textura con el mapa en pantalla - SDL_FRect dest = {0, 0, PLAY_AREA_WIDTH, PLAY_AREA_HEIGHT}; - map_surface_->render(nullptr, &dest); - -// Dibuja los tiles animados -#ifdef _DEBUG - if (!Debug::get()->getEnabled()) { - renderAnimatedTiles(); - } -#else - renderAnimatedTiles(); -#endif -} - -// Dibuja los enemigos en pantalla -void Room::renderEnemies() { - for (const auto& enemy : enemies_) { - enemy->render(); - } -} - -// Dibuja los objetos en pantalla -void Room::renderItems() { - for (const auto& item : items_) { - item->render(); - } -} - -// Actualiza las variables y objetos de la habitación -void Room::update(float delta_time) { - if (is_paused_) { - // Si está en modo pausa no se actualiza nada - return; - } - - // Actualiza el acumulador de tiempo - time_accumulator_ += delta_time; - - // Actualiza los tiles animados - updateAnimatedTiles(); - - for (const auto& enemy : enemies_) { - // Actualiza los enemigos - enemy->update(delta_time); - } - - for (const auto& item : items_) { - // Actualiza los items - item->update(delta_time); - } -} - -// Devuelve la cadena del fichero de la habitación contigua segun el borde -auto Room::getRoom(Border border) -> std::string { - switch (border) { - case Border::TOP: - return upper_room_; - break; - - case Border::BOTTOM: - return lower_room_; - break; - - case Border::RIGHT: - return right_room_; - break; - - case Border::LEFT: - return left_room_; - break; - - default: - break; - } - return ""; -} - -// Devuelve el tipo de tile que hay en ese pixel -auto Room::getTile(SDL_FPoint point) -> Tile { - const int POS = ((point.y / TILE_SIZE) * MAP_WIDTH) + (point.x / TILE_SIZE); - return getTile(POS); -} - -// Devuelve el tipo de tile que hay en ese indice -auto Room::getTile(int index) -> Tile { - // const bool onRange = (index > -1) && (index < mapWidth * mapHeight); - const bool ON_RANGE = (index > -1) && (index < (int)tile_map_.size()); - - if (ON_RANGE) { - // Las filas 0-8 son de tiles t_wall - if ((tile_map_[index] >= 0) && (tile_map_[index] < 9 * tile_set_width_)) { - return Tile::WALL; - } - - // Las filas 9-17 son de tiles t_passable - if ((tile_map_[index] >= 9 * tile_set_width_) && (tile_map_[index] < 18 * tile_set_width_)) { - return Tile::PASSABLE; - } - - // Las filas 18-20 es de tiles t_animated - if ((tile_map_[index] >= 18 * tile_set_width_) && (tile_map_[index] < 21 * tile_set_width_)) { - return Tile::ANIMATED; - } - - // La fila 21 es de tiles t_slope_r - if ((tile_map_[index] >= 21 * tile_set_width_) && (tile_map_[index] < 22 * tile_set_width_)) { - return Tile::SLOPE_R; - } - - // La fila 22 es de tiles t_slope_l - if ((tile_map_[index] >= 22 * tile_set_width_) && (tile_map_[index] < 23 * tile_set_width_)) { - return Tile::SLOPE_L; - } - - // La fila 23 es de tiles t_kill - if ((tile_map_[index] >= 23 * tile_set_width_) && (tile_map_[index] < 24 * tile_set_width_)) { - return Tile::KILL; - } - } - - return Tile::EMPTY; -} - -// Indica si hay colision con un enemigo a partir de un rectangulo -auto Room::enemyCollision(SDL_FRect& rect) -> bool { - return std::ranges::any_of(enemies_, [&rect](const auto& enemy) { - return checkCollision(rect, enemy->getCollider()); - }); -} - -// Indica si hay colision con un objeto a partir de un rectangulo -auto Room::itemCollision(SDL_FRect& rect) -> bool { - for (int i = 0; i < static_cast(items_.size()); ++i) { - if (checkCollision(rect, items_.at(i)->getCollider())) { - ItemTracker::get()->addItem(name_, items_.at(i)->getPos()); - items_.erase(items_.begin() + i); - Audio::get()->playSound("item.wav", Audio::Group::GAME); - data_->items++; - Options::stats.items = data_->items; - return true; - } - } - - return false; -} - -// Obten la coordenada de la cuesta a partir de un punto perteneciente a ese tile -auto Room::getSlopeHeight(SDL_FPoint p, Tile slope) -> int { - // Calcula la base del tile - int base = ((p.y / TILE_SIZE) * TILE_SIZE) + TILE_SIZE; -#ifdef _DEBUG - Debug::get()->add("BASE = " + std::to_string(base)); -#endif - - // Calcula cuanto se ha entrado en el tile horizontalmente - const int POS = (static_cast(p.x) % TILE_SIZE); // Esto da un valor entre 0 y 7 -#ifdef _DEBUG - Debug::get()->add("POS = " + std::to_string(POS)); -#endif - - // Se resta a la base la cantidad de pixeles pos en funcion de la rampa - if (slope == Tile::SLOPE_R) { - base -= POS + 1; -#ifdef _DEBUG - Debug::get()->add("BASE_R = " + std::to_string(base)); -#endif - } else { - base -= (TILE_SIZE - POS); -#ifdef _DEBUG - Debug::get()->add("BASE_L = " + std::to_string(base)); -#endif - } - - return base; -} - -// Helper: recopila tiles inferiores (muros sin muro debajo) -auto Room::collectBottomTiles() -> std::vector { - std::vector tile; - - // Busca todos los tiles de tipo muro que no tengan debajo otro muro - // Hay que recorrer la habitación por filas (excepto los de la última fila) - for (int i = 0; i < (int)tile_map_.size() - MAP_WIDTH; ++i) { - if (getTile(i) == Tile::WALL && getTile(i + MAP_WIDTH) != Tile::WALL) { - tile.push_back(i); - - // Si llega al final de la fila, introduce un separador - if (i % MAP_WIDTH == MAP_WIDTH - 1) { - tile.push_back(-1); - } - } - } - - // Añade un terminador - tile.push_back(-1); - return tile; -} - -// Helper: recopila tiles superiores (muros o pasables sin muro encima) -auto Room::collectTopTiles() -> std::vector { - std::vector tile; - - // Busca todos los tiles de tipo muro o pasable que no tengan encima un muro - // Hay que recorrer la habitación por filas (excepto los de la primera fila) - for (int i = MAP_WIDTH; i < (int)tile_map_.size(); ++i) { - if ((getTile(i) == Tile::WALL || getTile(i) == Tile::PASSABLE) && getTile(i - MAP_WIDTH) != Tile::WALL) { - tile.push_back(i); - - // Si llega al final de la fila, introduce un separador - if (i % MAP_WIDTH == MAP_WIDTH - 1) { - tile.push_back(-1); - } - } - } - - // Añade un terminador - tile.push_back(-1); - return tile; -} - -// Helper: construye lineas horizontales a partir de tiles consecutivos -void Room::buildHorizontalLines(const std::vector& tiles, std::vector& lines, bool is_bottom_surface) { - if (tiles.size() <= 1) { - return; - } - - int i = 0; - while (i < (int)tiles.size() - 1) { - LineHorizontal line; - line.x1 = (tiles[i] % MAP_WIDTH) * TILE_SIZE; - - // Calcula Y segun si es superficie inferior o superior - if (is_bottom_surface) { - line.y = ((tiles[i] / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; - } else { - line.y = (tiles[i] / MAP_WIDTH) * TILE_SIZE; - } - - int last_one = i; - i++; - - // Encuentra tiles consecutivos - if (i < (int)tiles.size()) { - while (tiles[i] == tiles[i - 1] + 1) { - last_one = i; - i++; - if (i >= (int)tiles.size()) { - break; - } - } - } - - line.x2 = ((tiles[last_one] % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; - lines.push_back(line); - - // Salta separadores - if (i < (int)tiles.size() && tiles[i] == -1) { - i++; - } - } -} - -// Calcula las superficies inferiores -void Room::setBottomSurfaces() { - std::vector tile = collectBottomTiles(); - buildHorizontalLines(tile, bottom_floors_, true); -} - -// Calcula las superficies superiores -void Room::setTopSurfaces() { - std::vector tile = collectTopTiles(); - buildHorizontalLines(tile, top_floors_, false); -} - -// Calcula las superficies laterales izquierdas -void Room::setLeftSurfaces() { - std::vector tile; - - // Busca todos los tiles de tipo muro que no tienen a su izquierda un tile de tipo muro - // Hay que recorrer la habitación por columnas (excepto los de la primera columna) - for (int i = 1; i < MAP_WIDTH; ++i) { - for (int j = 0; j < MAP_HEIGHT; ++j) { - const int POS = ((j * MAP_WIDTH) + i); - if (getTile(POS) == Tile::WALL && getTile(POS - 1) != Tile::WALL) { - tile.push_back(POS); - } - } - } - - // Añade un terminador - tile.push_back(-1); - - // Recorre el vector de tiles buscando tiles consecutivos - // (Los tiles de la misma columna, la diferencia entre ellos es de mapWidth) - // para localizar las superficies - if ((int)tile.size() > 1) { - int i = 0; - do { - LineVertical line; - line.x = (tile[i] % MAP_WIDTH) * TILE_SIZE; - line.y1 = ((tile[i] / MAP_WIDTH) * TILE_SIZE); - while (tile[i] + MAP_WIDTH == tile[i + 1]) { - if (i == (int)tile.size() - 1) { - break; - } - i++; - } - line.y2 = ((tile[i] / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; - left_walls_.push_back(line); - i++; - } while (i < (int)tile.size() - 1); - } -} - -// Calcula las superficies laterales derechas -void Room::setRightSurfaces() { - std::vector tile; - - // Busca todos los tiles de tipo muro que no tienen a su derecha un tile de tipo muro - // Hay que recorrer la habitación por columnas (excepto los de la última columna) - for (int i = 0; i < MAP_WIDTH - 1; ++i) { - for (int j = 0; j < MAP_HEIGHT; ++j) { - const int POS = ((j * MAP_WIDTH) + i); - if (getTile(POS) == Tile::WALL && getTile(POS + 1) != Tile::WALL) { - tile.push_back(POS); - } - } - } - - // Añade un terminador - tile.push_back(-1); - - // Recorre el vector de tiles buscando tiles consecutivos - // (Los tiles de la misma columna, la diferencia entre ellos es de mapWidth) - // para localizar las superficies - if ((int)tile.size() > 1) { - int i = 0; - do { - LineVertical line; - line.x = ((tile[i] % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; - line.y1 = ((tile[i] / MAP_WIDTH) * TILE_SIZE); - while (tile[i] + MAP_WIDTH == tile[i + 1]) { - if (i == (int)tile.size() - 1) { - break; - } - i++; - } - line.y2 = ((tile[i] / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; - right_walls_.push_back(line); - i++; - } while (i < (int)tile.size() - 1); - } -} - -// Encuentra todas las rampas que suben hacia la izquierda -void Room::setLeftSlopes() { - // Recorre la habitación entera por filas buscando tiles de tipo t_slope_l - std::vector found; - for (int i = 0; i < (int)tile_map_.size(); ++i) { - if (getTile(i) == Tile::SLOPE_L) { - found.push_back(i); - } - } - - // El primer elemento es el inicio de una rampa. Se añade ese elemento y se buscan los siguientes, - // que seran i + mapWidth + 1. Conforme se añaden se eliminan y se vuelve a escudriñar el vector de - // tiles encontrados hasta que esté vacío - - while (!found.empty()) { - LineDiagonal line; - line.x1 = (found[0] % MAP_WIDTH) * TILE_SIZE; - line.y1 = (found[0] / MAP_WIDTH) * TILE_SIZE; - int looking_for = found[0] + MAP_WIDTH + 1; - int last_one_found = found[0]; - found.erase(found.begin()); - for (int i = 0; i < (int)found.size(); ++i) { - if (found[i] == looking_for) { - last_one_found = looking_for; - looking_for += MAP_WIDTH + 1; - found.erase(found.begin() + i); - i--; - } - } - line.x2 = ((last_one_found % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; - line.y2 = ((last_one_found / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; - left_slopes_.push_back(line); - } -} - -// Encuentra todas las rampas que suben hacia la derecha -void Room::setRightSlopes() { - // Recorre la habitación entera por filas buscando tiles de tipo t_slope_r - std::vector found; - for (int i = 0; i < (int)tile_map_.size(); ++i) { - if (getTile(i) == Tile::SLOPE_R) { - found.push_back(i); - } - } - - // El primer elemento es el inicio de una rampa. Se añade ese elemento y se buscan los siguientes, - // que seran i + mapWidth - 1. Conforme se añaden se eliminan y se vuelve a escudriñar el vector de - // tiles encontrados hasta que esté vacío - - while (!found.empty()) { - LineDiagonal line; - line.x1 = ((found[0] % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; - line.y1 = (found[0] / MAP_WIDTH) * TILE_SIZE; - int looking_for = found[0] + MAP_WIDTH - 1; - int last_one_found = found[0]; - found.erase(found.begin()); - for (int i = 0; i < (int)found.size(); ++i) { - if (found[i] == looking_for) { - last_one_found = looking_for; - looking_for += MAP_WIDTH - 1; - found.erase(found.begin() + i); - i--; - } - } - line.x2 = (last_one_found % MAP_WIDTH) * TILE_SIZE; - line.y2 = ((last_one_found / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; - right_slopes_.push_back(line); - } -} - -// Calcula las superficies automaticas -// Helper: recopila tiles animados (para superficies automaticas/conveyor belts) -auto Room::collectAnimatedTiles() -> std::vector { - std::vector tile; - - // Busca todos los tiles de tipo animado - // Hay que recorrer la habitación por filas (excepto los de la primera fila) - for (int i = MAP_WIDTH; i < (int)tile_map_.size(); ++i) { - if (getTile(i) == Tile::ANIMATED) { - tile.push_back(i); - - // Si llega al final de la fila, introduce un separador - if (i % MAP_WIDTH == MAP_WIDTH - 1) { - tile.push_back(-1); - } - } - } - - // Añade un terminador si hay tiles - if (!tile.empty()) { - tile.push_back(-1); - } - - return tile; -} - -void Room::setAutoSurfaces() { - std::vector tile = collectAnimatedTiles(); - buildHorizontalLines(tile, conveyor_belt_floors_, false); -} - -// Localiza todos los tiles animados de la habitación -void Room::setAnimatedTiles() { - // Recorre la habitación entera por filas buscando tiles de tipo t_animated - for (int i = 0; i < (int)tile_map_.size(); ++i) { - if (getTile(i) == Tile::ANIMATED) { - // La i es la ubicación - const int X = (i % MAP_WIDTH) * TILE_SIZE; - const int Y = (i / MAP_WIDTH) * TILE_SIZE; - - // TileMap[i] es el tile a poner - const int XC = (tile_map_[i] % tile_set_width_) * TILE_SIZE; - const int YC = (tile_map_[i] / tile_set_width_) * TILE_SIZE; - - AnimatedTile at; - at.sprite = std::make_shared(surface_, X, Y, 8, 8); - at.sprite->setClip(XC, YC, 8, 8); - at.x_orig = XC; - animated_tiles_.push_back(at); - } - } -} - -// Actualiza los tiles animados -void Room::updateAnimatedTiles() { - const int NUM_FRAMES = 4; - - // Calcular frame actual basado en tiempo - const int CURRENT_FRAME = static_cast(time_accumulator_ / CONVEYOR_FRAME_DURATION) % NUM_FRAMES; - - // Calcular offset basado en dirección - int offset = 0; - if (conveyor_belt_direction_ == -1) { - offset = CURRENT_FRAME * TILE_SIZE; - } else { - offset = (NUM_FRAMES - 1 - CURRENT_FRAME) * TILE_SIZE; - } - - for (auto& a : animated_tiles_) { - SDL_FRect rect = a.sprite->getClip(); - rect.x = a.x_orig + offset; - a.sprite->setClip(rect); - } -} - -// Pinta los tiles animados en pantalla -void Room::renderAnimatedTiles() { - for (const auto& a : animated_tiles_) { - a.sprite->render(); - } -} - -// Comprueba las colisiones -auto Room::checkRightSurfaces(SDL_FRect& rect) -> int { - for (const auto& s : right_walls_) { - if (checkCollision(s, rect)) { - return s.x; - } - } - - return Collision::NONE; -} - -// Comprueba las colisiones -auto Room::checkLeftSurfaces(SDL_FRect& rect) -> int { - for (const auto& s : left_walls_) { - if (checkCollision(s, rect)) { - return s.x; - } - } - - return Collision::NONE; -} - -// Comprueba las colisiones -auto Room::checkTopSurfaces(SDL_FRect& rect) -> int { - for (const auto& s : top_floors_) { - if (checkCollision(s, rect)) { - return s.y; - } - } - - return Collision::NONE; -} - -// Comprueba las colisiones -auto Room::checkBottomSurfaces(SDL_FRect& rect) -> int { - for (const auto& s : bottom_floors_) { - if (checkCollision(s, rect)) { - return s.y; - } - } - - return Collision::NONE; -} - -// Comprueba las colisiones -auto Room::checkAutoSurfaces(SDL_FRect& rect) -> int { - for (const auto& s : conveyor_belt_floors_) { - if (checkCollision(s, rect)) { - return s.y; - } - } - - return Collision::NONE; -} - -// Comprueba las colisiones -auto Room::checkTopSurfaces(SDL_FPoint& p) -> bool { - return std::ranges::any_of(top_floors_, [&](const auto& s) { - return checkCollision(s, p); - }); -} - -// Comprueba las colisiones -auto Room::checkConveyorBelts(SDL_FPoint& p) -> bool { - return std::ranges::any_of(conveyor_belt_floors_, [&](const auto& s) { - return checkCollision(s, p); - }); -} - -// Comprueba las colisiones -auto Room::checkLeftSlopes(const LineVertical& line) -> int { - for (const auto& slope : left_slopes_) { - const auto P = checkCollision(slope, line); - if (P.x != -1) { - return P.y; - } - } - - return Collision::NONE; -} - -// Comprueba las colisiones -auto Room::checkLeftSlopes(SDL_FPoint& p) -> bool { - return std::ranges::any_of(left_slopes_, [&](const auto& slope) { - return checkCollision(p, slope); - }); -} - -// Comprueba las colisiones -auto Room::checkRightSlopes(const LineVertical& line) -> int { - for (const auto& slope : right_slopes_) { - const auto P = checkCollision(slope, line); - if (P.x != -1) { - return P.y; - } - } - - return Collision::NONE; -} - -// Comprueba las colisiones -auto Room::checkRightSlopes(SDL_FPoint& p) -> bool { - return std::ranges::any_of(right_slopes_, [&](const auto& slope) { - return checkCollision(p, slope); - }); -} - -// Abre la Jail si se da el caso +// Abre la jail para poder entrar void Room::openTheJail() { if (data_->jail_is_open && name_ == "THE JAIL") { // Elimina el último enemigo (Bry debe ser el último enemigo definido en el fichero) - if (!enemies_.empty()) { - enemies_.pop_back(); + if (!enemy_manager_->isEmpty()) { + enemy_manager_->removeLastEnemy(); } // Abre las puertas @@ -796,302 +103,156 @@ void Room::openTheJail() { } } -// Inicializa las superficies de colision -void Room::initRoomSurfaces() { - setBottomSurfaces(); - setTopSurfaces(); - setLeftSurfaces(); - setRightSurfaces(); - setLeftSlopes(); - setRightSlopes(); - setAutoSurfaces(); +// Dibuja el mapa en pantalla +void Room::renderMap() { + tilemap_renderer_->render(); } -// Asigna variables a una estructura RoomData -auto Room::setRoom(Data& room, const std::string& key, const std::string& value) -> bool { - // Indicador de éxito en la asignación - bool success = true; +// Dibuja los enemigos en pantalla +void Room::renderEnemies() { + enemy_manager_->render(); +} - try { - if (key == "tileMapFile") { - room.tile_map_file = value; - } else if (key == "name") { - room.name = value; - } else if (key == "bgColor") { - room.bg_color = value; - } else if (key == "border") { - room.border_color = value; - } else if (key == "itemColor1") { - room.item_color1 = value; - } else if (key == "itemColor2") { - room.item_color2 = value; - } else if (key == "tileSetFile") { - room.tile_set_file = value; - } else if (key == "roomUp") { - room.upper_room = value; - } else if (key == "roomDown") { - room.lower_room = value; - } else if (key == "roomLeft") { - room.left_room = value; - } else if (key == "roomRight") { - room.right_room = value; - } else if (key == "autoSurface") { - room.conveyor_belt_direction = (value == "right") ? 1 : -1; - } else if (key.empty() || key.substr(0, 1) == "#") { - // No se realiza ninguna acción para estas claves - } else { - success = false; - } - } catch (const std::exception& e) { - std::cerr << "Error al asignar la clave " << key << " con valor " << value << ": " << e.what() << '\n'; - success = false; +// Dibuja los objetos en pantalla +void Room::renderItems() { + item_manager_->render(); +} + +// Actualiza las variables y objetos de la habitación +void Room::update(float delta_time) { + if (is_paused_) { + // Si está en modo pausa no se actualiza nada + return; } - return success; + // Actualiza los tiles animados usando el renderer + tilemap_renderer_->update(delta_time); + + // Actualiza los enemigos usando el manager + enemy_manager_->update(delta_time); + + // Actualiza los items usando el manager + item_manager_->update(delta_time); } -// Asigna variables a una estructura EnemyData -auto Room::setEnemy(Enemy::Data& enemy, const std::string& key, const std::string& value) -> bool { - // Indicador de éxito en la asignación - bool success = true; +// Pone el mapa en modo pausa +void Room::setPaused(bool value) { + is_paused_ = value; + tilemap_renderer_->setPaused(value); + item_manager_->setPaused(value); +} - try { - if (key == "animation") { - enemy.animation_path = value; - } else if (key == "x") { - enemy.x = std::stof(value) * TILE_SIZE; - } else if (key == "y") { - enemy.y = std::stof(value) * TILE_SIZE; - } else if (key == "vx") { - enemy.vx = std::stof(value); - } else if (key == "vy") { - enemy.vy = std::stof(value); - } else if (key == "x1") { - enemy.x1 = std::stoi(value) * TILE_SIZE; - } else if (key == "x2") { - enemy.x2 = std::stoi(value) * TILE_SIZE; - } else if (key == "y1") { - enemy.y1 = std::stoi(value) * TILE_SIZE; - } else if (key == "y2") { - enemy.y2 = std::stoi(value) * TILE_SIZE; - } else if (key == "flip") { - enemy.flip = stringToBool(value); - } else if (key == "mirror") { - enemy.mirror = stringToBool(value); - } else if (key == "color") { - enemy.color = value; - } else if (key == "frame") { - enemy.frame = std::stoi(value); - } else if (key == "[/enemy]" || key == "tileSetFile" || key.substr(0, 1) == "#") { - // No se realiza ninguna acción para estas claves - } else { - success = false; - } - } catch (const std::exception& e) { - std::cerr << "Error al asignar la clave " << key << " con valor " << value << ": " << e.what() << '\n'; - success = false; +// Devuelve la cadena del fichero de la habitación contigua segun el borde +auto Room::getRoom(Border border) -> std::string { + switch (border) { + case Border::TOP: + return upper_room_; + case Border::BOTTOM: + return lower_room_; + case Border::RIGHT: + return right_room_; + case Border::LEFT: + return left_room_; + default: + return ""; } - - return success; } -// Asigna variables a una estructura ItemData -auto Room::setItem(Item::Data& item, const std::string& key, const std::string& value) -> bool { - // Indicador de éxito en la asignación - bool success = true; - - try { - if (key == "tileSetFile") { - item.tile_set_file = value; - } else if (key == "counter") { - item.counter = std::stoi(value); - } else if (key == "x") { - item.x = std::stof(value) * TILE_SIZE; - } else if (key == "y") { - item.y = std::stof(value) * TILE_SIZE; - } else if (key == "tile") { - item.tile = std::stof(value); - } else if (key == "[/item]") { - // No se realiza ninguna acción para esta clave - } else { - success = false; - } - } catch (const std::exception& e) { - std::cerr << "Error al asignar la clave " << key << " con valor " << value << ": " << e.what() << '\n'; - success = false; - } - - return success; +// Devuelve el tipo de tile que hay en ese pixel +auto Room::getTile(SDL_FPoint point) -> Tile { + // Delega a CollisionMap y convierte el resultado + const auto collision_tile = collision_map_->getTile(point); + return static_cast(collision_tile); } -// Carga las variables y texturas desde un fichero de mapa de tiles -auto Room::loadRoomTileFile(const std::string& file_path, bool verbose) -> std::vector { - std::vector tile_map_file; - const std::string FILENAME = file_path.substr(file_path.find_last_of("\\/") + 1); - - // Load file using ResourceHelper (supports both filesystem and pack) - auto file_data = Resource::Helper::loadFile(file_path); - - if (!file_data.empty()) { - // Convert bytes to string and parse - std::string content(file_data.begin(), file_data.end()); - std::istringstream stream(content); - std::string line; - - // Procesa el fichero linea a linea - while (std::getline(stream, line)) { // Lee el fichero linea a linea - if (line.find("data encoding") != std::string::npos) { - // Lee la primera linea - std::getline(stream, line); - // Trim line to handle Windows line endings - line.erase(0, line.find_first_not_of(" \t\r\n")); - line.erase(line.find_last_not_of(" \t\r\n") + 1); - while (line != "") { // Procesa lineas mientras haya - std::stringstream ss(line); - std::string tmp; - while (getline(ss, tmp, ',')) { - // Trim whitespace (including \r, \n, spaces, tabs) - tmp.erase(0, tmp.find_first_not_of(" \t\r\n")); - tmp.erase(tmp.find_last_not_of(" \t\r\n") + 1); - - // Skip empty strings (from trailing commas) - if (!tmp.empty()) { - tile_map_file.push_back(std::stoi(tmp) - 1); - } - } - - // Lee la siguiente linea - std::getline(stream, line); - // Trim line to handle Windows line endings - line.erase(0, line.find_first_not_of(" \t\r\n")); - line.erase(line.find_last_not_of(" \t\r\n") + 1); - } - } - } - - if (verbose) { - std::cout << "TileMap loaded: " << FILENAME.c_str() << '\n'; - } - } else { // El fichero no se puede abrir - if (verbose) { - std::cout << "Warning: Unable to open " << FILENAME.c_str() << " file" << '\n'; - } - } - - return tile_map_file; +// Devuelve el tipo de tile en un índice del tilemap +auto Room::getTile(int index) -> Tile { + // Delega a CollisionMap y convierte el resultado + const auto collision_tile = collision_map_->getTile(index); + return static_cast(collision_tile); } -// Carga las variables desde un fichero de mapa +// Indica si hay colision con un enemigo a partir de un rectangulo +auto Room::enemyCollision(SDL_FRect& rect) -> bool { + return enemy_manager_->checkCollision(rect); +} + +// Indica si hay colision con un objeto a partir de un rectangulo +auto Room::itemCollision(SDL_FRect& rect) -> bool { + return item_manager_->checkCollision(rect); +} + +// Obten la coordenada de la cuesta a partir de un punto perteneciente a ese tile +auto Room::getSlopeHeight(SDL_FPoint p, Tile slope) -> int { + // Delega a CollisionMap (método estático) + const auto collision_tile = static_cast(slope); + return CollisionMap::getSlopeHeight(p, collision_tile); +} + +// === Métodos de colisión (delegados a CollisionMap) === + +// Comprueba las colisiones con paredes derechas +auto Room::checkRightSurfaces(SDL_FRect& rect) -> int { + return collision_map_->checkRightSurfaces(rect); +} + +// Comprueba las colisiones con paredes izquierdas +auto Room::checkLeftSurfaces(SDL_FRect& rect) -> int { + return collision_map_->checkLeftSurfaces(rect); +} + +// Comprueba las colisiones con techos +auto Room::checkTopSurfaces(SDL_FRect& rect) -> int { + return collision_map_->checkTopSurfaces(rect); +} + +// Comprueba las colisiones punto con techos +auto Room::checkTopSurfaces(SDL_FPoint& p) -> bool { + return collision_map_->checkTopSurfaces(p); +} + +// Comprueba las colisiones con suelos +auto Room::checkBottomSurfaces(SDL_FRect& rect) -> int { + return collision_map_->checkBottomSurfaces(rect); +} + +// Comprueba las colisiones con conveyor belts +auto Room::checkAutoSurfaces(SDL_FRect& rect) -> int { + return collision_map_->checkAutoSurfaces(rect); +} + +// Comprueba las colisiones punto con conveyor belts +auto Room::checkConveyorBelts(SDL_FPoint& p) -> bool { + return collision_map_->checkConveyorBelts(p); +} + +// Comprueba las colisiones línea con rampas izquierdas +auto Room::checkLeftSlopes(const LineVertical& line) -> int { + return collision_map_->checkLeftSlopes(line); +} + +// Comprueba las colisiones punto con rampas izquierdas +auto Room::checkLeftSlopes(SDL_FPoint& p) -> bool { + return collision_map_->checkLeftSlopes(p); +} + +// Comprueba las colisiones línea con rampas derechas +auto Room::checkRightSlopes(const LineVertical& line) -> int { + return collision_map_->checkRightSlopes(line); +} + +// Comprueba las colisiones punto con rampas derechas +auto Room::checkRightSlopes(SDL_FPoint& p) -> bool { + return collision_map_->checkRightSlopes(p); +} + + +// Carga las variables desde un fichero de mapa (delegado a RoomLoader) auto Room::loadRoomFile(const std::string& file_path, bool verbose) -> Data { - Data room; - room.item_color1 = "yellow"; - room.item_color2 = "magenta"; - room.conveyor_belt_direction = 1; - - const std::string FILE_NAME = file_path.substr(file_path.find_last_of("\\/") + 1); - room.number = FILE_NAME.substr(0, FILE_NAME.find_last_of('.')); - - // Load file using ResourceHelper (supports both filesystem and pack) - auto file_data = Resource::Helper::loadFile(file_path); - - if (!file_data.empty()) { - // Convert bytes to string and parse - std::string content(file_data.begin(), file_data.end()); - std::istringstream stream(content); - std::string line; - - // Procesa el fichero linea a linea - while (std::getline(stream, line)) { - // Remove Windows line ending if present - if (!line.empty() && line.back() == '\r') { - line.pop_back(); - } - // Si la linea contiene el texto [enemy] se realiza el proceso de carga de un enemigo - if (line == "[enemy]") { - room.enemies.push_back(loadEnemyFromFile(stream, FILE_NAME, verbose)); - } - // Si la linea contiene el texto [item] se realiza el proceso de carga de un item - else if (line == "[item]") { - room.items.push_back(loadItemFromFile(stream, FILE_NAME, verbose)); - } - // En caso contrario se parsea el fichero para buscar las variables y los valores - else { - auto [key, value] = parseKeyValue(line); - if (!setRoom(room, key, value)) { - logUnknownParameter(FILE_NAME, key, verbose); - } - } - } - - if (verbose) { - std::cout << "Room loaded: " << FILE_NAME.c_str() << '\n'; - } - } else { // El fichero no se puede abrir - std::cout << "Warning: Unable to open " << FILE_NAME.c_str() << " file" << '\n'; - } - - return room; + return RoomLoader::loadRoomFile(file_path, verbose); } -// Parsea una línea en key y value separados por '=' -auto Room::parseKeyValue(const std::string& line) -> std::pair { - int pos = line.find('='); - std::string key = line.substr(0, pos); - std::string value = line.substr(pos + 1, line.length()); - return {key, value}; +// Carga las variables y texturas desde un fichero de mapa de tiles (delegado a RoomLoader) +auto Room::loadRoomTileFile(const std::string& file_path, bool verbose) -> std::vector { + return RoomLoader::loadRoomTileFile(file_path, verbose); } - -// Muestra un warning de parámetro desconocido -void Room::logUnknownParameter(const std::string& file_name, const std::string& key, bool verbose) { - if (verbose) { - std::cout << "Warning: file " << file_name.c_str() << "\n, unknown parameter \"" << key.c_str() << "\"" << '\n'; - } -} - -// Carga un bloque [enemy]...[/enemy] desde un archivo -auto Room::loadEnemyFromFile(std::istream& file, const std::string& file_name, bool verbose) -> Enemy::Data { - Enemy::Data enemy; - enemy.flip = false; - enemy.mirror = false; - enemy.frame = -1; - - std::string line; - do { - std::getline(file, line); - // Remove Windows line ending if present - if (!line.empty() && line.back() == '\r') { - line.pop_back(); - } - auto [key, value] = parseKeyValue(line); - - if (!setEnemy(enemy, key, value)) { - logUnknownParameter(file_name, key, verbose); - } - } while (line != "[/enemy]"); - - return enemy; -} - -// Carga un bloque [item]...[/item] desde un archivo -auto Room::loadItemFromFile(std::istream& file, const std::string& file_name, bool verbose) -> Item::Data { - Item::Data item; - item.counter = 0; - item.color1 = stringToColor("yellow"); - item.color2 = stringToColor("magenta"); - - std::string line; - do { - std::getline(file, line); - // Remove Windows line ending if present - if (!line.empty() && line.back() == '\r') { - line.pop_back(); - } - auto [key, value] = parseKeyValue(line); - - if (!setItem(item, key, value)) { - logUnknownParameter(file_name, key, verbose); - } - } while (line != "[/item]"); - - return item; -} \ No newline at end of file diff --git a/source/game/gameplay/room.hpp b/source/game/gameplay/room.hpp index 5cf6f5db..15d621cb 100644 --- a/source/game/gameplay/room.hpp +++ b/source/game/gameplay/room.hpp @@ -12,6 +12,10 @@ #include "utils/utils.hpp" // Para LineHorizontal, LineDiagonal, LineVertical class SurfaceSprite; // lines 12-12 class Surface; // lines 13-13 +class EnemyManager; +class ItemManager; +class CollisionMap; +class TilemapRenderer; class Room { public: @@ -54,7 +58,7 @@ class Room { // Constructor y destructor Room(const std::string& room_path, std::shared_ptr data); - ~Room() = default; + ~Room(); // Definido en .cpp para poder usar unique_ptr con forward declarations // --- Funciones --- [[nodiscard]] auto getName() const -> const std::string& { return name_; } // Devuelve el nombre de la habitación @@ -66,6 +70,7 @@ class Room { void update(float delta_time); // Actualiza las variables y objetos de la habitación auto getRoom(Border border) -> std::string; // Devuelve la cadena del fichero de la habitación contigua segun el borde auto getTile(SDL_FPoint point) -> Tile; // Devuelve el tipo de tile que hay en ese pixel + auto getTile(int index) -> Tile; // Devuelve el tipo de tile en un índice del tilemap auto enemyCollision(SDL_FRect& rect) -> bool; // Indica si hay colision con un enemigo a partir de un rectangulo auto itemCollision(SDL_FRect& rect) -> bool; // Indica si hay colision con un objeto a partir de un rectangulo static auto getTileSize() -> int { return TILE_SIZE; } // Obten el tamaño del tile @@ -81,30 +86,26 @@ class Room { auto checkLeftSlopes(SDL_FPoint& p) -> bool; // Comprueba las colisiones auto checkRightSlopes(const LineVertical& line) -> int; // Comprueba las colisiones auto checkRightSlopes(SDL_FPoint& p) -> bool; // Comprueba las colisiones - void setPaused(bool value) { is_paused_ = value; }; // Pone el mapa en modo pausa + void setPaused(bool value); // Pone el mapa en modo pausa [[nodiscard]] auto getConveyorBeltDirection() const -> int { return conveyor_belt_direction_; } // Obten la direccion de las superficies automaticas + + // Métodos de carga de archivos (delegados a RoomLoader) static auto loadRoomFile(const std::string& file_path, bool verbose = false) -> Data; // Carga las variables desde un fichero de mapa static auto loadRoomTileFile(const std::string& file_path, bool verbose = false) -> std::vector; // Carga las variables y texturas desde un fichero de mapa de tiles private: - // Tipos anidados privados - struct AnimatedTile { - std::shared_ptr sprite{nullptr}; // SurfaceSprite para dibujar el tile - int x_orig{0}; // Posición X donde se encuentra el primer tile de la animación en la tilesheet - }; - // Constantes - static constexpr int TILE_SIZE = 8; // Ancho del tile en pixels - static constexpr int MAP_WIDTH = 32; // Ancho del mapa en tiles - static constexpr int MAP_HEIGHT = 16; // Alto del mapa en tiles - static constexpr float CONVEYOR_FRAME_DURATION = 0.05F; // Duración de cada frame de conveyor belt (3 frames @ 60fps) + static constexpr int TILE_SIZE = 8; // Ancho del tile en pixels + static constexpr int MAP_WIDTH = 32; // Ancho del mapa en tiles + static constexpr int MAP_HEIGHT = 16; // Alto del mapa en tiles // Objetos y punteros - std::vector> enemies_; // Listado con los enemigos de la habitación - std::vector> items_; // Listado con los items que hay en la habitación - std::shared_ptr surface_; // Textura con los graficos de la habitación - std::shared_ptr map_surface_; // Textura para dibujar el mapa de la habitación - std::shared_ptr data_; // Puntero a los datos del marcador + std::unique_ptr enemy_manager_; // Gestor de enemigos de la habitación + std::unique_ptr item_manager_; // Gestor de items de la habitación + std::unique_ptr collision_map_; // Mapa de colisiones de la habitación + std::unique_ptr tilemap_renderer_; // Renderizador del mapa de tiles + std::shared_ptr surface_; // Textura con los graficos de la habitación + std::shared_ptr data_; // Puntero a los datos del marcador // --- Variables --- std::string number_; // Numero de la habitación @@ -117,47 +118,14 @@ class Room { std::string lower_room_; // Identificador de la habitación que se encuentra abajp std::string left_room_; // Identificador de la habitación que se encuentra a la izquierda std::string right_room_; // Identificador de la habitación que se encuentra a la derecha - std::string tile_set_file_; // Imagen con los graficos para la habitación - std::string tile_map_file_; // Fichero con el mapa de indices de tile - std::vector tile_map_; // Indice de los tiles a dibujar en la habitación - int conveyor_belt_direction_{0}; // Sentido en el que arrastran las superficies automáticas de la habitación - std::vector bottom_floors_; // Lista con las superficies inferiores de la habitación - std::vector top_floors_; // Lista con las superficies superiores de la habitación - std::vector left_walls_; // Lista con las superficies laterales de la parte izquierda de la habitación - std::vector right_walls_; // Lista con las superficies laterales de la parte derecha de la habitación - std::vector left_slopes_; // Lista con todas las rampas que suben hacia la izquierda - std::vector right_slopes_; // Lista con todas las rampas que suben hacia la derecha - float time_accumulator_{0.0F}; // Acumulador de tiempo para animaciones (time-based) - bool is_paused_{false}; // Indica si el mapa esta en modo pausa - std::vector animated_tiles_; // Vector con los indices de tiles animados - std::vector conveyor_belt_floors_; // Lista con las superficies automaticas de la habitación - int tile_set_width_{0}; // Ancho del tileset en tiles + std::string tile_set_file_; // Imagen con los graficos para la habitación + std::string tile_map_file_; // Fichero con el mapa de indices de tile + std::vector tile_map_; // Indice de los tiles a dibujar en la habitación + int conveyor_belt_direction_{0}; // Sentido en el que arrastran las superficies automáticas de la habitación + bool is_paused_{false}; // Indica si el mapa esta en modo pausa + int tile_set_width_{0}; // Ancho del tileset en tiles // --- Funciones --- - void initializeRoom(const Data& room); // Inicializa los valores - void fillMapTexture(); // Pinta el mapa de la habitación en la textura - auto collectBottomTiles() -> std::vector; // Helper para recopilar tiles inferiores - auto collectTopTiles() -> std::vector; // Helper para recopilar tiles superiores - auto collectAnimatedTiles() -> std::vector; // Helper para recopilar tiles animados (para superficies automaticas) - static void buildHorizontalLines(const std::vector& tiles, std::vector& lines, bool is_bottom_surface); // Helper para construir lineas horizontales a partir de tiles consecutivos - void setBottomSurfaces(); // Calcula las superficies inferiores - void setTopSurfaces(); // Calcula las superficies superiores - void setLeftSurfaces(); // Calcula las superficies laterales izquierdas - void setRightSurfaces(); // Calcula las superficies laterales derechas - void setLeftSlopes(); // Encuentra todas las rampas que suben hacia la izquierda - void setRightSlopes(); // Encuentra todas las rampas que suben hacia la derecha - void setAutoSurfaces(); // Calcula las superficies automaticas - void setAnimatedTiles(); // Localiza todos los tiles animados de la habitación - void updateAnimatedTiles(); // Actualiza los tiles animados - void renderAnimatedTiles(); // Pinta los tiles animados en pantalla - auto getTile(int index) -> Tile; // Devuelve el tipo de tile que hay en ese indice - void openTheJail(); // Abre la jail para poder entrar - void initRoomSurfaces(); // Inicializa las superficies de colision - static auto setRoom(Data& room, const std::string& key, const std::string& value) -> bool; // Asigna variables a una estructura RoomData - static auto setEnemy(Enemy::Data& enemy, const std::string& key, const std::string& value) -> bool; // Asigna variables a una estructura EnemyData - static auto setItem(Item::Data& item, const std::string& key, const std::string& value) -> bool; // Asigna variables a una estructura ItemData - static auto parseKeyValue(const std::string& line) -> std::pair; - static void logUnknownParameter(const std::string& file_name, const std::string& key, bool verbose); - static auto loadEnemyFromFile(std::istream& file, const std::string& file_name, bool verbose) -> Enemy::Data; - static auto loadItemFromFile(std::istream& file, const std::string& file_name, bool verbose) -> Item::Data; + void initializeRoom(const Data& room); // Inicializa los valores + void openTheJail(); // Abre la jail para poder entrar }; \ No newline at end of file diff --git a/source/game/gameplay/room_loader.cpp b/source/game/gameplay/room_loader.cpp new file mode 100644 index 00000000..552a1041 --- /dev/null +++ b/source/game/gameplay/room_loader.cpp @@ -0,0 +1,298 @@ +#include "room_loader.hpp" + +#include // Para exception +#include // Para cout, cerr +#include // Para istringstream, stringstream + +#include "core/resources/resource_helper.hpp" // Para Resource::Helper +#include "utils/defines.hpp" // Para TILE_SIZE +#include "utils/utils.hpp" // Para stringToBool, stringToColor + +// Asigna variables a una estructura RoomData +auto RoomLoader::setRoom(Room::Data& room, const std::string& key, const std::string& value) -> bool { + // Indicador de éxito en la asignación + bool success = true; + + try { + if (key == "tileMapFile") { + room.tile_map_file = value; + } else if (key == "name") { + room.name = value; + } else if (key == "bgColor") { + room.bg_color = value; + } else if (key == "border") { + room.border_color = value; + } else if (key == "itemColor1") { + room.item_color1 = value; + } else if (key == "itemColor2") { + room.item_color2 = value; + } else if (key == "tileSetFile") { + room.tile_set_file = value; + } else if (key == "roomUp") { + room.upper_room = value; + } else if (key == "roomDown") { + room.lower_room = value; + } else if (key == "roomLeft") { + room.left_room = value; + } else if (key == "roomRight") { + room.right_room = value; + } else if (key == "autoSurface") { + room.conveyor_belt_direction = (value == "right") ? 1 : -1; + } else if (key.empty() || key.substr(0, 1) == "#") { + // No se realiza ninguna acción para estas claves + } else { + success = false; + } + } catch (const std::exception& e) { + std::cerr << "Error al asignar la clave " << key << " con valor " << value << ": " << e.what() << '\n'; + success = false; + } + + return success; +} + +// Asigna variables a una estructura EnemyData +auto RoomLoader::setEnemy(Enemy::Data& enemy, const std::string& key, const std::string& value) -> bool { + // Indicador de éxito en la asignación + bool success = true; + + try { + if (key == "animation") { + enemy.animation_path = value; + } else if (key == "x") { + enemy.x = std::stof(value) * TILE_SIZE; + } else if (key == "y") { + enemy.y = std::stof(value) * TILE_SIZE; + } else if (key == "vx") { + enemy.vx = std::stof(value); + } else if (key == "vy") { + enemy.vy = std::stof(value); + } else if (key == "x1") { + enemy.x1 = std::stoi(value) * TILE_SIZE; + } else if (key == "x2") { + enemy.x2 = std::stoi(value) * TILE_SIZE; + } else if (key == "y1") { + enemy.y1 = std::stoi(value) * TILE_SIZE; + } else if (key == "y2") { + enemy.y2 = std::stoi(value) * TILE_SIZE; + } else if (key == "flip") { + enemy.flip = stringToBool(value); + } else if (key == "mirror") { + enemy.mirror = stringToBool(value); + } else if (key == "color") { + enemy.color = value; + } else if (key == "frame") { + enemy.frame = std::stoi(value); + } else if (key == "[/enemy]" || key == "tileSetFile" || key.substr(0, 1) == "#") { + // No se realiza ninguna acción para estas claves + } else { + success = false; + } + } catch (const std::exception& e) { + std::cerr << "Error al asignar la clave " << key << " con valor " << value << ": " << e.what() << '\n'; + success = false; + } + + return success; +} + +// Asigna variables a una estructura ItemData +auto RoomLoader::setItem(Item::Data& item, const std::string& key, const std::string& value) -> bool { + // Indicador de éxito en la asignación + bool success = true; + + try { + if (key == "tileSetFile") { + item.tile_set_file = value; + } else if (key == "counter") { + item.counter = std::stoi(value); + } else if (key == "x") { + item.x = std::stof(value) * TILE_SIZE; + } else if (key == "y") { + item.y = std::stof(value) * TILE_SIZE; + } else if (key == "tile") { + item.tile = std::stof(value); + } else if (key == "[/item]") { + // No se realiza ninguna acción para esta clave + } else { + success = false; + } + } catch (const std::exception& e) { + std::cerr << "Error al asignar la clave " << key << " con valor " << value << ": " << e.what() << '\n'; + success = false; + } + + return success; +} + +// Carga las variables y texturas desde un fichero de mapa de tiles +auto RoomLoader::loadRoomTileFile(const std::string& file_path, bool verbose) -> std::vector { + std::vector tile_map_file; + const std::string FILENAME = file_path.substr(file_path.find_last_of("\\/") + 1); + + // Load file using ResourceHelper (supports both filesystem and pack) + auto file_data = Resource::Helper::loadFile(file_path); + + if (!file_data.empty()) { + // Convert bytes to string and parse + std::string content(file_data.begin(), file_data.end()); + std::istringstream stream(content); + std::string line; + + // Procesa el fichero linea a linea + while (std::getline(stream, line)) { // Lee el fichero linea a linea + if (line.find("data encoding") != std::string::npos) { + // Lee la primera linea + std::getline(stream, line); + // Trim line to handle Windows line endings + line.erase(0, line.find_first_not_of(" \t\r\n")); + line.erase(line.find_last_not_of(" \t\r\n") + 1); + while (line != "") { // Procesa lineas mientras haya + std::stringstream ss(line); + std::string tmp; + while (getline(ss, tmp, ',')) { + // Trim whitespace (including \r, \n, spaces, tabs) + tmp.erase(0, tmp.find_first_not_of(" \t\r\n")); + tmp.erase(tmp.find_last_not_of(" \t\r\n") + 1); + + // Skip empty strings (from trailing commas) + if (!tmp.empty()) { + tile_map_file.push_back(std::stoi(tmp) - 1); + } + } + + // Lee la siguiente linea + std::getline(stream, line); + // Trim line to handle Windows line endings + line.erase(0, line.find_first_not_of(" \t\r\n")); + line.erase(line.find_last_not_of(" \t\r\n") + 1); + } + } + } + + if (verbose) { + std::cout << "TileMap loaded: " << FILENAME.c_str() << '\n'; + } + } else { // El fichero no se puede abrir + if (verbose) { + std::cout << "Warning: Unable to open " << FILENAME.c_str() << " file" << '\n'; + } + } + + return tile_map_file; +} + +// Carga las variables desde un fichero de mapa +auto RoomLoader::loadRoomFile(const std::string& file_path, bool verbose) -> Room::Data { + Room::Data room; + room.item_color1 = "yellow"; + room.item_color2 = "magenta"; + room.conveyor_belt_direction = 1; + + const std::string FILE_NAME = file_path.substr(file_path.find_last_of("\\/") + 1); + room.number = FILE_NAME.substr(0, FILE_NAME.find_last_of('.')); + + // Load file using ResourceHelper (supports both filesystem and pack) + auto file_data = Resource::Helper::loadFile(file_path); + + if (!file_data.empty()) { + // Convert bytes to string and parse + std::string content(file_data.begin(), file_data.end()); + std::istringstream stream(content); + std::string line; + + // Procesa el fichero linea a linea + while (std::getline(stream, line)) { + // Remove Windows line ending if present + if (!line.empty() && line.back() == '\r') { + line.pop_back(); + } + // Si la linea contiene el texto [enemy] se realiza el proceso de carga de un enemigo + if (line == "[enemy]") { + room.enemies.push_back(loadEnemyFromFile(stream, FILE_NAME, verbose)); + } + // Si la linea contiene el texto [item] se realiza el proceso de carga de un item + else if (line == "[item]") { + room.items.push_back(loadItemFromFile(stream, FILE_NAME, verbose)); + } + // En caso contrario se parsea el fichero para buscar las variables y los valores + else { + auto [key, value] = parseKeyValue(line); + if (!setRoom(room, key, value)) { + logUnknownParameter(FILE_NAME, key, verbose); + } + } + } + + if (verbose) { + std::cout << "Room loaded: " << FILE_NAME.c_str() << '\n'; + } + } else { // El fichero no se puede abrir + std::cout << "Warning: Unable to open " << FILE_NAME.c_str() << " file" << '\n'; + } + + return room; +} + +// Parsea una línea en key y value separados por '=' +auto RoomLoader::parseKeyValue(const std::string& line) -> std::pair { + int pos = line.find('='); + std::string key = line.substr(0, pos); + std::string value = line.substr(pos + 1, line.length()); + return {key, value}; +} + +// Muestra un warning de parámetro desconocido +void RoomLoader::logUnknownParameter(const std::string& file_name, const std::string& key, bool verbose) { + if (verbose) { + std::cout << "Warning: file " << file_name.c_str() << "\n, unknown parameter \"" << key.c_str() << "\"" << '\n'; + } +} + +// Carga un bloque [enemy]...[/enemy] desde un archivo +auto RoomLoader::loadEnemyFromFile(std::istream& file, const std::string& file_name, bool verbose) -> Enemy::Data { + Enemy::Data enemy; + enemy.flip = false; + enemy.mirror = false; + enemy.frame = -1; + + std::string line; + do { + std::getline(file, line); + // Remove Windows line ending if present + if (!line.empty() && line.back() == '\r') { + line.pop_back(); + } + auto [key, value] = parseKeyValue(line); + + if (!setEnemy(enemy, key, value)) { + logUnknownParameter(file_name, key, verbose); + } + } while (line != "[/enemy]"); + + return enemy; +} + +// Carga un bloque [item]...[/item] desde un archivo +auto RoomLoader::loadItemFromFile(std::istream& file, const std::string& file_name, bool verbose) -> Item::Data { + Item::Data item; + item.counter = 0; + item.color1 = stringToColor("yellow"); + item.color2 = stringToColor("magenta"); + + std::string line; + do { + std::getline(file, line); + // Remove Windows line ending if present + if (!line.empty() && line.back() == '\r') { + line.pop_back(); + } + auto [key, value] = parseKeyValue(line); + + if (!setItem(item, key, value)) { + logUnknownParameter(file_name, key, verbose); + } + } while (line != "[/item]"); + + return item; +} diff --git a/source/game/gameplay/room_loader.hpp b/source/game/gameplay/room_loader.hpp new file mode 100644 index 00000000..aead9f11 --- /dev/null +++ b/source/game/gameplay/room_loader.hpp @@ -0,0 +1,116 @@ +#pragma once + +#include // Para istream +#include // Para string +#include // Para pair +#include // Para vector + +#include "game/entities/enemy.hpp" // Para Enemy::Data +#include "game/entities/item.hpp" // Para Item::Data +#include "game/gameplay/room.hpp" // Para Room::Data + +/** + * @brief Cargador de archivos de habitaciones + * + * Responsabilidades: + * - Cargar archivos de room (.room) + * - Cargar archivos de tilemap (.tmx) + * - Parsear datos de room, enemy e item + * - Validar y convertir valores de configuración + * + * Esta clase contiene solo métodos estáticos y no debe instanciarse. + */ +class RoomLoader { + public: + // Constructor eliminado para prevenir instanciación + RoomLoader() = delete; + ~RoomLoader() = delete; + RoomLoader(const RoomLoader&) = delete; + auto operator=(const RoomLoader&) -> RoomLoader& = delete; + RoomLoader(RoomLoader&&) = delete; + auto operator=(RoomLoader&&) -> RoomLoader& = delete; + + /** + * @brief Carga un archivo de room (.room) + * @param file_path Ruta al archivo de room + * @param verbose Si true, muestra información de debug + * @return Room::Data con todos los datos de la habitación + * + * Parsea un archivo .room que contiene: + * - Variables de configuración (name, colors, borders, etc.) + * - Bloques [enemy]...[/enemy] con datos de enemigos + * - Bloques [item]...[/item] con datos de items + */ + static auto loadRoomFile(const std::string& file_path, bool verbose = false) -> Room::Data; + + /** + * @brief Carga un archivo de tilemap (.tmx) + * @param file_path Ruta al archivo de tilemap + * @param verbose Si true, muestra información de debug + * @return Vector de índices de tiles + * + * Parsea un archivo .tmx en formato CSV generado por Tiled + */ + static auto loadRoomTileFile(const std::string& file_path, bool verbose = false) -> std::vector; + + private: + /** + * @brief Asigna valores a una estructura Room::Data + * @param room Estructura a modificar + * @param key Nombre de la variable + * @param value Valor a asignar + * @return true si la variable fue reconocida y asignada, false si no + */ + static auto setRoom(Room::Data& room, const std::string& key, const std::string& value) -> bool; + + /** + * @brief Asigna valores a una estructura Enemy::Data + * @param enemy Estructura a modificar + * @param key Nombre de la variable + * @param value Valor a asignar + * @return true si la variable fue reconocida y asignada, false si no + */ + static auto setEnemy(Enemy::Data& enemy, const std::string& key, const std::string& value) -> bool; + + /** + * @brief Asigna valores a una estructura Item::Data + * @param item Estructura a modificar + * @param key Nombre de la variable + * @param value Valor a asignar + * @return true si la variable fue reconocida y asignada, false si no + */ + static auto setItem(Item::Data& item, const std::string& key, const std::string& value) -> bool; + + /** + * @brief Parsea una línea en formato "key=value" + * @param line Línea a parsear + * @return Par {key, value}. Si no hay '=', devuelve {"", ""} + */ + static auto parseKeyValue(const std::string& line) -> std::pair; + + /** + * @brief Registra un parámetro desconocido en la consola + * @param file_name Nombre del archivo siendo parseado + * @param key Nombre del parámetro desconocido + * @param verbose Si true, muestra el mensaje + */ + static void logUnknownParameter(const std::string& file_name, const std::string& key, bool verbose); + + /** + * @brief Carga un bloque [enemy]...[/enemy] desde un stream + * @param file Stream del archivo + * @param file_name Nombre del archivo (para debug) + * @param verbose Si true, muestra información de debug + * @return Enemy::Data con los datos del enemigo + */ + static auto loadEnemyFromFile(std::istream& file, const std::string& file_name, bool verbose) -> Enemy::Data; + + /** + * @brief Carga un bloque [item]...[/item] desde un stream + * @param file Stream del archivo + * @param file_name Nombre del archivo (para debug) + * @param verbose Si true, muestra información de debug + * @return Item::Data con los datos del item + */ + static auto loadItemFromFile(std::istream& file, const std::string& file_name, bool verbose) -> Item::Data; +}; diff --git a/source/game/gameplay/tilemap_renderer.cpp b/source/game/gameplay/tilemap_renderer.cpp new file mode 100644 index 00000000..5952ae64 --- /dev/null +++ b/source/game/gameplay/tilemap_renderer.cpp @@ -0,0 +1,193 @@ +#include "tilemap_renderer.hpp" + +#include "core/rendering/screen.hpp" +#include "core/rendering/surface.hpp" +#include "core/rendering/surface_sprite.hpp" +#include "core/system/debug.hpp" +#include "game/gameplay/collision_map.hpp" +#include "utils/utils.hpp" + +// Constructor +TilemapRenderer::TilemapRenderer(std::vector tile_map, int tile_set_width, + std::shared_ptr tileset_surface, std::string bg_color, + int conveyor_belt_direction) + : tile_map_(std::move(tile_map)), + tile_set_width_(tile_set_width), + tileset_surface_(std::move(tileset_surface)), + bg_color_(std::move(bg_color)), + conveyor_belt_direction_(conveyor_belt_direction) { + // Crear la surface del mapa + map_surface_ = std::make_shared(PLAY_AREA_WIDTH, PLAY_AREA_HEIGHT); +} + +// Inicializa el renderizador +void TilemapRenderer::initialize(const CollisionMap* collision_map) { + setAnimatedTiles(collision_map); + fillMapTexture(collision_map); +} + +// Actualiza las animaciones de tiles +void TilemapRenderer::update(float delta_time) { + if (is_paused_) { + return; + } + + // Actualiza el acumulador de tiempo + time_accumulator_ += delta_time; + + // Actualiza los tiles animados + updateAnimatedTiles(); +} + +// Renderiza el mapa completo en pantalla +void TilemapRenderer::render() { + // Dibuja la textura con el mapa en pantalla + SDL_FRect dest = {0, 0, PLAY_AREA_WIDTH, PLAY_AREA_HEIGHT}; + map_surface_->render(nullptr, &dest); + +// Dibuja los tiles animados +#ifdef _DEBUG + if (!Debug::get()->getEnabled()) { + renderAnimatedTiles(); + } +#else + renderAnimatedTiles(); +#endif +} + +// Pinta el mapa estático y debug lines +void TilemapRenderer::fillMapTexture(const CollisionMap* collision_map) { + const Uint8 COLOR = stringToColor(bg_color_); + auto previous_renderer = Screen::get()->getRendererSurface(); + Screen::get()->setRendererSurface(map_surface_); + map_surface_->clear(COLOR); + + // Los tileSetFiles son de 20x20 tiles. El primer tile es el 0. Cuentan hacia la derecha y hacia abajo + + SDL_FRect clip = {0, 0, TILE_SIZE, TILE_SIZE}; + for (int y = 0; y < MAP_HEIGHT; ++y) { + for (int x = 0; x < MAP_WIDTH; ++x) { + // Tiled pone los tiles vacios del mapa como cero y empieza a contar de 1 a n. + // Al cargar el mapa en memoria, se resta uno, por tanto los tiles vacios son -1 + // Tampoco hay que dibujar los tiles animados que estan en la fila 19 (indices) + const int INDEX = (y * MAP_WIDTH) + x; + const bool A = (tile_map_[INDEX] >= 18 * tile_set_width_) && (tile_map_[INDEX] < 19 * tile_set_width_); + const bool B = tile_map_[INDEX] > -1; + + if (B && !A) { + clip.x = (tile_map_[INDEX] % tile_set_width_) * TILE_SIZE; + clip.y = (tile_map_[INDEX] / tile_set_width_) * TILE_SIZE; + tileset_surface_->render(x * TILE_SIZE, y * TILE_SIZE, &clip); + } + } + } + +#ifdef _DEBUG + if (Debug::get()->getEnabled()) { + auto surface = Screen::get()->getRendererSurface(); + + // BottomSurfaces + { + for (auto l : collision_map->getBottomFloors()) { + surface->drawLine(l.x1, l.y, l.x2, l.y, static_cast(PaletteColor::BLUE)); + } + } + + // TopSurfaces + { + for (auto l : collision_map->getTopFloors()) { + surface->drawLine(l.x1, l.y, l.x2, l.y, static_cast(PaletteColor::RED)); + } + } + + // LeftSurfaces + { + for (auto l : collision_map->getLeftWalls()) { + surface->drawLine(l.x, l.y1, l.x, l.y2, static_cast(PaletteColor::GREEN)); + } + } + + // RightSurfaces + { + for (auto l : collision_map->getRightWalls()) { + surface->drawLine(l.x, l.y1, l.x, l.y2, static_cast(PaletteColor::MAGENTA)); + } + } + + // LeftSlopes + { + for (auto l : collision_map->getLeftSlopes()) { + surface->drawLine(l.x1, l.y1, l.x2, l.y2, static_cast(PaletteColor::CYAN)); + } + } + + // RightSlopes + { + for (auto l : collision_map->getRightSlopes()) { + surface->drawLine(l.x1, l.y1, l.x2, l.y2, static_cast(PaletteColor::YELLOW)); + } + } + + // AutoSurfaces + { + for (auto l : collision_map->getConveyorBeltFloors()) { + surface->drawLine(l.x1, l.y, l.x2, l.y, static_cast(PaletteColor::WHITE)); + } + } + } + +#endif // _DEBUG + Screen::get()->setRendererSurface(previous_renderer); +} + +// Localiza todos los tiles animados +void TilemapRenderer::setAnimatedTiles(const CollisionMap* collision_map) { + // Recorre la habitación entera por filas buscando tiles de tipo t_animated + for (int i = 0; i < (int)tile_map_.size(); ++i) { + const auto TILE_TYPE = collision_map->getTile(i); + if (TILE_TYPE == CollisionMap::Tile::ANIMATED) { + // La i es la ubicación + const int X = (i % MAP_WIDTH) * TILE_SIZE; + const int Y = (i / MAP_WIDTH) * TILE_SIZE; + + // TileMap[i] es el tile a poner + const int XC = (tile_map_[i] % tile_set_width_) * TILE_SIZE; + const int YC = (tile_map_[i] / tile_set_width_) * TILE_SIZE; + + AnimatedTile at; + at.sprite = std::make_shared(tileset_surface_, X, Y, 8, 8); + at.sprite->setClip(XC, YC, 8, 8); + at.x_orig = XC; + animated_tiles_.push_back(at); + } + } +} + +// Actualiza tiles animados +void TilemapRenderer::updateAnimatedTiles() { + const int NUM_FRAMES = 4; + + // Calcular frame actual basado en tiempo + const int CURRENT_FRAME = static_cast(time_accumulator_ / CONVEYOR_FRAME_DURATION) % NUM_FRAMES; + + // Calcular offset basado en dirección + int offset = 0; + if (conveyor_belt_direction_ == -1) { + offset = CURRENT_FRAME * TILE_SIZE; + } else { + offset = (NUM_FRAMES - 1 - CURRENT_FRAME) * TILE_SIZE; + } + + for (auto& a : animated_tiles_) { + SDL_FRect rect = a.sprite->getClip(); + rect.x = a.x_orig + offset; + a.sprite->setClip(rect); + } +} + +// Renderiza tiles animados +void TilemapRenderer::renderAnimatedTiles() { + for (const auto& a : animated_tiles_) { + a.sprite->render(); + } +} diff --git a/source/game/gameplay/tilemap_renderer.hpp b/source/game/gameplay/tilemap_renderer.hpp new file mode 100644 index 00000000..99741cc6 --- /dev/null +++ b/source/game/gameplay/tilemap_renderer.hpp @@ -0,0 +1,107 @@ +#pragma once + +#include + +#include // Para shared_ptr +#include // Para string +#include // Para vector + +class Surface; +class SurfaceSprite; +class CollisionMap; + +/** + * @brief Renderizador de tilemap de una habitación + * + * Responsabilidades: + * - Renderizar el mapa de tiles estático + * - Gestionar tiles animados (conveyor belts) + * - Actualizar animaciones basadas en tiempo + * - Renderizar debug visualization (en modo DEBUG) + */ +class TilemapRenderer { + public: + /** + * @brief Constructor + * @param tile_map Vector con índices de tiles de la habitación + * @param tile_set_width Ancho del tileset en tiles + * @param tileset_surface Surface con los gráficos del tileset + * @param bg_color Color de fondo de la habitación (como string) + * @param conveyor_belt_direction Dirección de las cintas transportadoras (-1, 0, +1) + */ + TilemapRenderer(std::vector tile_map, int tile_set_width, std::shared_ptr tileset_surface, + std::string bg_color, int conveyor_belt_direction); + ~TilemapRenderer() = default; + + // Prohibir copia y movimiento + TilemapRenderer(const TilemapRenderer&) = delete; + auto operator=(const TilemapRenderer&) -> TilemapRenderer& = delete; + TilemapRenderer(TilemapRenderer&&) = delete; + auto operator=(TilemapRenderer&&) -> TilemapRenderer& = delete; + + /** + * @brief Inicializa el renderizador + * @param collision_map Mapa de colisiones para determinar tiles animados + * + * Crea la textura del mapa, pinta los tiles estáticos, y localiza tiles animados + */ + void initialize(const CollisionMap* collision_map); + + /** + * @brief Actualiza las animaciones de tiles + * @param delta_time Tiempo transcurrido desde el último frame (segundos) + */ + void update(float delta_time); + + /** + * @brief Renderiza el mapa completo en pantalla + * + * Dibuja la textura del mapa y los tiles animados + */ + void render(); + + /** + * @brief Activa/desactiva modo pausa + * @param paused true para pausar, false para reanudar + * + * Nota: Actualmente no afecta al renderizado, pero mantiene consistencia con Room + */ + void setPaused(bool paused) { is_paused_ = paused; } + + // Getter para la surface del mapa (usado por Room para acceso directo si es necesario) + [[nodiscard]] auto getMapSurface() const -> std::shared_ptr { return map_surface_; } + + private: + // Estructura para tiles animados (conveyor belts) + struct AnimatedTile { + std::shared_ptr sprite{nullptr}; // SurfaceSprite para dibujar el tile + int x_orig{0}; // Posición X del primer tile de la animación en tilesheet + }; + + // === Constantes === + static constexpr int TILE_SIZE = 8; // Ancho del tile en pixels + static constexpr int MAP_WIDTH = 32; // Ancho del mapa en tiles + static constexpr int MAP_HEIGHT = 16; // Alto del mapa en tiles + static constexpr int PLAY_AREA_WIDTH = 256; // Ancho del área de juego en pixels + static constexpr int PLAY_AREA_HEIGHT = 128; // Alto del área de juego en pixels + static constexpr float CONVEYOR_FRAME_DURATION = 0.05F; // Duración de cada frame (3 frames @ 60fps) + + // === Datos de la habitación === + std::vector tile_map_; // Índices de tiles de la habitación + int tile_set_width_; // Ancho del tileset en tiles + std::shared_ptr tileset_surface_; // Gráficos del tileset + std::string bg_color_; // Color de fondo + int conveyor_belt_direction_; // Dirección de conveyor belts + + // === Renderizado === + std::shared_ptr map_surface_; // Textura para el mapa de la habitación + std::vector animated_tiles_; // Tiles animados (conveyor belts) + float time_accumulator_{0.0F}; // Acumulador de tiempo para animaciones + bool is_paused_{false}; // Indica si está en modo pausa + + // === Métodos privados === + void fillMapTexture(const CollisionMap* collision_map); // Pinta el mapa estático y debug lines + void setAnimatedTiles(const CollisionMap* collision_map); // Localiza todos los tiles animados + void updateAnimatedTiles(); // Actualiza tiles animados + void renderAnimatedTiles(); // Renderiza tiles animados +}; diff --git a/tools/linter/cppcheck-result-warning-style-performance.txt b/tools/linter/cppcheck-result-warning-style-performance.txt new file mode 100644 index 00000000..a31d8a04 --- /dev/null +++ b/tools/linter/cppcheck-result-warning-style-performance.txt @@ -0,0 +1,3 @@ +/home/sergio/gitea/jaildoctors_dilemma/source/utils/utils.hpp:8:1: error: syntax error [syntaxError] +enum class PaletteColor : Uint8 { +^