diff --git a/source/game/entities/concrete/mummy.cpp b/source/game/entities/concrete/mummy.cpp index 90c0eca..a53ffa1 100644 --- a/source/game/entities/concrete/mummy.cpp +++ b/source/game/entities/concrete/mummy.cpp @@ -261,7 +261,7 @@ void Mummy::step(float delta_time) { // TODO(refactor): mateixa cascada a `decideAtIntersection` amb la // variant "afegir recta a Nivell 1". Factoritzable // quan tinga sentit (3+ callsites). - auto pools = collectDirectionPools(speed_ * delta_time, /*avoid_decals=*/true); + auto pools = collectDirectionPools(speed_ * delta_time, /*avoid_blocked=*/true); const std::vector* chosen = nullptr; if (!pools.perp.empty()) { chosen = &pools.perp; @@ -270,7 +270,7 @@ void Mummy::step(float delta_time) { } DirectionPools pools_unfiltered; if (chosen == nullptr) { - pools_unfiltered = collectDirectionPools(speed_ * delta_time, /*avoid_decals=*/false); + pools_unfiltered = collectDirectionPools(speed_ * delta_time, /*avoid_blocked=*/false); if (!pools_unfiltered.perp.empty()) { chosen = &pools_unfiltered.perp; } else if (!pools_unfiltered.uturn.empty()) { @@ -350,13 +350,13 @@ void Mummy::decideAtIntersection(float step_px) { // permetia mai en creuaments, però és preferible recular que caure al // forat quan totes les altres surten a decal. // TODO(refactor): cascada paral·lela a la branca de bloqueig de `step`. - auto pools = collectDirectionPools(step_px, /*avoid_decals=*/true); + auto pools = collectDirectionPools(step_px, /*avoid_blocked=*/true); // Nivell 1: perpendiculars (filtrades) + recta si lliure i sense decal. std::vector level1 = std::move(pools.perp); const auto STRAIGHT_BOX = candidateBox(dir_x_ * step_px, dir_y_ * step_px); const bool STRAIGHT_OPEN = Map::insidePlayfield(STRAIGHT_BOX) && !Map::collides(STRAIGHT_BOX); - if (STRAIGHT_OPEN && !isDecalAheadInDir(dir_x_, dir_y_)) { + if (STRAIGHT_OPEN && !isBlockedAheadInDir(dir_x_, dir_y_)) { level1.push_back(currentDirection()); } @@ -376,7 +376,7 @@ void Mummy::decideAtIntersection(float step_px) { DirectionPools pools_unfiltered; std::vector level3; if (chosen == nullptr) { - pools_unfiltered = collectDirectionPools(step_px, /*avoid_decals=*/false); + pools_unfiltered = collectDirectionPools(step_px, /*avoid_blocked=*/false); level3 = std::move(pools_unfiltered.perp); if (STRAIGHT_OPEN) { level3.push_back(currentDirection()); } if (!level3.empty()) { @@ -390,7 +390,7 @@ void Mummy::decideAtIntersection(float step_px) { applyChoice(chooseDirection(*chosen)); } -auto Mummy::collectDirectionPools(float step, bool avoid_decals) const -> DirectionPools { +auto Mummy::collectDirectionPools(float step, bool avoid_blocked) const -> DirectionPools { struct Candidate { float dx; float dy; @@ -412,9 +412,9 @@ auto Mummy::collectDirectionPools(float step, bool avoid_decals) const -> Direct if (!Map::insidePlayfield(TEST) || Map::collides(TEST)) { continue; } // Filtre de decals (Hole i futurs decals trigger): comprova el // tile cardinal contigu; si conté un trigger, descartem la - // direcció. El caller pot demanar `avoid_decals=false` com a + // direcció. El caller pot demanar `avoid_blocked=false` com a // fallback quan totes les filtrades estan bloquejades. - if (avoid_decals && isDecalAheadInDir(c.dx, c.dy)) { continue; } + if (avoid_blocked && isBlockedAheadInDir(c.dx, c.dy)) { continue; } const bool IS_UTURN = (c.dx * dir_x_ < 0.0F) || (c.dy * dir_y_ < 0.0F); if (IS_UTURN) { pools.uturn.push_back(c.dir); @@ -425,10 +425,7 @@ auto Mummy::collectDirectionPools(float step, bool avoid_decals) const -> Direct return pools; } -auto Mummy::isDecalAheadInDir(float dx_signed, float dy_signed) const -> bool { - // Sense manager injectat (smoke tests sense `EntityManager::add`), el - // filtre és no-op: la mòmia es comporta exactament com abans del feature. - if (entity_manager_ == nullptr) { return false; } +auto Mummy::isBlockedAheadInDir(float dx_signed, float dy_signed) const -> bool { if (dx_signed == 0.0F && dy_signed == 0.0F) { return false; } // El rect a consultar és el AABB del tile cardinal contigu (mida // `Map::tile_size`), ancorat al tile actual de la mòmia. Calculem el @@ -457,7 +454,13 @@ auto Mummy::isDecalAheadInDir(float dx_signed, float dy_signed) const -> bool { .w = TS, .h = TS, }; - return entity_manager_->findFloorTriggerAt(NEXT) != nullptr; + // Ordre per cost creixent: comparació de coordenades, després iteració + // sobre sarcs i porta, i finalment cerca a l'index d'entitats. Sense + // manager (smoke tests) saltem només l'últim filtre. + if (!Map::insidePlayfield(NEXT)) { return true; } + if (Map::collides(NEXT)) { return true; } + if (entity_manager_ != nullptr && entity_manager_->findFloorTriggerAt(NEXT) != nullptr) { return true; } + return false; } void Mummy::applyChoice(Direction dir) { diff --git a/source/game/entities/concrete/mummy.hpp b/source/game/entities/concrete/mummy.hpp index 41505ab..15c15d2 100644 --- a/source/game/entities/concrete/mummy.hpp +++ b/source/game/entities/concrete/mummy.hpp @@ -87,24 +87,30 @@ class Mummy : public DynamicHazard { // `perpendiculars` (girs) i `uturn` (inversa). La direcció actual s'exclou // perquè ja sabem que està bloquejada. Cada candidata es valida amb // `Map::insidePlayfield` + `Map::collides` sobre la caja resultant. Si - // `avoid_decals` és true i hi ha `EntityManager` injectat, també es + // `avoid_blocked` és true i hi ha `EntityManager` injectat, també es // descarten les direccions que apunten a un tile amb un decal trigger // (Hole o futurs pinchos plans/charcos). El caller és responsable de - // re-cridar amb `avoid_decals=false` com a fallback si totes les + // re-cridar amb `avoid_blocked=false` com a fallback si totes les // direccions filtrades estan bloquejades. struct DirectionPools { std::vector perp; std::vector uturn; }; - [[nodiscard]] auto collectDirectionPools(float step, bool avoid_decals = true) const -> DirectionPools; + [[nodiscard]] auto collectDirectionPools(float step, bool avoid_blocked = true) const -> DirectionPools; // True si el tile cardinal contigu en la direcció (signe de dx, signe - // de dy) conté un decal trigger (`is_floor_decal_ && triggers_on_overlap_`) + // de dy) està bloquejat per qualsevol motiu: fora del playfield, ocupat + // per geometria estàtica (sarcs, door_corridor tancat) via `Map::collides`, + // o conté un decal trigger (`is_floor_decal_ && triggers_on_overlap_`) // segons `EntityManager::findFloorTriggerAt`. Captura `Hole` i qualsevol - // futur decal trigger sense hardcodejar tipus. Si `entity_manager_` no - // està injectat (smoke tests sense manager), retorna false silenciosament - // — la mòmia es comporta com abans del feature. - [[nodiscard]] auto isDecalAheadInDir(float dx_signed, float dy_signed) const -> bool; + // futur decal trigger sense hardcodejar tipus, i evita que la mòmia + // triï a un creuament una direcció que el frame següent toparia (cas + // típic: a la intersecció anterior al límit nord del playfield, on el + // pas immediat encara hi cap però el tile sencer següent ja està fora). + // Si `entity_manager_` no està injectat (smoke tests sense manager), + // el filtre de decals és silenciós però els altres dos (playfield i + // collides) segueixen actius. + [[nodiscard]] auto isBlockedAheadInDir(float dx_signed, float dy_signed) const -> bool; // Canvi de direcció + selecció de l'animació corresponent (un cardinal // per branch). Tots els camins que trien nova direcció (ctor amb