198 lines
7.6 KiB
C++
198 lines
7.6 KiB
C++
#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;
|
||
}
|