Table of Contents

Struct ModifierSource

Namespace
FishMMO.Shared
Assembly
FishMMO.Shared.dll

Identifies one contributor to a CharacterAttribute's external modifier.

public readonly struct ModifierSource : IEquatable<ModifierSource>
Implements
Inherited Members

Remarks

Why attribution exists at all. ExternalModifier used to be a bare accumulator: every contributor called AddModifier(delta) and nothing recorded who had added what. A contribution could therefore never be REMOVED, only negated — and a negation is only correct while the negating code knows exactly what was added and is certain it ran once. Neither is guaranteed: ItemGenerator subtracted a stored value that could have drifted, the buff templates reversed themselves through a running tick counter, NPC dungeon scaling had no reversal at all, and ApplyRegionAttributeAction had to be cut down to resources because a region bonus had no owner and nothing reversed it on leaving, dying, disconnecting inside the region or changing scene.

It never travels. No source id is on the wire, and none needs to be. Which peer applies which source is already a settled invariant: the server owns every buff and item instance; the owner runs the same buff simulation (SimulatesBuffEffects) and the same equip callbacks, so it keys its own entries from ids it already read; and an observer applies nothing at all, so its ledger is a single Authoritative entry. The reconcile and the payload carry exactly the bytes they carried before.

Keys are cheap and comparisons are linear. An attribute carries a handful of sources at most, so the ledger is a short list walked with a struct compare rather than a dictionary — no hashing, and nothing allocated until an attribute actually gains a source.

Constructors

ModifierSource(ModifierSourceKind, long, int)

public ModifierSource(ModifierSourceKind kind, long id = 0, int index = 0)

Parameters

kind ModifierSourceKind
id long
index int

Fields

Id

Which one, within its kind. Zero for kinds that can only have one instance per attribute.

public readonly long Id

Field Value

long

Index

Which CONTRIBUTION, within one source, on one attribute. Zero for a source that can only contribute once.

public readonly int Index

Field Value

int

Remarks

A source is not a contribution. SetSource STATES a whole contribution rather than adding to one, so a source that writes to the same attribute twice under one key keeps only the second write — silently, and permanently, because the release removes exactly what is there. That is not hypothetical: an item's generated attributes, a buff's BonusAttributes and an NPC's AttributeBonuses are all authored LISTS, and nothing stops two entries in one of them naming the same CharacterAttributeTemplate — a designer splitting a bonus into a flat part and a scalar part, or an armour piece rolling a second Armor affix on top of its base one. Under a single key the character silently gets one of the two.

This is what separates them. NpcBonus solved it first by packing the index into the high word of Id; that trick does not generalise, because Item's id is a full 64-bit database row and has no spare word. A field costs four bytes on a struct that never travels and is the same answer for every kind.

The release side must not have to know the indices. Whatever an apply pass chooses as an index, CharacterAttribute.ClearSourceGroup releases every entry sharing a (Kind, Id) — so an item, a buff or a region lets go of its whole contribution without reconstructing the index scheme that wrote it. Releasing one index at a time would strand the rest the moment the two sides disagreed.

Kind

What kind of contributor this is.

public readonly ModifierSourceKind Kind

Field Value

ModifierSourceKind

Properties

Authoritative

The server's residual total.

public static ModifierSource Authoritative { get; }

Property Value

ModifierSource

Unattributed

The bucket for an unattributed AddModifier(int).

public static ModifierSource Unattributed { get; }

Property Value

ModifierSource

Methods

Buff(int, int)

One of a buff's attribute bonuses, keyed by the buff's template id and by the entry's position in the authored list. See Index.

public static ModifierSource Buff(int templateID, int entryIndex = 0)

Parameters

templateID int

The buff template, which is also its key in the buff container.

entryIndex int

The entry's position in the authored bonus list.

Returns

ModifierSource

DungeonScaling(int)

Dungeon difficulty scaling on an NPC, keyed by the attribute template the scaling entry names. Pass zero for the sheet-wide resource multiplier, which names no template.

public static ModifierSource DungeonScaling(int templateID = 0)

Parameters

templateID int

Returns

ModifierSource

Equals(ModifierSource)

public bool Equals(ModifierSource other)

Parameters

other ModifierSource

Returns

bool

Equals(object)

public override bool Equals(object obj)

Parameters

obj object

Returns

bool

GetHashCode()

public override int GetHashCode()

Returns

int

IsSameContributor(ModifierSource)

True when other comes from the same contributor, whichever of its contributions it is. The predicate CharacterAttribute.ClearSourceGroup releases by.

public bool IsSameContributor(ModifierSource other)

Parameters

other ModifierSource

Returns

bool

Item(long, int)

One of an equipped item's generated bonuses, keyed by ID — its row in character_item — and by which of the item's attributes this is. See Item for why a zero id must never reach here, and Index for why one item needs more than one key.

public static ModifierSource Item(long itemID, int entryIndex = 0)

Parameters

itemID long

The item's character_item row.

entryIndex int

Identifies which of the item's generated attributes this is. ItemGenerator passes the ItemAttributeTemplate's own id, which is stable and order-independent — two affixes that both raise Armor are different templates and so keep separate entries.

Returns

ModifierSource

NpcBonus(int, int)

An NPC's authored attribute bonus, keyed by the template it names and its position in the authored list.

public static ModifierSource NpcBonus(int templateID, int entryIndex)

Parameters

templateID int

The attribute template this entry names.

entryIndex int

The entry's position in the NPC's authored bonus list.

Returns

ModifierSource

Remarks

Keying on the template alone merged two entries that named it — the exact case the template-level key was introduced to fix, and which it did not actually fix, because two entries naming one template still produce one key. The index is the part that differs.

The two used to be packed into the one 64-bit id, the template in the high word and the index in the low. They ride Id and Index now: the packing was this kind's private answer to a problem every kind has, and it could not be lent to Item, whose id is a full 64-bit database row with no spare word.

The index is a position in an authored list, so it is stable for as long as the asset is: re-ordering AttributeBonuses re-keys its entries, which matters only if a designer edits the list while an NPC carrying it is alive. The bonuses are applied once at spawn and released wholesale by ClearAllModifierSources, so nothing outlives that.

Region(long, int)

A region effect, keyed by the region's instance id.

public static ModifierSource Region(long instanceID, int entryIndex = 0)

Parameters

instanceID long

The region's NetworkObject.ObjectId.

entryIndex int

Distinguishes several contributions from one region to one attribute. Zero is correct for a region with a single ApplyRegionAttributeAction per attribute, which is every authored region today; see Index.

Returns

ModifierSource

ToString()

public override string ToString()

Returns

string

Operators

operator ==(ModifierSource, ModifierSource)

public static bool operator ==(ModifierSource a, ModifierSource b)

Parameters

a ModifierSource
b ModifierSource

Returns

bool

operator !=(ModifierSource, ModifierSource)

public static bool operator !=(ModifierSource a, ModifierSource b)

Parameters

a ModifierSource
b ModifierSource

Returns

bool