Table of Contents

Class DamageNegationBuffTemplate

Namespace
FishMMO.Shared
Assembly
FishMMO.Shared.dll

Buff template that takes damage off incoming hits — the Block half of block and deflect.

[CreateAssetMenu(fileName = "New Damage Negation Buff Template", menuName = "FishMMO/Character/Buff/Damage Negation Buff", order = 3)]
public class DamageNegationBuffTemplate : BaseBuffTemplate, ICachedObject, ITooltip
Inheritance
Object
ScriptableObject
DamageNegationBuffTemplate
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
Extension Methods

Remarks

Passive, and queried rather than applied. Unlike AttributeBuffTemplate this writes nothing to the attribute ledger: negation is not a stat, it is a decision taken while one specific hit is being resolved, and it needs the attacker's position to answer the facing question. DamageMitigation.Negate is what reads it, from inside CharacterDamageController.Damage.

The three modes are the three block abilities. A channelled shield holds Reduce or Immune for as long as the button is down, because a channel re-applies its buff every tick and a duration-only buff refreshes cleanly. A consumable barrier — "absorbs 500 damage, then vanishes" — is Absorb. See the block-and-deflect section of the buff README for how to wire either through ECA.

Facing is what makes a shield a shield. With RequiresFacing set, only damage arriving from within FacingAngleDegrees of where the defender is looking is negated — so a player who blocks the sword in front still takes the arrow in the back. The test runs server-side against real positions; see DamageMitigation for why it cannot be lag compensated and why that is the right answer here.

Fields

Amount

The pool size for Absorb, or the percentage for Reduce. Unused by Immune.

[Tooltip("Absorb: damage points the pool holds. Reduce: percent taken off each hit (0-100). Immune: unused.")]
[Min(0)]
public int Amount

Field Value

int

Remarks

One field for both because a template is only ever in one mode, and two fields would let a designer fill in the one the mode does not read and wonder why nothing happened.

FacingAngleDegrees

Total spread of the protected arc, in degrees — 120 means 60 either side of forward.

[Tooltip("Total spread of the protected arc in degrees. 120 protects 60 degrees either side of forward.")]
[Range(0, 360)]
public float FacingAngleDegrees

Field Value

float

Remarks

Measured the same way IsWithinCone(Vector3, Vector3, Vector3, float) measures every other cone in the project, so "in front" means one thing across targeting and mitigation. An attacker standing exactly on the defender is outside every cone, which is the same rule and the same reason: a direction test cannot be satisfied by a point with no direction.

Mode

How this buff takes damage off a hit.

[Tooltip("Absorb spends a pool of damage points and ends at zero. Reduce takes a percentage off every hit. Immune negates every hit.")]
public DamageNegationMode Mode

Field Value

DamageNegationMode

RequiresFacing

True to negate only damage arriving from in front of the defender.

[Tooltip("Negate only damage arriving from within FacingAngleDegrees of where the defender is looking.")]
public bool RequiresFacing

Field Value

bool

Shield

The physical shield this buff raises: a real volume with real dimensions, standing where the character holds it.

[Tooltip("Physical shield volume. Ability objects that strike inside it are stopped outright, whatever the mode above.")]
public ShieldVolume Shield

Field Value

ShieldVolume

Remarks

A different question from the arc above, answered for a different kind of damage. RequiresFacing gates the MITIGATION — how much is taken off damage that reaches the character, whether that is a sword, a burning ground or an explosion, none of which has a meaningful impact point on a shield. This volume gates PHYSICAL INTERCEPTION: an ability object whose impact lands inside it never touched the character at all, so it is stopped outright rather than reduced, and the mode above never comes into it.

Leaving Shape at None gives a buff that only mitigates — a ward or a damage shield rather than a held shield. Setting it gives an object in the world that arrows stop against, and it is the same shape ShieldInterceptAction sweeps for things in flight.

VolumeBlockCost

Charges spent each time Shield stops something. Zero — the default — means blocking does not wear the buff down.

[Tooltip("Charges spent per object the shield volume stops. 0 means blocking never wears it down.")]
[Min(0)]
public int VolumeBlockCost

Field Value

int

Remarks

The knob that separates a shield bounded by TIME from one bounded by PUNISHMENT. Zero suits a channelled block, which ends when the player releases it. A positive value against Absorb makes each stopped projectile eat that much of the pool, so a barrier that has already turned three arrows has visibly less left in it.

Properties

InitialCharges

The pool a fresh application carries, which is Amount in Absorb and nothing in the other two.

public override int InitialCharges { get; }

Property Value

int

Remarks

This is what makes the buff "disappear when the remaining amount hits 0": a non-zero value is what IsSpent tests, and the reduce and immune modes must NOT report one or they would be removed the first time anything asked.

Methods

OnApply(Buff, ICharacter)

Nothing to install. The pool is seeded by RefreshCharges() from InitialCharges, and the negation itself is read at damage time.

public override void OnApply(Buff buff, ICharacter target)

Parameters

buff Buff
target ICharacter

Remarks

Deliberately empty rather than absent: OnApply(Buff, ICharacter) is abstract, and a passive buff having nothing to do on apply is a real answer rather than an omission.

OnApplyStack(Buff, ICharacter)

Refills the pool when a stack lands, so holding or re-casting a shield restores it.

public override void OnApplyStack(Buff buff, ICharacter target)

Parameters

buff Buff
target ICharacter

Remarks

Buff.AddStack calls this BEFORE incrementing Stacks, so the count after the operation is Stacks + 1 — the same off-by-one OnApplyStack(Buff, ICharacter) documents. Refreshing to the post-operation count is what makes two stacks hold twice the damage rather than the same pool twice.

OnRemove(Buff, ICharacter)

Nothing to reverse — see OnApply(Buff, ICharacter).

public override void OnRemove(Buff buff, ICharacter target)

Parameters

buff Buff
target ICharacter

OnRemoveStack(Buff, ICharacter)

Trims the pool back to what the remaining stacks are worth.

public override void OnRemoveStack(Buff buff, ICharacter target)

Parameters

buff Buff
target ICharacter

Remarks

Buff.RemoveStack decrements after this returns, so the post-operation count is Stacks - 1. Never RAISES the pool: a shield that has already spent most of its charge must not be topped up by losing a stack.

ResolveNegation(int, int)

Damage this buff takes off incoming, without spending anything.

public int ResolveNegation(int incoming, int remainingCharges)

Parameters

incoming int

Damage remaining after resistances and any earlier negation.

remainingCharges int

The buff instance's pool, for Absorb.

Returns

int

How much of incoming this buff would take off.

Remarks

Pure, so the whole mitigation rule can be exercised without a character, a buff container or a physics scene — the same reason LagCompensationTick.ResolveAnchor and CharacterPredictionController.IsTransformRedundant are shaped this way. The caller is responsible for the facing test and for spending the pool; this only answers "how much".

SecondaryTooltip(TooltipContent)

Appends a secondary tooltip describing what this buff blocks.

public override void SecondaryTooltip(TooltipContent content)

Parameters

content TooltipContent