1097 lines
36 KiB
C++
1097 lines
36 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.hpp" // Para Resource
|
|
#include "core/resources/resource_helper.hpp" // Para ResourceHelper
|
|
#include "core/system/debug.hpp" // Para Debug
|
|
#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<ScoreboardData> data)
|
|
: data_(std::move(std::move(data))) {
|
|
auto room = Resource::get()->getRoom(room_path);
|
|
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<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
|
|
}
|
|
|
|
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;
|
|
time_accumulator_ = 0.0F;
|
|
|
|
// Crear los enemigos
|
|
for (const auto& enemy_data : room.enemies) {
|
|
enemies_.emplace_back(std::make_shared<Enemy>(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>(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<Uint8>(PaletteColor::BLUE));
|
|
}
|
|
}
|
|
|
|
// TopSurfaces
|
|
{
|
|
for (auto l : top_floors_) {
|
|
surface->drawLine(l.x1, l.y, l.x2, l.y, static_cast<Uint8>(PaletteColor::RED));
|
|
}
|
|
}
|
|
|
|
// LeftSurfaces
|
|
{
|
|
for (auto l : left_walls_) {
|
|
surface->drawLine(l.x, l.y1, l.x, l.y2, static_cast<Uint8>(PaletteColor::GREEN));
|
|
}
|
|
}
|
|
|
|
// RightSurfaces
|
|
{
|
|
for (auto l : right_walls_) {
|
|
surface->drawLine(l.x, l.y1, l.x, l.y2, static_cast<Uint8>(PaletteColor::MAGENTA));
|
|
}
|
|
}
|
|
|
|
// LeftSlopes
|
|
{
|
|
for (auto l : left_slopes_) {
|
|
surface->drawLine(l.x1, l.y1, l.x2, l.y2, static_cast<Uint8>(PaletteColor::CYAN));
|
|
}
|
|
}
|
|
|
|
// RightSlopes
|
|
{
|
|
for (auto l : right_slopes_) {
|
|
surface->drawLine(l.x1, l.y1, l.x2, l.y2, static_cast<Uint8>(PaletteColor::YELLOW));
|
|
}
|
|
}
|
|
|
|
// AutoSurfaces
|
|
{
|
|
for (auto l : conveyor_belt_floors_) {
|
|
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() {
|
|
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<int>(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<int>(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<int> {
|
|
std::vector<int> 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<int> {
|
|
std::vector<int> 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<int>& tiles, std::vector<LineHorizontal>& 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<int> tile = collectBottomTiles();
|
|
buildHorizontalLines(tile, bottom_floors_, true);
|
|
}
|
|
|
|
// Calcula las superficies superiores
|
|
void Room::setTopSurfaces() {
|
|
std::vector<int> tile = collectTopTiles();
|
|
buildHorizontalLines(tile, top_floors_, false);
|
|
}
|
|
|
|
// Calcula las superficies laterales izquierdas
|
|
void Room::setLeftSurfaces() {
|
|
std::vector<int> 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<int> 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<int> 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<int> 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<int> {
|
|
std::vector<int> 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<int> 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<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();
|
|
}
|
|
}
|
|
|
|
// 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
|
|
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 == "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 = Jdd::ResourceHelper::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 = Jdd::ResourceHelper::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;
|
|
} |