feat(mummy): evita tiles amb decal trigger (Hole) en decidir direcció

This commit is contained in:
JailDesigner committed 2026-04-30 16:50:26 +02:00
1 parent 3e5217066f
commit 3bfca35f3a
5 files changed
+111 -27

No files matched your search

+59 -7
View File
@@ -8,6 +8,7 @@
#include <utility>
#include "game/entities/entity_factory.hpp"
#include "game/entities/entity_manager.hpp"
#include "game/entities/event_bus.hpp"
#include "game/entities/player.hpp"
#include "game/map/map.hpp"
@@ -235,8 +236,15 @@ void Mummy::step(float delta_time) {
// Cal girar. Preferim perpendiculars; només fem U-turn si no hi ha cap
// altra opció (comportament típic d'Oh Mummy!: no es recula sense motiu).
const auto POOLS = collectDirectionPools(speed_ * delta_time);
const auto& pool = !POOLS.perp.empty() ? POOLS.perp : POOLS.uturn;
// Primer intentem amb el filtre de decals (Hole) actiu; si totes les
// alternatives apunten a un decal trigger, fallback al pool sense filtre
// — millor caure al forat que quedar-se paralitzada en una sala mal
// dissenyada (la mòmia no mor pels forats).
auto pools = collectDirectionPools(speed_ * delta_time, /*avoid_decals=*/true);
if (pools.perp.empty() && pools.uturn.empty()) {
pools = collectDirectionPools(speed_ * delta_time, /*avoid_decals=*/false);
}
const auto& pool = !pools.perp.empty() ? pools.perp : pools.uturn;
applyChoice(chooseDirection(pool));
// No avancem aquest frame: la direcció nova s'aplicarà al pròxim step.
}
@@ -260,24 +268,38 @@ auto Mummy::currentDirection() const -> Direction {
void Mummy::decideAtIntersection(float step_px) {
// Pool base: perpendiculars lliures (collectDirectionPools ja exclou la
// direcció actual i la mitja volta va al seu propi calaix, que ací
// ignorem — mai fem U-turn a un creuament).
auto pools = collectDirectionPools(step_px);
// ignorem — mai fem U-turn a un creuament). Primera passa amb filtre
// de decals (Hole) actiu.
auto pools = collectDirectionPools(step_px, /*avoid_decals=*/true);
std::vector<Direction> pool = std::move(pools.perp);
// Afegim la recta si segueix essent lliure. A un creuament "net" ho és
// sempre (la mòmia hi ha arribat per aquest corredor), però als creua-
// ments de la vora del playfield la recta pot estar al límit i no
// permetre un pas més — en aquest cas la descartem.
// permetre un pas més — en aquest cas la descartem. També la descartem
// si apunta a un decal trigger: aquesta és la regla "evita forats" en
// mode "seguir recte".
const auto STRAIGHT_BOX = candidateBox(dir_x_ * step_px, dir_y_ * step_px);
if (Map::insidePlayfield(STRAIGHT_BOX) && !Map::collides(STRAIGHT_BOX)) {
const bool STRAIGHT_OPEN = Map::insidePlayfield(STRAIGHT_BOX) && !Map::collides(STRAIGHT_BOX);
if (STRAIGHT_OPEN && !isDecalAheadInDir(dir_x_, dir_y_)) {
pool.push_back(currentDirection());
}
// Fallback: si totes les opcions filtrades porten a decal o estan
// bloquejades, repetim el pool sense filtre i tornem a afegir la recta
// si és lliure (encara que vaja a forat). Així la mòmia mai es queda
// paralitzada en un creuament rodejat de forats.
if (pool.empty()) {
auto pools_unfiltered = collectDirectionPools(step_px, /*avoid_decals=*/false);
pool = std::move(pools_unfiltered.perp);
if (STRAIGHT_OPEN) { pool.push_back(currentDirection()); }
}
if (pool.empty()) { return; }
applyChoice(chooseDirection(pool));
}
auto Mummy::collectDirectionPools(float step) const -> DirectionPools {
auto Mummy::collectDirectionPools(float step, bool avoid_decals) const -> DirectionPools {
struct Candidate {
float dx;
float dy;
@@ -297,6 +319,11 @@ auto Mummy::collectDirectionPools(float step) const -> DirectionPools {
if (IS_CURRENT) { continue; }
const auto TEST = candidateBox(c.dx, c.dy);
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
// fallback quan totes les filtrades estan bloquejades.
if (avoid_decals && isDecalAheadInDir(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);
@@ -307,6 +334,31 @@ auto Mummy::collectDirectionPools(float step) const -> DirectionPools {
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; }
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`), NO el AABB de la mòmia desplaçat un pas — així
// captura "si la mòmia estiguera centrada en aquell tile, hi pisaria
// un decal?" amb marge.
const auto BOX = getCollisionRect();
const auto TS = static_cast<float>(Map::tile_size);
const auto SIGN_OF = [](float v) -> float {
if (v > 0.0F) { return 1.0F; }
if (v < 0.0F) { return -1.0F; }
return 0.0F;
};
const SDL_FRect NEXT{
.x = BOX.x + (SIGN_OF(dx_signed) * TS),
.y = BOX.y + (SIGN_OF(dy_signed) * TS),
.w = TS,
.h = TS,
};
return entity_manager_->findFloorTriggerAt(NEXT) != nullptr;
}
void Mummy::applyChoice(Direction dir) {
switch (dir) {
case Direction::NORTH: