using FishNet.Component.Observing; using FishNet.Documenting; using FishNet.Managing; using FishNet.Managing.Timing; using FishNet.Object; using GameKit.Dependencies.Utilities; using System; using System.Collections.Generic; using UnityEngine.SceneManagement; using static FishNet.Managing.Timing.EstimatedTick; namespace FishNet.Connection { public static class NetworkConnectionExtensions { /// /// True if this connection is valid. An invalid connection indicates no client is set for this reference. /// Null references can be used with this method. /// public static bool IsValid(this NetworkConnection c) { if (c == null) return false; else return c.IsValid; } } /// /// A container for a connected client used to perform actions on and gather information for the declared client. /// public partial class NetworkConnection : IResettable, IEquatable { #region Internal. /// /// Tick when Disconnecting was set. /// internal uint DisconnectingTick { get; private set; } /// /// ObjectIds to use for predicted spawning. /// internal Queue PredictedObjectIds = new(); /// /// True if the client has sent the same version that the server is on. /// internal bool HasSentVersion; /// /// LocalTick of the server when this connection was established. This value is not set for clients. /// internal uint ServerConnectionTick; #endregion #region Public. /// /// Called after this connection has loaded start scenes. Boolean will be true if asServer. Available to this connection and server. /// public event Action OnLoadedStartScenes; /// /// Called after connection gains ownership of an object, and after the object has been added to Objects. Available to this connection and server. /// public event Action OnObjectAdded; /// /// Called after connection loses ownership of an object, and after the object has been removed from Objects. Available to this connection and server. /// public event Action OnObjectRemoved; /// /// NetworkManager managing this class. /// public NetworkManager NetworkManager { get; private set; } /// /// True if connection has loaded start scenes. Available to this connection and server. /// public bool LoadedStartScenes() => (_loadedStartScenesAsServer || _loadedStartScenesAsClient); /// /// /// public bool LoadedStartScenes(bool asServer) { if (asServer) return _loadedStartScenesAsServer; else return _loadedStartScenesAsClient; } /// /// TransportIndex this connection is on. /// For security reasons this value will be unset on clients if this is not their connection. /// This is not yet used. /// public int TransportIndex { get; private set; } = -1; /// /// True if this connection is authenticated. Only available to server. /// public bool IsAuthenticated { get; private set; } [Obsolete("Use IsAuthenticated.")] //Remove in V5 public bool Authenticated { get => IsAuthenticated; set => IsAuthenticated = value; } /// /// True if this connection IsValid and not Disconnecting. /// public bool IsActive => (ClientId >= 0 && !Disconnecting); /// /// True if this connection is valid. An invalid connection indicates no client is set for this reference. /// public bool IsValid => (ClientId >= 0); /// /// Unique Id for this connection. /// public int ClientId = -1; /// /// Objects owned by this connection. Available to this connection and server. /// public HashSet Objects = new(); /// /// The first object within Objects. /// public NetworkObject FirstObject { get; private set; } /// /// Sets a custom FirstObject. This connection must be owner of the specified object. /// /// public void SetFirstObject(NetworkObject nob) { //Invalid object. if (!Objects.Contains(nob)) { string errMessage = $"FirstObject for {ClientId} cannot be set to {nob.name} as it's not within Objects for this connection."; NetworkManager.LogError(errMessage); return; } FirstObject = nob; } /// /// Scenes this connection is in. Available to this connection and server. /// public HashSet Scenes { get; private set; } = new(); /// /// True if this connection is being disconnected. Only available to server. /// public bool Disconnecting { get; private set; } /// /// Custom data associated with this connection which may be modified by the user. /// The value of this field are not synchronized over the network. /// public object CustomData = null; /// /// Tick of the last packet received from this connection which was not out of order. /// This value is only available on the server. /// public EstimatedTick PacketTick { get; private set; } = new(); /// /// Approximate local tick as it is on this connection. /// This also contains the last set value for local and remote. /// public EstimatedTick LocalTick { get; private set; } = new(); #endregion #region Private. /// /// True if loaded start scenes as server. /// private bool _loadedStartScenesAsServer; /// /// True if loaded start scenes as client. /// private bool _loadedStartScenesAsClient; #endregion #region Const. /// /// Value used when ClientId has not been set. /// public const int UNSET_CLIENTID_VALUE = -1; /// /// Maximum value a ClientId can be. /// public const int MAXIMUM_CLIENTID_VALUE = int.MaxValue; /// /// Maximum value a ClientId can be excluding simulated value. /// public const int MAXIMUM_CLIENTID_WITHOUT_SIMULATED_VALUE = (int.MaxValue - 1); /// /// Value to use as a ClientId when simulating a local client without actually using a socket. /// public const int SIMULATED_CLIENTID_VALUE = int.MaxValue; /// /// Number of bytes to reserve for a connectionId if writing the value uncompressed. /// public const int CLIENTID_UNCOMPRESSED_RESERVE_LENGTH = 4; #endregion #region Comparers. public override bool Equals(object obj) { if (obj is NetworkConnection nc) return (nc.ClientId == this.ClientId); else return false; } public bool Equals(NetworkConnection nc) { if (nc is null) return false; //If either is -1 Id. if (this.ClientId == NetworkConnection.UNSET_CLIENTID_VALUE || nc.ClientId == NetworkConnection.UNSET_CLIENTID_VALUE) return false; //Same object. if (System.Object.ReferenceEquals(this, nc)) return true; return (this.ClientId == nc.ClientId); } public override int GetHashCode() { return ClientId; } public static bool operator ==(NetworkConnection a, NetworkConnection b) { if (a is null && b is null) return true; if (a is null && !(b is null)) return false; return (b == null) ? a.Equals(b) : b.Equals(a); } public static bool operator !=(NetworkConnection a, NetworkConnection b) { return !(a == b); } #endregion [APIExclude] public NetworkConnection() { } [APIExclude] public NetworkConnection(NetworkManager manager, int clientId, int transportIndex, bool asServer) { Initialize(manager, clientId, transportIndex, asServer); } /// /// Outputs data about this connection as a string. /// /// public override string ToString() { int clientId = ClientId; string ip = (NetworkManager != null) ? NetworkManager.TransportManager.Transport.GetConnectionAddress(clientId) : "Unset"; return $"Id [{ClientId}] Address [{ip}]"; } /// /// Initializes this for use. /// private void Initialize(NetworkManager nm, int clientId, int transportIndex, bool asServer) { NetworkManager = nm; LocalTick.Initialize(nm.TimeManager); PacketTick.Initialize(nm.TimeManager); if (asServer) ServerConnectionTick = nm.TimeManager.LocalTick; TransportIndex = transportIndex; ClientId = clientId; /* Set PacketTick to current values so * that timeouts and other things around * first packet do not occur due to an unset value. */ PacketTick.Update(nm.TimeManager, 0, OldTickOption.SetLastRemoteTick); Observers_Initialize(nm); Prediction_Initialize(nm, asServer); //Only the server uses the ping and buffer. if (asServer) { InitializeBuffer(); InitializePing(); } } /// /// Sets Disconnecting boolean for this connection. /// internal void SetDisconnecting(bool value) { Disconnecting = value; if (Disconnecting) DisconnectingTick = NetworkManager.TimeManager.LocalTick; } /// /// Disconnects this connection. /// /// True to disconnect immediately. False to send any pending data first. public void Disconnect(bool immediately) { if (!IsValid) { NetworkManager.LogWarning($"Disconnect called on an invalid connection."); return; } if (Disconnecting) { NetworkManager.LogWarning($"ClientId {ClientId} is already disconnecting."); return; } SetDisconnecting(true); //If immediately then force disconnect through transport. if (immediately) NetworkManager.TransportManager.Transport.StopConnection(ClientId, true); //Otherwise mark dirty so server will push out any pending information, and then disconnect. else ServerDirty(); } /// /// Returns if just loaded start scenes and sets them as loaded if not. /// /// internal bool SetLoadedStartScenes(bool asServer) { bool loadedToCheck = (asServer) ? _loadedStartScenesAsServer : _loadedStartScenesAsClient; //Result becomes true if not yet loaded start scenes. bool result = !loadedToCheck; if (asServer) _loadedStartScenesAsServer = true; else _loadedStartScenesAsClient = true; OnLoadedStartScenes?.Invoke(this, asServer); return result; } /// /// Sets connection as authenticated. /// internal void ConnectionAuthenticated() { IsAuthenticated = true; } /// /// Adds to Objects owned by this connection. /// /// internal void AddObject(NetworkObject nob) { if (!IsValid) return; Objects.Add(nob); //If adding the first object then set new FirstObject. if (Objects.Count == 1) SetFirstObject(); OnObjectAdded?.Invoke(nob); } /// /// Removes from Objects owned by this connection. /// /// internal void RemoveObject(NetworkObject nob) { if (!IsValid) { ClearObjects(); return; } Objects.Remove(nob); //If removing the first object then set a new one. if (nob == FirstObject) SetFirstObject(); OnObjectRemoved?.Invoke(nob); } /// /// Clears all Objects. /// private void ClearObjects() { Objects.Clear(); FirstObject = null; } /// /// Sets FirstObject using the first element in Objects. /// private void SetFirstObject() { if (Objects.Count == 0) { FirstObject = null; } else { foreach (NetworkObject nob in Objects) { FirstObject = nob; Observers_FirstObjectChanged(); break; } } } /// /// Adds a scene to this connections Scenes. /// /// /// internal bool AddToScene(Scene scene) { return Scenes.Add(scene); } /// /// Removes a scene to this connections Scenes. /// /// /// internal bool RemoveFromScene(Scene scene) { return Scenes.Remove(scene); } /// /// Resets all states for re-use. /// public void ResetState() { MatchCondition.RemoveFromMatchesWithoutRebuild(this, NetworkManager); foreach (PacketBundle p in _toClientBundles) p.Dispose(); _toClientBundles.Clear(); ServerConnectionTick = 0; PacketTick.Reset(); LocalTick.Reset(); TransportIndex = -1; ClientId = -1; ClearObjects(); IsAuthenticated = false; HasSentVersion = false; NetworkManager = null; _loadedStartScenesAsClient = false; _loadedStartScenesAsServer = false; SetDisconnecting(false); Scenes.Clear(); PredictedObjectIds.Clear(); ResetPingPong(); Observers_Reset(); Prediction_Reset(); } public void InitializeState() { } } }