#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/rendering/screen.hpp" // Para Screen #include "core/rendering/surface.hpp" // Para Surface #include "core/rendering/surface_sprite.hpp" // Para SSprite #include "core/resources/resource.hpp" // Para Resource #include "core/system/debug.hpp" // Para Debug #include "external/jail_audio.h" // Para JA_PlaySound #include "game/gameplay/item_tracker.hpp" // Para ItemTracker #include "game/gameplay/scoreboard.hpp" // Para ScoreboardData #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::get()->getRoom(room_path); initializeRoom(*room); // Abre la Jail si se da el caso openTheJail(); // Inicializa las superficies de colision initRoomSurfaces(); // Busca los tiles animados setAnimatedTiles(); // Crea la textura para el mapa de tiles de la habitación map_surface_ = std::make_shared(PLAY_AREA_WIDTH, PLAY_AREA_HEIGHT); // Pinta el mapa de la habitación en la textura fillMapTexture(); // Establece el color del borde Screen::get()->setBorderColor(stringToColor(border_color_)); } 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::get()->getTileMap(room.tile_map_file); surface_ = Resource::get()->getSurface(room.tile_set_file); tile_set_width_ = surface_->getWidth() / TILE_SIZE; is_paused_ = false; counter_ = 0; // Crear los enemigos for (const auto& enemy_data : room.enemies) { enemies_.emplace_back(std::make_shared(enemy_data)); } // Crear los items 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 items_.emplace_back(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 contador (mantenido para compatibilidad temporalmente) counter_++; // 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); JA_PlaySound(Resource::get()->getSound("item.wav")); 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; int offset = 0; if (conveyor_belt_direction_ == -1) { offset = ((counter_ / 3) % NUM_FRAMES * TILE_SIZE); } else { offset = ((NUM_FRAMES - 1 - ((counter_ / 3) % NUM_FRAMES)) * 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 -1; } // Comprueba las colisiones auto Room::checkLeftSurfaces(SDL_FRect* rect) -> int { for (const auto& s : left_walls_) { if (checkCollision(s, *rect)) { return s.x; } } return -1; } // Comprueba las colisiones auto Room::checkTopSurfaces(SDL_FRect* rect) -> int { for (const auto& s : top_floors_) { if (checkCollision(s, *rect)) { return s.y; } } return -1; } // Comprueba las colisiones auto Room::checkBottomSurfaces(SDL_FRect* rect) -> int { for (const auto& s : bottom_floors_) { if (checkCollision(s, *rect)) { return s.y; } } return -1; } // 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 -1; } // 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::checkAutoSurfaces(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 -1; } // 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 -1; } // 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 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(); } // 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; } } } // Inicializa las superficies de colision void Room::initRoomSurfaces() { setBottomSurfaces(); setTopSurfaces(); setLeftSurfaces(); setRightSurfaces(); setLeftSlopes(); setRightSlopes(); setAutoSurfaces(); } // 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 == "tileSetFile") { enemy->surface_path = value; } else if (key == "animation") { enemy->animation_path = value; } else if (key == "width") { enemy->w = std::stoi(value); } else if (key == "height") { enemy->h = std::stoi(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); std::ifstream file(file_path); // El fichero se puede abrir if (file.good()) { std::string line; // Procesa el fichero linea a linea while (std::getline(file, line)) { // Lee el fichero linea a linea if (line.find("data encoding") != std::string::npos) { // Lee la primera linea std::getline(file, line); while (line != "") { // Procesa lineas mientras haya std::stringstream ss(line); std::string tmp; while (getline(ss, tmp, ',')) { tile_map_file.push_back(std::stoi(tmp) - 1); } // Lee la siguiente linea std::getline(file, line); } } } // Cierra el fichero if (verbose) { std::cout << "TileMap loaded: " << FILENAME.c_str() << '\n'; } file.close(); } 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('.')); std::ifstream file(file_path); // El fichero se puede abrir if (file.good()) { std::string line; // Procesa el fichero linea a linea while (std::getline(file, line)) { // Si la linea contiene el texto [enemy] se realiza el proceso de carga de un enemigo if (line == "[enemy]") { room.enemies.push_back(loadEnemyFromFile(file, 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(file, 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); } } } // Cierra el fichero if (verbose) { std::cout << "Room loaded: " << FILE_NAME.c_str() << '\n'; } file.close(); } // El fichero no se puede abrir else { 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::ifstream& 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); 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::ifstream& 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); auto [key, value] = parseKeyValue(line); if (!setItem(&item, key, value)) { logUnknownParameter(file_name, key, verbose); } } while (line != "[/item]"); return item; }