using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using FishNet.CodeGenerating; using FishNet.Connection; using FishNet.Managing; using FishNet.Managing.Logging; using FishNet.Managing.Server; using FishNet.Object; using FishNet.Object.Synchronizing; using GameKit.Dependencies.Utilities; using UnityEngine; namespace FishNet.Component.Ownership { /// /// Adding this component allows any client to take ownership of the object and begin modifying it immediately. /// public class PredictedOwner : NetworkBehaviour { #region Public. /// /// True if the local client used TakeOwnership and is awaiting an ownership change. /// public bool TakingOwnership { get; private set; } /// /// Owner on client prior to taking ownership. This can be used to reverse a failed ownership attempt. /// public NetworkConnection PreviousOwner { get; private set; } = NetworkManager.EmptyConnection; #endregion #region Serialized. /// /// True if to enable this component. /// [Tooltip("True if to enable this component.")] [SerializeField] private bool _allowTakeOwnership = true; private readonly SyncVar _allowTakeOwnershipSyncVar = new(); /// /// Sets the next value for AllowTakeOwnership and synchronizes it. /// Only the server may use this method. /// /// Next value to use. [Server] public void SetAllowTakeOwnership(bool value) => _allowTakeOwnershipSyncVar.Value = value; #endregion protected virtual void Awake() { _allowTakeOwnershipSyncVar.Value = _allowTakeOwnership; _allowTakeOwnershipSyncVar.UpdateSendRate(0f); _allowTakeOwnershipSyncVar.OnChange += _allowTakeOwnershipSyncVar_OnChange; } /// /// Called when SyncVar value changes for AllowTakeOwnership. /// private void _allowTakeOwnershipSyncVar_OnChange(bool prev, bool next, bool asServer) { if (asServer || !base.IsHostStarted) _allowTakeOwnership = next; } /// /// Called on the client after gaining or losing ownership. /// /// Previous owner of this object. public override void OnOwnershipClient(NetworkConnection prevOwner) { /* Unset taken ownership either way. * If the new owner it won't be used, * if no longer owner then another client * took it. */ TakingOwnership = false; PreviousOwner = base.Owner; } [Client] [Obsolete("Use TakeOwnership(bool).")] public virtual void TakeOwnership() => TakeOwnership(includeNested: true); /// /// Gives ownership of this to the local client and allows immediate control. /// /// True to also take ownership of nested objects. public virtual void TakeOwnership(bool includeNested) { if (!_allowTakeOwnershipSyncVar.Value) return; //Already owner. if (base.IsOwner) return; NetworkConnection c = base.ClientManager.Connection; TakingOwnership = true; //If not server go through the server. if (!base.IsServerStarted) { base.NetworkObject.SetLocalOwnership(c, includeNested); ServerTakeOwnership(includeNested); } //Otherwise take directly without rpcs. else { OnTakeOwnership(c, includeNested); } } /// /// Takes ownership of this object. /// [ServerRpc(RequireOwnership = false)] private void ServerTakeOwnership(bool includeNested, NetworkConnection caller = null) { OnTakeOwnership(caller, includeNested); } [Server] [Obsolete("Use OnTakeOwnership(bool).")] protected virtual void OnTakeOwnership(NetworkConnection caller) => OnTakeOwnership(caller, recursive: false); /// /// Called on the server when a client tries to take ownership of this object. /// /// Connection trying to take ownership. [Server] protected virtual void OnTakeOwnership(NetworkConnection caller, bool recursive) { //Client somehow disconnected between here and there. if (!caller.IsActive) return; //Feature is not enabled. if (!_allowTakeOwnershipSyncVar.Value) return; //Already owner. if (caller == base.Owner) return; base.GiveOwnership(caller); if (recursive) { List allNested = base.NetworkObject.GetNetworkObjects(GetNetworkObjectOption.AllNestedRecursive); foreach (NetworkObject nob in allNested) { PredictedOwner po = nob.PredictedOwner; if (po != null) po.OnTakeOwnership(caller, recursive: true); } CollectionCaches.Store(allNested); } /* No need to send a response back because an ownershipchange will handle changes. * Although if you were to override with this your own behavior * you could send responses for approved/denied. */ } } }