Class EquipmentController
Controls the character's equipment slots, handling equip/unequip logic and network synchronization.
Implements IPredictableController at Order 93 so equipment participates in the prediction pipeline: an owner's equip or unequip is INPUT, carried by EquipmentRequest, applied by both the owner and the server inside the replicate body for that tick, and confirmed by the reconcile. Equipment-driven attribute changes therefore land on the same tick on both peers, and the attribute reconcile (Order 95) never has a socket to correct that the replay would not also have re-applied.
Three network paths feed this container and they must agree with one another:
- The spawn payload (WritePayload(NetworkConnection, Writer)/ReadPayload(NetworkConnection, Reader)) — owner shaped for the owner, template+seed only for everyone else.
- The owner's replicate input and reconcile snapshot: the input moves items, the snapshot says which instance sits in each socket, and RestoreFromReconcile(EquipmentReconcileEntry[], uint) settles any disagreement by MOVING the real instance, never by cloning it. A reconcile that predates a request restores the earlier state and the replay re-applies the request.
- EquipmentObservedSlotBroadcast, sent by the server to the character's observers after every successful equip/unequip, applied by ApplyObservedSlot(int, int, int).
public class EquipmentController : ItemContainer, IEquipmentController, ICharacterBehaviour, IItemContainer, IPredictableController
- Inheritance
-
ObjectComponentBehaviourMonoBehaviourNetworkBehaviourEquipmentController
- 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
Fields
ServerRequestValidator
Server-side gate consulted before a replicated request is applied: is this character allowed to act at all, and may it reach the named container right now?
public static Func<ICharacter, InventoryType, bool> ServerRequestValidator
Field Value
Remarks
Installed by the server's inventory system. It answers the two questions this shared class cannot — "can the character act" is a server-side validation rule set, and "is a banker in range" needs the scene object registry. Null on every other peer, and null on the server means no gate, which is the permissive default a test harness expects.
Properties
OnEquipTriggers
Triggers invoked when this character equips an item. EventData: EquipItemEventData.
public List<Trigger> OnEquipTriggers { get; }
Property Value
OnUnequipTriggers
Triggers invoked when this character unequips an item. EventData: EquipItemEventData.
public List<Trigger> OnUnequipTriggers { get; }
Property Value
Order
Execution order in the unified prediction pipeline. Runs after CooldownController (90) and before CharacterAttributeController (95) so equipment attribute modifiers are settled before the attribute reconcile snapshot.
public int Order { get; }
Property Value
Remarks
Must stay below Order (95). Restoring an
item restates its own ledger entry through
ItemGenerator.ApplyAttributes → CharacterAttribute.SetSource; the attribute
reconcile then installs the server's total as the residual over whatever those entries sum
to. Raising this above 95 would have that residual computed before this tick's equipment is
settled, so an item equipped on the reconciled tick counts twice. See the remarks on
Order.
Methods
Activate(int)
Activates the item in the specified equipment slot, typically triggering its use effect.
public void Activate(int index)
Parameters
indexintThe equipment slot index to activate.
ApplyUnequipDestination(long, InventoryType, int)
Moves an unequipped item to the slot the server put it in, if this peer has it anywhere else. Owner only; a no-op when the item is already there.
public void ApplyUnequipDestination(long itemID, InventoryType container, int slot)
Parameters
itemIDlongIdentity of the item that was unequipped.
containerInventoryTypeThe container the server placed it in.
slotintThe slot within that container.
Equip(Item, int, IItemContainer, ItemSlot)
Equips the specified item into the given equipment slot, handling swaps and unequips as needed.
public bool Equip(Item item, int inventoryIndex, IItemContainer container, ItemSlot toSlot)
Parameters
itemItemThe item to equip.
inventoryIndexintThe index in the source inventory.
containerIItemContainerThe source item container (e.g., inventory or bank).
toSlotItemSlotThe equipment slot to equip the item into.
Returns
- bool
True if the item was successfully equipped, false otherwise.
Remarks
Applies immediately on whichever peer calls it. Server-side paths that are not the owner's predicted request — ECA actions, for instance — call this directly; the owner learns of the change through the reconcile. The owner's own requests must go through RequestEquip(Item, int, InventoryType, ItemSlot) instead, so they run inside the replicate tick.
OnAwake()
Called before InitializeOnce, character will not be set yet. Override for early setup.
public override void OnAwake()
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 data)
Parameters
OnReconcile(CharacterReconcileData, Channel)
Restores this controller's state from the server's authoritative reconcile data.
public void OnReconcile(CharacterReconcileData data, Channel channel)
Parameters
dataCharacterReconcileDatachannelChannel
OnReplicate(ref CharacterReplicateData, ReplicateState, Channel)
Executes this controller's simulation for the current tick.
May modify input for observer prediction.
public void OnReplicate(ref CharacterReplicateData input, ReplicateState state, Channel channel)
Parameters
inputCharacterReplicateDatastateReplicateStatechannelChannel
OnStartCharacter()
Called after Character.OnStartClient. Use this for local client initialization.
public override void OnStartCharacter()
OnStartNetwork()
Called when the network has initialized this object. May be called for server or client but will only be called once. When as host or server this method will run before OnStartServer. When as client only the method will run before OnStartClient.
public override void OnStartNetwork()
OnStopCharacter()
Called right before Character.OnStopClient. Use this for local client cleanup.
public override void OnStopCharacter()
PopulateInput(ref CharacterReplicateData)
Populates this controller's portion of the unified input data (owner only). Called once per tick before OnReplicate(ref CharacterReplicateData, ReplicateState, Channel).
public void PopulateInput(ref CharacterReplicateData input)
Parameters
inputCharacterReplicateData
ReadPayload(NetworkConnection, Reader)
Called before network start callbacks, but after the object is initialized with network values. This may be used to read information from predicted spawning, or simply have values set before initialization without depending on SyncTypes.
public override void ReadPayload(NetworkConnection conn, Reader reader)
Parameters
connNetworkConnectionreaderReader
RequestEquip(Item, int, InventoryType, ItemSlot)
Queues an equip to be applied on the owner's next replicate tick.
public bool RequestEquip(Item item, int sourceIndex, InventoryType fromContainer, ItemSlot socket)
Parameters
itemItemThe item to equip, as the caller currently sees it.
sourceIndexintIts index in
fromContainer.fromContainerInventoryTypeThe container it is in.
socketItemSlotThe equipment socket it is going to.
Returns
- bool
True when the request was queued.
Remarks
Owner only. Validates what can be validated locally — the item is where the request says, it fits the socket, it has a database identity, neither slot is locked — and refuses without queueing when it cannot. One request is held at a time; a second call before the first has ridden a replicate replaces it, and the displaced request is reported through OnRequestResolved as not applied.
RequestUnequip(ItemSlot, InventoryType)
Queues an unequip to be applied on the owner's next replicate tick.
public bool RequestUnequip(ItemSlot socket, InventoryType toContainer)
Parameters
socketItemSlotThe equipment socket being emptied.
toContainerInventoryTypeThe container the item should land in.
Returns
- bool
True when the request was queued.
Remarks
ResetState(bool)
Drops the per-spawn container state before the object returns to the pool.
public override void ResetState(bool asServer)
Parameters
asServerboolTrue if called on the server.
Remarks
The locks are the reason this exists. A slot is locked for the duration of an
operation — a consumable activation, an equip round trip — and unlocked when that
operation resolves. A despawn mid-flight resolves nothing, so the lock was still set
when the object went into the pool, and OnDestroying was the only thing that
cleared it: a path a pooled object never takes. The next character to occupy the slot
inherited a permanently locked inventory slot, which
IsSlotLocked(int) makes unswappable, unremovable and untransferable for
the rest of that character's session, with nothing in the UI to explain it.
The subscriber lists go with them. Both events are held by client panels bound to the character that is being torn down; leaving them attached to a recycled container would drive the previous occupant's UI from the next occupant's slot writes.
Items are deliberately NOT cleared here. Each concrete container clears its own in its
ResetState, and EquipmentController has to run work either side of that
call — see its override for why the order there cannot be inverted.
Unequip(IItemContainer, byte, out List<Item>)
Unequips the item from the specified slot and adds it to the given container (e.g., inventory or bank).
public bool Unequip(IItemContainer container, byte slot, out List<Item> modifiedItems)
Parameters
containerIItemContainerThe destination item container.
slotbyteThe equipment slot to unequip from.
modifiedItemsList<Item>The list of items modified during the operation.
Returns
- bool
True if the item was successfully unequipped and added, false otherwise.
Remarks
See Equip(Item, int, IItemContainer, ItemSlot) for when to call this directly.
WritePayload(NetworkConnection, Writer)
Called when writing a spawn. This may be used to deliver information for predicted spawning, or simply have values set before initialization without depending on SyncTypes.
public override void WritePayload(NetworkConnection conn, Writer writer)
Parameters
connNetworkConnectionwriterWriter
Events
OnItemEquipped
Fired after an item is successfully equipped. Subscribers receive the item and the slot.
public event Action<Item, ItemSlot> OnItemEquipped
Event Type
OnItemUnequipped
Fired after an item is successfully unequipped. Subscribers receive the item and the slot.
public event Action<Item, ItemSlot> OnItemUnequipped
Event Type
OnRequestResolved
Fired on the owner when a queued request has run in the replicate body, whether or not it was applied. Arguments: the kind, the socket, the container the request named, the container index (or -1 for an unequip), and whether the move happened.
public event Action<EquipmentRequestKind, ItemSlot, InventoryType, int, bool> OnRequestResolved
Event Type
Remarks
This is how a panel that marked two slots as waiting learns that nothing will change them. A refusal here is LOCAL — the item was no longer where the request said, or the socket did not fit it. A refusal by the SERVER is not reported through this event at all: it arrives as a reconcile that puts the item back, which updates the slots directly.
OnServerEquipmentChanged
Fired on the server after any equip or unequip has been applied to this controller, from whichever path applied it. The persistence layer is the intended subscriber.
public event Action<IEquipmentController, EquipmentChange> OnServerEquipmentChanged