using FishNet.Connection; using FishNet.Managing; using FishNet.Managing.Logging; using FishNet.Managing.Server; using FishNet.Object; using FishNet.Observing; using GameKit.Dependencies.Utilities; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using UnityEngine; namespace FishNet.Component.Observing { /// /// When this observer condition is placed on an object, a client must be within the same match to view the object. /// [CreateAssetMenu(menuName = "FishNet/Observers/Match Condition", fileName = "New Match Condition")] public class MatchCondition : ObserverCondition { #region Types. /// /// MatchCondition collections used. /// public class ConditionCollections { public Dictionary> MatchConnections = new(); public Dictionary> ConnectionMatches = new(); public Dictionary> MatchObjects = new(); public Dictionary> ObjectMatches = new(); } #endregion #region Private. /// /// Collections for each NetworkManager instance. /// private static Dictionary _collections = new(); #endregion #region Collections. /// /// Stores collections for a manager. /// /// internal static void StoreCollections(NetworkManager manager) { ConditionCollections cc; if (!_collections.TryGetValue(manager, out cc)) return; foreach (HashSet item in cc.ObjectMatches.Values) CollectionCaches.Store(item); foreach (HashSet item in cc.MatchConnections.Values) CollectionCaches.Store(item); foreach (HashSet item in cc.MatchObjects.Values) CollectionCaches.Store(item); foreach (HashSet item in cc.ConnectionMatches.Values) CollectionCaches.Store(item); _collections.Remove(manager); } /// /// Gets condition collections for a NetowrkManager. /// private static ConditionCollections GetCollections(NetworkManager manager = null) { if (manager == null) manager = InstanceFinder.NetworkManager; ConditionCollections cc; if (!_collections.TryGetValue(manager, out cc)) { cc = new(); _collections[manager] = cc; } return cc; } /// /// Returns matches and connections in each match. /// /// NetworkManager to use. /// public static Dictionary> GetMatchConnections(NetworkManager manager = null) { ConditionCollections cc = GetCollections(manager); return cc.MatchConnections; } /// /// Returns connections and the matches they are in. /// /// NetworkManager to use. /// public static Dictionary> GetConnectionMatches(NetworkManager manager = null) { ConditionCollections cc = GetCollections(manager); return cc.ConnectionMatches; } /// /// Returns matches and objects within each match. /// /// NetworkManager to use. /// public static Dictionary> GetMatchObjects(NetworkManager manager = null) { ConditionCollections cc = GetCollections(manager); return cc.MatchObjects; } /// /// Returns objects and the matches they are in. /// /// NetworkManager to use. /// public static Dictionary> GetObjectMatches(NetworkManager manager = null) { ConditionCollections cc = GetCollections(manager); return cc.ObjectMatches; } #endregion #region Add to match NetworkConnection. /// /// Adds a connection to a match. /// private static bool AddToMatch(int match, NetworkConnection conn, NetworkManager manager, bool replaceMatch, bool rebuild) { Dictionary> matchConnections = GetMatchConnections(manager); if (replaceMatch) RemoveFromMatchesWithoutRebuild(conn, manager); /* Get current connections in match. This is where the conn * will be added to. If does not exist then make new * collection. */ HashSet matchConnValues; if (!matchConnections.TryGetValueIL2CPP(match, out matchConnValues)) { matchConnValues = CollectionCaches.RetrieveHashSet(); matchConnections.Add(match, matchConnValues); } bool r = matchConnValues.Add(conn); AddToConnectionMatches(conn, match, manager); if (r && rebuild) GetServerObjects(manager).RebuildObservers(); return r; } /// /// Adds a connection to a match. /// /// Match to add conn to. /// Connection to add to match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. /// True to replace other matches with the new match. public static void AddToMatch(int match, NetworkConnection conn, NetworkManager manager = null, bool replaceMatch = false) { AddToMatch(match, conn, manager, replaceMatch, true); } /// /// Updates a connection within ConnectionMatches to contain match. /// private static void AddToConnectionMatches(NetworkConnection conn, int match, NetworkManager manager) { Dictionary> connectionMatches = GetConnectionMatches(manager); HashSet matches; if (!connectionMatches.TryGetValueIL2CPP(conn, out matches)) { matches = CollectionCaches.RetrieveHashSet(); connectionMatches[conn] = matches; } matches.Add(match); } /// /// Adds connections to a match. /// /// Match to add conns to. /// Connections to add to match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. /// True to replace other matches with the new match. public static void AddToMatch(int match, NetworkConnection[] conns, NetworkManager manager = null, bool replaceMatch = false) { AddToMatch(match, conns.ToList(), manager, replaceMatch); } /// /// Adds connections to a match. /// /// Match to add conns to. /// Connections to add to match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. /// True to replace other matches with the new match. public static void AddToMatch(int match, List conns, NetworkManager manager = null, bool replaceMatch = false) { bool added = false; foreach (NetworkConnection c in conns) added |= AddToMatch(match, c, manager, replaceMatch, false); if (added) GetServerObjects(manager).RebuildObservers(); } #endregion #region Add to match NetworkObject. /// /// Adds an object to a match. /// private static bool AddToMatch(int match, NetworkObject nob, NetworkManager manager, bool replaceMatch, bool rebuild) { Dictionary> matchObjects = GetMatchObjects(manager); Dictionary> objectMatches = GetObjectMatches(manager); if (replaceMatch) RemoveFromMatchWithoutRebuild(nob, manager); HashSet matchObjectsValues; if (!matchObjects.TryGetValueIL2CPP(match, out matchObjectsValues)) { matchObjectsValues = CollectionCaches.RetrieveHashSet(); matchObjects.Add(match, matchObjectsValues); } bool added = matchObjectsValues.Add(nob); /* Also add to reverse dictionary. */ HashSet objectMatchesValues; if (!objectMatches.TryGetValueIL2CPP(nob, out objectMatchesValues)) { objectMatchesValues = CollectionCaches.RetrieveHashSet(); objectMatches.Add(nob, objectMatchesValues); } objectMatchesValues.Add(match); if (added && rebuild) GetServerObjects(manager).RebuildObservers(); return added; } /// /// Adds an object to a match. /// /// Match to add conn to. /// Connection to add to match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. /// True to replace other matches with the new match. public static void AddToMatch(int match, NetworkObject nob, NetworkManager manager = null, bool replaceMatch = false) { AddToMatch(match, nob, manager, replaceMatch, true); } /// /// Adds objects to a match. /// /// Match to add conns to. /// Connections to add to match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. /// True to replace other matches with the new match. public static void AddToMatch(int match, NetworkObject[] nobs, NetworkManager manager = null, bool replaceMatch = false) { AddToMatch(match, nobs.ToList(), manager, replaceMatch); } /// /// Adds objects to a match. /// /// Match to add conns to. /// Connections to add to match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. /// True to replace other matches with the new match. public static void AddToMatch(int match, List nobs, NetworkManager manager = null, bool replaceMatch = false) { //Remove from current matches. if (replaceMatch) { foreach (NetworkObject n in nobs) RemoveFromMatchWithoutRebuild(n, manager); } bool added = false; //Add to matches. foreach (NetworkObject n in nobs) added |= AddToMatch(match, n, manager, replaceMatch, false); if (added) GetServerObjects(manager).RebuildObservers(); } #endregion #region TryRemoveKey. /// /// Removes a key if values are empty, and caches values. /// private static void TryRemoveKey(Dictionary> dict, int key, HashSet value) { bool isEmpty = true; if (value != null) { isEmpty = (value.Count == 0); if (isEmpty) CollectionCaches.Store(value); } if (isEmpty) dict.Remove(key); } /// /// Removes a key if values are empty, and caches values. /// private static void TryRemoveKey(Dictionary> dict, int key) { HashSet value; dict.TryGetValue(key, out value); TryRemoveKey(dict, key, value); } /// /// Removes a key if values are empty, and caches values. /// private static void TryRemoveKey(Dictionary> dict, NetworkObject key, HashSet value) { bool isEmpty = true; if (value != null) { isEmpty = (value.Count == 0); if (isEmpty) CollectionCaches.Store(value); } if (isEmpty) dict.Remove(key); } /// /// Removes a key if values are empty, and caches values. /// private static void TryRemoveKey(Dictionary> dict, NetworkObject key) { HashSet value; dict.TryGetValueIL2CPP(key, out value); TryRemoveKey(dict, key, value); } /// /// Removes a key if values are empty, and caches values. /// private static void TryRemoveKey(Dictionary> dict, int key, HashSet value) { bool isEmpty = true; if (value != null) { isEmpty = (value.Count == 0); if (isEmpty) CollectionCaches.Store(value); } if (isEmpty) dict.Remove(key); } /// /// Removes a key if values are empty, and caches values. /// private static void TryRemoveKey(Dictionary> dict, int key) { HashSet value; dict.TryGetValueIL2CPP(key, out value); TryRemoveKey(dict, key, value); } /// /// Removes a key if values are empty, and caches values. /// private static void TryRemoveKey(Dictionary> dict, NetworkConnection key, HashSet value) { bool isEmpty = true; if (value != null) { isEmpty = (value.Count == 0); if (isEmpty) CollectionCaches.Store(value); } if (isEmpty) dict.Remove(key); } /// /// Removes a key and caches collections where needed. /// private static void TryRemoveKey(Dictionary> dict, NetworkConnection key) { HashSet value; dict.TryGetValueIL2CPP(key, out value); TryRemoveKey(dict, key, value); } #endregion #region Remove from match NetworkConnection. /// /// Removes a connection from all matches without rebuilding observers. /// /// Connection to remove from matches. /// NetworkManager connection belongs to. This is not currently used. internal static bool RemoveFromMatchesWithoutRebuild(NetworkConnection conn, NetworkManager manager) { Dictionary> connectionMatches = GetConnectionMatches(manager); Dictionary> matchConnections = GetMatchConnections(manager); bool removed = false; //If found to be in a match. if (connectionMatches.TryGetValueIL2CPP(conn, out HashSet connectionMatchesValues)) { removed = (connectionMatchesValues.Count > 0); foreach (int m in connectionMatchesValues) { HashSet matchConnsValues; //If match is found. if (matchConnections.TryGetValue(m, out matchConnsValues)) { matchConnsValues.Remove(conn); TryRemoveKey(matchConnections, m, matchConnsValues); } } //Clear matches connection is in. connectionMatchesValues.Clear(); //Remove from connectionMatches. TryRemoveKey(connectionMatches, conn, connectionMatchesValues); } return removed; } /// /// Removes a connection from all matches. /// /// NetworkConnection to remove. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. public static void RemoveFromMatch(NetworkConnection conn, NetworkManager manager) { bool removed = RemoveFromMatchesWithoutRebuild(conn, manager); if (removed) GetServerObjects(manager).RebuildObservers(); } /// /// Removes a connection from a match. /// private static bool RemoveFromMatch(int match, NetworkConnection conn, NetworkManager manager, bool rebuild) { Dictionary> connectionMatches = GetConnectionMatches(manager); Dictionary> matchConnections = GetMatchConnections(manager); bool removed = false; HashSet matchConnsValues; if (matchConnections.TryGetValueIL2CPP(match, out matchConnsValues)) { removed |= matchConnsValues.Remove(conn); HashSet connectionMatchesValues; if (connectionMatches.TryGetValueIL2CPP(conn, out connectionMatchesValues)) { connectionMatchesValues.Remove(match); TryRemoveKey(connectionMatches, conn, connectionMatchesValues); } if (removed && rebuild) { TryRemoveKey(matchConnections, match, matchConnsValues); GetServerObjects(manager).RebuildObservers(); } } return removed; } /// /// Removes a connection from a match. /// /// Match to remove conn from. /// Connection to remove from match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. public static bool RemoveFromMatch(int match, NetworkConnection conn, NetworkManager manager = null) { return RemoveFromMatch(match, conn, manager, true); } /// /// Removes connections from a match. /// /// Match to remove conns from. /// Connections to remove from match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. public static void RemoveFromMatch(int match, NetworkConnection[] conns, NetworkManager manager) { RemoveFromMatch(match, conns.ToList(), manager); } /// /// Removes connections from a match. /// /// Match to remove conns from. /// Connections to remove from match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. public static void RemoveFromMatch(int match, List conns, NetworkManager manager) { bool removed = false; foreach (NetworkConnection c in conns) removed |= RemoveFromMatch(match, c, manager, false); if (removed) GetServerObjects(manager).RebuildObservers(); } #endregion #region Remove from match NetworkObject. /// /// Removes a network object from any match without rebuilding observers. /// /// NetworkObject to remove. /// Manager which the network object belongs to. This value is not yet used. internal static bool RemoveFromMatchWithoutRebuild(NetworkObject nob, NetworkManager manager) { Dictionary> objectMatches = GetObjectMatches(manager); Dictionary> matchObjects = GetMatchObjects(manager); HashSet objectMatchesValues; bool removed = false; //If found to be in a match. if (objectMatches.TryGetValueIL2CPP(nob, out objectMatchesValues)) { removed = (objectMatchesValues.Count > 0); foreach (int m in objectMatchesValues) { //If match is found. if (matchObjects.TryGetValue(m, out HashSet matchObjectsValues)) { matchObjectsValues.Remove(nob); TryRemoveKey(matchObjects, m, matchObjectsValues); } } //Since object is being removed from all matches this can be cleared. objectMatchesValues.Clear(); TryRemoveKey(objectMatches, nob, objectMatchesValues); } return removed; } /// /// Removes nob from all matches. /// /// NetworkObject to remove. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. public static bool RemoveFromMatch(NetworkObject nob, NetworkManager manager = null) { bool removed = RemoveFromMatchWithoutRebuild(nob, manager); if (removed) GetServerObjects(manager).RebuildObservers(nob); return removed; } /// /// Removes a network object from all matches. /// /// NetworkObjects to remove. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. public static void RemoveFromMatch(NetworkObject[] nobs, NetworkManager manager = null) { RemoveFromMatch(nobs.ToList(), manager); } /// /// Removes network objects from all matches. /// /// NetworkObjects to remove. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. public static void RemoveFromMatch(List nobs, NetworkManager manager = null) { bool removed = false; foreach (NetworkObject n in nobs) removed |= RemoveFromMatchWithoutRebuild(n, manager); if (removed) GetServerObjects(manager).RebuildObservers(nobs); } /// /// Removes a network object from a match. /// /// Match to remove conn from. /// NetworkObject to remove from match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. public static void RemoveFromMatch(int match, NetworkObject nob, NetworkManager manager = null) { Dictionary> matchObjects = GetMatchObjects(manager); Dictionary> objectMatches = GetObjectMatches(manager); HashSet matchObjectsValues; if (matchObjects.TryGetValueIL2CPP(match, out matchObjectsValues)) { bool removed = matchObjectsValues.Remove(nob); if (removed) { /* Check if nob is still in matches. If not then remove * nob from ObjectMatches. */ HashSet objectMatchesValues; if (objectMatches.TryGetValueIL2CPP(nob, out objectMatchesValues)) { objectMatchesValues.Remove(match); TryRemoveKey(objectMatches, nob, objectMatchesValues); } TryRemoveKey(matchObjects, match, matchObjectsValues); GetServerObjects(manager).RebuildObservers(nob); } } } /// /// Removes network objects from a match. /// /// Match to remove conns from. /// NetworkObjects to remove from match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. public static void RemoveFromMatch(int match, NetworkObject[] nobs, NetworkManager manager = null) { Dictionary> matchObjects = GetMatchObjects(manager); Dictionary> objectMatches = GetObjectMatches(manager); if (matchObjects.TryGetValueIL2CPP(match, out HashSet matchObjectsValues)) { bool removed = false; for (int i = 0; i < nobs.Length; i++) { NetworkObject n = nobs[i]; removed |= matchObjectsValues.Remove(n); objectMatches.Remove(n); } if (removed) { TryRemoveKey(matchObjects, match, matchObjectsValues); GetServerObjects(manager).RebuildObservers(nobs); } } } /// /// Removes network objects from a match. /// /// Match to remove conns from. /// NetworkObjects to remove from match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. public static void RemoveFromMatch(int match, List nobs, NetworkManager manager = null) { Dictionary> matchObjects = GetMatchObjects(manager); Dictionary> objectMatches = GetObjectMatches(manager); if (matchObjects.TryGetValueIL2CPP(match, out HashSet matchObjectsValues)) { bool removed = false; for (int i = 0; i < nobs.Count; i++) { NetworkObject n = nobs[i]; removed |= matchObjectsValues.Remove(n); objectMatches.Remove(n); } if (removed) { TryRemoveKey(matchObjects, match, matchObjectsValues); GetServerObjects(manager).RebuildObservers(nobs); } } } #endregion /// /// Returns if the object which this condition resides should be visible to connection. /// /// Connection which the condition is being checked for. /// True if the connection currently has visibility of this object. /// True if the condition was not processed. This can be used to skip processing for performance. While output as true this condition result assumes the previous ConditionMet value. public override bool ConditionMet(NetworkConnection connection, bool currentlyAdded, out bool notProcessed) { //If here then checks are being processed. notProcessed = false; NetworkConnection owner = base.NetworkObject.Owner; /* If object is owned then check if owner * and connection share a match. */ if (owner.IsValid) { Dictionary> connectionMatches = GetConnectionMatches(base.NetworkObject.NetworkManager); //Output owner matches. HashSet ownerMatches; /* This objects owner is not in a match so treat it like * a networkobject without an owner. Objects not in matches * are visible to everyone. */ if (!connectionMatches.TryGetValueIL2CPP(owner, out ownerMatches)) { return true; } /* Owner is in a match. See if connection is in any of * the same matches. */ else { //If conn is not in any matches then they cannot see this object, as it is. if (!connectionMatches.TryGetValue(connection, out HashSet connMatches)) { return false; } //See if conn is in any of the same matches. else { foreach (int m in connMatches) { if (ownerMatches.Contains(m)) return true; } } //Fall through, not found. return false; } } /* If no owner see if the object is in a match and if so * then compare that. */ else { Dictionary> objectMatches = GetObjectMatches(base.NetworkObject.NetworkManager); Dictionary> connectionMatches = GetConnectionMatches(base.NetworkObject.NetworkManager); //Object isn't in a match. Is visible with no owner. HashSet objectMatchesValues; if (!objectMatches.TryGetValueIL2CPP(base.NetworkObject, out objectMatchesValues)) return true; /* See if connection is in any of same matches as the object. * If connection isn't in a match then it fails as at this point * object would be, but not conn. */ if (!connectionMatches.TryGetValueIL2CPP(connection, out HashSet connectionMatchesValues)) return false; //Compare for same matches. foreach (int cM in connectionMatchesValues) { if (objectMatchesValues.Contains(cM)) return true; } //Fall through, not in any of the matches. return false; } } /// /// Returns which ServerObjects to rebuild observers on. /// /// /// private static ServerObjects GetServerObjects(NetworkManager manager) { return (manager == null) ? InstanceFinder.ServerManager.Objects : manager.ServerManager.Objects; } /// /// How a condition is handled. /// /// public override ObserverConditionType GetConditionType() => ObserverConditionType.Normal; } }