Class CharacterAttributeController
Controls all character attributes and resource attributes for an entity. Handles initialization from template databases, network payload serialization, parent/child/dependency relationship wiring, tick-based resource regeneration, and reconcile-driven synchronization of both base and resource attributes via the unified CharacterReconcileData. There is no longer a separate broadcast path for non-resource attributes.
public class CharacterAttributeController : CharacterBehaviour, ICharacterAttributeController, ICharacterBehaviour, IPredictableController
- Inheritance
-
ObjectComponentBehaviourMonoBehaviourNetworkBehaviourCharacterAttributeController
- Implements
- Inherited Members
-
NetworkBehaviour.IsSpawnedNetworkBehaviour.ComponentIndexNetworkBehaviour.NetworkObjectNetworkBehaviour.MAXIMUM_NETWORKBEHAVIOURSNetworkBehaviour.UNSET_NETWORKBEHAVIOUR_IDNetworkBehaviour.ToString()NetworkBehaviour.Reset()NetworkBehaviour.OnValidate()NetworkBehaviour.IsBehaviourReconcilingNetworkBehaviour.ClearReplicateCache()NetworkBehaviour.CreateReconcile()NetworkBehaviour.Reconcile_Reader<T>(PooledReader, ref T)NetworkBehaviour.OnStartServerCalledNetworkBehaviour.OnStartClientCalledNetworkBehaviour.OnStopNetwork()NetworkBehaviour.OnStartServer()NetworkBehaviour.OnStopServer()NetworkBehaviour.OnOwnershipServer(NetworkConnection)NetworkBehaviour.OnSpawnServer(NetworkConnection)NetworkBehaviour.OnDespawnServer(NetworkConnection)NetworkBehaviour.OnStartClient()NetworkBehaviour.OnStopClient()NetworkBehaviour.OnOwnershipClient(NetworkConnection)NetworkBehaviour.ClearBuffedRpcs()NetworkBehaviour.ExcludeOwnerFromUnbufferedObserversRpcsNetworkBehaviour.IsClientOnlyNetworkBehaviour.IsServerOnlyNetworkBehaviour.IsHostNetworkBehaviour.IsClientNetworkBehaviour.IsServerNetworkBehaviour.IsDeinitializingNetworkBehaviour.NetworkManagerNetworkBehaviour.ServerManagerNetworkBehaviour.ClientManagerNetworkBehaviour.ObserverManagerNetworkBehaviour.TransportManagerNetworkBehaviour.TimeManagerNetworkBehaviour.SceneManagerNetworkBehaviour.PredictionManagerNetworkBehaviour.RollbackManagerNetworkBehaviour.NetworkObserverNetworkBehaviour.IsClientInitializedNetworkBehaviour.IsClientStartedNetworkBehaviour.IsClientOnlyInitializedNetworkBehaviour.IsClientOnlyStartedNetworkBehaviour.IsServerInitializedNetworkBehaviour.IsServerStartedNetworkBehaviour.IsServerOnlyInitializedNetworkBehaviour.IsServerOnlyStartedNetworkBehaviour.IsHostInitializedNetworkBehaviour.IsHostStartedNetworkBehaviour.IsOfflineNetworkBehaviour.IsNetworkedNetworkBehaviour.GetIsNetworked()NetworkBehaviour.IsManagerReconcilingNetworkBehaviour.ObserversNetworkBehaviour.IsOwnerNetworkBehaviour.IsControllerNetworkBehaviour.HasAuthorityNetworkBehaviour.OwnerNetworkBehaviour.OwnerIdNetworkBehaviour.ObjectIdNetworkBehaviour.LocalConnectionNetworkBehaviour.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.destroyCancellationTokenMonoBehaviour.useGUILayoutMonoBehaviour.didStartMonoBehaviour.didAwakeMonoBehaviour.runInEditModeBehaviour.enabledBehaviour.isActiveAndEnabledComponent.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.transformComponent.transformHandleComponent.gameObjectComponent.tagObject.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.nameObject.hideFlags
- Extension Methods
Fields
CharacterAttributeDatabase
Reference to the ScriptableObject database containing all character attribute templates. Used to initialize and manage available attributes for this character.
public CharacterAttributeTemplateDatabase CharacterAttributeDatabase
Field Value
HealthRegenerationTemplateID
Template ID for the health regeneration attribute.
[TemplateReference(typeof(CharacterAttributeTemplate))]
public int HealthRegenerationTemplateID
Field Value
HealthResourceTemplateID
Template ID for the health resource attribute (e.g., HP).
[TemplateReference(typeof(CharacterAttributeTemplate))]
public int HealthResourceTemplateID
Field Value
ManaRegenerationTemplateID
Template ID for the mana regeneration attribute.
[TemplateReference(typeof(CharacterAttributeTemplate))]
public int ManaRegenerationTemplateID
Field Value
ManaResourceTemplateID
Template ID for the mana resource attribute (e.g., MP).
[TemplateReference(typeof(CharacterAttributeTemplate))]
public int ManaResourceTemplateID
Field Value
StaminaRegenerationTemplateID
Template ID for the stamina regeneration attribute.
[TemplateReference(typeof(CharacterAttributeTemplate))]
public int StaminaRegenerationTemplateID
Field Value
StaminaResourceTemplateID
Template ID for the stamina resource attribute.
[TemplateReference(typeof(CharacterAttributeTemplate))]
public int StaminaResourceTemplateID
Field Value
Properties
Attributes
Public accessor for all non-resource character attributes.
public Dictionary<int, CharacterAttribute> Attributes { get; }
Property Value
IsPropagating
True while the attribute graph is propagating value changes. Notifications are deferred until propagation completes.
public bool IsPropagating { get; }
Property Value
Order
Execution order in the unified prediction pipeline. Runs after buff reconcile/tick and before ability processing so regenerated resources are available for same-tick activation checks.
public int Order { get; }
Property Value
Remarks
Must stay above Order (85) and
Order (93). This is what stops attribute modifiers
compounding on every reconcile, and it is load-bearing rather than tidy.
Reconcile(CharacterReconcileData, Channel) walks the controllers in this
order. Buff and equipment restore their own ATTRIBUTED entries — Buff.Apply and
ItemGenerator.ApplyAttributes both call CharacterAttribute.SetSource —
while ApplyAttributeSnapshot(AttributeReconcileEntry[]) installs the server's TOTAL, with
SetModifierDirect, as the residual between that total and whatever those sources
currently sum to. Running last is what makes the residual right: it is computed against a
ledger this tick's buff and equipment restores have already settled. Reorder this below
either of them and the residual is measured against the PREVIOUS tick's sources, so every
buff gained or item equipped on the reconciled tick lands on the sheet twice until the
next reconcile happens to be measured after it.
Pinned by PredictionAuditRegressionTests.AttributeReconcile_RunsAfterBuffAndEquipment.
ResourceAttributes
Public accessor for all resource character attributes.
public Dictionary<int, CharacterResourceAttribute> ResourceAttributes { get; }
Property Value
Methods
AddAttribute(CharacterAttribute)
Adds a non-resource attribute instance to the controller if not already present.
public void AddAttribute(CharacterAttribute instance)
Parameters
instanceCharacterAttributeThe attribute instance to add.
AddResourceAttribute(CharacterResourceAttribute)
Adds a resource attribute instance to the controller if not already present.
public void AddResourceAttribute(CharacterResourceAttribute instance)
Parameters
instanceCharacterResourceAttributeThe resource attribute instance to add.
ApplyObservedAttributes(AttributeReconcileEntry[], bool)
Applies a set of authoritative attribute entries by template id.
public void ApplyObservedAttributes(AttributeReconcileEntry[] entries, bool isFullSet = false)
Parameters
entriesAttributeReconcileEntry[]Entries to apply. Null or empty is a no-op.
isFullSetboolTrue when
entriesstates the character's entire sheet.
Remarks
Both the base value and the external modifier are applied, which is what lets the
receiver arrive at the same FinalValue the server holds. Attributes the message
does not name are left alone: the sheet is fixed when the character spawns, so an
omission means "unchanged", never "removed".
That is exactly why isFullSet is a diagnostic here rather than a mode.
It travelled on the wire and no consumer read it, which made the sender's one real use of
it silent: FishMMO.Shared.CharacterAttributeController.MaybePushObservedAttributes() sets it when the sheet's LENGTH
changed, meaning the character was rebuilt — and a receiver that merges by id cannot
honour that. It cannot drop the attributes the message leaves out, because absence has
always meant "unchanged". Since nothing removes an attribute at runtime the case does not
arise today, so the flag is checked and reported rather than acted on: if the assumption
ever stops holding, this says so instead of the sheets quietly diverging.
ApplyObservedResourceState(float, int, float, int, float, int)
Applies a resource state snapshot to restore health, mana, stamina, and the regen schedule. Used by FishNet's Replicate/Reconcile prediction system.
Each resource is processed independently via ApplyIndividualResourceState(int, int, float) so entities that legitimately lack one of the standard resources (NPCs without mana, monsters without stamina) still have their present resources correctly reconciled. A template ID of 0 means the resource is not present on this entity and is silently skipped.
public void ApplyObservedResourceState(float health, int maxHealth, float mana, int maxMana, float stamina, int maxStamina)
Parameters
Remarks
The observer broadcast is whole-unit values only; it deliberately carries no regen tick. See the call site for why applying a zeroed schedule would be wrong.
ApplyResourceState(CharacterAttributeResourceState)
Applies a resource state (health, mana, stamina, regen delta) to the controller.
public void ApplyResourceState(CharacterAttributeResourceState resourceState)
Parameters
resourceStateCharacterAttributeResourceStateResource state to apply.
BeginNotificationSuppression()
Begins a scope that suppresses all attribute update notifications while still allowing deterministic attribute/resource state mutations to run. Used by prediction replay so UI, ECA, and passive listeners do not observe intermediate resimulation states.
public void BeginNotificationSuppression()
BeginPropagation()
Begins a propagation batch. While active, EnqueueNotification(CharacterAttribute) collects changed attributes instead of firing events immediately. Must be paired with EndPropagation().
public void BeginPropagation()
ClearModifierSource(ModifierSource)
Releases one contributor from every attribute on the sheet.
public void ClearModifierSource(ModifierSource source)
Parameters
sourceModifierSourceThe contributor to release everywhere. Only its Kind and Id are read.
Remarks
For a source whose owner goes away without knowing which attributes it touched — a region a character walks out of, which cannot enumerate what its authored actions modified. Cheap because ClearSource(ModifierSource) is a no-op on an attribute that source never touched, and an attribute holds a handful of contributors at most.
Resources are walked too: a region action is free to modify a resource's maximum, and a contributor the sheet forgot on one half would be exactly the orphaned modifier the ledger exists to prevent.
Every entry the contributor holds, not the one entry source names.
A contributor may write to one attribute more than once — see
Index — and this is the call for a source whose owner is
going away, so releasing one index and stranding the rest would be exactly the orphaned
modifier above. Index is therefore ignored here: the caller
names the CONTRIBUTOR and gets all of it.
EndNotificationSuppression()
Ends a notification-suppression scope and discards any queued updates without invoking OnAttributeUpdated listeners.
public void EndNotificationSuppression()
EndPropagation()
Ends a propagation batch. If this is the outermost batch (depth returns to zero), fires all deferred OnAttributeUpdated events.
public void EndPropagation()
EnqueueNotification(CharacterAttribute)
Enqueues an attribute for deferred notification during propagation. If not propagating, callers should invoke the event directly instead.
public void EnqueueNotification(CharacterAttribute attribute)
Parameters
attributeCharacterAttributeThe attribute to notify after propagation completes.
GetResourceState()
Captures the current resource state as a snapshot for FishNet's Replicate/Reconcile prediction system. Each resource is sampled independently; a template ID of 0 means the resource is not present on this entity and its fields remain at the default (0), which is harmless for the delta serializer.
public CharacterAttributeResourceState GetResourceState()
Returns
- CharacterAttributeResourceState
A snapshot of present resource values plus the regen schedule.
InitializeAttributeDependents()
Initializes parent/child/dependency relationships for all non-resource attributes.
public void InitializeAttributeDependents()
InitializeOnce()
CharacterBehaviour function which is typically called once during initialization and typically before Awake or OnAwake. Initializes all attributes and resource attributes from the CharacterAttributeDatabase, and sets up dependencies.
public override void InitializeOnce()
InitializeResourceAttributeDependents()
Initializes parent/child/dependency relationships for all resource attributes.
public void InitializeResourceAttributeDependents()
OnCreateReconcile(ref CharacterReconcileData)
Writes this controller's authoritative state into the reconcile data (server only). Called once per tick after OnReplicate(ref CharacterReplicateData, ReplicateState, Channel).
public void OnCreateReconcile(ref CharacterReconcileData reconcileData)
Parameters
reconcileDataCharacterReconcileData
OnReconcile(CharacterReconcileData, Channel)
Restores resource state and the full non-resource attribute snapshot
from authoritative reconcile data. Notifications are batched through
the propagation system; dirty-tracking is suppressed during the restore
so the rebuilt snapshot retains ReferenceEquals identity.
public void OnReconcile(CharacterReconcileData rd, Channel channel)
Parameters
rdCharacterReconcileDataUnified reconcile payload.
channelChannelTransport channel.
Remarks
The owner always reconciles: this is the authority for the resource values its own predicted regeneration advances from.
A non-owner only reconciles a forwarded object. With forwarding off an observer's copy of
this character is fed by CharacterResourcesBroadcast and
CharacterAttributesBroadcast instead, and it would not receive this
reconcile anyway — so the guard states the contract rather than defending against a
message that cannot arrive. Stating it matters because the two paths carry the same
fields at different precisions: the broadcast rounds resources to whole units and zeroes
NextRegenTick, so letting both write would let a coarser value land on top of a
finer one depending on arrival order. See ObserverSyncMode.
OnReplicate(ref CharacterReplicateData, ReplicateState, Channel)
Advances deterministic resource regeneration for the current prediction tick.
public void OnReplicate(ref CharacterReplicateData input, ReplicateState state, Channel channel)
Parameters
inputCharacterReplicateDataUnified replicate input for this tick.
stateReplicateStateCurrent replicate execution state.
channelChannelTransport channel.
OnStartNetwork()
Initializes the tick-based regen interval. Attribute state replication is handled entirely through the unified reconcile pipeline — there is no per-tick flush subscription.
public override void OnStartNetwork()
PopulateInput(ref CharacterReplicateData)
Attributes do not contribute owner input into CharacterReplicateData.
public void PopulateInput(ref CharacterReplicateData input)
Parameters
inputCharacterReplicateDataUnified replicate input for this tick.
ReadPayload(NetworkConnection, Reader)
Reads attribute and resource attribute base values from the network payload and applies them.
public override void ReadPayload(NetworkConnection conn, Reader reader)
Parameters
connNetworkConnectionThe network connection.
readerReaderThe network reader containing serialized attribute data.
Regenerate(uint)
Processes resource regeneration for health, mana, and stamina using the authoritative simulation tick. Fires when the tick reaches the absolute FishMMO.Shared.CharacterAttributeController.nextRegenTick schedule point, then advances that point by FishMMO.Shared.CharacterAttributeController.regenTickInterval.
Absolute scheduling (vs modulo) prevents a runtime TickDelta change from immediately firing a spurious pulse: the existing scheduled tick fires naturally, and the new interval applies only for subsequent pulses.
IMPORTANT — Replay non-determinism: Regen is intentionally NOT replayed during rollback. The monotonic guard (FishMMO.Shared.CharacterAttributeController.lastProcessedRegenTick) causes all replayed ticks within the rollback window to be skipped. Authoritative resource values are restored by ApplyResourceState(CharacterAttributeResourceState) via reconcile. After a deep rewind, FishMMO.Shared.CharacterAttributeController.lastProcessedRegenTick can remain ahead of the replayed local tick until live simulation catches up, so client-side regen presentation may pause briefly after high-RTT reconciles. Server authority is unaffected because resource values continue to arrive through reconcile. Future maintainers must NOT assume replay-deterministic regen — gameplay systems that depend on intermediate regen events during replay will not observe them.
KNOWN OMISSION — the lockout state is predicted and NOT reconciled.
FishMMO.Shared.CharacterAttributeController.lastProcessedRegenTick, FishMMO.Shared.CharacterAttributeController.lastConsumedResourceTicks and
FishMMO.Shared.CharacterAttributeController.lastObservedResourceValues are all written from inside the replicate body
and none of them travels in CharacterReconcileData. The owner and the server can
therefore disagree about whether a resource is still inside the no-regeneration window
that spending it opened, so a pulse can fire on one and not the other.
Left that way deliberately, and it is bounded: the resource VALUE is reconciled every tick, so a disagreement costs at most one pulse's worth of regeneration before the server's number overwrites it. Carrying three more fields — one of them a dictionary — on every reconcile to correct a sub-pulse timing difference is not a trade worth making. Anything that comes to depend on the lockout being exact must reconcile it first.
public void Regenerate(uint tick)
Parameters
tickuintAuthoritative simulation tick for this replicate step.
ResetState(bool)
Resets the state of all resource attributes by restoring their current values to their final values.
public override void ResetState(bool asServer)
Parameters
asServerboolTrue if called on the server.
SetAttribute(int, int, int?)
Sets the base value and optional external modifier of a non-resource attribute by template ID.
public void SetAttribute(int id, int value, int? modifier = null)
Parameters
idintThe template ID of the attribute.
valueintThe new base value.
modifierint?Optional external modifier value to set.
SetResourceAttribute(int, int, float, int?)
Sets the base value, current value, and optional external modifier of a resource attribute by template ID.
public void SetResourceAttribute(int id, int value, float currentValue, int? modifier = null)
Parameters
idintThe template ID of the resource attribute.
valueintThe new base value.
currentValuefloatThe new current depletable value.
modifierint?Optional external modifier value to set.
TryGetAttribute(CharacterAttributeTemplate, out CharacterAttribute)
Attempts to retrieve a non-resource attribute by its template reference.
public bool TryGetAttribute(CharacterAttributeTemplate template, out CharacterAttribute attribute)
Parameters
templateCharacterAttributeTemplateThe attribute template to look up.
attributeCharacterAttributeThe found attribute instance, or null.
Returns
- bool
True if the attribute was found.
TryGetAttribute(int, out CharacterAttribute)
Attempts to retrieve a non-resource attribute by template ID.
public bool TryGetAttribute(int id, out CharacterAttribute attribute)
Parameters
idintThe template ID to look up.
attributeCharacterAttributeThe found attribute instance, or null.
Returns
- bool
True if the attribute was found.
TryGetHealthAttribute(out CharacterResourceAttribute)
Attempts to retrieve the health resource attribute.
public bool TryGetHealthAttribute(out CharacterResourceAttribute health)
Parameters
healthCharacterResourceAttributeThe found health resource attribute, or null.
Returns
- bool
True if the health attribute was found.
TryGetManaAttribute(out CharacterResourceAttribute)
Attempts to retrieve the mana resource attribute.
public bool TryGetManaAttribute(out CharacterResourceAttribute mana)
Parameters
manaCharacterResourceAttributeThe found mana resource attribute, or null.
Returns
- bool
True if the mana attribute was found.
TryGetResourceAttribute(CharacterAttributeTemplate, out CharacterResourceAttribute)
Attempts to retrieve a resource attribute by its template reference.
public bool TryGetResourceAttribute(CharacterAttributeTemplate template, out CharacterResourceAttribute attribute)
Parameters
templateCharacterAttributeTemplateThe resource attribute template to look up.
attributeCharacterResourceAttributeThe found resource attribute instance, or null.
Returns
- bool
True if the resource attribute was found.
TryGetResourceAttribute(int, out CharacterResourceAttribute)
Attempts to retrieve a resource attribute by template ID.
public bool TryGetResourceAttribute(int id, out CharacterResourceAttribute attribute)
Parameters
idintThe template ID to look up.
attributeCharacterResourceAttributeThe found resource attribute instance, or null.
Returns
- bool
True if the resource attribute was found.
TryGetStaminaAttribute(out CharacterResourceAttribute)
Attempts to retrieve the stamina resource attribute.
public bool TryGetStaminaAttribute(out CharacterResourceAttribute stamina)
Parameters
staminaCharacterResourceAttributeThe found stamina resource attribute, or null.
Returns
- bool
True if the stamina attribute was found.
WritePayload(NetworkConnection, Writer)
Writes all attribute and resource attribute base values to the network payload for client synchronization. Attributes are written in ascending Template.ID order for deterministic serialization across platforms regardless of Dictionary enumeration order.
public override void WritePayload(NetworkConnection conn, Writer writer)
Parameters
connNetworkConnectionThe network connection.
writerWriterThe network writer to serialize attribute data into.