1094 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			1094 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using FishNet.Connection;
 | |
| using FishNet.Managing;
 | |
| using FishNet.Object;
 | |
| using FishNet.Transporting;
 | |
| using FishNet.Utility;
 | |
| using System.Collections.Generic;
 | |
| using System.Runtime.CompilerServices;
 | |
| using UnityEngine;
 | |
| 
 | |
| namespace FishNet.Component.Prediction
 | |
| {
 | |
|     public partial class PredictedObject : NetworkBehaviour
 | |
|     {
 | |
|         #region All.
 | |
|         #region Internal.
 | |
|         /// <summary>
 | |
|         /// True if owner and implements prediction methods.
 | |
|         /// </summary>
 | |
|         internal bool IsPredictingOwner() => (base.IsOwner && _implementsPredictionMethods);
 | |
|         #endregion
 | |
|         #region Private.
 | |
|         /// <summary>
 | |
|         /// Pauser for rigidbodies when they cannot be rolled back.
 | |
|         /// </summary>
 | |
|         private RigidbodyPauser _rigidbodyPauser = new RigidbodyPauser();
 | |
|         /// <summary>
 | |
|         /// Next tick to resend data when resend type is set to interval.
 | |
|         /// </summary>
 | |
|         private uint _nextIntervalResend;
 | |
|         /// <summary>
 | |
|         /// Number of resends remaining when the object has not changed.
 | |
|         /// </summary>
 | |
|         private ushort _resendsRemaining;
 | |
|         /// <summary>
 | |
|         /// True if object was changed previous tick.
 | |
|         /// </summary>
 | |
|         private bool _previouslyChanged;
 | |
|         /// <summary>
 | |
|         /// Animators found on the graphical object.
 | |
|         /// </summary>
 | |
|         private Animator[] _graphicalAnimators;
 | |
|         /// <summary>
 | |
|         /// True if GraphicalAniamtors have been intialized.
 | |
|         /// </summary>
 | |
|         private bool _animatorsInitialized;
 | |
|         /// <summary>
 | |
|         /// Tick on the last received state.
 | |
|         /// </summary>
 | |
|         private uint _lastStateLocalTick;
 | |
|         /// <summary>
 | |
|         /// True if a connection is owner and prediction methods are implemented.
 | |
|         /// </summary>
 | |
|         private bool _isPredictingOwner(NetworkConnection c) => (c == base.Owner && _implementsPredictionMethods);
 | |
|         /// <summary>
 | |
|         /// Current interpolation value.
 | |
|         /// </summary>
 | |
|         private byte _currentSpectatorInterpolation;
 | |
|         /// <summary>
 | |
|         /// Target interpolation when collision is exited.
 | |
|         /// </summary>
 | |
|         private byte _targetSpectatorInterpolation;
 | |
|         /// <summary>
 | |
|         /// Target interpolation when collision is entered.
 | |
|         /// </summary>
 | |
|         private byte _targetCollisionSpectatorInterpolation;
 | |
|         /// <summary>
 | |
|         /// How often in ticks to move towards targets when not collisionEntered is true.
 | |
|         /// </summary>
 | |
|         private uint _collisionChangeDivisor = 2;
 | |
|         /// <summary>
 | |
|         /// How often in ticks to move towards targets when not collisionEntered is false.
 | |
|         /// </summary>
 | |
|         private uint _changeDivisor = 5;
 | |
|         /// <summary>
 | |
|         /// Current number of ticks to ignore when replaying.
 | |
|         /// </summary>
 | |
|         private uint _currentIgnoredTicks;
 | |
|         /// <summary>
 | |
|         /// Target number of ticks to ignore when replaying.
 | |
|         /// </summary>
 | |
|         private uint _targetIgnoredTicks;
 | |
|         /// <summary>
 | |
|         /// Last local tick that collision has stayed with local client objects.
 | |
|         /// </summary>
 | |
|         private uint _collisionStayedTick;
 | |
|         /// <summary>
 | |
|         /// Local client objects this object is currently colliding with.
 | |
|         /// </summary>
 | |
|         private HashSet<GameObject> _localClientCollidedObjects = new HashSet<GameObject>();
 | |
|         #endregion
 | |
| 
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         private void Rigidbodies_OnSpawnServer(NetworkConnection c)
 | |
|         {
 | |
|             if (!IsRigidbodyPrediction)
 | |
|                 return;
 | |
|             if (c == base.Owner)
 | |
|                 return;
 | |
|             if (c.IsLocalClient)
 | |
|                 return;
 | |
| 
 | |
|             uint tick = c.LastPacketTick;
 | |
|             if (_predictionType == PredictionType.Rigidbody)
 | |
|                 SendRigidbodyState(tick, c, true);
 | |
|             else
 | |
|                 SendRigidbody2DState(tick, c, true);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called when the client starts.
 | |
|         /// </summary>
 | |
|         private void Rigidbodies_OnStartClient()
 | |
|         {
 | |
|             //Store up to 1 second of states.
 | |
|             int capacity = base.TimeManager.TickRate;
 | |
|             /* Only need to check one collection capacity since they both will be the same.
 | |
|              * If capacity does not line up then re-initialize. */
 | |
|             if (capacity != _rigidbodyStates.Capacity)
 | |
|             {
 | |
|                 _rigidbodyStates.Initialize(capacity);
 | |
|                 _rigidbody2dStates.Initialize(capacity);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called on client when ownership changes for this object.
 | |
|         /// </summary>
 | |
|         /// <param name="prevOwner"></param>
 | |
|         private void Rigidbodies_OnOwnershipClient(NetworkConnection prevOwner)
 | |
|         {
 | |
|             if (!IsRigidbodyPrediction)
 | |
|                 return;
 | |
|             //If owner no need to fix for animators.
 | |
|             if (base.IsOwner)
 | |
|                 return;
 | |
|             //Would have already fixed if animators are set.
 | |
|             if (_animatorsInitialized)
 | |
|                 return;
 | |
| 
 | |
|             _animatorsInitialized = true;
 | |
|             _graphicalAnimators = _graphicalObject.GetComponentsInChildren<Animator>(true);
 | |
| 
 | |
|             if (_graphicalAnimators.Length > 0)
 | |
|             {
 | |
|                 for (int i = 0; i < _graphicalAnimators.Length; i++)
 | |
|                     _graphicalAnimators[i].keepAnimatorStateOnDisable = true;
 | |
| 
 | |
|                 /* True if at least one animator is on the graphical root. 
 | |
|                 * Unity gets components in order so it's safe to assume
 | |
|                  * 0 would be the topmost animator. This has to be done
 | |
|                  * to prevent animation jitter when pausing the rbs. */
 | |
|                 if (_graphicalAnimators[0].transform == _graphicalObject)
 | |
|                 {
 | |
|                     Transform graphicalHolder = new GameObject().transform;
 | |
|                     graphicalHolder.name = "GraphicalObjectHolder";
 | |
|                     graphicalHolder.SetParent(transform);
 | |
|                     graphicalHolder.localPosition = _graphicalInstantiatedOffsetPosition;
 | |
|                     graphicalHolder.localRotation = _graphicalInstantiatedOffsetRotation;
 | |
|                     graphicalHolder.localScale = _graphicalObject.localScale;
 | |
|                     _graphicalObject.SetParent(graphicalHolder);
 | |
|                     _graphicalObject.localPosition = Vector3.zero;
 | |
|                     _graphicalObject.localRotation = Quaternion.identity;
 | |
|                     _graphicalObject.localScale = Vector3.one;
 | |
|                     SetGraphicalObject(graphicalHolder);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called after a tick occurs; physics would have simulated if using PhysicsMode.TimeManager.
 | |
|         /// </summary>
 | |
|         private void Rigidbodies_TimeManager_OnPostTick()
 | |
|         {
 | |
|             if (!IsRigidbodyPrediction)
 | |
|                 return;
 | |
|             if (base.IsServer)
 | |
|                 return;
 | |
| 
 | |
|             bool is2D = (_predictionType == PredictionType.Rigidbody2D);
 | |
|             TrySetCollisionExited(is2D);
 | |
| 
 | |
|             /* Can check either one. They may not be initialized yet if host. */
 | |
|             if (_rigidbodyStates.Initialized)
 | |
|             {
 | |
|                 if (_localTick == 0)
 | |
|                     _localTick = base.TimeManager.LocalTick;
 | |
| 
 | |
|                 if (!is2D)
 | |
|                     _rigidbodyStates.Add(new RigidbodyState(_rigidbody, _localTick));
 | |
|                 else
 | |
|                     _rigidbody2dStates.Add(new Rigidbody2DState(_rigidbody2d, _localTick));
 | |
|             }
 | |
| 
 | |
|             if (CanPredict())
 | |
|             {
 | |
|                 UpdateSpectatorSmoothing();
 | |
|                 if (!is2D)
 | |
|                     PredictVelocity(gameObject.scene.GetPhysicsScene());
 | |
|                 else
 | |
|                     PredictVelocity(gameObject.scene.GetPhysicsScene2D());
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Unsets collision values if collision was known to be entered but there are no longer any contact points.
 | |
|         /// </summary>
 | |
|         private void TrySetCollisionExited(bool is2d)
 | |
|         {
 | |
|             /* If this object is no longer
 | |
|              * colliding with local client objects
 | |
|              * then unset collision.
 | |
|              * This is done here instead of using
 | |
|              * OnCollisionExit because often collisionexit
 | |
|              * will be missed due to ignored ticks. 
 | |
|              * While not ignoring ticks is always an option
 | |
|              * its not ideal because ignoring ticks helps
 | |
|              * prevent over predicting. */
 | |
|             if (_collisionStayedTick != 0 && (base.TimeManager.LocalTick != _collisionStayedTick))
 | |
|                 CollisionExited();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called before performing a reconcile on NetworkBehaviour.
 | |
|         /// </summary>
 | |
|         private void Rigidbodies_TimeManager_OnPreReconcile(NetworkBehaviour nb)
 | |
|         {
 | |
|             /* Exit if owner and implements prediction methods
 | |
|              * because csp would be handled by prediction methods
 | |
|              * rather than predicted object. */
 | |
|             if (IsPredictingOwner())
 | |
|                 return;
 | |
|             if (nb.gameObject == gameObject)
 | |
|                 return;
 | |
|             if (!IsRigidbodyPrediction)
 | |
|                 return;
 | |
| 
 | |
|             bool is2D = (_predictionType == PredictionType.Rigidbody2D);
 | |
|             uint lastNbTick = nb.GetLastReconcileTick();
 | |
|             int stateIndex = GetCachedStateIndex(lastNbTick, is2D);
 | |
| 
 | |
|             /* If running again on the same reconcile or state is for a different
 | |
|              * tick then do make RBs kinematic. Resetting to a different state
 | |
|              * could cause a desync and there's no reason to run the same
 | |
|              * tick twice. */
 | |
|             if (stateIndex == -1)
 | |
|             {
 | |
|                 _spectatorSmoother?.SetLocalReconcileTick(-1);
 | |
|                 _rigidbodyPauser.Pause();
 | |
|             }
 | |
|             //If state was found then reset to it.
 | |
|             else
 | |
|             {
 | |
|                 _spectatorSmoother?.SetLocalReconcileTick(lastNbTick);
 | |
|                 if (is2D)
 | |
|                 {
 | |
|                     _rigidbody2dStates.RemoveRange(true, stateIndex);
 | |
|                     ResetRigidbody2DToData(_rigidbody2dStates[0]);
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     _rigidbodyStates.RemoveRange(true, stateIndex);
 | |
|                     ResetRigidbodyToData(_rigidbodyStates[0]);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called after performing a reconcile on NetworkBehaviour.
 | |
|         /// </summary>
 | |
|         private void Rigidbodies_TimeManager_OnPostReconcile(NetworkBehaviour nb)
 | |
|         {
 | |
|             _rigidbodyPauser.Unpause();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called before physics is simulated when replaying a replicate method.
 | |
|         /// Contains the PhysicsScene and PhysicsScene2D which was simulated.
 | |
|         /// </summary>
 | |
|         private void Rigidbodies_PredictionManager_OnPreReplicateReplay(uint tick, PhysicsScene ps, PhysicsScene2D ps2d)
 | |
|         {
 | |
|             if (!CanPredict())
 | |
|                 return;
 | |
| 
 | |
|             if (_localTick - tick < _currentIgnoredTicks)
 | |
|                 _rigidbodyPauser.Pause();
 | |
| 
 | |
|             if (_predictionType == PredictionType.Rigidbody)
 | |
|                 PredictVelocity(ps);
 | |
|             else if (_predictionType == PredictionType.Rigidbody2D)
 | |
|                 PredictVelocity(ps2d);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called before physics is simulated when replaying a replicate method.
 | |
|         /// Contains the PhysicsScene and PhysicsScene2D which was simulated.
 | |
|         /// </summary>
 | |
|         private void Rigidbodies_PredictionManager_OnPostReplicateReplay(uint tick, PhysicsScene ps, PhysicsScene2D ps2d)
 | |
|         {
 | |
|             if (!CanPredict())
 | |
|                 return;
 | |
|             if (_rigidbodyPauser.Paused)
 | |
|                 return;
 | |
| 
 | |
|             if (_predictionType == PredictionType.Rigidbody)
 | |
|             {
 | |
|                 int index = GetCachedStateIndex(tick, false);
 | |
|                 if (index != -1)
 | |
|                 {
 | |
|                     bool prevKinematic = _rigidbodyStates[index].IsKinematic;
 | |
|                     _rigidbodyStates[index] = new RigidbodyState(_rigidbody, prevKinematic, tick);
 | |
|                 }
 | |
|             }
 | |
|             if (_predictionType == PredictionType.Rigidbody2D)
 | |
|             {
 | |
|                 int index = GetCachedStateIndex(tick, true);
 | |
|                 if (index != -1)
 | |
|                 {
 | |
|                     bool prevSimulated = _rigidbody2dStates[index].Simulated;
 | |
|                     _rigidbody2dStates[index] = new Rigidbody2DState(_rigidbody2d, prevSimulated, tick);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called when ping updates for the local client.
 | |
|         /// </summary>
 | |
|         private void Rigidbodies_OnRoundTripTimeUpdated(long ping)
 | |
|         {
 | |
|             SetTargetSmoothing(ping, false);
 | |
|         }
 | |
|         /// <summary>
 | |
|         /// Sets target smoothing values.
 | |
|         /// </summary>
 | |
|         /// <param name="setImmediately">True to set current values to targets immediately.</param>
 | |
|         private void SetTargetSmoothing(long ping, bool setImmediately)
 | |
|         {
 | |
|             if (_spectatorSmoother == null)
 | |
|                 return;
 | |
| 
 | |
|             const long maxPing = 300;
 | |
|             //Percentage that ping is between 0f and maxPing.
 | |
|             float rttPercent = Mathf.InverseLerp(0, maxPing, ping);
 | |
| 
 | |
|             //Min and max interpolation to use when not colliding.
 | |
|             byte minTargetInterpolation;
 | |
|             byte maxTargetInterpolation;
 | |
|             //Min and max interpolation to use when colliding.
 | |
|             const byte minCollisionInterpolation = 1;
 | |
|             byte maxCollisionInterpolation;
 | |
|             //Multiplier determining ignored ticks value.
 | |
|             float maxIgnoredTicksMultiplier;
 | |
|             //Multiplier to apply towards accelerating graphics to lower interpolation.
 | |
|             float collisionMoveMultiplier;
 | |
|             //Set values for variables above based on settings.
 | |
|             SetFlatValues();
 | |
| 
 | |
|             //Target interpolation.
 | |
|             _targetSpectatorInterpolation = (byte)Mathf.CeilToInt(
 | |
|                 Mathf.Lerp(0f, (float)maxTargetInterpolation, rttPercent)
 | |
|                 );
 | |
|             if (_targetSpectatorInterpolation < minTargetInterpolation)
 | |
|                 _targetSpectatorInterpolation = minTargetInterpolation;
 | |
|             //Collision interpolation.
 | |
|             _targetCollisionSpectatorInterpolation = (byte)Mathf.FloorToInt(Mathf.Lerp(minCollisionInterpolation, maxCollisionInterpolation, rttPercent));
 | |
| 
 | |
|             float pingToMs = (float)(base.TimeManager.RoundTripTime / 1000f);
 | |
|             float ignoredTicKMultiplier = Mathf.Lerp(0f, maxIgnoredTicksMultiplier, rttPercent);
 | |
|             _targetIgnoredTicks = (uint)Mathf.CeilToInt(base.TimeManager.TimeToTicks((pingToMs * ignoredTicKMultiplier)));
 | |
| 
 | |
|             _spectatorSmoother.SetExcessBufferMultiplier(collisionMoveMultiplier);
 | |
| 
 | |
|             //If to apply values to targets immediately.
 | |
|             if (setImmediately)
 | |
|             {
 | |
|                 _currentIgnoredTicks = _targetIgnoredTicks;
 | |
|                 _currentSpectatorInterpolation = (CollidingWithLocalClient()) ? _targetCollisionSpectatorInterpolation : _targetSpectatorInterpolation;
 | |
|                 _spectatorSmoother.SetIgnoredTicks(_currentIgnoredTicks);
 | |
|                 _spectatorSmoother.SetInterpolation(_currentSpectatorInterpolation);
 | |
|             }
 | |
| 
 | |
|             //Sets ranges to use based on smoothing type.
 | |
|             void SetFlatValues()
 | |
|             {
 | |
|                 //Changing divisor is always a flat rate of x times a second.
 | |
|                 _changeDivisor = (uint)Mathf.CeilToInt((float)base.TimeManager.TickRate * 0.33f);
 | |
| 
 | |
|                 if (_spectatorSmoothingType == SpectatorSmoothingType.Accuracy)
 | |
|                 {
 | |
|                     minTargetInterpolation = 2; //2
 | |
|                     maxTargetInterpolation = 14; //13
 | |
|                     maxCollisionInterpolation = 2; //3
 | |
|                     maxIgnoredTicksMultiplier = 0.2f; //0.4
 | |
|                     collisionMoveMultiplier = 6.5f; //7
 | |
|                     _collisionChangeDivisor = 1; //1
 | |
|                 }
 | |
|                 else if (_spectatorSmoothingType == SpectatorSmoothingType.Mixed)
 | |
|                 {
 | |
|                     minTargetInterpolation = 3;
 | |
|                     maxTargetInterpolation = 16;
 | |
|                     maxCollisionInterpolation = 4;
 | |
|                     maxIgnoredTicksMultiplier = 0.4f;
 | |
|                     collisionMoveMultiplier = 3.5f;
 | |
|                     _collisionChangeDivisor = 2;
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     minTargetInterpolation = 3;
 | |
|                     maxTargetInterpolation = 18;
 | |
|                     maxCollisionInterpolation = 5;
 | |
|                     maxIgnoredTicksMultiplier = 0.5f;
 | |
|                     collisionMoveMultiplier = 1.5f;
 | |
|                     _collisionChangeDivisor = 3;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Returns if this object is colliding with any local client objects.
 | |
|         /// </summary>
 | |
|         /// <returns></returns>
 | |
|         private bool CollidingWithLocalClient()
 | |
|         {
 | |
|             /* If it's been more than 1 tick since collision stayed
 | |
|              * then do not consider as collided. */
 | |
|             return (base.TimeManager.LocalTick - _collisionStayedTick) < 1;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Updates spectator smoothing values to move towards their targets.
 | |
|         /// </summary>
 | |
|         private void UpdateSpectatorSmoothing()
 | |
|         {
 | |
|             byte targetInterpolation;
 | |
|             uint divisor;
 | |
|             if (CollidingWithLocalClient())
 | |
|             {
 | |
|                 targetInterpolation = _targetCollisionSpectatorInterpolation;
 | |
|                 byte interpolationGoal = _targetCollisionSpectatorInterpolation;
 | |
|                 _currentSpectatorInterpolation = interpolationGoal;
 | |
|                 divisor = _collisionChangeDivisor;
 | |
| 
 | |
|                 if (base.TimeManager.LocalTick % divisor == 0)
 | |
|                     MoveTowardsIgnoredTicks();
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 targetInterpolation = _targetSpectatorInterpolation;
 | |
|                 byte interpolationGoal = _targetSpectatorInterpolation;
 | |
|                 //Move values over 10 times a second.
 | |
|                 divisor = _changeDivisor;
 | |
| 
 | |
|                 if (base.TimeManager.LocalTick % divisor == 0)
 | |
|                 {
 | |
|                     //Ignored ticks.
 | |
|                     MoveTowardsIgnoredTicks();
 | |
|                     //Interpolation/
 | |
|                     if (_currentSpectatorInterpolation < interpolationGoal)
 | |
|                         _currentSpectatorInterpolation++;
 | |
|                     else if (_currentSpectatorInterpolation > interpolationGoal)
 | |
|                         _currentSpectatorInterpolation--;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             void MoveTowardsIgnoredTicks()
 | |
|             {
 | |
|                 if (_currentIgnoredTicks < _targetIgnoredTicks)
 | |
|                     _currentIgnoredTicks++;
 | |
|                 else if (_currentIgnoredTicks > _targetIgnoredTicks)
 | |
|                     _currentIgnoredTicks--;
 | |
|             }
 | |
| 
 | |
|             _spectatorSmoother.SetInterpolation(_currentSpectatorInterpolation);
 | |
|             _spectatorSmoother.SetIgnoredTicks(_currentIgnoredTicks);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called when a collision occurs and the smoothing type must perform operations.
 | |
|         /// </summary>
 | |
|         private bool CollisionEnteredLocalClientObject(GameObject go)
 | |
|         {
 | |
|             if (go.TryGetComponent<NetworkObject>(out NetworkObject nob))
 | |
|                 return nob.Owner.IsLocalClient;
 | |
| 
 | |
|             //Fall through.
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends the rigidbodies state to Observers of a NetworkBehaviour.
 | |
|         /// </summary>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         private void SendRigidbodyState(NetworkBehaviour nb)
 | |
|         {
 | |
|             NetworkConnection owner = nb.Owner;
 | |
|             if (!owner.IsActive)
 | |
|                 return;
 | |
|             NetworkManager nm = nb.NetworkManager;
 | |
|             if (nm == null)
 | |
|                 return;
 | |
| 
 | |
|             uint tick = nb.GetLastReplicateTick();
 | |
|             TrySendRigidbodyState(nb, tick);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Send current state to a connection.
 | |
|         /// </summary>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         private void TrySendRigidbodyState(NetworkBehaviour nb, uint tick)
 | |
|         {
 | |
|             if (!IsRigidbodyPrediction)
 | |
|                 return;
 | |
|             NetworkConnection nbOwner = nb.Owner;
 | |
|             //No need to send to self unless doesnt implement prediction methods.
 | |
|             if (_isPredictingOwner(nbOwner))
 | |
|                 return;
 | |
|             //If clientHost.
 | |
|             if (nbOwner.IsLocalClient)
 | |
|                 return;
 | |
|             /* Not an observer. SendTargetRpc normally
 | |
|              * already checks this when ValidateTarget
 | |
|              * is true but we want to save perf by exiting
 | |
|              * early before checks and serialization when
 | |
|              * we know the conn is not an observer. */
 | |
|             if (!base.Observers.Contains(nbOwner))
 | |
|                 return;
 | |
| 
 | |
|             bool hasChanged = base.TransformMayChange();
 | |
|             if (!hasChanged)
 | |
|             {
 | |
|                 //Not changed but was previous tick. Reset resends.
 | |
|                 if (_previouslyChanged)
 | |
|                     _resendsRemaining = base.TimeManager.TickRate;
 | |
| 
 | |
|                 uint currentTick = base.TimeManager.Tick;
 | |
|                 //Resends remain.
 | |
|                 if (_resendsRemaining > 0)
 | |
|                 {
 | |
|                     _resendsRemaining--;
 | |
|                     //If now 0 then update next send interval.
 | |
|                     if (_resendsRemaining == 0)
 | |
|                         UpdateNextIntervalResend();
 | |
|                 }
 | |
|                 //No more resends.
 | |
|                 else
 | |
|                 {
 | |
|                     //No resend interval.
 | |
|                     if (_resendType == ResendType.Disabled)
 | |
|                         return;
 | |
|                     //Interval not yet met.
 | |
|                     if (currentTick < _nextIntervalResend)
 | |
|                         return;
 | |
| 
 | |
|                     UpdateNextIntervalResend();
 | |
|                 }
 | |
| 
 | |
|                 //Updates the next tick when a resend should occur.
 | |
|                 void UpdateNextIntervalResend()
 | |
|                 {
 | |
|                     _nextIntervalResend = (currentTick + _resendInterval);
 | |
|                 }
 | |
| 
 | |
|             }
 | |
|             _previouslyChanged = hasChanged;
 | |
| 
 | |
|             if (_predictionType == PredictionType.Rigidbody)
 | |
|                 SendRigidbodyState(tick, nbOwner, false);
 | |
|             else
 | |
|                 SendRigidbody2DState(tick, nbOwner, false);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Gets a cached state index in actual array position.
 | |
|         /// </summary>
 | |
|         /// <returns></returns>
 | |
|         private int GetCachedStateIndex(uint tick, bool is2d)
 | |
|         {
 | |
|             int count;
 | |
|             uint firstTick;
 | |
|             //3d.
 | |
|             if (!is2d)
 | |
|             {
 | |
|                 count = _rigidbodyStates.Count;
 | |
|                 if (count == 0)
 | |
|                     return -1;
 | |
|                 firstTick = _rigidbodyStates[0].LocalTick;
 | |
|             }
 | |
|             //2d.
 | |
|             else
 | |
|             {
 | |
|                 count = _rigidbody2dStates.Count;
 | |
|                 if (count == 0)
 | |
|                     return -1;
 | |
|                 firstTick = _rigidbody2dStates[0].LocalTick;
 | |
|             }
 | |
| 
 | |
|             //First tick is higher than current, no match is possibloe.
 | |
|             if (firstTick > tick)
 | |
|                 return -1;
 | |
| 
 | |
|             long difference = (tick - firstTick);
 | |
|             //Desired tick would be out of bounds. This should never happen.
 | |
|             if (difference >= count)
 | |
|                 return -1;
 | |
| 
 | |
|             return (int)difference;
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Tries to predict velocity for a Vector3.
 | |
|         /// </summary>
 | |
|         protected bool PredictVector3Velocity(ref float? velocityBaseline, ref Vector3 lastVelocity, Vector3 velocity, out Vector3 result)
 | |
|         {
 | |
|             float velocityDifference;
 | |
|             float directionDifference;
 | |
| 
 | |
|             /* Velocity. */
 | |
|             directionDifference = (velocityBaseline != null) ?
 | |
|                 Vector3.SqrMagnitude(lastVelocity.normalized - velocity.normalized) :
 | |
|                 0f;
 | |
|             //If direction has changed too much then reset the baseline.
 | |
|             if (directionDifference > 0.01f)
 | |
|             {
 | |
|                 velocityBaseline = null;
 | |
|             }
 | |
|             //Direction hasn't changed enough to reset baseline.
 | |
|             else
 | |
|             {
 | |
|                 //Difference in velocity since last simulation.
 | |
|                 velocityDifference = Vector3.Magnitude(lastVelocity - velocity);
 | |
|                 //If there is no baseline.
 | |
|                 if (velocityBaseline == null)
 | |
|                 {
 | |
|                     if (velocityDifference > 0)
 | |
|                         velocityBaseline = velocityDifference;
 | |
|                 }
 | |
|                 //If there is a baseline.
 | |
|                 else
 | |
|                 {
 | |
|                     //If the difference exceeds the baseline by 10% then reset baseline so another will be calculated.
 | |
|                     if (velocityDifference > (velocityBaseline.Value * 1.1f) || velocityDifference < (velocityBaseline.Value * 0.9f))
 | |
|                     {
 | |
|                         velocityBaseline = null;
 | |
|                     }
 | |
|                     //Velocity difference is close enough to the baseline to where it doesn't need to be reset, so use prediction.
 | |
|                     else
 | |
|                     {
 | |
|                         Vector3 changeMultiplied = (velocity - lastVelocity) * _maintainedVelocity;
 | |
|                         //Retaining velocity.
 | |
|                         if (_maintainedVelocity > 0f)
 | |
|                         {
 | |
|                             result = (velocity + changeMultiplied);
 | |
|                         }
 | |
|                         //Reducing velocity.
 | |
|                         else
 | |
|                         {
 | |
|                             result = (velocity + changeMultiplied);
 | |
|                             /* When reducing velocity make sure the direction
 | |
|                              * did not change. When this occurs it means the velocity
 | |
|                              * was reduced into the opposite direction. To prevent
 | |
|                              * this from happening just zero out velocity instead. */
 | |
|                             if (velocity.normalized != result.normalized)
 | |
|                                 result = Vector3.zero;
 | |
|                         }
 | |
|                         return true;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             //Fall through.
 | |
|             result = Vector3.zero;
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Tries to predict velocity for a float.
 | |
|         /// </summary>
 | |
|         private bool PredictFloatVelocity(ref float? velocityBaseline, ref float lastVelocity, float velocity, out float result)
 | |
|         {
 | |
|             float velocityDifference;
 | |
|             float directionDifference;
 | |
| 
 | |
|             /* Velocity. */
 | |
|             directionDifference = (velocityBaseline != null) ? (velocity - lastVelocity) : 0f;
 | |
| 
 | |
|             //If direction has changed too much then reset the baseline.
 | |
|             if (directionDifference > 0.01f)
 | |
|             {
 | |
|                 velocityBaseline = null;
 | |
|             }
 | |
|             //Direction hasn't changed enough to reset baseline.
 | |
|             else
 | |
|             {
 | |
|                 //Difference in velocity since last simulation.
 | |
|                 velocityDifference = Mathf.Abs(lastVelocity - velocity);
 | |
|                 //If there is no baseline.
 | |
|                 if (velocityBaseline == null)
 | |
|                 {
 | |
|                     if (velocityDifference > 0)
 | |
|                         velocityBaseline = velocityDifference;
 | |
|                 }
 | |
|                 //If there is a baseline.
 | |
|                 else
 | |
|                 {
 | |
|                     //If the difference exceeds the baseline by 10% then reset baseline so another will be calculated.
 | |
|                     if (velocityDifference > (velocityBaseline.Value * 1.1f) || velocityDifference < (velocityBaseline.Value * 0.9f))
 | |
|                     {
 | |
|                         velocityBaseline = null;
 | |
|                     }
 | |
|                     //Velocity difference is close enough to the baseline to where it doesn't need to be reset, so use prediction.
 | |
|                     else
 | |
|                     {
 | |
|                         float changeMultiplied = (velocity - lastVelocity) * _maintainedVelocity;
 | |
|                         //Retaining velocity.
 | |
|                         if (_maintainedVelocity > 0f)
 | |
|                         {
 | |
|                             result = (velocity + changeMultiplied);
 | |
|                         }
 | |
|                         //Reducing velocity.
 | |
|                         else
 | |
|                         {
 | |
|                             result = (velocity + changeMultiplied);
 | |
|                             /* When reducing velocity make sure the direction
 | |
|                              * did not change. When this occurs it means the velocity
 | |
|                              * was reduced into the opposite direction. To prevent
 | |
|                              * this from happening just zero out velocity instead. */
 | |
|                             if (Mathf.Abs(velocity) != Mathf.Abs(result))
 | |
|                                 result = 0f;
 | |
|                         }
 | |
|                         return true;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             //Fall through.
 | |
|             result = 0f;
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Returns if prediction can be used on this rigidbody.
 | |
|         /// </summary>
 | |
|         /// <returns></returns>
 | |
|         private bool CanPredict()
 | |
|         {
 | |
|             if (!IsRigidbodyPrediction)
 | |
|                 return false;
 | |
|             if (base.IsServer || IsPredictingOwner())
 | |
|                 return false;
 | |
| 
 | |
|             return true;
 | |
|         }
 | |
|         #endregion
 | |
| 
 | |
|         #region Rigidbody.
 | |
|         #region Private.
 | |
|         /// <summary>
 | |
|         /// Past RigidbodyStates.
 | |
|         /// </summary>
 | |
|         private RingBuffer<RigidbodyState> _rigidbodyStates = new RingBuffer<RigidbodyState>();
 | |
|         /// <summary>
 | |
|         /// Velocity from previous simulation.
 | |
|         /// </summary>
 | |
|         private Vector3 _lastVelocity;
 | |
|         /// <summary>
 | |
|         /// Angular velocity from previous simulation.
 | |
|         /// </summary>
 | |
|         private Vector3 _lastAngularVelocity;
 | |
|         /// <summary>
 | |
|         /// Baseline for velocity magnitude.
 | |
|         /// </summary>
 | |
|         private float? _velocityBaseline;
 | |
|         /// <summary>
 | |
|         /// Baseline for angular velocity magnitude.
 | |
|         /// </summary>
 | |
|         private float? _angularVelocityBaseline;
 | |
|         /// <summary>
 | |
|         /// PhysicsScene for this object when OnPreReconcile is called.
 | |
|         /// </summary>
 | |
|         private PhysicsScene _physicsScene;
 | |
|         #endregion
 | |
| 
 | |
|         private void OnCollisionEnter(Collision collision)
 | |
|         {
 | |
|             if (_predictionType != PredictionType.Rigidbody)
 | |
|                 return;
 | |
| 
 | |
|             GameObject go = collision.gameObject;
 | |
|             if (CollisionEnteredLocalClientObject(go))
 | |
|                 CollisionEntered(go);
 | |
|         }
 | |
| 
 | |
| 
 | |
|         private void OnCollisionStay(Collision collision)
 | |
|         {
 | |
|             if (_predictionType != PredictionType.Rigidbody)
 | |
|                 return;
 | |
| 
 | |
|             if (_localClientCollidedObjects.Contains(collision.gameObject))
 | |
|                 _collisionStayedTick = base.TimeManager.LocalTick;
 | |
|         }
 | |
|         private bool _gotBack;
 | |
|         private bool _gotb2;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Resets the rigidbody to a state.
 | |
|         /// </summary>
 | |
|         private void ResetRigidbodyToData(RigidbodyState state)
 | |
|         {
 | |
|             //Update transform and rigidbody.
 | |
|             _rigidbody.transform.position = state.Position;
 | |
|             _rigidbody.transform.rotation = state.Rotation;
 | |
|             bool isKinematic = state.IsKinematic;
 | |
|             _rigidbody.isKinematic = isKinematic;
 | |
|             if (!isKinematic)
 | |
|             {
 | |
|                 _rigidbody.velocity = state.Velocity;
 | |
|                 _rigidbody.angularVelocity = state.AngularVelocity;
 | |
|             }
 | |
| 
 | |
|             /* Do not need to sync transforms because it's done internally by the reconcile method.
 | |
|              * That is, so long as this is called using OnPreReconcile. */
 | |
| 
 | |
|             //Set prediction defaults.
 | |
|             _velocityBaseline = null;
 | |
|             _angularVelocityBaseline = null;
 | |
|             _lastVelocity = _rigidbody.velocity;
 | |
|             _lastAngularVelocity = _rigidbody.angularVelocity;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sets the next predicted velocity on the rigidbody.
 | |
|         /// </summary>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         private void PredictVelocity(PhysicsScene ps)
 | |
|         {
 | |
|             if (_maintainedVelocity == 0f)
 | |
|                 return;
 | |
|             if (ps != _physicsScene)
 | |
|                 return;
 | |
| 
 | |
|             Vector3 result;
 | |
|             if (PredictVector3Velocity(ref _velocityBaseline, ref _lastVelocity, _rigidbody.velocity, out result))
 | |
|                 _rigidbody.velocity = result;
 | |
|             if (PredictVector3Velocity(ref _angularVelocityBaseline, ref _lastAngularVelocity, _rigidbody.angularVelocity, out result))
 | |
|                 _rigidbody.angularVelocity = result;
 | |
| 
 | |
|             _lastVelocity = _rigidbody.velocity;
 | |
|             _lastAngularVelocity = _rigidbody.angularVelocity;
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends current states of this object to client.
 | |
|         /// </summary>
 | |
|         private void SendRigidbodyState(uint reconcileTick, NetworkConnection conn, bool applyImmediately)
 | |
|         {
 | |
|             //No need to send to owner if they implement prediction methods.
 | |
|             if (_isPredictingOwner(conn))
 | |
|                 return;
 | |
|             reconcileTick = (conn == base.NetworkObject.PredictedSpawner) ? conn.LastPacketTick : reconcileTick;
 | |
|             RigidbodyState state = new RigidbodyState(_rigidbody, reconcileTick);
 | |
|             TargetSendRigidbodyState(conn, state, applyImmediately);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends transform and rigidbody state to spectators.
 | |
|         /// </summary>
 | |
|         [TargetRpc(ValidateTarget = false)]
 | |
|         private void TargetSendRigidbodyState(NetworkConnection c, RigidbodyState state, bool applyImmediately, Channel channel = Channel.Unreliable)
 | |
|         {
 | |
|             if (!CanPredict())
 | |
|                 return;
 | |
| 
 | |
|             uint localTick = state.LocalTick;
 | |
|             if (applyImmediately)
 | |
|             {
 | |
|                 /* If PredictedSpawner is self then this client
 | |
|                  * was the one to predicted spawn this object. When that is
 | |
|                  * the case do not apply initial velocities, but so allow
 | |
|                  * regular updates/corrections. */
 | |
|                 if (base.NetworkObject.PredictedSpawner.IsLocalClient)
 | |
|                     return;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 if (!CanProcessReceivedState(localTick))
 | |
|                     return;
 | |
|             }
 | |
| 
 | |
|             if (applyImmediately)
 | |
|             {
 | |
|                 _rigidbodyStates.Clear();
 | |
|                 ResetRigidbodyToData(state);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 int index = GetCachedStateIndex(localTick, false);
 | |
|                 if (index != -1)
 | |
|                     _rigidbodyStates[index] = state;
 | |
|                 else
 | |
|                     _rigidbodyStates.Add(state);
 | |
|             }
 | |
|         }
 | |
|         #endregion
 | |
| 
 | |
|         #region Rigidbody2D.
 | |
|         #region Private.
 | |
|         /// <summary>
 | |
|         /// Past RigidbodyStates.
 | |
|         /// </summary>
 | |
|         private RingBuffer<Rigidbody2DState> _rigidbody2dStates = new RingBuffer<Rigidbody2DState>();
 | |
|         /// <summary>
 | |
|         /// Velocity from previous simulation.
 | |
|         /// </summary>
 | |
|         private Vector3 _lastVelocity2D;
 | |
|         /// <summary>
 | |
|         /// Angular velocity from previous simulation.
 | |
|         /// </summary>
 | |
|         private float _lastAngularVelocity2D;
 | |
|         /// <summary>
 | |
|         /// Baseline for velocity magnitude.
 | |
|         /// </summary>
 | |
|         private float? _velocityBaseline2D;
 | |
|         /// <summary>
 | |
|         /// Baseline for angular velocity magnitude.
 | |
|         /// </summary>
 | |
|         private float? _angularVelocityBaseline2D;
 | |
|         /// <summary>
 | |
|         /// PhysicsScene for this object when OnPreReconcile is called.
 | |
|         /// </summary>
 | |
|         private PhysicsScene2D _physicsScene2D;
 | |
|         #endregion
 | |
| 
 | |
|         private void OnCollisionEnter2D(Collision2D collision)
 | |
|         {
 | |
|             if (_predictionType != PredictionType.Rigidbody2D)
 | |
|                 return;
 | |
| 
 | |
|             GameObject go = collision.gameObject;
 | |
|             if (CollisionEnteredLocalClientObject(go))
 | |
|                 CollisionEntered(go);
 | |
|         }
 | |
| 
 | |
|         private void OnCollisionStay2D(Collision2D collision)
 | |
|         {
 | |
|             if (_predictionType != PredictionType.Rigidbody2D)
 | |
|                 return;
 | |
| 
 | |
|             if (_localClientCollidedObjects.Contains(collision.gameObject))
 | |
|                 _collisionStayedTick = base.TimeManager.LocalTick;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called when collision has entered a local clients object.
 | |
|         /// </summary>
 | |
|         private void CollisionEntered(GameObject go)
 | |
|         {
 | |
|             _collisionStayedTick = base.TimeManager.LocalTick;
 | |
|             _localClientCollidedObjects.Add(go);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Called when collision has exited a local clients object.
 | |
|         /// </summary>
 | |
|         private void CollisionExited()
 | |
|         {
 | |
|             _localClientCollidedObjects.Clear();
 | |
|             _collisionStayedTick = 0;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Resets the Rigidbody2D to last received data.
 | |
|         /// </summary>
 | |
|         private void ResetRigidbody2DToData(Rigidbody2DState state)
 | |
|         {
 | |
|             //Update transform and rigidbody.
 | |
|             _rigidbody2d.transform.position = state.Position;
 | |
|             _rigidbody2d.transform.rotation = state.Rotation;
 | |
|             bool simulated = state.Simulated;
 | |
|             _rigidbody2d.simulated = simulated;
 | |
|             _rigidbody2d.isKinematic = !simulated;
 | |
|             if (simulated)
 | |
|             {
 | |
|                 _rigidbody2d.velocity = state.Velocity;
 | |
|                 _rigidbody2d.angularVelocity = state.AngularVelocity;
 | |
|             }
 | |
| 
 | |
|             /* Do not need to sync transforms because it's done internally by the reconcile method.
 | |
|              * That is, so long as this is called using OnPreReconcile. */
 | |
| 
 | |
|             //Set prediction defaults.
 | |
|             _velocityBaseline2D = null;
 | |
|             _angularVelocityBaseline2D = null;
 | |
|             _lastVelocity2D = _rigidbody2d.velocity;
 | |
|             _lastAngularVelocity2D = _rigidbody2d.angularVelocity;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sets the next predicted velocity on the rigidbody.
 | |
|         /// </summary>
 | |
|         [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | |
|         private void PredictVelocity(PhysicsScene2D ps)
 | |
|         {
 | |
|             if (_maintainedVelocity == 0f)
 | |
|                 return;
 | |
|             if (ps != _physicsScene2D)
 | |
|                 return;
 | |
| 
 | |
|             Vector3 v3Result;
 | |
|             if (PredictVector3Velocity(ref _velocityBaseline2D, ref _lastVelocity2D, _rigidbody2d.velocity, out v3Result))
 | |
|                 _rigidbody2d.velocity = v3Result;
 | |
|             float floatResult;
 | |
|             if (PredictFloatVelocity(ref _angularVelocityBaseline2D, ref _lastAngularVelocity2D, _rigidbody2d.angularVelocity, out floatResult))
 | |
|                 _rigidbody2d.angularVelocity = floatResult;
 | |
| 
 | |
|             _lastVelocity2D = _rigidbody2d.velocity;
 | |
|             _lastAngularVelocity2D = _rigidbody2d.angularVelocity;
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends current Rigidbody2D state to a connection.
 | |
|         /// </summary>
 | |
|         private void SendRigidbody2DState(uint reconcileTick, NetworkConnection conn, bool applyImmediately)
 | |
|         {
 | |
|             Rigidbody2DState state = new Rigidbody2DState(_rigidbody2d, reconcileTick);
 | |
|             TargetSendRigidbody2DState(conn, state, applyImmediately);
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Sends transform and rigidbody state to spectators.
 | |
|         /// </summary>
 | |
|         [TargetRpc(ValidateTarget = false)]
 | |
|         private void TargetSendRigidbody2DState(NetworkConnection c, Rigidbody2DState state, bool applyImmediately, Channel channel = Channel.Unreliable)
 | |
|         {
 | |
|             if (!CanPredict())
 | |
|                 return;
 | |
| 
 | |
|             uint localTick = state.LocalTick;
 | |
|             if (applyImmediately)
 | |
|             {
 | |
|                 /* If PredictedSpawner is self then this client
 | |
|                  * was the one to predicted spawn this object. When that is
 | |
|                  * the case do not apply initial velocities, but so allow
 | |
|                  * regular updates/corrections. */
 | |
|                 if (base.NetworkObject.PredictedSpawner.IsLocalClient)
 | |
|                     return;
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 if (!CanProcessReceivedState(localTick))
 | |
|                     return;
 | |
|             }
 | |
| 
 | |
|             if (applyImmediately)
 | |
|             {
 | |
|                 _rigidbody2dStates.Clear();
 | |
|                 ResetRigidbody2DToData(state);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 int index = GetCachedStateIndex(localTick, true);
 | |
|                 if (index != -1)
 | |
|                     _rigidbody2dStates[index] = state;
 | |
|                 else
 | |
|                     _rigidbody2dStates.Add(state);
 | |
|             }
 | |
| 
 | |
| 
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Returns if a received state can be processed based on it's tick.
 | |
|         /// </summary>
 | |
|         /// <param name="stateTick"></param>
 | |
|         /// <returns></returns>
 | |
|         private bool CanProcessReceivedState(uint stateTick)
 | |
|         {
 | |
|             //Older than another received value.
 | |
|             if (stateTick <= _lastStateLocalTick)
 | |
|                 return false;
 | |
|             _lastStateLocalTick = stateTick;
 | |
| 
 | |
|             return true;
 | |
|         }
 | |
|         #endregion
 | |
| 
 | |
|     }
 | |
| 
 | |
| 
 | |
| } |