Table of Contents

Class DamageMitigation

Namespace
FishMMO.Shared
Assembly
FishMMO.Shared.dll

Reads a defender's block and deflect buffs and answers the two questions combat asks of them: how much of this hit is taken off, and does this object get turned away.

public static class DamageMitigation
Inheritance
DamageMitigation
Inherited Members

Remarks

One place, because the two halves must not drift. Negation is applied by CharacterDamageController.Damage and deflection by AbilityObject.ApplyHit — different subsystems, different ticks in the frame — but both walk the same buff container, both need the same "is this coming at my front" test, and both have to spend charges the same way or a shield ends at a different moment depending on which one used it last.

Deterministic order, for free. IBuffController.Buffs is a SortedDictionary<TKey, TValue> keyed by template id, so every peer walks a defender's shields in the same order without this file having to sort anything. That matters because two absorb shields spend in sequence, and which one empties first is observable.

Live positions, no rewind, deliberately. The facing test asks where the DEFENDER was looking, and the defender is not the peer whose view is being reconstructed — a rewind reproduces what the ATTACKER saw. Rewinding the defender's own facing to the attacker's view would let a player be hit through a shield they had already turned towards the attacker, and there is nothing to gain: the shield is the defender's decision about the defender's own character, taken on the tick the server resolves the hit.

Mutation is gated by the caller, not here. Negate(ICharacter, ICharacter, int, bool) and TryDeflect(ICharacter, Vector3, Vector3, bool, out Vector3) both take a mutate flag: a peer that does not simulate this defender's buffs may still want the NUMBER (so the caster's client can predict a blocked hit honestly) but must never spend a pool it does not own. See IBuffController.SimulatesBuffEffects.

Methods

CollectShieldVolumes(ICharacter, List<ShieldVolume>)

Every shield volume defender currently has raised.

public static void CollectShieldVolumes(ICharacter defender, List<ShieldVolume> into)

Parameters

defender ICharacter

The character whose shields are wanted.

into List<ShieldVolume>

Receives the active volumes. Not cleared first.

Remarks

For the outward-looking half — ShieldInterceptAction sweeps these for objects in flight. Appended rather than returned, so the caller owns the list and a per-tick query allocates nothing.

Negate(ICharacter, ICharacter, int, bool)

Damage left after every qualifying negation buff on defender has taken its share.

public static int Negate(ICharacter defender, ICharacter attacker, int amount, bool mutate)

Parameters

defender ICharacter

The character taking the hit. Null returns amount.

attacker ICharacter

Who is hitting them, for the facing test. Null skips facing-gated buffs.

amount int

Damage remaining after resistances.

mutate bool

True to spend absorb pools and remove emptied buffs. False to answer the question without touching state — for a peer that does not simulate this defender.

Returns

int

The damage that survives, never below zero and never above amount.

Remarks

Modes are applied in the order that makes each one mean what it says: Immune first, because an immunity that ran after a shield would let the shield spend charges on damage that was never going to land; then Reduce, so a percentage is taken off the full hit rather than off whatever a pool happened to leave; then Absorb pools, which spend what actually remains.

A pool that empties is REMOVED, which is the "disappears when the remaining damage negation amount hits 0" behaviour — and it is removed through IBuffController.Remove rather than by dropping the entry, so the strip, the FX and the observer push all hear about it exactly as they would for an expiry.

TryBlockAtVolume(ICharacter, Vector3, bool)

True when an ability object striking localImpactPoint met one of defender's raised shields rather than the defender.

public static bool TryBlockAtVolume(ICharacter defender, Vector3 localImpactPoint, bool mutate)

Parameters

defender ICharacter

The character the object struck.

localImpactPoint Vector3

Impact point in the defender's local space.

mutate bool

True to spend the block cost and remove a shield it empties.

Returns

bool

True when a shield took the hit and the object must not be treated as having landed.

Remarks

Interception, not mitigation. A hit that lands inside a shield volume never reached the character, so there is no damage to reduce and the buff's DamageNegationMode does not enter into it. The caller stops the object outright.

Local space, and that is what makes it correct. The point comes from LocalPoint or LocalPoint, captured inside the rewind scope and expressed in the defender's own frame; the volume is authored in that same frame. Neither has to be transformed, so neither can be read out of the wrong world — the trap that a world-space volume compared against a rewound impact point falls straight into, since hits are dispatched only after the scope has closed.

The FIRST shield in template order that covers the point wins and is the one charged, so two overlapping shields do not both pay for one projectile.

TryDeflect(ICharacter, Vector3, Vector3, bool, out Vector3)

True when defender turns away an object arriving along incomingHeading, and if so what heading it leaves on.

public static bool TryDeflect(ICharacter defender, Vector3 incomingHeading, Vector3 impactNormal, bool mutate, out Vector3 deflectedHeading)

Parameters

defender ICharacter

The character the object is about to hit.

incomingHeading Vector3

The direction the object is travelling.

impactNormal Vector3

Surface normal the server measured at the impact.

mutate bool

True to spend a deflection charge and remove an emptied buff.

deflectedHeading Vector3

The heading to redirect along. Meaningless when this returns false.

Returns

bool

True when the object was deflected and must not be treated as a hit.

Remarks

The FIRST qualifying buff in template order wins and is the one charged, so two overlapping guards do not both spend a charge on one projectile.