feat(mummy): evita tiles amb decal trigger (Hole) en decidir direcció
This commit is contained in:
1 parent
3e5217066f
commit
3bfca35f3a
5 files changed
+111
-27
No files matched your search
@@ -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:
|
||||
|
||||
@@ -85,12 +85,25 @@ class Mummy : public DynamicHazard {
|
||||
// Recull les direccions alternatives viables des de la posició actual:
|
||||
// `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.
|
||||
// `Map::insidePlayfield` + `Map::collides` sobre la caja resultant. Si
|
||||
// `avoid_decals` é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
|
||||
// direccions filtrades estan bloquejades.
|
||||
struct DirectionPools {
|
||||
std::vector<Direction> perp;
|
||||
std::vector<Direction> uturn;
|
||||
};
|
||||
[[nodiscard]] auto collectDirectionPools(float step) const -> DirectionPools;
|
||||
[[nodiscard]] auto collectDirectionPools(float step, bool avoid_decals = 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_`)
|
||||
// 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;
|
||||
|
||||
// Canvi de direcció + selecció de l'animació corresponent (un cardinal
|
||||
// per branch). Tots els camins que trien nova direcció (ctor amb
|
||||
|
||||
Reference in new issue
Block a user