Table of Contents

Class AggressionController

Namespace
FishMMO.Shared
Assembly
FishMMO.Shared.dll

Manages an aggression (threat) table for a single NPC. Tracks threat from damage, healing, resource expenditure, and arbitrary point adjustments. Threat decays over time. Target selection uses both raw threat points and a vulnerability multiplier based on the target's current health and mana percentages.

Plain C# class — one instance per NPC, owned by AggressionState.

public class AggressionController
Inheritance
AggressionController
Inherited Members

Fields

DamageWeight

Points per 1 damage dealt to the NPC.

public float DamageWeight

Field Value

float

DecayRate

Points per second each entry decays while no new events arrive.

public float DecayRate

Field Value

float

HealingWeight

Points per 1 healing witnessed on an enemy of the NPC.

public float HealingWeight

Field Value

float

HitBonusPoints

Flat points added per hit regardless of damage amount.

public float HitBonusPoints

Field Value

float

LowHealthThreatMultiplier

Multiplier applied to threat points for targets below 30% health. Makes the AI prefer to finish off wounded enemies.

public float LowHealthThreatMultiplier

Field Value

float

LowHealthThreshold

Health threshold (0-1) below which LowHealthThreatMultiplier activates.

public float LowHealthThreshold

Field Value

float

LowResourceThreatMultiplier

Multiplier applied to threat points for targets below 20% mana. Makes the AI pressure casters who are running out of resources.

public float LowResourceThreatMultiplier

Field Value

float

LowResourceThreshold

Resource threshold (0-1) below which LowResourceThreatMultiplier activates.

public float LowResourceThreshold

Field Value

float

ResourceWeight

Points per 1 resource point (mana/stamina) spent casting near the NPC.

public float ResourceWeight

Field Value

float

StaleEntryTimeout

Seconds after last event before a zero-point entry is removed.

public float StaleEntryTimeout

Field Value

float

TargetVarietyChance

Chance (0-1) to pick a secondary target for variety.

public float TargetVarietyChance

Field Value

float

Properties

Clock

Seconds of AI time this table has observed, advanced only by Tick(float).

public float Clock { get; }

Property Value

float

Remarks

The staleness clock used to be UnityEngine.Time.time — Unity's wall clock. That tied threat expiry to real elapsed time while the decay it is paired with advanced on the AI tick, so the two disagreed whenever the two rates did: an NPC throttled down to the Far LOD tier decayed its threat slowly but expired entries at full speed, and a server hitch expired threat for NPCs that had not run a single update during it.

Deriving both from the same tick-advanced clock makes expiry mean "this many seconds of AI time without an event", which is what the tuning value has always claimed to mean, and makes the whole table reproducible from a tick count.

Count

Number of entries currently tracked in the aggression table.

public int Count { get; }

Property Value

int

HasAggression

Returns true if any characters are tracked in the table.

public bool HasAggression { get; }

Property Value

bool

MaximumVulnerabilityMultiplier

The largest VulnerabilityMultiplier(ICharacter) any character could currently receive.

public float MaximumVulnerabilityMultiplier { get; }

Property Value

float

Remarks

The bound a taunt needs. The table stores raw points and holds no character references, so it cannot compute another entry's SCORE — but score is bounded above by raw * this for every entry, so beating highestRaw * this beats every actual score whichever entry happens to carry it. Both multipliers can apply at once (a caster that is both wounded and out of mana), so they compound here exactly as they do there.

Floored at the neutral 1 per factor, so a multiplier a designer tuned BELOW one cannot turn this bound into an under-estimate.

Table

The raw aggression table keyed by character ID. Read-only.

public IReadOnlyDictionary<long, AggressionEntry> Table { get; }

Property Value

IReadOnlyDictionary<long, AggressionEntry>

Methods

AddPoints(long, float)

Adds arbitrary points (positive for taunt, negative for de-aggro).

public void AddPoints(long characterId, float points)

Parameters

characterId long
points float

Clear()

public void Clear()

GetEntry(long)

Returns the aggression entry for a character, or null if not tracked.

public AggressionEntry GetEntry(long characterId)

Parameters

characterId long

Returns

AggressionEntry

GetHighestPoints(long)

Returns the highest raw threat currently held by any tracked character, optionally ignoring one of them.

public float GetHighestPoints(long excludeCharacterId = 0)

Parameters

excludeCharacterId long

Character to leave out of the comparison, or 0 for none.

Returns

float

The highest threat points, or 0 when nothing is tracked.

Remarks

Used by ApplyTauntAction to place a taunter decisively on top rather than adding a flat bonus that a long fight has already outgrown.

GetPoints(long)

Returns raw threat points for a character, or 0 if not tracked.

public float GetPoints(long characterId)

Parameters

characterId long

Returns

float

GetThreatScore(long, ICharacter)

Computes a threat score for a character, factoring in vulnerability. Low health and low mana targets get a multiplier so the AI finishes weak enemies and pressures casters running out of resources.

public float GetThreatScore(long characterId, ICharacter character)

Parameters

characterId long
character ICharacter

Returns

float

Remarks

This, not GetPoints(long), is what decides who an NPC attacks — see PickTarget(List<ICharacter>, DeterministicRNG). Anything that means to move a character up or down the target order has to reason in this space; ApplyTauntAction compared raw points and its "guarantee" was therefore not one.

PickTarget(List<ICharacter>, DeterministicRNG)

Selects the best target from candidates using threat scoring with vulnerability.

public ICharacter PickTarget(List<ICharacter> candidates, DeterministicRNG rng = null)

Parameters

candidates List<ICharacter>
rng DeterministicRNG

Returns

ICharacter

RecordDamage(long, int)

Records damage dealt to this NPC.

public void RecordDamage(long attackerId, int amount)

Parameters

attackerId long
amount int

RecordHealing(long, int)

Records healing witnessed by this NPC on one of its enemies.

public void RecordHealing(long healerId, int amount)

Parameters

healerId long
amount int

RecordResourceSpent(long, int)

Records resource expenditure (mana/stamina) from a character casting near this NPC. Casters who spend heavily to damage or heal draw additional threat.

public void RecordResourceSpent(long characterId, int amount)

Parameters

characterId long
amount int

RemoveEntry(long)

Removes a single character's entry, returning it to the pool. No-op when the character is not tracked — importantly, it does not create an entry in order to remove it.

public bool RemoveEntry(long characterId)

Parameters

characterId long

The character whose threat should be forgotten.

Returns

bool

True if an entry was removed.

Reset()

Clears all entries and returns them to the pool.

public void Reset()

ShouldSwitchTarget(long, long, float)

Returns true if candidate should replace current target based on threat delta.

public bool ShouldSwitchTarget(long currentId, long candidateId, float threshold = 50)

Parameters

currentId long
candidateId long
threshold float

Returns

bool

Tick(float)

Decays all entries and removes stale ones.

public void Tick(float deltaTime)

Parameters

deltaTime float

VulnerabilityMultiplier(ICharacter)

The vulnerability scaling GetThreatScore(long, ICharacter) applies to a character's raw points.

public float VulnerabilityMultiplier(ICharacter character)

Parameters

character ICharacter

The character to weigh. Null scores no scaling.

Returns

float

The multiplier, never less than the neutral 1.

Remarks

Split out of GetThreatScore(long, ICharacter) so a caller that needs to work backwards from a desired SCORE to the raw points that produce it uses the same rule the forward direction does, rather than a second copy of it that can drift.