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

+14
View File
@@ -99,6 +99,20 @@ class DynamicHazard : public Hazard {
[[nodiscard]] auto isSpawning() const -> bool { return spawn_state_ == SpawnState::SPAWNING; }
[[nodiscard]] auto getSpawnDurationS() const -> float { return spawn_duration_s_; }
// Just després d'instanciar-se al món (via spawnPendingActors /
// spawnActor), l'actor entra automàticament en la fase EMERGING:
// arma el comptador i comença l'animació de sortida. Substitueix el
// dynamic_cast que el manager feia abans per cridar `triggerSpawnExit`.
void onJustSpawned() override { triggerSpawnExit(); }
// Mentre està en SPAWN (HIDDEN/EMERGING), l'actor NO rep contacte
// ni amb el jugador ni amb attacks: visible però "no juga". Quan
// passa a ACTIVE, recupera el comportament estàndard d'Entity.
[[nodiscard]] auto shouldReceiveContact() const -> bool override {
if (isSpawning()) { return false; }
return Entity::shouldReceiveContact();
}
// Política de moviment per frame. La base desplaça el pixel_rect_
// proporcionalment a velocitat i direcció; les subclasses amb IA la
// sobreescriuen per triar nova direcció a les interseccions o rebotar.