Table of Contents

Class HealerAttackingState

Namespace
FishMMO.Shared
Assembly
FishMMO.Shared.dll

Healer archetype. Keeps caster spacing from the enemy, but interrupts its damage rotation to top up the most wounded nearby ally.

[CreateAssetMenu(fileName = "New AI Healer Attacking State", menuName = "FishMMO/Character/NPC/AI/Healer Attacking State", order = 4)]
public class HealerAttackingState : BaseAttackingState, ICachedObject
Inheritance
Object
ScriptableObject
HealerAttackingState
Implements
Inherited Members
ScriptableObject.SetDirty()
ScriptableObject.CreateInstance<T>()
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.ToString()
Object.name
Object.hideFlags

Remarks

This is one of the three archetypes that genuinely needs code rather than tuning: it has to scan for a second, friendly target that the shared combat decision knows nothing about. Everything after "no ally needs healing" falls through to BaseAttackingState's shared logic.

Heals identify themselves. An ability counts as a heal when AIAbilityClassifier finds a healing action in its ECA graph — no list of template IDs on the archetype, no naming convention. That matters because the list version was a second copy of a fact the ability asset already stated, and the two drifted silently: a designer adding a third heal got a healer that went on casting only the two it had been told about, with nothing logged and nothing to notice. It also stopped one healer asset being shared by creatures with different spellbooks, which is most of them.

HealAbilityTemplateIDs survives as an override for abilities the classifier reads wrongly. Leave it empty in the normal case.

Fields

AllyLayers

The physics layers to check when scanning for allies. Should include the layers that allied characters (other NPCs, pets, players) are on.

[Header("Healer Behavior")]
[Tooltip("Physics layers to scan for allies.")]
public LayerMask AllyLayers

Field Value

LayerMask

AllyScanInterval

Seconds between ally scans.

[Tooltip("Seconds between ally scans. Higher is cheaper; health changes slowly enough that this can be coarse.")]
public float AllyScanInterval

Field Value

float

Remarks

The ally scan is an OverlapSphere plus an interface component lookup per hit, and it used to run on every single combat tick — so a healer cost roughly twice what any other archetype cost, purely to re-answer a question whose answer barely changes between ticks. Health does not move fast enough for a fifth of a second of staleness to matter, and the result is cached in between.

AllyScanRadius

Radius within which to scan for injured allies.

[Tooltip("Radius within which to scan for allies.")]
public float AllyScanRadius

Field Value

float

CanHealSelf

Whether the healer counts itself as a heal candidate when it is the most wounded character in range.

[Tooltip("Allow the healer to heal itself when it is the most wounded character in range.")]
public bool CanHealSelf

Field Value

bool

HealAbilityTemplateIDs

Optional escape hatch: template IDs to treat as heals in addition to whatever AIAbilityClassifier identifies.

[Tooltip("Optional. Extra AbilityTemplate IDs to force-treat as heals. Normally empty — heals are detected from the ability's ECA actions.")]
public List<int> HealAbilityTemplateIDs

Field Value

List<int>

Remarks

Normally empty. Heals are recognised from the ability's own ECA actions; this is only for an ability that heals by some route the classifier cannot see, such as a project specific action type.

HealThreshold

Health percentage (0-1) below which an ally is considered injured and eligible for healing. E.g., 0.8 means heal allies below 80% health.

[Range(0, 1)]
[Tooltip("Heal allies whose health is below this fraction of max.")]
public float HealThreshold

Field Value

float

Methods

Enter(AIController)

Called when entering the healer attacking state.

public override void Enter(AIController controller)

Parameters

controller AIController

The AI controller.

PickAbility(AIController)

Excludes heal abilities from the damage picker so the healer never "attacks" with a heal.

protected override Ability PickAbility(AIController controller)

Parameters

controller AIController

The AI controller.

Returns

Ability

The best damage ability, or null.

TryAttack(AIController, ICharacter)

Heals first, fights second. Falls through to the shared combat logic when nobody needs healing or no heal is available.

protected override void TryAttack(AIController controller, ICharacter targetCharacter)

Parameters

controller AIController

The AI controller.

targetCharacter ICharacter

The current enemy target.