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;
}
}
}