Refactor del sistema d'entitats per substituir filtres i
dynamic_casts dispersos per dispatch polimòrfic.
Quatre virtuals nous a Entity (`onWhipHit`, `onJustSpawned`,
`onRemoved`, `shouldReceiveContact`) encapsulen dins de cada
subclasse les decisions que abans vivien al manager:
- WhipAttack::applyDamage ja no fa `dynamic_cast<Mummy*>`.
- spawnPendingActors/spawnActor ja no fan `dynamic_cast<DynamicHazard*>`.
- removeEntity ja no compara `getName() == "sarcophagus"`.
- checkPlayerCollisions filtra amb un sol predicat virtual.
Amb el polimorfisme madurat, el sac dual `map_entities_`/`actors_`
introduit a la rama anterior ja no aporta valor: tornem a un sol
`entities_` (manté `pending_actors_` per al spawn diferit). El
manager itera un sol vector i cada entitat decideix què fa.
Cap dynamic_cast nou per a lògica de joc — només queden els
inevitables (validació de factory i ruteig de la Door).
discriminació
Reverteix la separació en sacs introduïda al commit 62f4b85 (rama
anterior). Amb els virtuals afegits al commit precedent
(`onWhipHit`, `onJustSpawned`, `shouldReceiveContact`, `onRemoved`),
ja no cal separar `map_entities_` i `actors_`: cada entitat decideix
internament què fa, el manager itera un sol vector i confia en el
dispatch virtual.
Canvis:
- `entities_` torna a ser el contenidor únic. Es desfà el dispatch
`dynamic_cast<DynamicHazard*>` que `add()` feia per enrutar.
- `addSarcophagus`, `removeEntity`, `update`, `notifyPlayerTilePainted`,
`findFloorTriggerAt`, `renderAll`, `reload` simplificats: un sol
bucle, sense distingir categories.
- `checkPlayerCollisions` només recorre `entities_` cridant
`shouldReceiveContact` polimòrfic — la lògica de "saltar SPAWN"
viu a `DynamicHazard::shouldReceiveContact`.
- `getMapEntities()` i `getActors()` desapareixen. Es manté
`getEntities()` com a accessor únic.
- `WhipAttack::applyDamage` itera `getEntities()` cridant `onWhipHit`
polimòrfic. Les entitats no-whippables retornen false (cost: una
crida virtual buida) i el callsite no discrimina tipus.
- `GameScene::render` mergeja entities + players (dos transforms en
comptes de tres) per al z-order.
`pending_actors_` es manté: és un concepte ortogonal — entitats que
encara no s'han instanciat al món fins que GameScene les "spawneja"
al text-exit de ROOM_INTRO.
Beneficis del nou disseny:
- Cap dynamic_cast dispers per a la lògica de joc (només queden els
inevitables: validació de factory i ruteig de la Door).
- Afegir un nou enemic implica `class X : public DynamicHazard` i
override dels virtuals rellevants — res al manager.
- Si un sarc, antorxa, o decoració futura volgués reaccionar al
fueteig (trencar-se, encendre's), basta amb un `onWhipHit` override
i no cal tocar `WhipAttack::applyDamage`.
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_`.