Table of Contents

Class CharacterDamageController

Namespace
FishMMO.Shared
Assembly
FishMMO.Shared.dll

Controls damage, healing, kill, and resurrection logic. Handles resistance calculation, ECA trigger dispatch, immortal state, combat state transitions, and combat-escape prevention via the IsInCombat flag.

public class CharacterDamageController : CharacterBehaviour, ICharacterDamageController, ICharacterBehaviour, IDamageable, IHealable
Inheritance
Object
Component
Behaviour
MonoBehaviour
NetworkBehaviour
CharacterDamageController
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

Properties

CombatDurationTicks

Gets the configured combat duration in ticks.

public uint CombatDurationTicks { get; }

Property Value

uint

Immortal

Gets or sets whether the character is immortal (cannot be damaged or killed).

public bool Immortal { get; set; }

Property Value

bool

IsAlive

Returns true if the character is alive (resource attribute's current value is above zero).

public bool IsAlive { get; }

Property Value

bool

IsInCombat

Gets whether this character is currently in combat (within the combat duration window).

public bool IsInCombat { get; }

Property Value

bool

LastCombatTick

Gets the tick of the last combat action.

public uint LastCombatTick { get; }

Property Value

uint

OnDamageTriggers

Triggers invoked when this character deals damage to another. EventData: DamageEventData.

public List<Trigger> OnDamageTriggers { get; }

Property Value

List<Trigger>

OnDamagedTriggers

Triggers invoked when this character receives damage from another. EventData: DamageEventData.

public List<Trigger> OnDamagedTriggers { get; }

Property Value

List<Trigger>

OnHealTriggers

Triggers invoked when this character heals another. EventData: HealEventData.

public List<Trigger> OnHealTriggers { get; }

Property Value

List<Trigger>

OnHealedTriggers

Triggers invoked when this character is healed by another. EventData: HealEventData.

public List<Trigger> OnHealedTriggers { get; }

Property Value

List<Trigger>

OnKillTriggers

Triggers invoked when this character kills another. EventData with TargetCharacter set.

public List<Trigger> OnKillTriggers { get; }

Property Value

List<Trigger>

OnKilledTriggers

Triggers invoked when this character is killed by another. EventData with TargetCharacter set.

public List<Trigger> OnKilledTriggers { get; }

Property Value

List<Trigger>

OnResurrectTriggers

Triggers invoked when this character resurrects another. EventData with TargetCharacter set.

public List<Trigger> OnResurrectTriggers { get; }

Property Value

List<Trigger>

OnResurrectedTriggers

Triggers invoked when this character is resurrected by another. EventData with TargetCharacter set.

public List<Trigger> OnResurrectedTriggers { get; }

Property Value

List<Trigger>

ResourceInstance

Gets the cached health resource attribute for this character. Returns null when the attribute controller or health attribute is missing, reporting the first occurrence only.

public CharacterResourceAttribute ResourceInstance { get; }

Property Value

CharacterResourceAttribute

Methods

ApplyModifiers(ICharacter, int, DamageAttributeTemplate)

Applies resistance modifiers to the damage amount for the target character. Subtracts the target's resistance value from the incoming damage and clamps the result.

If target has no ICharacterAttributeController or damageAttribute is null, the resistance lookup is skipped and the original amount is returned unchanged — absence of resistance metadata means untyped damage, not immunity.

public int ApplyModifiers(ICharacter target, int amount, DamageAttributeTemplate damageAttribute)

Parameters

target ICharacter

The character receiving damage.

amount int

The base damage amount.

damageAttribute DamageAttributeTemplate

The damage type being applied.

Returns

int

The modified damage amount after resistance is applied.

ClearCombatContributions()

Drops all contribution bookkeeping in both directions.

public void ClearCombatContributions()

CompleteHeal()

Fully restores this character's health resource to its maximum (final) value. Does nothing if the character is dead.

public void CompleteHeal()

Damage(ICharacter, int, DamageAttributeTemplate, bool)

Applies damage to this character from an attacker. Handles resistance calculation, kill detection, combat state, and ECA trigger dispatch. Does nothing if the character is immortal or already dead. Resistance-reduced damage below 1 is silently discarded.

public void Damage(ICharacter attacker, int amount, DamageAttributeTemplate damageAttribute, bool ignoreAchievements = false)

Parameters

attacker ICharacter

The character dealing damage, or null for environmental damage.

amount int

Base damage before resistance is applied.

damageAttribute DamageAttributeTemplate

The damage type; determines which resistance stat is checked.

ignoreAchievements bool

If true, suppresses ECA trigger dispatch for this hit.

EnterCombat()

Enters combat state, refreshing the timer. Sets IsInCombat and records the current tick. Safe to call every combat action — repeated calls within the combat window simply refresh the expiry.

public void EnterCombat()

EvaluateCombatTimer(uint, uint, ref uint)

Decides whether the combat window has elapsed, tolerating a reference tick that moves backwards.

public static CharacterDamageController.CombatTimerStep EvaluateCombatTimer(uint currentTick, uint combatDurationTicks, ref uint lastCombatTick)

Parameters

currentTick uint

The tick to evaluate against.

combatDurationTicks uint

Ticks of inactivity before combat ends.

lastCombatTick uint

Tick of the last combat action; re-baselined on regression.

Returns

CharacterDamageController.CombatTimerStep

What the caller should do about this tick.

Remarks

Pure and static so the arithmetic can be proven in isolation — the surrounding behaviour needs a live FishNet TimeManager, which would otherwise make this logic untestable.

The subtraction is unsigned, so a regression of even one tick becomes a value near MaxValue, trivially satisfies the expiry test, and silently drops the character out of combat. That is the state the teleport gate and the combat-logout hold both key off, so it reads as a combat-escape exploit rather than a clock glitch.

The regression is not hypothetical. ResolveCurrentCombatTick prefers the owner's replicate tick, which in client-side prediction runs AHEAD of the server's local tick; the moment ownership is removed — which is exactly what starting a combat-logout linger does — IsController flips true on the server and the resolver falls back to that slower local tick. A client hitch or a reconnect moves it backwards the same way.

Re-baselining rather than expiring means the character stays in combat and the window is measured afresh from the new domain, so an ownership handover costs the player a fresh combat window instead of instantly clearing their combat state.

HasCombatContributor(long)

True when characterID has contributed to this character's current combat.

public bool HasCombatContributor(long characterID)

Parameters

characterID long

The character to look for.

Returns

bool

Remarks

A peek, unlike TryConsumeContributors(out List<long>), which hands the list out and clears it — this is asked once per viewer per observer-scheduling pass and must not disturb the loot-rights ledger it reads.

Exists so observer streaming can pin the characters a player is actually fighting. The visibility budget can otherwise despawn an enemy mid-fight when it is neither the player's current target nor a party member, and a despawned victim takes its combat reports with it — no damage numbers, and the player's own predicted numbers greying out as unconfirmed while every hit is landing.

Heal(ICharacter, int, bool)

Heals this character by the specified amount. Events and ECA triggers are only fired when healing actually changes the resource value; healing a dead character, healing for zero, or attempting to heal a full-health character are all silent no-ops. If the target is in combat, the healer also enters combat.

public void Heal(ICharacter healer, int amount, bool ignoreAchievements = false)

Parameters

healer ICharacter

The character providing the healing, or null.

amount int

The amount to heal.

ignoreAchievements bool

If true, suppresses ECA trigger dispatch.

Kill(ICharacter)

Kills this character. Handles faction rewards, ECA triggers, ability cancellation, death animation, and the OnKilled event. Buff removal and pet despawning are handled by the server-side OnKilled subscriber (CharacterSystem.Connection.cs).

public void Kill(ICharacter killer)

Parameters

killer ICharacter

The character responsible for the kill, or null for non-player kills.

OnStartNetwork()

Caches the prediction controller reference and subscribes to tick events for combat timer management.

public override void OnStartNetwork()

OnStopNetwork()

Unsubscribes from tick events and clears cached references.

public override void OnStopNetwork()

PropagateCombatContribution(ICharacter)

Extends this character's own contribution credit to supporter.

public void PropagateCombatContribution(ICharacter supporter)

Parameters

supporter ICharacter

The character to extend credit to.

Remarks

This is how healing earns loot rights. A healer never touches the victim, so there is nothing for RecordCombatContribution(ICharacter, CombatContributionKind) to key off; instead the character who WAS healed pushes its credit outward to everyone still fighting whatever it is fighting. Called on the healed character, not on the victim.

RecordCombatContribution(ICharacter, CombatContributionKind)

Credits contributor with a share of this character's death.

public void RecordCombatContribution(ICharacter contributor, CombatContributionKind kind)

Parameters

contributor ICharacter

The character whose action earned the credit.

kind CombatContributionKind

How the credit was earned.

Remarks

Server-only, and idempotent per contributor — a thousand hits earn the same single share as one. Credit is resolved to the controlling player, so a pet's damage counts for its owner; contributions from anything that is not ultimately a player are discarded, since only a player can open a loot window.

ResetState(bool)

Clears the cached health resource attribute reference and combat state so it is re-resolved on the next access. Prevents a stale object reference if the CharacterAttributeController is re-initialized (character pooling, hot-reload, or any scenario where attribute instances are recreated).

public override void ResetState(bool asServer)

Parameters

asServer bool

Revive(ICharacter, int)

Revives (resurrects) a dead character, ADDING the given amount to its health. Equivalent to setting it only when the character is at zero, which a dead one is. Unlike Heal(), this works when CurrentValue is 0. Resets death animation.

public void Revive(ICharacter resurrector, int amount)

Parameters

resurrector ICharacter

The character performing the resurrection, or null.

amount int

The amount of health to restore.

TryConsumeContributors(out List<long>)

Takes the accumulated contributor list and clears it.

public bool TryConsumeContributors(out List<long> contributorIDs)

Parameters

contributorIDs List<long>

Returns

bool

True when at least one contributor was credited.

Remarks

Consuming rather than reading keeps the death path single-shot: whoever takes the list owns it, and a second call cannot hand the same corpse's loot rights out twice.

Events

OnCombatEventReceived

Raised on a client for each combat event the server reported against a character.

public static event Action<ICharacter, ICharacter, int, DamageAttributeTemplate, CombatEventKind, int> OnCombatEventReceived

Event Type

Action<ICharacter, ICharacter, int, DamageAttributeTemplate, CombatEventKind, int>

Remarks

Deliberately separate from OnDamaged. That event fires wherever the arithmetic happens: on the server for everything, and additionally on the owning client for its own predicted damage-over-time ticks. Feeding floating text from it produced no numbers at all for the common case — an ability's hit is resolved only on the server, whose events reach no client — and duplicate numbers for the case it did cover, since the owner re-ran its own ticks on every reconcile replay.

This event is raised only from the server's report, so a number appears exactly once, for the amount that actually landed, on every client that can see the target.

Arguments: source (may be null for environmental damage or an unobserved attacker), target, amount, damage type (null for heals), the kind, and how many separate hits the amount was merged from — always at least one. The last is what lets the caster's display settle every predicted label a coalesced report stands for rather than only the first.