migrades portes i plataformes a solidActor

This commit is contained in:
2026-04-11 12:54:54 +02:00
parent 49f6ed41e6
commit 5b2f986d32
22 changed files with 686 additions and 286 deletions

View File

@@ -0,0 +1,197 @@
#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;
}