Table of Contents

Class AIController

Namespace
FishMMO.Shared
Assembly
FishMMO.Shared.dll

Controls AI navigation, state transitions, and behavior for NPCs using NavMeshAgent. Handles movement, enemy detection, leash logic, waypoints, state management, and provides a virtual camera for aiming abilities at targets during combat.

[RequireComponent(typeof(NavMeshAgent))]
public class AIController : CharacterBehaviour, IAIController, IAINavigation, IAIStateMachine, IAIWaypoints, ICharacterBehaviour
Inheritance
Object
Component
Behaviour
MonoBehaviour
NetworkBehaviour
AIController
Implements
Inherited Members
NetworkBehaviour.IsSpawned
NetworkBehaviour.ComponentIndex
NetworkBehaviour.NetworkObject
NetworkBehaviour.MAXIMUM_NETWORKBEHAVIOURS
NetworkBehaviour.UNSET_NETWORKBEHAVIOUR_ID
NetworkBehaviour.ToString()
NetworkBehaviour.Reset()
NetworkBehaviour.OnValidate()
NetworkBehaviour.IsBehaviourReconciling
NetworkBehaviour.ClearReplicateCache()
NetworkBehaviour.CreateReconcile()
NetworkBehaviour.Reconcile_Reader<T>(PooledReader, ref T)
NetworkBehaviour.OnStartServerCalled
NetworkBehaviour.OnStartClientCalled
NetworkBehaviour.WritePayload(NetworkConnection, Writer)
NetworkBehaviour.ReadPayload(NetworkConnection, Reader)
NetworkBehaviour.OnStartServer()
NetworkBehaviour.OnStopServer()
NetworkBehaviour.OnOwnershipServer(NetworkConnection)
NetworkBehaviour.OnSpawnServer(NetworkConnection)
NetworkBehaviour.OnDespawnServer(NetworkConnection)
NetworkBehaviour.OnStartClient()
NetworkBehaviour.OnStopClient()
NetworkBehaviour.OnOwnershipClient(NetworkConnection)
NetworkBehaviour.ClearBuffedRpcs()
NetworkBehaviour.ExcludeOwnerFromUnbufferedObserversRpcs
NetworkBehaviour.IsClientOnly
NetworkBehaviour.IsServerOnly
NetworkBehaviour.IsHost
NetworkBehaviour.IsClient
NetworkBehaviour.IsServer
NetworkBehaviour.IsDeinitializing
NetworkBehaviour.NetworkManager
NetworkBehaviour.ServerManager
NetworkBehaviour.ClientManager
NetworkBehaviour.ObserverManager
NetworkBehaviour.TransportManager
NetworkBehaviour.TimeManager
NetworkBehaviour.SceneManager
NetworkBehaviour.PredictionManager
NetworkBehaviour.RollbackManager
NetworkBehaviour.NetworkObserver
NetworkBehaviour.IsClientInitialized
NetworkBehaviour.IsClientStarted
NetworkBehaviour.IsClientOnlyInitialized
NetworkBehaviour.IsClientOnlyStarted
NetworkBehaviour.IsServerInitialized
NetworkBehaviour.IsServerStarted
NetworkBehaviour.IsServerOnlyInitialized
NetworkBehaviour.IsServerOnlyStarted
NetworkBehaviour.IsHostInitialized
NetworkBehaviour.IsHostStarted
NetworkBehaviour.IsOffline
NetworkBehaviour.IsNetworked
NetworkBehaviour.GetIsNetworked()
NetworkBehaviour.IsManagerReconciling
NetworkBehaviour.Observers
NetworkBehaviour.IsOwner
NetworkBehaviour.IsController
NetworkBehaviour.HasAuthority
NetworkBehaviour.Owner
NetworkBehaviour.OwnerId
NetworkBehaviour.ObjectId
NetworkBehaviour.LocalConnection
NetworkBehaviour.OwnerMatches(NetworkConnection)
NetworkBehaviour.Despawn(GameObject, DespawnType?)
NetworkBehaviour.Despawn(NetworkObject, DespawnType?)
NetworkBehaviour.Despawn(DespawnType?)
NetworkBehaviour.Spawn(GameObject, NetworkConnection, Scene)
NetworkBehaviour.Spawn(NetworkObject, NetworkConnection, Scene)
NetworkBehaviour.RemoveOwnership()
NetworkBehaviour.GiveOwnership(NetworkConnection)
NetworkBehaviour.GetInstance<T>()
NetworkBehaviour.TryRegisterInstance<T>(T)
NetworkBehaviour.UnregisterInstance<T>()
NetworkBehaviour.CanLog(LoggingType)
MonoBehaviour.IsInvoking()
MonoBehaviour.CancelInvoke()
MonoBehaviour.StopCoroutine(Coroutine)
MonoBehaviour.StopAllCoroutines()
MonoBehaviour.destroyCancellationToken
MonoBehaviour.useGUILayout
MonoBehaviour.didStart
MonoBehaviour.didAwake
MonoBehaviour.runInEditMode
Behaviour.enabled
Behaviour.isActiveAndEnabled
Component.GetComponent<T>()
Component.TryGetComponent<T>(out T)
Component.GetComponentInChildren<T>()
Component.GetComponentsInChildren<T>()
Component.GetComponentInParent<T>()
Component.GetComponentsInParent<T>()
Component.GetComponents<T>()
Component.GetComponentIndex()
Component.CompareTag(TagHandle)
Component.transform
Component.transformHandle
Component.gameObject
Component.tag
Object.GetEntityId()
Object.GetInstanceID()
Object.GetHashCode()
Object.InstantiateAsync<T>(T)
Object.InstantiateAsync<T>(T, Transform)
Object.InstantiateAsync<T>(T, Vector3, Quaternion)
Object.InstantiateAsync<T>(T, Transform, Vector3, Quaternion)
Object.Instantiate(Object, Vector3, Quaternion)
Object.Instantiate(Object, Vector3, Quaternion, Transform)
Object.Instantiate(Object)
Object.Instantiate(Object, Scene)
Object.Instantiate<T>(T, InstantiateParameters)
Object.Instantiate<T>(T, Vector3, Quaternion, InstantiateParameters)
Object.Instantiate(Object, Transform)
Object.Instantiate<T>(T)
Object.Instantiate<T>(T, Vector3, Quaternion)
Object.Instantiate<T>(T, Vector3, Quaternion, Transform)
Object.Instantiate<T>(T, Transform)
Object.Destroy(Object)
Object.DestroyImmediate(Object)
Object.DontDestroyOnLoad(Object)
Object.DestroyObject(Object)
Object.FindObjectsOfType<T>()
Object.FindObjectsByType<T>(FindObjectsSortMode)
Object.FindObjectsByType<T>(FindObjectsInactive, FindObjectsSortMode)
Object.FindObjectOfType<T>()
Object.FindFirstObjectByType<T>()
Object.FindAnyObjectByType<T>()
Object.FindFirstObjectByType<T>(FindObjectsInactive)
Object.FindAnyObjectByType<T>(FindObjectsInactive)
Object.FindObjectsByType<T>()
Object.FindObjectsByType<T>(FindObjectsInactive)
Object.name
Object.hideFlags

Remarks

Every AI state previously drove the NavMeshAgent with the same three-line pattern: NavMesh.SamplePosition, SetDestination, and then !pathPending && remainingDistance < 1 to decide it had arrived. All three steps have failure modes that the pattern silently swallows:

  • SamplePosition returns false when the sampled point has no NavMesh near it, and the caller then simply did not move — with no retry and no fallback.
  • An unreachable destination does not fail. Unity returns a partial path to the closest reachable point, so the agent walks to the near side of the obstacle and stops.
  • remainingDistance is 0 when there is no path at all, and pathPending is false at the same moment — so "no path" and "arrived" are indistinguishable. A patrol whose first waypoint failed to sample cycled through every waypoint once per tick without walking anywhere.

These helpers collapse that pattern into calls that report what actually happened.

Fields

ARRIVAL_TOLERANCE

Distance from the destination at which an NPC counts as arrived, in world units.

public const float ARRIVAL_TOLERANCE = 1

Field Value

float

AiTickRate

How many times per second this NPC's brain runs, in hertz.

[Header("Tick Rate")]
[Tooltip("Brain updates per second. 5-10 is the useful band. Rounded to a divisor of the network tick rate.")]
[Range(1, 30)]
public float AiTickRate

Field Value

float

Remarks

Rounded to the nearest whole divisor of the FishNet tick rate, so the brain always lands on network ticks and never drifts against them. At the project's 30 Hz network tick, 8 Hz resolves to every 4th tick — 7.5 Hz exactly, forever, on any hardware. EffectiveAiTickRate reports what a requested rate actually resolved to.

5-10 Hz is the useful band for an MMO brain. Decisions below about 5 Hz start to read as sluggish reaction time to a player; above about 10 Hz the NPC is re-deciding faster than its own pathing and animation can respond, so the extra ticks buy nothing but CPU.

AllyScanTimer

Seconds until a healer archetype rescans for wounded allies.

[NonSerialized]
public float AllyScanTimer

Field Value

float

AttackCooldownTimer

Seconds remaining before this NPC may activate another ability.

[NonSerialized]
public float AttackCooldownTimer

Field Value

float

Remarks

Lives on the controller, not on the attacking state, because the state is a ScriptableObject shared by every NPC of that archetype — a timer stored there would be one global pacing clock for the whole population.

BossScript

[Header("Boss Script")]
[Tooltip("Optional boss script for phased encounters.")]
public BossScript BossScript

Field Value

BossScript

CachedHealTarget

The ally a healer archetype last chose to heal, re-validated cheaply between scans.

[NonSerialized]
public ICharacter CachedHealTarget

Field Value

ICharacter

FlankTimer

Seconds of manoeuvring budget remaining for RogueAttackingState's flanking attempt. Per-NPC for the same reason as AttackCooldownTimer.

[NonSerialized]
public float FlankTimer

Field Value

float

Group

The NPC group this controller belongs to. Set by NPCGroup.

[NonSerialized]
public NPCGroup Group

Field Value

NPCGroup

GroupRole

This NPC's role within its group. Set by NPCGroup.

[NonSerialized]
public NPCGroupRole GroupRole

Field Value

NPCGroupRole

HEADING_SPEED_THRESHOLD

Speed below which the agent's velocity is not worth turning toward, in metres per second.

public const float HEADING_SPEED_THRESHOLD = 0.05

Field Value

float

Kite

Per-NPC kiting allowance. See AIKiteBudget.

[NonSerialized]
public AIKiteBudget Kite

Field Value

AIKiteBudget

LookTarget

The current look target for the AI (used for facing/rotation).

public Transform LookTarget

Field Value

Transform

OFF_MESH_RESEAT_INTERVAL

Seconds between attempts to put an agent that has left the NavMesh back on it.

public const float OFF_MESH_RESEAT_INTERVAL = 1

Field Value

float

OrbitAngle

Per-NPC orbit angle (radians) used by OrbitState. Stored here instead of on the ScriptableObject to avoid the shared-instance mutable state problem.

[NonSerialized]
public float OrbitAngle

Field Value

float

PetStuckTimer

Seconds a pet has spent unable to reach its owner. Drives the follow state's teleport escape hatch. Per-NPC, for the same reason as the other timers here.

[NonSerialized]
public float PetStuckTimer

Field Value

float

RandomizeState

If true, the AI will randomize its movement state.

public bool RandomizeState

Field Value

bool

RepathInterval

[Header("Pathfinding")]
[Tooltip("Minimum seconds between NavMeshAgent.SetDestination calls via SetThrottledDestination.")]
public float RepathInterval

Field Value

float

RotationIndex

Per-NPC rotation index used by AIAbilityRotation in Sequence mode. Tracks which entry in the rotation to try next.

[NonSerialized]
public int RotationIndex

Field Value

int

STUCK_SPEED_THRESHOLD

Speed below which an agent that is supposed to be moving counts as not moving.

public const float STUCK_SPEED_THRESHOLD = 0.05

Field Value

float

SeparationRadius

[Header("Separation")]
[Tooltip("Distance at which another NPC starts pushing this one away. 0 = twice the agent radius.")]
public float SeparationRadius

Field Value

float

SeparationSpeed

Push speed when fully overlapped with another NPC, in metres per second.

[Tooltip("Push speed when fully overlapped with another NPC. 0 disables separation.")]
public float SeparationSpeed

Field Value

float

StuckTimeout

Seconds of no progress before the NPC is considered stuck.

[Header("Navigation Recovery")]
[Tooltip("Seconds of no movement, while trying to move, before the NPC is considered stuck.")]
public float StuckTimeout

Field Value

float

StuckWarpTimeout

Seconds of continued no progress after the first recovery attempt before the NPC is teleported to its destination. 0 disables teleport recovery.

[Tooltip("Seconds stuck before the NPC is warped free. 0 = never warp.")]
public float StuckWarpTimeout

Field Value

float

SubStateTimer

Countdown used by bounded combat sub-states such as OrbitState to know when their manoeuvre is finished. Per-NPC, for the same reason as the other timers here.

[NonSerialized]
public float SubStateTimer

Field Value

float

SweepHits

Buffer for storing colliders hit during enemy sweep. Grown on demand — see SweepForEnemies(AIController, List<ICharacter>).

public Collider[] SweepHits

Field Value

Collider[]

Remarks

Not a fixed 20. A non-allocating overlap returns at most buffer.Length results and says nothing about how many it discarded, and the ones it discarded were chosen by the physics broadphase — so an NPC in a fight larger than its buffer detected an arbitrary, run-varying subset of its attackers and ignored the rest. The sweep re-queries into a larger buffer through TargetOrdering.TryGrowQueryBuffer until it stops coming back full, which is the same treatment every other spatial query in the project gets.

TurnRate

How quickly the NPC turns to face its look target, in radians-ish per second.

[Header("Facing")]
[Tooltip("How quickly the NPC turns to face its target. Higher is snappier.")]
public float TurnRate

Field Value

float

Remarks

Feeds an exponential smoothing factor, so the value is a rate rather than a hard angular speed: higher snaps faster, and the result is identical at any frame rate.

UnreachableTargetTimer

Seconds the NPC has spent unable to reach its combat target.

[NonSerialized]
public float UnreachableTargetTimer

Field Value

float

WasAttackingLastTick

True when the previous combat tick resolved to attacking or holding position rather than moving. Feeds the range hysteresis in AICombatDecision.

[NonSerialized]
public bool WasAttackingLastTick

Field Value

bool

Waypoints

The waypoints available to this AI controller.

public Vector3[] Waypoints

Field Value

Vector3[]

Properties

AbilityRotation

Optional ability rotation. When assigned, PickBestAbility(float) evaluates the rotation first. If no entry matches and FallbackToDefault is true, the default scoring-based picker runs as a fallback. A boss phase's override wins over the archetype's while the phase is in force.

public AIAbilityRotation AbilityRotation { get; }

Property Value

AIAbilityRotation

Agent

The NavMeshAgent component used for navigation.

public NavMeshAgent Agent { get; }

Property Value

NavMeshAgent

Aggression

Convenience accessor for the underlying aggression controller.

public AggressionController Aggression { get; }

Property Value

AggressionController

AggressionState

The aggression (threat) state for this NPC. Manages the threat table, event subscriptions, and target re-evaluation timer. One instance per NPC — not shared.

public AggressionState AggressionState { get; }

Property Value

AggressionState

AiTickIndex

Monotonic count of brain updates. Drives the LOD stagger.

public uint AiTickIndex { get; }

Property Value

uint

Archetype

The archetype this NPC runs. See the field remarks for what assigning one at runtime does.

public AIArchetypeTemplate Archetype { get; set; }

Property Value

AIArchetypeTemplate

AttackingState

The combat state, or null when this NPC cannot fight. A boss phase's override wins over the archetype's while the phase is in force.

public BaseAIState AttackingState { get; }

Property Value

BaseAIState

AvoidancePriority

The NavMeshAgent avoidance priority (affects how strongly it avoids other agents).

public AgentAvoidancePriority AvoidancePriority { get; }

Property Value

AgentAvoidancePriority

BehaviorTree

Optional behavior tree that provides high-level decision making above the state machine. When assigned, the tree is evaluated each tick before the current state's UpdateState. If the tree produces a state transition (returns Success), UpdateState is skipped that tick. A boss phase's override wins over the archetype's while the phase is in force.

public AIBehaviorTree BehaviorTree { get; }

Property Value

AIBehaviorTree

BossState

Runtime state for the boss script. Null when no BossScript is assigned.

public BossScriptState BossState { get; }

Property Value

BossScriptState

CombatTargetBuffer

Reusable buffer for collecting targets during combat state updates. Used by attacking states to avoid per-frame GC allocations.

public List<ICharacter> CombatTargetBuffer { get; }

Property Value

List<ICharacter>

CurrentLodTier

Current AI LOD tier. Determines how frequently this NPC's brain ticks.

public AILodTier CurrentLodTier { get; }

Property Value

AILodTier

CurrentState

The current AI state.

public BaseAIState CurrentState { get; }

Property Value

BaseAIState

CurrentWaypointIndex

The current waypoint index.

public int CurrentWaypointIndex { get; }

Property Value

int

DeadState

Optional state entered on death.

public BaseAIState DeadState { get; }

Property Value

BaseAIState

EffectiveAiTickRate

The brain rate actually achieved, after rounding to a whole number of network ticks.

public float EffectiveAiTickRate { get; }

Property Value

float

EnemySweepRate

How often (in seconds) to sweep for nearby enemies while out of combat.

public float EnemySweepRate { get; }

Property Value

float

EyeTransform

The transform used for vision checks. Defaults to the character's transform if not set.

public Transform EyeTransform { get; }

Property Value

Transform

HasNearbyPlayer

True when a player is close enough for this NPC's expensive per-tick polling — the enemy sweep above all — to be worth doing.

public bool HasNearbyPlayer { get; }

Property Value

bool

Remarks

Inside the Nearby band, or unmeasured. Never false merely because nothing is streaming this NPC: an Observers.Count < 1 test here meant a budget-evicted monster could not aggro anybody, including the player who had just pulled it.

Home

The anchor this AI leashes and wanders around.

public Vector3 Home { get; set; }

Property Value

Vector3

Remarks

For a normal NPC this is its spawn point. For a pet it is its owner — a pet's home is a moving target, and every leash check, wander radius and return-home destination in the AI reads this property, so anchoring it to the owner here fixes all of them at once. Previously each site that cared had to remember to overwrite the field with the owner's position, and the ones that forgot dragged the pet back toward wherever it happened to be summoned.

A pet ordered to Stay is the exception: it holds the position it was standing in, which is what the setter stores.

IdleState

The state this NPC falls back to when it has nothing to do.

public BaseAIState IdleState { get; }

Property Value

BaseAIState

InitialState

The state the NPC starts in when it spawns.

public BaseAIState InitialState { get; }

Property Value

BaseAIState

LastAiDeltaTime

Seconds elapsed during the AI tick currently executing.

public float LastAiDeltaTime { get; }

Property Value

float

Remarks

Published so helpers reached from deep inside a state's update — which do not receive deltaTime as a parameter — can still advance per-tick timers on the same clock the state machine runs on, rather than sampling UnityEngine.Time.deltaTime and getting one frame instead of one AI tick.

LastPathWasPartial

True when the last accepted destination request produced only a partial path.

public bool LastPathWasPartial { get; }

Property Value

bool

Remarks

Consumed by GetMovementProgress(float, float): an agent that has run out of a partial path has stopped somewhere it was not asked to go, which is a stuck condition rather than an arrival.

LodSettings

Optional LOD settings for distance-based update throttling. When assigned, replaces the fixed 1-in-3 stagger with distance-based tiers: Active (nearby players), Nearby, Far, and Dormant (no observers). Null means always Active.

public AILodSettings LodSettings { get; }

Property Value

AILodSettings

MaxOffensiveReach

The longest reach among this NPC's offensive abilities, in metres. 0 when it knows none.

public float MaxOffensiveReach { get; }

Property Value

float

Remarks

What an archetype's spacing is checked against: a comfort distance no ability can attack from is not kiting, it is running away. Refreshed with the ability cache.

NpcRNG

The seeded RNG from the owning NPC. All AI randomisation should use this instead of DeterministicRNG.Shared so that NPC behaviour is fully deterministic given the same seed. Returns null for non-NPC characters.

public DeterministicRNG NpcRNG { get; }

Property Value

DeterministicRNG

OwningPet

The pet this controller drives, or null when it drives a normal NPC.

public Pet OwningPet { get; }

Property Value

Pet

PatrolState

Waypoint movement, or null when the archetype does not patrol.

public BaseAIState PatrolState { get; }

Property Value

BaseAIState

PendingState

The state that is about to become CurrentState, visible to the outgoing state's Exit(AIController).

public BaseAIState PendingState { get; }

Property Value

BaseAIState

Remarks

Exists so an attacking state can tell "combat is over" from "combat is continuing in a sub-state". Exit(AIController) clears the target and interrupts the cast, which is right when the NPC disengages and catastrophic when it does not: the melee archetype's flanking roll called ChangeState(GetBehindState), Exit wiped the target on the way out, and GetBehindState then found no target and dropped the NPC to idle. Every configured orbit / flank / strafe roll silently ended the fight.

Null outside of a transition.

Personality

Optional combat personality that biases ability selection via per-category score multipliers. When assigned, PickBestAbility(float) applies the personality's weight and bonus to each ability's score. Two NPCs with the same abilities but different personalities will favour different abilities in combat.

public AICombatPersonality Personality { get; }

Property Value

AICombatPersonality

PhysicsScene

The physics scene associated with this AI controller.

public PhysicsScene PhysicsScene { get; }

Property Value

PhysicsScene

RequestedDestination

The destination most recently requested through TryMoveTo(Vector3, bool, float).

public Vector3 RequestedDestination { get; }

Property Value

Vector3

RetreatState

Flee state, or null when the archetype fights to the death.

public BaseAIState RetreatState { get; }

Property Value

BaseAIState

ReturnHomeState

Leash return, or null when the archetype never leashes.

public BaseAIState ReturnHomeState { get; }

Property Value

BaseAIState

StateDeltaTime

Seconds covered by the state update currently executing.

public float StateDeltaTime { get; }

Property Value

float

Remarks

A state updates every updateRate seconds, not every brain tick, so a timer a state advances must use this rather than LastAiDeltaTime. See AIStateClock for what happened when it did not.

Target

The current target for the AI (e.g., enemy, destination). Setting this property updates the agent's destination.

public Transform Target { get; set; }

Property Value

Transform

TargetCharacter

The current combat target as an ICharacter, or null.

public ICharacter TargetCharacter { get; }

Property Value

ICharacter

Remarks

Prefer this over calling Target.GetComponent<ICharacter>(). The result is cached against the transform, so it costs a field read rather than an interface component lookup. Returns null when the pooled object behind the transform has been re-issued to a different character since targeting — see FishMMO.Shared.AIController.cachedTargetCharacterID — so every consumer's null check drops the stale target instead of attacking the pool's next occupant.

TargetReevaluationTimer

Per-NPC timer for mid-combat target re-evaluation. Delegates to TargetReevaluationTimer.

public float TargetReevaluationTimer { get; set; }

Property Value

float

VirtualCameraPosition

Virtual camera position used by the ability system to aim projectiles. Computed from the eye transform, aimed toward the current target's center. Mirrors the role of KCCController.VirtualCameraPosition for player characters.

public Vector3 VirtualCameraPosition { get; }

Property Value

Vector3

VirtualCameraRotation

Virtual camera rotation used by the ability system to aim projectiles. Points from the eye transform toward the current target's center. Mirrors the role of KCCController.VirtualCameraRotation for player characters.

public Quaternion VirtualCameraRotation { get; }

Property Value

Quaternion

WanderState

Random movement around the home position, or null when the archetype does not wander.

public BaseAIState WanderState { get; }

Property Value

BaseAIState

Methods

AgentIsUsable()

True when the NavMeshAgent can accept movement commands.

public bool AgentIsUsable()

Returns

bool

True if the agent is enabled and on a NavMesh.

Remarks

Unity rejects isStopped and SetDestination with an error for an agent that is disabled or not on a NavMesh. Spawn paths legitimately touch the brain around the moment an object is activated and placed, so guard rather than log a wall of errors.

AllowsLeashReset(AILodTier, bool, bool, float, float)

Whether a tier change may run the leash reset — interrupt, full heal, threat clear, boss phase reset — as a pure function so it is testable without a NetworkManager.

public static bool AllowsLeashReset(AILodTier newTier, bool inAttackingState, bool hasProximityMeasurement, float nearestPlayerSqrDistance, float leashResetSqrDistance)

Parameters

newTier AILodTier

Tier being transitioned to.

inAttackingState bool

True when the NPC is currently in a BaseAttackingState.

hasProximityMeasurement bool

False when nothing has measured the nearest player yet.

nearestPlayerSqrDistance float

Squared distance to the nearest player.

leashResetSqrDistance float

Squared distance a player must be beyond for the reset to be allowed.

Returns

bool

True when the reset may run.

Remarks

The reset is authoritative simulation state, so it asks proximity directly rather than trusting the tier that triggered it. The tier says how much WORK to do; only distance says whether a fight is really over.

Truth table, first matching row wins:

not in an attacking state → false. There is no fight to end.
tier below Far → false. The NPC is still being simulated in full.
no proximity measurement → false. Never hand a monster its health back on an unanswered question.
nearest player inside leashResetSqrDistance → false. Somebody is right there, whatever the network is streaming them.
otherwise → true.

ChangeState(BaseAIState, List<ICharacter>)

Changes the AI state, optionally providing targets for attacking states. Handles speed and state transitions.

public void ChangeState(BaseAIState newState, List<ICharacter> targets = null)

Parameters

newState BaseAIState

The new state to transition to.

targets List<ICharacter>

Optional list of targets for attacking states.

ClearPath()

Clears the agent's path without moving it.

public void ClearPath()

ClearPhaseOverrides()

Drops every boss phase override so the archetype's own slots show through again.

public void ClearPhaseOverrides()

FaceLookTarget(float)

Rotates the character to face the current look target smoothly.

public void FaceLookTarget(float deltaTime)

Parameters

deltaTime float

ForceTarget(ICharacter)

Forces this NPC onto a specific character immediately, entering combat if it is not already fighting.

public bool ForceTarget(ICharacter character)

Parameters

character ICharacter

The character to attack. Ignored when null or dead.

Returns

bool

True if the NPC took the new target.

Remarks

The scripted-aggro entry point, used by ApplyTauntAction. Distinct from setting Target directly, which changes who the NPC is fighting without putting it into a state that fights.

GetHealthPercent()

Returns this NPC's health as a fraction (0-1) of its maximum, or 1 when it has no health resource.

public float GetHealthPercent()

Returns

float

GetMovementProgress(float, float)

Classifies how the agent is doing against its destination, and accumulates the stuck timer.

public AIMovementProgress GetMovementProgress(float deltaTime, float tolerance = 1)

Parameters

deltaTime float

Seconds since the previous AI tick.

tolerance float

Arrival tolerance.

Returns

AIMovementProgress

The current progress classification.

Remarks

Call once per AI tick from a state that is trying to move. States that are deliberately standing still must not call it, or a stationary NPC would be reported stuck.

GetSqrDistanceToTarget()

Returns the squared distance from this NPC to its current target. Returns float.MaxValue if there is no target.

public float GetSqrDistanceToTarget()

Returns

float

HaltMovement()

Stops the NPC where it stands and forgets what it was doing, for a death.

public void HaltMovement()

Remarks

Called by Despawn() alongside disabling the controller. Disabling stops the brain; this stops the body: the agent's path is cleared, the target and look target are dropped so nothing re-engages, and the threat table is emptied. Without it the corpse's agent kept its destination and, when the brain was later re-enabled for the next pool occupant, was still heading for wherever its killer had been standing.

HasAbilityInRange(float)

Returns true if the NPC has at least one ability with range >= the given distance that is off cooldown and meets activation conditions.

public bool HasAbilityInRange(float minRange)

Parameters

minRange float

Minimum ability range required.

Returns

bool

HasArrived(float)

True when the agent has arrived at its destination.

public bool HasArrived(float tolerance = 1)

Parameters

tolerance float

Distance from the destination that counts as arrived.

Returns

bool

True if arrived.

Remarks

Requires an actual path to exist. Without that check, an agent whose destination never took reports remainingDistance == 0 and pathPending == false — which the naive test reads as "arrived" on the very first tick.

Initialize(Vector3, Vector3[])

Initializes the controller with a home position and waypoints. Sets agent dimensions and initial state.

public void Initialize(Vector3 home, Vector3[] waypoints = null)

Parameters

home Vector3

The home position for the AI.

waypoints Vector3[]

Optional waypoints for patrol.

InitializeOnce()

Initializes the controller and NavMeshAgent. Sets avoidance priority, speed, and movement states.

public override void InitializeOnce()

MeasureTickSpeedSqr(Vector3, Vector3, float)

The squared speed a displacement over one tick amounts to.

public static float MeasureTickSpeedSqr(Vector3 before, Vector3 after, float tickDelta)

Parameters

before Vector3

Position at the start of the tick.

after Vector3

Position the tick actually reached, after NavMesh projection.

tickDelta float

Seconds per tick.

Returns

float

Squared metres per second; zero for a non-positive tick.

Remarks

Separated so the stuck detector's input can be asserted directly.

OnDestroying()

Unsubscribes from global events on destroy to prevent memory leaks.

public override void OnDestroying()

OnStartNetwork()

Called when the network starts. Disables the controller if not running on the server.

public override void OnStartNetwork()

OnStopNetwork()

Releases the tick subscription.

public override void OnStopNetwork()

OnThreatReceived(ICharacter)

public void OnThreatReceived(ICharacter attacker)

Parameters

attacker ICharacter

PetStanceAllowsAutoEngage(bool)

Returns whether this NPC's pet stance permits engaging on its own. Always true for anything that is not a pet.

public bool PetStanceAllowsAutoEngage(bool requiresAggressive)

Parameters

requiresAggressive bool

True to require the Aggressive stance (hunting for a fight); false to accept anything except Passive (fighting back).

Returns

bool

True if the NPC may engage.

PickBestAbility(float)

Selects the best ability to use against the current target from the NPC's known abilities.

When an AbilityRotation is assigned, it is evaluated first. If it returns an ability, that ability is used. If no rotation entry matches and FallbackToDefault is true, the default scoring-based picker runs as a fallback.

Prefers abilities whose range covers the current distance. Among those, picks one at random weighted toward longer-cooldown (typically stronger) abilities. Returns null if no ability is usable (all on cooldown, out of resources, or no abilities known).

public Ability PickBestAbility(float preferredMaxRange = 3.4028235E+38)

Parameters

preferredMaxRange float

Maximum desired range. Abilities with range beyond this are still considered but deprioritized.

Returns

Ability

The chosen ability, or null if nothing is available.

PickBestAbility(float, Func<Ability, bool>)

Selects the best ability to use against the current target, optionally restricted to abilities matching a predicate.

public Ability PickBestAbility(float preferredMaxRange, Func<Ability, bool> filter)

Parameters

preferredMaxRange float

Maximum desired range.

filter Func<Ability, bool>

Optional predicate an ability must satisfy to be considered. Used by HealerAttackingState to keep heals out of the damage rotation.

Returns

Ability

The chosen ability, or null if nothing is available.

PickNearestWaypoint()

Picks the nearest waypoint to the current position and sets it as the destination.

public bool PickNearestWaypoint()

Returns

bool

True if a waypoint destination was set.

PickScoredAbility(float, Func<Ability, bool>, float)

Scores every usable known ability against a subject at the given squared distance and returns the highest scorer.

public Ability PickScoredAbility(float sqrDistanceToSubject, Func<Ability, bool> filter, float jitter)

Parameters

sqrDistanceToSubject float

Squared distance to whatever the ability will be aimed at.

filter Func<Ability, bool>

Optional predicate an ability must satisfy. Null accepts all.

jitter float

Maximum random score jitter, for variety.

Returns

Ability

The best-scoring usable ability, or null.

Remarks

Shared by the default enemy picker and by HealerAttackingState's heal picker, which scores against an ally's distance rather than the target's. Both previously carried their own near-identical copy of this loop.

ResetState(bool)

Resets the controller's state, clearing home, target, look target, and virtual camera.

public override void ResetState(bool asServer)

Parameters

asServer bool

Whether the reset is performed on the server.

ResolveAbilityReach(Ability)

How far ability can hit something from, for this NPC's body size.

public float ResolveAbilityReach(Ability ability)

Parameters

ability Ability

Returns

float

Remarks

Always use this rather than Range in AI code: the raw range is zero for anything that does not travel. See AIAbilityReach.

ResolveLodTier(AILodSettings, bool, float)

The tier rule, as a pure function so it is testable without a NetworkManager.

public static AILodTier ResolveLodTier(AILodSettings settings, bool hasProximityMeasurement, float nearestPlayerSqrDistance)

Parameters

settings AILodSettings

Authored LOD bands, or null.

hasProximityMeasurement bool

False when nothing has measured the nearest player yet.

nearestPlayerSqrDistance float

Squared distance to the nearest player.

Returns

AILodTier

The tier to run at.

Remarks

Truth table, first matching row wins:

no LOD settings → Active. Nothing has authored a throttle for this NPC.
no proximity measurement → Active. An unanswered question is not permission to suspend a brain; the answer arrives within one scheduling pass, and running one extra tier for half a second costs far less than a monster that stops thinking with a player in front of it.
measured → the authored distance bands, infinity (an empty scene) landing in Dormant.

ResolveTickHeading(Quaternion, Vector3, float, float, out Quaternion)

Turns a heading toward the direction of travel, bounded by an angular speed.

public static bool ResolveTickHeading(Quaternion current, Vector3 velocity, float angularSpeed, float tickDelta, out Quaternion result)

Parameters

current Quaternion

The current rotation.

velocity Vector3

The agent's velocity.

angularSpeed float

Maximum turn, in degrees per second.

tickDelta float

Seconds per tick.

result Quaternion

The rotation to apply.

Returns

bool

True if the heading changed.

Remarks

Mirrors what NavMeshAgent.updateRotation does per frame, on the tick instead. Vertical velocity is ignored so a slope does not pitch the character, and a velocity below HEADING_SPEED_THRESHOLD leaves the heading alone: an agent braking to a stop or being nudged by avoidance must not spin to face the nudge.

ResolveTickStep(Vector3, float)

The displacement one tick of travel at velocity covers.

public static Vector3 ResolveTickStep(Vector3 velocity, float tickDelta)

Parameters

velocity Vector3
tickDelta float

Returns

Vector3

Remarks

Separated so the tick-uniformity StepAgent relies on can be asserted directly.

Resume()

Resumes the agent's movement.

public void Resume()

SetPhaseOverrides(BaseAIState, AIBehaviorTree, AIAbilityRotation)

Puts a boss phase's overrides in front of the archetype's slots. A null argument leaves that slot's current override in place, so a later phase that only replaces the rotation keeps the attacking state an earlier phase installed.

public void SetPhaseOverrides(BaseAIState attackingState, AIBehaviorTree behaviorTree, AIAbilityRotation abilityRotation)

Parameters

attackingState BaseAIState

Attacking state for the phase, or null to keep the current one.

behaviorTree AIBehaviorTree

Behavior tree for the phase, or null to keep the current one.

abilityRotation AIAbilityRotation

Ability rotation for the phase, or null to keep the current one.

SetRandomDestination(float)

Sets a random destination within a radius around the current position.

public bool SetRandomDestination(float radius = 5)

Parameters

radius float

Radius to randomize destination.

Returns

bool

SetRandomHomeDestination(float)

Sets a random destination within a radius around the home position.

public bool SetRandomHomeDestination(float radius = 5)

Parameters

radius float

Radius to randomize destination.

Returns

bool

SetThrottledDestination(Vector3)

Throttled destination setter. Only calls UnityEngine.AI.NavMeshAgent.SetDestination(UnityEngine.Vector3) if enough time has elapsed since the last repath (controlled by RepathInterval). Use this for ongoing movement toward a moving target (chase, orbit, retreat) to prevent path recalculation spam. For one-time destinations (waypoint arrival, warp), use UnityEngine.AI.NavMeshAgent.SetDestination(UnityEngine.Vector3) directly.

public bool SetThrottledDestination(Vector3 position)

Parameters

position Vector3

The world position to navigate toward.

Returns

bool

True if the destination was updated, false if throttled.

ShouldAttemptReseat(ref float, float, float)

Counts down between off-mesh re-seat attempts and reports when one is due.

public static bool ShouldAttemptReseat(ref float timer, float tickDelta, float interval)

Parameters

timer float

Seconds until the next attempt; rearmed to interval when one is due.

tickDelta float

Seconds since the previous call.

interval float

Seconds between attempts.

Returns

bool

True when an attempt should be made now.

Remarks

Separated so the cadence can be asserted directly. The first call of an episode is due at once (the timer is zero when the agent is on the mesh); every later one waits interval, so a hopeless NPC costs one widening NavMesh sample a second, not thirty.

Stop()

Stops the agent's movement.

public void Stop()

TransitionToIdleState()

Transitions to the idle state.

public void TransitionToIdleState()

TransitionToNextWaypoint()

Transitions to the next waypoint in the waypoint array.

public bool TransitionToNextWaypoint()

Returns

bool

TransitionToRandomMovementState()

Transitions to a random movement state from the available movement states.

public virtual void TransitionToRandomMovementState()

TryGetNearestPlayerSqrDistance(out float)

Squared distance from this NPC to the nearest player in its scene.

public bool TryGetNearestPlayerSqrDistance(out float sqrDistance)

Parameters

sqrDistance float

Squared distance to the nearest player; infinity when the scene holds none.

Returns

bool

True when the measurement is valid.

Remarks

From ObserverStreamingRegistry, which measures viewer-to-object distance for every pair it considers anyway, before it applies any range or budget filter. That is a PROXIMITY question and this is the only place the server answers it honestly.

It used to be answered from NetworkObject.Observers. Observer membership is a BANDWIDTH decision — ObserverBudgetCondition admits the top thirty monsters per viewer and no more — so a monster evicted from every viewer's budget reported "no player is anywhere near me" while standing in the middle of a raid, and the tier machinery below full-healed it, cleared its threat table and reset its boss phases on the strength of it.

Returns false when no pass has measured this NPC yet (it registered between two passes, or the registry is not running at all, as in the simulation harness scenes). Callers must not read false as "nobody is near" — see ResolveLodTier(AILodSettings, bool, float) and AllowsLeashReset(AILodTier, bool, bool, float, float), both of which treat it as an unanswered question.

TryMoveTo(Vector3, bool, float)

Asks the agent to move to a world position, reporting whether the path actually reaches it.

public AIMovementResult TryMoveTo(Vector3 destination, bool throttle = true, float sampleRadius = 2)

Parameters

destination Vector3

Desired world position.

throttle bool

True to respect RepathInterval. Use for ongoing movement toward something that moves; pass false for one-shot destinations such as a waypoint or a spawn point, where a silently dropped request means the NPC never sets off at all.

sampleRadius float

Initial NavMesh sample radius.

Returns

AIMovementResult

What happened.

TryRecoverFromStuck(Vector3)

Attempts to free a stuck NPC, escalating with each call within the same episode.

public bool TryRecoverFromStuck(Vector3 fallback)

Parameters

fallback Vector3

Where to go if the original destination cannot be recovered — a pet's owner, an NPC's home. Pass the NPC's own position to mean "just get unstuck where you are".

Returns

bool

True if a recovery action was taken.

Remarks

First a nudge: re-sample the destination from a wider radius and repath, which clears the common case of two agents wedged against each other or a destination that sampled onto the wrong side of a wall. Then, once StuckWarpTimeout has elapsed, a warp — the only remedy for genuinely unreachable geometry.

Warping is a last resort rather than the first move on purpose: it is visible to players, so an NPC should be seen to try to walk before it blinks.

TrySampleNavMesh(Vector3, out Vector3, float)

Places a world position onto the NavMesh, widening the search until it lands or the attempts run out.

public static bool TrySampleNavMesh(Vector3 position, out Vector3 result, float initialRadius = 2)

Parameters

position Vector3

The desired world position.

result Vector3

The nearest position actually on the NavMesh.

initialRadius float

Radius for the first attempt.

Returns

bool

True if a NavMesh position was found.

Remarks

A single fixed-radius sample is the difference between "the NPC walks to a slightly different spot" and "the NPC does not move at all", and the old call sites all took the second outcome silently.

WarpTo(Vector3)

Places the agent at a world position, using UnityEngine.AI.NavMeshAgent.Warp(UnityEngine.Vector3) so the agent's internal NavMesh position is updated rather than only its transform.

public bool WarpTo(Vector3 position)

Parameters

position Vector3

The world position to place the agent at.

Returns

bool

True if the agent was placed on the NavMesh.

Remarks

Required for object pooling. A recycled NPC is reactivated at a new position, and assigning transform.position alone leaves the agent believing it is still where the previous occupant died — it then either refuses to path or walks back toward the old location.