forked from phenom/healer-man
142 lines
8.2 KiB
Markdown
142 lines
8.2 KiB
Markdown
# Enemy fear movement follow-up — 2026-10-06
|
||
|
||
This pass integrates source-informed movement for the pinned enemy shout fear
|
||
auras into player, party and mobile owned-summon consumers. Voluntary movement,
|
||
jumping, attacks and casts remain suppressed while fear controls the actor.
|
||
Movement requires reviewed navigation and collision/LOS approval. Missing or
|
||
blocked spatial support safely holds the actor. Other controls retain their
|
||
existing policy; this does not claim exact historical-server path or RNG parity.
|
||
|
||
## Historical core behavior
|
||
|
||
The read-only historical source is AzerothCore commit
|
||
`e822d718322289dd036fb2daf8867ba41283bf9a`, specifically
|
||
`src/server/game/Movement/MovementGenerators/FleeingMovementGenerator.cpp`,
|
||
`src/server/game/Movement/MotionMaster.cpp`,
|
||
`src/server/game/Entities/Unit/Unit.cpp`, and
|
||
`src/server/game/Spells/Auras/SpellAuraEffects.cpp` under
|
||
`../wow-pvt-server-backups-2/azerothcore-wotlk`.
|
||
|
||
`HandleModFear` passes the live aura caster to `Unit::SetControlled`; that
|
||
passes it to `SetFeared`, then `MotionMaster::MoveFleeing`. The generator looks
|
||
up the caster GUID when it chooses each new destination. It does not preserve
|
||
the caster's cast-time position. If that lookup fails, it picks a random angle
|
||
and follows its near-source distance branch with a source distance of zero. A
|
||
dead caster can still resolve while its unit remains in-world; after removal,
|
||
the source-less fallback applies.
|
||
|
||
The source's exact movement bounds and retries are:
|
||
|
||
| Core rule | Source value |
|
||
| --- | ---: |
|
||
| Quiet-range lower bound | 28 yd |
|
||
| Quiet-range upper bound | 43 yd |
|
||
| Minimum accepted path | 2 yd |
|
||
| Path length limit | 30 yd |
|
||
| Retry after destination LOS failure | 200 ms |
|
||
| Retry after failed, short, or invalid path | 100 ms |
|
||
| Repath wait after spline travel | random 800–1500 ms |
|
||
|
||
Below 28 yards, the server picks a distance between `0.4` and `1.3` times the
|
||
remaining distance to 28 yards and a heading within ±π/8 of the direction away
|
||
from the source. Above 43 yards, it picks `0.4`–`1.0` times the 15-yard band
|
||
width and heads partly back toward the source, within ±π/4. Inside the quiet
|
||
band, it picks `0.6`–`1.2` times the band width in a random direction. A source
|
||
distance at or below 0.2 yards uses a random base angle. The core constrains the
|
||
candidate with `MovePositionToFirstCollision`, checks LOS to the resulting
|
||
point, then asks `PathGenerator` for a path and rejects shortcuts, no-path,
|
||
far-from-poly, paths shorter than 2 yards, and paths beyond the 30-yard limit.
|
||
After five failed paths it stops looking up the source and uses the
|
||
source-less random fallback.
|
||
|
||
`SetControlled` does not start fear while stun or confusion already owns the
|
||
unit. When the higher-priority control ends, it rechecks active fear auras and
|
||
resolves their caster GUID. This helper accepts the actor's active stun end time
|
||
and holds fear movement until that higher-priority control expires.
|
||
|
||
## Planner boundary
|
||
|
||
[`src/game/enemyFearMovement.ts`](../src/game/enemyFearMovement.ts) carries the
|
||
source distance bands, steering ranges, LOS/path retries, and path length
|
||
limits. Its hash-based choices are simulator decisions: they reproduce the
|
||
source's ranges with stable values derived from actor ID, source ID, aura
|
||
application time, and decision index. They do not reproduce the core's global
|
||
random-number stream. `worldUnitsPerYard` is required input because imported map
|
||
coordinates can have a different scale. The estimated time to the next choice
|
||
uses approved path length divided by this actor's current effective speed,
|
||
then adds a seeded wait from the core's 800–1500 ms range.
|
||
|
||
Each actor supplies its own fear expiry, stun expiry, base movement speed, and
|
||
source speed multiplier. The shout's +25% speed is therefore applied only when
|
||
that target has the still-active source speed aura. The current adapters are
|
||
limited to tagged source fear. `getSourcePosition` runs once per repath so
|
||
the goal uses the live caster location at that decision; the caller should
|
||
cache the resulting goal until `nextDecisionAtMs` instead of recomputing it
|
||
every frame.
|
||
|
||
The planner requires three map adapters: a projection that rejects excessive
|
||
snaps and floor changes, an LOS test, and a complete actor-safe path-length
|
||
query. Missing map support, an unsafe projection, blocked LOS, or an invalid
|
||
route yields a hold with the source retry timing. The destination is a route
|
||
goal only. The caller must move through actor collision and approved navigation
|
||
waypoints; it must never assign the destination directly to an actor position.
|
||
`stepEnemyFearTowardDestination` only computes a bounded request and does not
|
||
replace those checks.
|
||
|
||
## Integration seams before this pass
|
||
|
||
- `PlayerRig` resolves the player's shout control near its frame movement step.
|
||
`enemyControlPreventsMovement` currently blocks all fear before movement, and
|
||
`Rapier`'s character controller checks the player's capsule against terrain.
|
||
For a tagged source fear with no active stun, the frame can use the cached
|
||
flee goal to set the autonomous world movement vector, then submit its bounded
|
||
delta through the existing character controller. The goal must not be
|
||
derived from camera input or change camera yaw.
|
||
- `PartyPopulation` similarly blocks fear before companion route planning. It
|
||
already has the reviewed navigation graph, `planPartyRoute`, direct-segment
|
||
collision checks, and route-segment clearance validation. A fear goal should
|
||
use those route helpers and hold when no safe route exists.
|
||
- The summon loop in `combatStore.ts` returns early when `controlledUntil` is
|
||
active, before movement and actions. `CombatSummon` currently keeps a control
|
||
expiry but no control mechanic. A source fear adapter needs to preserve the
|
||
per-summon fear and stun state, move only when fear is the active control,
|
||
and continue to suppress actions. `CombatSpatialProvider` provides live actor
|
||
positions, LOS, ground projection, and the reviewed navigation graph; the
|
||
adapter still needs to validate a complete route before advancing a summon.
|
||
- The shout control resolver currently returns only mechanic and expiry. The
|
||
integration must identify the active tagged `ActiveAura` per actor to retain
|
||
its source ID and `appliedAt` for live source lookup and deterministic seeding.
|
||
That lookup can use the mob runtime position while the caster exists; when
|
||
the source ID no longer resolves, pass `null` and use the source-less branch.
|
||
|
||
The existing hostile NPC fear path is separate. `MobPopulation` already calls
|
||
`stepFearedMobAway` and clamps that direct step to the home leash, using the
|
||
current aggro target as the threat. That helper does not perform LOS or route
|
||
validation for the flee step. This follow-up leaves it unchanged and does not
|
||
claim it matches the historical generator.
|
||
|
||
## Integrated consumers and verification
|
||
|
||
`enemyFearMovementRuntime.ts` retains the selected aura's source/application,
|
||
caches each goal and implements the five-failure source fallback. Concurrent
|
||
tagged stun and generic stun/confuse/root/sleep auras hold movement even when
|
||
the actor's longest control timer belongs to fear. Expiry or purge clears the
|
||
per-actor goal; a refreshed aura starts a new decision sequence.
|
||
|
||
PlayerRig follows approved route waypoints through its existing Rapier character
|
||
controller, including gravity. Keyboard movement and jumping do not steer fear.
|
||
PartyPopulation uses its reviewed graph and capsule route validators, normal
|
||
party speed with actor-local source modifiers, and follows forced fear even
|
||
under Stop. Fear never enables catch-up speed or stuck-recovery relocation.
|
||
Camera look retains its ordinary hold-and-drag behavior.
|
||
|
||
`enemyFearSummonMovement.ts` requires a loaded graph and validates projection,
|
||
LOS, path segments and each bounded step. The actual combat tick consumes it
|
||
before summon actions, holds concurrent controls, and preserves the pet's Prowl
|
||
movement modifier. Totems and stationary zones remain stationary. Remote actor
|
||
authority and hostile-NPC flee policy remain outside this bounded integration.
|
||
|
||
Focused planner/cache/summon verification passes 20 tests, including actual pet
|
||
ticks, blocked/missing navigation and simultaneous stun expiry. Input regressions
|
||
pass 33 tests. Final build/live evidence is recorded in the combined follow-up report.
|