86 lines
3.5 KiB
Markdown
86 lines
3.5 KiB
Markdown
# Hostile NPC Behavior
|
|
|
|
## Authority and update order
|
|
|
|
Hostile NPC behavior is simulated by `LocationGameState` and
|
|
`CharacterState`. Rendering objects do not select targets and do not change
|
|
NPC combat state.
|
|
|
|
During each authoritative simulation tick:
|
|
|
|
1. The location gathers the enabled players and NPCs visible to the
|
|
simulation.
|
|
2. Each hostile NPC selects or retains a player target.
|
|
3. Every character advances using the same tick-start observations.
|
|
4. Movement reservations and attack damage are resolved deterministically.
|
|
5. Navigation recovery events are applied by `InternalGameServer`.
|
|
|
|
The resulting state and recovery decisions are replicated through the existing
|
|
WebSocket lockstep stream. NPC target changes are simulation consequences, not
|
|
client commands.
|
|
|
|
## Eligible players and nearest selection
|
|
|
|
An eligible target must:
|
|
|
|
- exist in the campaign player map;
|
|
- be enabled;
|
|
- have positive health; and
|
|
- be within the NPC's `visibleRange` when a new target is selected.
|
|
|
|
Distance is measured in the X/Z plane. If two players are equally close,
|
|
their stable player IDs are compared lexicographically. This gives every
|
|
lockstep replica the same result regardless of hash-map iteration order.
|
|
|
|
## Ordinary target locking
|
|
|
|
Once a hostile NPC has a valid target, it keeps that target during ordinary
|
|
ticks. The target remains locked even if it moves outside `visibleRange`.
|
|
The lock ends when the player dies or becomes disabled.
|
|
|
|
When a lock ends, the NPC selects the nearest eligible player in range. If no
|
|
player qualifies, it clears the attack target, leaves combat state, and
|
|
requests a route to its `homePosition`.
|
|
|
|
## Movement blockage and target reconsideration
|
|
|
|
NPC movement uses a retry schedule after a character blocks its path. The
|
|
blocked NPC first stops and waits for its deterministic navigation retry
|
|
cooldown. When the retry event emits `forceNavigationReplan`, the authoritative
|
|
location state performs a fresh target selection across all eligible players.
|
|
|
|
This blocked-replan selection differs from ordinary target locking:
|
|
|
|
- the current target is not privileged;
|
|
- the nearest eligible player may remain the target;
|
|
- a nearer eligible player replaces the current target;
|
|
- the route is rebuilt from the NPC's current position toward the selected
|
|
player's current position.
|
|
|
|
If no eligible player is in range during blocked recovery, the NPC clears its
|
|
stale chase target and returns toward its home position instead. The blocker is
|
|
still passed to path planning so the immediate replan does not route through
|
|
the character that caused the recovery event.
|
|
|
|
## Death, disconnect, and attack safety
|
|
|
|
Dead or disabled players are excluded from target acquisition. A target that
|
|
dies or disconnects is therefore invalidated on the next authoritative
|
|
selection pass, and the NPC either switches to another player or returns home.
|
|
|
|
Attack impacts are also validated at application time. Damage is ignored if
|
|
the recorded target no longer exists, is disabled, or has already reached
|
|
zero health. This prevents a queued animation impact from damaging a stale
|
|
target.
|
|
|
|
## Regression expectations
|
|
|
|
The state and pathfinding tests should preserve these guarantees:
|
|
|
|
- nearest in-range player selection is deterministic;
|
|
- a valid ordinary lock is retained;
|
|
- blocked NPC recovery can replace the target and rebuild the chase route;
|
|
- no-target blocked recovery clears combat and routes home;
|
|
- dead and disabled players cannot be selected or damaged;
|
|
- retry cooldowns and blocker identity remain deterministic.
|