using FishNet.Connection; using FishNet.Documenting; using FishNet.Managing.Logging; using FishNet.Object; using System.Collections.Generic; using System.Runtime.CompilerServices; using UnityEngine; using UnityEngine.Serialization; namespace FishNet.Observing { /// /// Controls which clients can see and get messages for an object. /// [DisallowMultipleComponent] [AddComponentMenu("FishNet/Component/NetworkObserver")] public sealed class NetworkObserver : MonoBehaviour { #region Types. /// /// How ObserverManager conditions are used. /// public enum ConditionOverrideType { /// /// Keep current conditions, add new conditions from manager. /// AddMissing = 1, /// /// Replace current conditions with manager conditions. /// UseManager = 2, /// /// Keep current conditions, ignore manager conditions. /// IgnoreManager = 3, } #endregion #region Serialized. /// /// /// [Tooltip("How ObserverManager conditions are used.")] [SerializeField] private ConditionOverrideType _overrideType = ConditionOverrideType.IgnoreManager; /// /// How ObserverManager conditions are used. /// public ConditionOverrideType OverrideType { get => _overrideType; internal set => _overrideType = value; } /// /// /// [Tooltip("True to update visibility for clientHost based on if they are an observer or not.")] [FormerlySerializedAs("_setHostVisibility")] [SerializeField] private bool _updateHostVisibility = true; /// /// True to update visibility for clientHost based on if they are an observer or not. /// public bool UpdateHostVisibility { get => _updateHostVisibility; private set => _updateHostVisibility = value; } /// /// /// [Tooltip("Conditions connections must met to be added as an observer. Multiple conditions may be used.")] [SerializeField] internal List _observerConditions = new List(); /// /// Conditions connections must met to be added as an observer. Multiple conditions may be used. /// public IReadOnlyList ObserverConditions => _observerConditions; [APIExclude] #if MIRROR public List ObserverConditionsInternal #else internal List ObserverConditionsInternal #endif { get => _observerConditions; set => _observerConditions = value; } #endregion #region Private. /// /// Conditions under this component which are timed. /// private List _timedConditions = new List(); /// /// True if all non-timed conditions passed. /// private bool _nonTimedMet; /// /// NetworkObject this belongs to. /// private NetworkObject _networkObject; /// /// Becomes true when registered with ServerObjects as Timed observers. /// private bool _registeredAsTimed; #endregion private void OnEnable() { if (_networkObject != null && _networkObject.IsServer) RegisterTimedConditions(); } private void OnDisable() { if (_networkObject != null && _networkObject.IsDeinitializing) UnregisterTimedConditions(); } private void OnDestroy() { if (_networkObject != null) UnregisterTimedConditions(); } /// /// Initializes this script for use. /// /// internal void PreInitialize(NetworkObject networkObject) { _networkObject = networkObject; bool ignoringManager = (OverrideType == ConditionOverrideType.IgnoreManager); //Check to override SetHostVisibility. if (!ignoringManager) UpdateHostVisibility = networkObject.ObserverManager.UpdateHostVisibility; bool observerFound = false; for (int i = 0; i < _observerConditions.Count; i++) { if (_observerConditions[i] != null) { observerFound = true; /* Make an instance of each condition so values are * not overwritten when the condition exist more than * once in the scene. Double edged sword of using scriptable * objects for conditions. */ _observerConditions[i] = _observerConditions[i].Clone(); ObserverCondition oc = _observerConditions[i]; oc.InitializeOnce(_networkObject); //If timed also register as containing timed conditions. if (oc.Timed()) _timedConditions.Add(oc); } else { _observerConditions.RemoveAt(i); i--; } } //No observers specified if (!observerFound) { /* Print warning and remove component if not using * IgnoreManager. This is because other overrides would * suggest conditions should be added in someway, but * none are specified. * * Where-as no conditions with ignore manager would * make sense if the manager had conditions, but you wanted * this object global visible, thus no conditions. */ if (!ignoringManager) { if (networkObject.NetworkManager.CanLog(LoggingType.Warning)) Debug.LogWarning($"NetworkObserver exist on {gameObject.name} but there are no observer conditions. This script has been removed."); Destroy(this); } return; } RegisterTimedConditions(); } /// /// Returns a condition if found within Conditions. /// /// public ObserverCondition GetObserverCondition() where T : ObserverCondition { /* Do not bother setting local variables, * condition collections aren't going to be long * enough to make doing so worth while. */ System.Type conditionType = typeof(T); for (int i = 0; i < _observerConditions.Count; i++) { if (_observerConditions[i].GetType() == conditionType) return _observerConditions[i]; } //Fall through, not found. return null; } /// /// Returns ObserverStateChange by comparing conditions for a connection. /// /// True if added to Observers. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ObserverStateChange RebuildObservers(NetworkConnection connection, bool timedOnly) { bool currentlyAdded = (_networkObject.Observers.Contains(connection)); //True if all conditions are met. bool allConditionsMet = true; /* If cnnection is owner then they can see the object. */ bool notOwner = (connection != _networkObject.Owner); /* Only check conditions if not owner. Owner will always * have visibility. */ if (notOwner) { /* If a timed update and nonTimed * have not been met then there's * no reason to check timed. */ if (timedOnly && !_nonTimedMet) { allConditionsMet = false; } else { //Return as failed if there is a parent nob which doesn't have visibility. if (_networkObject.ParentNetworkObject != null && !_networkObject.ParentNetworkObject.Observers.Contains(connection)) { allConditionsMet = false; } else { //Becomes true if a non-timed condition fails. bool nonTimedFailed = false; List collection = (timedOnly) ? _timedConditions : _observerConditions; for (int i = 0; i < collection.Count; i++) { ObserverCondition condition = collection[i]; /* If any observer returns removed then break * from loop and return removed. If one observer has * removed then there's no reason to iterate * the rest. */ bool conditionMet = condition.ConditionMet(connection, currentlyAdded, out bool notProcessed); if (notProcessed) conditionMet = currentlyAdded; //Condition not met. if (!conditionMet) { allConditionsMet = false; if (!condition.Timed()) nonTimedFailed = true; break; } } //If all conditions are being checked. if (!timedOnly) _nonTimedMet = !nonTimedFailed; } } } //If all conditions met. if (allConditionsMet) return ReturnPassedConditions(currentlyAdded); else return ReturnFailedCondition(currentlyAdded); } /// /// Registers timed observer conditions. /// private void RegisterTimedConditions() { if (_timedConditions.Count == 0) return; //Already registered or no timed conditions. if (_registeredAsTimed) return; _registeredAsTimed = true; _networkObject.NetworkManager.ServerManager.Objects.AddTimedNetworkObserver(_networkObject); } /// /// Unregisters timed conditions. /// private void UnregisterTimedConditions() { if (_timedConditions.Count == 0) return; if (!_registeredAsTimed) return; _registeredAsTimed = false; _networkObject.NetworkManager.ServerManager.Objects.RemoveTimedNetworkObserver(_networkObject); } /// /// Returns an ObserverStateChange when a condition fails. /// /// /// private ObserverStateChange ReturnFailedCondition(bool currentlyAdded) { if (currentlyAdded) return ObserverStateChange.Removed; else return ObserverStateChange.Unchanged; } /// /// Returns an ObserverStateChange when all conditions pass. /// /// /// private ObserverStateChange ReturnPassedConditions(bool currentlyAdded) { if (currentlyAdded) return ObserverStateChange.Unchanged; else return ObserverStateChange.Added; } /// /// Sets a new value for UpdateHostVisibility. /// This does not immediately update renderers. /// You may need to combine with NetworkObject.SetRenderersVisible(bool). /// /// New value. public void SetUpdateHostVisibility(bool value) { //Unchanged. if (value == UpdateHostVisibility) return; UpdateHostVisibility = value; } } }