#include "solid_actor_manager.hpp" #include // 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 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; }