Files
projecte_2026/source/game/gameplay/solid_actor_manager.cpp

198 lines
7.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "solid_actor_manager.hpp"
#include <algorithm> // Para std::ranges::find, std::max, std::min
#include "game/entities/solid_actor.hpp" // Para SolidActor
#include "utils/defines.hpp" // Para Collision::NONE, PlayArea::WIDTH/HEIGHT
// Registro de actores
void SolidActorManager::registerActor(SolidActor* actor) {
if (actor == nullptr) { return; }
actors_.push_back(actor);
}
// Desregistro (no borra el objeto — la entidad concreta vive en su propio manager)
void SolidActorManager::unregisterActor(SolidActor* actor) {
auto it = std::ranges::find(actors_, actor);
if (it != actors_.end()) {
actors_.erase(it);
}
}
void SolidActorManager::clear() {
actors_.clear();
}
void SolidActorManager::setAdjacent(const AdjacentActors& adjacent) {
adjacent_ = adjacent;
}
void SolidActorManager::clearAdjacent() {
adjacent_ = {};
}
// Itera sobre todos los actores (local + 4 vecinos cardinales), aplicando
// el offset pertinente al AABB vecino para traerlo al sistema de coordenadas
// de la room actual. Las rooms vecinas tienen sus actores en coordenadas
// locales propias; para verlos desde aquí, se traslada:
// - left: +WIDTH en X (porque en la room actual están a la izquierda del
// borde 0 de coordenadas; trasladándolos por -WIDTH los colocamos "a la
// izquierda" del Player, con x negativas).
// - right: -WIDTH en X (análogo).
// - upper: +HEIGHT en Y.
// - lower: -HEIGHT en Y.
//
// NOTA: el Player siempre está en [0, WIDTH) × [0, HEIGHT) de la room actual.
// Los actores vecinos se muestran con coordenadas fuera de ese rango, y los
// sweeps solo los ven cuando el Player está lo bastante cerca del borde como
// para solapar con ellos.
template <typename Fn>
void SolidActorManager::forEachActor(Fn&& fn) const {
// Locales
for (auto* a : actors_) {
fn(a, a->getAABB());
}
// Adyacente izquierda: sus actores están en [0, WIDTH) de SU room;
// desde la room actual, esos actores están en [-WIDTH, 0). Offset: -WIDTH en X.
if (adjacent_.left != nullptr) {
for (auto* a : adjacent_.left->actors_) {
SDL_FRect r = a->getAABB();
r.x -= PlayArea::WIDTH;
fn(a, r);
}
}
// Adyacente derecha: offset +WIDTH en X.
if (adjacent_.right != nullptr) {
for (auto* a : adjacent_.right->actors_) {
SDL_FRect r = a->getAABB();
r.x += PlayArea::WIDTH;
fn(a, r);
}
}
// Adyacente superior: offset -HEIGHT en Y (arriba tiene y negativa).
if (adjacent_.upper != nullptr) {
for (auto* a : adjacent_.upper->actors_) {
SDL_FRect r = a->getAABB();
r.y -= PlayArea::HEIGHT;
fn(a, r);
}
}
// Adyacente inferior: offset +HEIGHT en Y.
if (adjacent_.lower != nullptr) {
for (auto* a : adjacent_.lower->actors_) {
SDL_FRect r = a->getAABB();
r.y += PlayArea::HEIGHT;
fn(a, r);
}
}
}
// Devuelve el borde derecho del actor bloqueante "a la izquierda" del Player.
// Criterio: el AABB del actor solapa el rect del Player y el borde izquierdo
// del actor está a la izquierda del borde izquierdo del Player (el Player se
// mete en él por la izquierda).
auto SolidActorManager::checkWallLeft(float px, float py, float pw, float ph) const -> float {
float result = Collision::NONE;
forEachActor([&](const SolidActor* a, const SDL_FRect& r) {
if (!a->isBlocking()) { return; }
if (a->isOneWayTop()) { return; } // Los jump-through no son pared lateral
// Y-overlap
if (py + ph <= r.y) { return; }
if (py >= r.y + r.h) { return; }
// X-overlap y "a la izquierda": el Player (px) está dentro del actor.
const float RIGHT_EDGE = r.x + r.w;
if (r.x < px && RIGHT_EDGE > px) {
if (result == Collision::NONE || RIGHT_EDGE > result) {
result = RIGHT_EDGE;
}
}
});
return result;
}
// Devuelve el borde izquierdo del actor bloqueante "a la derecha" del Player.
auto SolidActorManager::checkWallRight(float px, float py, float pw, float ph) const -> float {
float result = Collision::NONE;
forEachActor([&](const SolidActor* a, const SDL_FRect& r) {
if (!a->isBlocking()) { return; }
if (a->isOneWayTop()) { return; }
// Y-overlap
if (py + ph <= r.y) { return; }
if (py >= r.y + r.h) { return; }
// X-overlap y "a la derecha": el borde derecho del Player entra en el actor.
const float PLAYER_RIGHT = px + pw;
if (r.x < PLAYER_RIGHT && r.x + r.w > PLAYER_RIGHT) {
if (result == Collision::NONE || r.x < result) {
result = r.x;
}
}
});
return result;
}
// Techo: devuelve el borde inferior del actor bloqueante que está por encima
// del Player y solapa su parte superior.
auto SolidActorManager::checkCeiling(float px, float py, float pw) const -> float {
float result = Collision::NONE;
forEachActor([&](const SolidActor* a, const SDL_FRect& r) {
if (!a->isBlocking()) { return; }
if (a->isOneWayTop()) { return; } // Los jump-through no tienen techo
// X-overlap
if (px + pw <= r.x) { return; }
if (px >= r.x + r.w) { return; }
// Y: el actor debe estar por encima del Player (su bottom edge entre py y py+ph)
const float BOTTOM_EDGE = r.y + r.h;
if (r.y < py && BOTTOM_EDGE > py) {
if (result == Collision::NONE || BOTTOM_EDGE > result) {
result = BOTTOM_EDGE;
}
}
});
return result;
}
// Suelo: el actor más cercano (por arriba) cuyo top edge esté entre
// foot_y_cur y foot_y_new. Para ONEWAY_TOP, solo cuentan si foot_y_cur <= r.y.
auto SolidActorManager::checkFloor(float px, float foot_y_cur, float pw, float foot_y_new) const -> FloorHit {
FloorHit hit;
forEachActor([&](SolidActor* a, const SDL_FRect& r) {
// Un actor participa en el suelo si es bloqueante O si tiene CARRY_ON_TOP
// (las plataformas jump-through tienen CARRY_ON_TOP sin BLOCKS_PLAYER).
const bool PARTICIPATES = a->isBlocking() || a->carriesOnTop();
if (!PARTICIPATES) { return; }
// X-overlap
if (px + pw <= r.x) { return; }
if (px >= r.x + r.w) { return; }
// Y: el top del actor debe estar en [foot_y_cur, foot_y_new]
if (r.y < foot_y_cur) { return; } // actor por encima: no es suelo, es techo
if (r.y > foot_y_new) { return; } // actor fuera del rango de caída
// ONEWAY_TOP: ya garantizado por foot_y_cur <= r.y (chequeo implícito arriba)
// Actualizar hit si es más alto (menor y) que el actual
if (hit.y == Collision::NONE || r.y < hit.y) {
hit.y = r.y;
hit.carrier = a->carriesOnTop() ? a : nullptr;
}
});
return hit;
}
// Hay suelo "inmediato" debajo (tolerancia 1 px). Usado por checkFalling para
// no desengancharse de plataformas móviles que bajan/suben pixel a pixel.
auto SolidActorManager::hasGroundBelow(float px, float foot_y, float pw) const -> bool {
bool found = false;
forEachActor([&](const SolidActor* a, const SDL_FRect& r) {
if (found) { return; }
const bool PARTICIPATES = a->isBlocking() || a->carriesOnTop();
if (!PARTICIPATES) { return; }
// X-overlap
if (px + pw <= r.x) { return; }
if (px >= r.x + r.w) { return; }
// Top del actor dentro de [foot_y, foot_y + 1]
if (r.y >= foot_y && r.y <= foot_y + 1.0F) {
found = true;
}
});
return found;
}