refactor(entities): polimorfisme per substituir dynamic_casts

Quatre virtuals nous a `Entity` capten decisions que abans vivien al
manager com a discriminacions de tipus:

- `onWhipHit(WhipAttack*)` → bool. Default no-op/false. Override a
  `Mummy` per entrar HURT si és whippable. WhipAttack::applyDamage
  passa de `dynamic_cast<Mummy*>` + `isWhippable()` a una crida
  polimòrfica neta. RollingRock no override → immune al látigo per
  disseny, sense flag.

- `onJustSpawned()`. Default no-op. Override a `DynamicHazard` per
  disparar `triggerSpawnExit()`. `spawnPendingActors` i `spawnActor`
  passen de `dynamic_cast<DynamicHazard*>(observer)->triggerSpawnExit()`
  a `observer->onJustSpawned()`.

- `onRemoved()`. Default no-op. Override a `Sarcophagus` per cridar
  `entity_manager_->onSarcophagusRemoved()`. `removeEntity` passa de
  `getName() == "sarcophagus"` a dispatch polimòrfic.

- `shouldReceiveContact()`. Default `collides() || triggersOnOverlap()`.
  Override a `DynamicHazard` per excloure SPAWN. `checkPlayerCollisions`
  passa de tres condicions inline per a sacs distints a una crida
  unificada.

`whipDirToMummyDir` es mou de whip_attack.cpp a mummy.cpp — el mapping
viu on s'usa (dins de `Mummy::onWhipHit`).

`hitMummyIfApplicable` es renomena a `hitEntityIfApplicable`: ja no
discrimina tipus, aplica `entity->onWhipHit` i marca `already_hit_`
segons el retorn.

Comportament idèntic, tests verds. Aquest commit prepara el següent:
amb el polimorfisme madurat, el sac dual ja no aporta valor i podem
tornar a un únic `entities_`.
This commit is contained in:
JailDesigner committed 2026-05-11 13:01:26 +02:00
1 parent e8418f89e0
commit 28ff425c89
11 files changed
+177 -58

No files matched your search

+28
View File
@@ -10,6 +10,7 @@
#include "core/audio/audio.hpp"
#include "core/resources/resource_cache.hpp"
#include "game/entities/concrete/whip_attack.hpp"
#include "game/entities/entity_factory.hpp"
#include "game/entities/entity_manager.hpp"
#include "game/entities/event_bus.hpp"
@@ -65,6 +66,24 @@ namespace {
// més d'una lane (cas marginal a velocitats típiques, però pot passar
// amb dt grans). Comparació estricta a un costat: no dispara quan la
// caixa ja es troba alineada a la lane (hi està després del snap).
// Mapatge WhipAttack::Direction → Mummy::Direction. Viuen amb noms i
// valors paral·lels però són dos enums distints (cada classe modela el
// seu propi domini). Conversió 1-a-1; `NONE` no apareix com a possible
// sortida del whip, però la deixem per coherència defensiva.
auto whipDirToMummyDir(WhipAttack::Direction d) -> Mummy::Direction {
switch (d) {
case WhipAttack::Direction::NORTH:
return Mummy::Direction::NORTH;
case WhipAttack::Direction::SOUTH:
return Mummy::Direction::SOUTH;
case WhipAttack::Direction::EAST:
return Mummy::Direction::EAST;
case WhipAttack::Direction::WEST:
return Mummy::Direction::WEST;
}
return Mummy::Direction::NONE;
}
auto findLaneCross(float prev, float delta, float base, float pitch, int max_idx) -> std::optional<float> {
const float NEXT = prev + delta;
if (delta > 0.0F) {
@@ -520,6 +539,15 @@ void Mummy::applyChoice(Direction dir) {
}
}
auto Mummy::onWhipHit(WhipAttack* attack) -> bool {
// Defensiu: el filtre `shouldReceiveContact` ja salta DynamicHazards en
// SPAWN, però blindem aquesta crida si arribara per un camí no central.
if (isSpawning()) { return false; }
if (!whippable_ || attack == nullptr) { return false; }
enterHurt(whipDirToMummyDir(attack->getDirection()));
return true;
}
void Mummy::onPlayerContact(Player* player) {
// Defensiu: el filtre central a EntityManager::checkPlayerCollisions ja
// salta DynamicHazards en SPAWN, però blindem la crida directa.