#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 "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/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 // Constructor Room::Room(const std::string& room_path, std::shared_ptr data) : data_(std::move(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); // 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; name_ = room.name; bg_color_ = room.bg_color; border_color_ = room.border_color; item_color1_ = room.item_color1.empty() ? "yellow" : room.item_color1; item_color2_ = room.item_color2.empty() ? "magenta" : room.item_color2; upper_room_ = room.upper_room; lower_room_ = room.lower_room; left_room_ = room.left_room; right_room_ = room.right_room; tile_set_file_ = room.tile_set_file; tile_map_file_ = room.tile_map_file; conveyor_belt_direction_ = room.conveyor_belt_direction; tile_map_ = Resource::Cache::get()->getTileMap(room.tile_map_file); surface_ = Resource::Cache::get()->getSurface(room.tile_set_file); tile_set_width_ = surface_->getWidth() / TILE_SIZE; is_paused_ = false; // Crear los enemigos usando el manager for (const auto& enemy_data : room.enemies) { enemy_manager_->addEnemy(std::make_shared(enemy_data)); } // Crear los items usando el manager for (const auto& item : room.items) { const SDL_FPoint ITEM_POS = {item.x, item.y}; if (!ItemTracker::get()->hasBeenPicked(room.name, ITEM_POS)) { // Crear una copia local de los datos del item Item::Data item_copy = item; item_copy.color1 = stringToColor(item_color1_); item_copy.color2 = stringToColor(item_color2_); // Crear el objeto Item usando la copia modificada item_manager_->addItem(std::make_shared(item_copy)); } } } // 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 (!enemy_manager_->isEmpty()) { enemy_manager_->removeLastEnemy(); } // Abre las puertas constexpr int TILE_A = 16 + (13 * 32); constexpr int TILE_B = 16 + (14 * 32); if (TILE_A < tile_map_.size()) { tile_map_[TILE_A] = -1; } if (TILE_B < tile_map_.size()) { tile_map_[TILE_B] = -1; } } } // Dibuja el mapa en pantalla void Room::renderMap() { tilemap_renderer_->render(); } // Dibuja los enemigos en pantalla void Room::renderEnemies() { enemy_manager_->render(); } // 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; } // 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); } // Pone el mapa en modo pausa void Room::setPaused(bool value) { is_paused_ = value; tilemap_renderer_->setPaused(value); item_manager_->setPaused(value); } // 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 { // Delega a CollisionMap y convierte el resultado const auto collision_tile = collision_map_->getTile(point); return static_cast(collision_tile); } // 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); } // 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); } // 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; 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 Room::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 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; } // 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; } // Carga las variables desde un fichero de mapa 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; } // 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}; } // 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; }