using FishNet.Connection;
using FishNet.Documenting;
using FishNet.Managing.Logging;
using FishNet.Object.Prediction.Delegating;
using FishNet.Serializing;
using FishNet.Serializing.Helping;
using FishNet.Transporting;
using FishNet.Utility.Constant;
using FishNet.Utility.Extension;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
[assembly: InternalsVisibleTo(UtilityConstants.CODEGEN_ASSEMBLY_NAME)]
namespace FishNet.Object
{
public abstract partial class NetworkBehaviour : MonoBehaviour
{
#region Public.
///
///
///
private uint _lastReconcileTick;
///
/// Gets the last tick this NetworkBehaviour reconciled with.
///
public uint GetLastReconcileTick() => _lastReconcileTick;
///
/// Sets the last tick this NetworkBehaviour reconciled with.
///
[CodegenMakePublic] //Internal only.
protected internal void SetLastReconcileTick(uint value)
{
_lastReconcileTick = value;
}
///
/// True if this object is reconciling.
///
public bool IsReconciling { get; internal set; }
#endregion
#region Private.
///
/// Registered Replicate methods.
///
private readonly Dictionary _replicateRpcDelegates = new Dictionary();
///
/// Registered Reconcile methods.
///
private readonly Dictionary _reconcileRpcDelegates = new Dictionary();
///
/// True if initialized compnents for prediction.
///
private bool _predictionInitialized;
///
/// Rigidbody found on this object. This is used for prediction.
///
private Rigidbody _predictionRigidbody;
///
/// Rigidbody2D found on this object. This is used for prediction.
///
private Rigidbody2D _predictionRigidbody2d;
///
/// Last position for TransformMayChange.
///
private Vector3 _lastMayChangePosition;
///
/// Last rotation for TransformMayChange.
///
private Quaternion _lastMayChangeRotation;
///
/// Last scale for TransformMayChange.
///
private Vector3 _lastMayChangeScale;
#endregion
///
/// Registers a RPC method.
/// Internal use.
///
///
///
[APIExclude] //codegen this can be made protected internal then set public via codegen
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected internal void RegisterReplicateRpc(uint hash, ReplicateRpcDelegate del)
{
_replicateRpcDelegates[hash] = del;
}
///
/// Registers a RPC method.
/// Internal use.
///
///
///
[APIExclude] //codegen this can be made protected internal then set public via codegen
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected internal void RegisterReconcileRpc(uint hash, ReconcileRpcDelegate del)
{
_reconcileRpcDelegates[hash] = del;
}
///
/// Called when a replicate is received.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void OnReplicateRpc(uint? methodHash, PooledReader reader, NetworkConnection sendingClient, Channel channel)
{
if (methodHash == null)
methodHash = ReadRpcHash(reader);
if (sendingClient == null)
{
if (_networkObjectCache.NetworkManager.CanLog(LoggingType.Error))
Debug.LogError($"NetworkConnection is null. Replicate {methodHash.Value} on {gameObject.name}, behaviour {GetType().Name} will not complete. Remainder of packet may become corrupt.");
return;
}
if (_replicateRpcDelegates.TryGetValueIL2CPP(methodHash.Value, out ReplicateRpcDelegate del))
{
del.Invoke(this, reader, sendingClient);
}
else
{
if (_networkObjectCache.NetworkManager.CanLog(LoggingType.Warning))
Debug.LogWarning($"Replicate not found for hash {methodHash.Value} on {gameObject.name}, behaviour {GetType().Name}. Remainder of packet may become corrupt.");
}
}
///
/// Called when a reconcile is received.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void OnReconcileRpc(uint? methodHash, PooledReader reader, Channel channel)
{
if (methodHash == null)
methodHash = ReadRpcHash(reader);
if (_reconcileRpcDelegates.TryGetValueIL2CPP(methodHash.Value, out ReconcileRpcDelegate del))
{
del.Invoke(this, reader);
}
else
{
if (_networkObjectCache.NetworkManager.CanLog(LoggingType.Warning))
Debug.LogWarning($"Reconcile not found for hash {methodHash.Value}. Remainder of packet may become corrupt.");
}
}
///
/// Clears cached replicates. This can be useful to call on server and client after teleporting.
///
/// True to reset values for server, false to reset values for client.
public void ClearReplicateCache(bool asServer) { InternalClearReplicateCache(asServer); }
///
/// Clears cached replicates.
/// For internal use only.
///
///
[APIExclude]
protected internal virtual void InternalClearReplicateCache(bool asServer) { }
///
/// Writes number of past inputs from buffer to writer and sends it to the server.
/// Internal use.
/// //codegen can be made internal, then public via codegen
[APIExclude]
public void SendReplicateRpc(uint hash, List replicateBuffer, int count)
{
if (!IsSpawnedWithWarning())
return;
int lastBufferIndex = (replicateBuffer.Count - 1);
//Nothing to send; should never be possible.
if (lastBufferIndex < 0)
return;
/* Where to start writing from. When passed
* into the writer values from this offset
* and forward will be written. */
int offset = replicateBuffer.Count - count;
if (offset < 0)
offset = 0;
Channel channel = Channel.Unreliable;
//Write history to methodWriter.
PooledWriter methodWriter = WriterPool.GetWriter();
methodWriter.WriteList(replicateBuffer, offset);
PooledWriter writer;
//if (_rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
//writer = CreateLinkedRpc(link, methodWriter, Channel.Unreliable);
//else //todo add support for -> server rpc links.
writer = CreateRpc(hash, methodWriter, PacketId.Replicate, channel);
NetworkManager.TransportManager.SendToServer((byte)channel, writer.GetArraySegment(), false);
methodWriter.Dispose();
writer.Dispose();
}
///
/// Sends a RPC to target.
/// Internal use.
///
[APIExclude] //codegen this can be made internal then set public via codegen
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SendReconcileRpc(uint hash, T reconcileData, Channel channel)
{
if (!IsSpawned)
return;
if (!Owner.IsActive)
return;
PooledWriter methodWriter = WriterPool.GetWriter();
methodWriter.Write(reconcileData);
PooledWriter writer;
#if UNITY_EDITOR || DEVELOPMENT_BUILD
if (NetworkManager.DebugManager.ReconcileRpcLinks && _rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
#else
if (_rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
#endif
writer = CreateLinkedRpc(link, methodWriter, channel);
else
writer = CreateRpc(hash, methodWriter, PacketId.Reconcile, channel);
_networkObjectCache.NetworkManager.TransportManager.SendToClient((byte)channel, writer.GetArraySegment(), Owner);
methodWriter.Dispose();
writer.Dispose();
}
///
/// Returns if there is a chance the transform may change after the tick.
///
///
protected internal bool TransformMayChange()
{
if (!_predictionInitialized)
{
_predictionInitialized = true;
_predictionRigidbody = GetComponentInParent();
_predictionRigidbody2d = GetComponentInParent();
}
/* Use distance when checking if changed because rigidbodies can twitch
* or move an extremely small amount. These small moves are not worth
* resending over because they often fix themselves each frame. */
float changeDistance = 0.000004f;
bool positionChanged = (transform.position - _lastMayChangePosition).sqrMagnitude > changeDistance;
bool rotationChanged = (transform.rotation.eulerAngles - _lastMayChangeRotation.eulerAngles).sqrMagnitude > changeDistance;
bool scaleChanged = (transform.localScale - _lastMayChangeScale).sqrMagnitude > changeDistance;
bool transformChanged = (positionChanged || rotationChanged || scaleChanged);
/* Returns true if transform.hasChanged, or if either
* of the rigidbodies have velocity. */
bool changed = (
transformChanged ||
(_predictionRigidbody != null && (_predictionRigidbody.velocity != Vector3.zero || _predictionRigidbody.angularVelocity != Vector3.zero)) ||
(_predictionRigidbody2d != null && (_predictionRigidbody2d.velocity != Vector2.zero || _predictionRigidbody2d.angularVelocity != 0f))
);
//If transform changed update last values.
if (transformChanged)
{
_lastMayChangePosition = transform.position;
_lastMayChangeRotation = transform.rotation;
_lastMayChangeScale = transform.localScale;
}
return changed;
}
}
}