Files
jaildoctors_dilemma/source/game/gameplay/room.cpp
Sergio c5ad8041e8 Fase 2: Refactorización de Room - Extracción del sistema de colisiones
## Cambios principales

### Nuevo componente: CollisionMap
- **collision_map.hpp/cpp**: Nueva clase que encapsula toda la lógica de detección de colisiones
- Responsabilidades extraídas de Room:
  - Determinación de tipos de tile (getTile)
  - Generación de geometría de colisión (superficies, rampas, conveyor belts)
  - Queries de colisión para Player y entidades
  - 14 métodos de detección de colisión
  - Getters para visualización debug

### Modificaciones en Room
- **room.hpp**:
  - Añadido CollisionMap como miembro (unique_ptr)
  - Removidos 7 vectores de geometría de colisión
  - Removidos 13 métodos privados de generación de geometría
  - Añadido getTile(int index) para soporte de animated tiles
  - Añadido destructor explícito (necesario para unique_ptr con forward declaration)

- **room.cpp**:
  - Constructor: Inicializa CollisionMap con tile_map, tile_set_width, conveyor_belt_direction
  - Delegación: Todos los métodos de colisión ahora llaman a collision_map_
  - Restaurados métodos de animated tiles (openTheJail, setAnimatedTiles, updateAnimatedTiles, renderAnimatedTiles)
  - Actualizado openTheJail() para usar enemy_manager_ en lugar de enemies_
  - Debug visualization actualizada para usar getters de CollisionMap

### Build system
- **CMakeLists.txt**: Añadido collision_map.cpp a las fuentes del proyecto

## Métricas
- **Código eliminado de Room**: ~465 líneas de lógica de colisión
- **Nuevo CollisionMap**: 487 líneas (collision_map.cpp)
- **Reducción neta en room.cpp**: Significativa mejora en cohesión

## Verificación
-  Compilación exitosa sin errores
-  Juego inicia y carga recursos correctamente
-  clang-tidy: Sin warnings en código de usuario
-  cppcheck: Sin issues reales (solo false positive en utils.hpp)

## Próximos pasos
- Fase 3: Extracción del sistema de renderizado de tilemap
- Fase 4: Extracción del parseo de archivos
- Fase 5: Limpieza final y reducción de Room a coordinador ligero

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 08:51:01 +01:00

701 lines
24 KiB
C++

#include "game/gameplay/room.hpp"
#include <algorithm> // Para std::ranges::any_of
#include <exception> // Para exception
#include <fstream> // Para basic_ostream, operator<<, basic_istream
#include <iostream> // Para cout, cerr
#include <sstream> // Para basic_stringstream
#include <utility>
#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/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<Scoreboard::Data> 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<EnemyManager>();
item_manager_ = std::make_unique<ItemManager>(room->name, data_);
initializeRoom(*room);
// Crea el mapa de colisiones (necesita tile_map_, tile_set_width_, conveyor_belt_direction_)
collision_map_ = std::make_unique<CollisionMap>(tile_map_, tile_set_width_, conveyor_belt_direction_);
openTheJail(); // Abre la Jail si se da el caso
setAnimatedTiles(); // Busca los tiles animados
map_surface_ = std::make_shared<Surface>(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
}
// 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;
time_accumulator_ = 0.0F;
// Crear los enemigos usando el manager
for (const auto& enemy_data : room.enemies) {
enemy_manager_->addEnemy(std::make_shared<Enemy>(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>(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;
}
}
}
// 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 : collision_map_->getBottomFloors()) {
surface->drawLine(l.x1, l.y, l.x2, l.y, static_cast<Uint8>(PaletteColor::BLUE));
}
}
// TopSurfaces
{
for (auto l : collision_map_->getTopFloors()) {
surface->drawLine(l.x1, l.y, l.x2, l.y, static_cast<Uint8>(PaletteColor::RED));
}
}
// LeftSurfaces
{
for (auto l : collision_map_->getLeftWalls()) {
surface->drawLine(l.x, l.y1, l.x, l.y2, static_cast<Uint8>(PaletteColor::GREEN));
}
}
// RightSurfaces
{
for (auto l : collision_map_->getRightWalls()) {
surface->drawLine(l.x, l.y1, l.x, l.y2, static_cast<Uint8>(PaletteColor::MAGENTA));
}
}
// LeftSlopes
{
for (auto l : collision_map_->getLeftSlopes()) {
surface->drawLine(l.x1, l.y1, l.x2, l.y2, static_cast<Uint8>(PaletteColor::CYAN));
}
}
// RightSlopes
{
for (auto l : collision_map_->getRightSlopes()) {
surface->drawLine(l.x1, l.y1, l.x2, l.y2, static_cast<Uint8>(PaletteColor::YELLOW));
}
}
// AutoSurfaces
{
for (auto l : collision_map_->getConveyorBeltFloors()) {
surface->drawLine(l.x1, l.y, l.x2, l.y, static_cast<Uint8>(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() {
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 el acumulador de tiempo
time_accumulator_ += delta_time;
// Actualiza los tiles animados
updateAnimatedTiles();
// 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;
item_manager_->setPaused(value);
}
// 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<SurfaceSprite>(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<int>(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();
}
}
// 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<Tile>(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<Tile>(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<CollisionMap::Tile>(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<int> {
std::vector<int> 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 != "</data>") { // 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<std::string, std::string> {
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;
}