using FishNet.Managing;
using GameKit.Dependencies.Utilities;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using UnityEngine;
namespace FishNet.Component.Prediction
{
///
/// Pauses and unpauses rigidbodies. While paused rigidbodies cannot be interacted with or simulated.
///
public class RigidbodyPauser : IResettable
{
#region Types.
///
/// Data for a rigidbody before being set kinematic.
///
private struct RigidbodyData
{
///
/// Rigidbody for data.
///
public Rigidbody Rigidbody;
///
/// Cached velocity when being set kinematic.
///
public Vector3 Velocity;
///
/// Cached velocity when being set kinematic.
///
public Vector3 AngularVelocity;
///
/// True if the rigidbody was kinematic prior to being paused.
///
public bool IsKinematic;
///
/// Detection mode of the Rigidbody.
///
public CollisionDetectionMode CollisionDetectionMode;
public RigidbodyData(Rigidbody rb)
{
Rigidbody = rb;
Velocity = Vector3.zero;
AngularVelocity = Vector3.zero;
IsKinematic = rb.isKinematic;
CollisionDetectionMode = rb.collisionDetectionMode;
}
public void Update(Rigidbody rb)
{
Velocity = rb.linearVelocity;
AngularVelocity = rb.angularVelocity;
IsKinematic = rb.isKinematic;
CollisionDetectionMode = rb.collisionDetectionMode;
}
}
///
/// Data for a rigidbody2d before being set kinematic.
///
private struct Rigidbody2DData
{
///
/// Rigidbody for data.
///
public Rigidbody2D Rigidbody2d;
///
/// Cached velocity when being set kinematic.
///
public Vector2 Velocity;
///
/// Cached velocity when being set kinematic.
///
public float AngularVelocity;
///
/// True if the rigidbody was kinematic prior to being paused.
///
public bool IsKinematic;
///
/// True if the rigidbody was simulated prior to being paused.
///
public bool Simulated;
///
/// Detection mode of the rigidbody.
///
public CollisionDetectionMode2D CollisionDetectionMode;
public Rigidbody2DData(Rigidbody2D rb)
{
Rigidbody2d = rb;
Velocity = Vector2.zero;
AngularVelocity = 0f;
Simulated = rb.simulated;
IsKinematic = rb.isKinematic;
CollisionDetectionMode = rb.collisionDetectionMode;
}
public void Update(Rigidbody2D rb)
{
Velocity = rb.linearVelocity;
AngularVelocity = rb.angularVelocity;
Simulated = rb.simulated;
IsKinematic = rb.isKinematic;
CollisionDetectionMode = rb.collisionDetectionMode;
}
}
#endregion
#region Public.
///
/// True if the rigidbodies are considered paused.
///
public bool Paused { get; private set; }
#endregion
#region Private.
///
/// Rigidbody datas for found rigidbodies.
///
private List _rigidbodyDatas = new();
///
/// Rigidbody2D datas for found rigidbodies;
///
private List _rigidbody2dDatas = new();
///
/// True to get rigidbodies in children of transform.
///
private bool _getInChildren;
///
/// Transform to get rigidbodies on.
///
private Transform _transform;
///
/// Type of prediction movement which is being used.
///
private RigidbodyType _rigidbodyType;
///
/// True if initialized at least once.
///
private bool _initialized;
#endregion
///
/// Assigns rigidbodies using initialized settings.
///
public void UpdateRigidbodies()
{
if (!_initialized)
{
InstanceFinder.NetworkManager.LogError($"T{GetType().Name} has not been initialized yet. This method cannot be used.");
return;
}
UpdateRigidbodies(_transform, _rigidbodyType, _getInChildren);
}
///
/// Assigns rigidbodies manually and initializes component.
///
public void UpdateRigidbodies(Rigidbody[] rbs)
{
List rigidbodies = CollectionCaches.RetrieveList();
foreach (Rigidbody rb in rbs)
rigidbodies.Add(rb);
UpdateRigidbodies(rigidbodies);
CollectionCaches.Store(rigidbodies);
}
///
/// Assigns rigidbodies manually and initializes component.
///
private void UpdateRigidbodies(List rbs)
{
_rigidbodyDatas.Clear();
foreach (Rigidbody rb in rbs)
_rigidbodyDatas.Add(new(rb));
_initialized = true;
}
///
/// Assigns rigidbodies manually and initializes component.
///
public void UpdateRigidbodies2D(Rigidbody2D[] rbs)
{
List rigidbodies = CollectionCaches.RetrieveList();
foreach (Rigidbody2D rb in rbs)
rigidbodies.Add(rb);
UpdateRigidbodies2D(rigidbodies);
CollectionCaches.Store(rigidbodies);
}
///
/// Assigns rigidbodies manually and initializes component.
///
private void UpdateRigidbodies2D(List rbs)
{
_rigidbody2dDatas.Clear();
foreach (Rigidbody2D rb in rbs)
_rigidbody2dDatas.Add(new(rb));
_initialized = true;
}
///
/// Assigns rigidbodies.
///
/// Rigidbodies2D to use.
public void UpdateRigidbodies(Transform t, RigidbodyType rbType, bool getInChildren)
{
_rigidbodyType = rbType;
_getInChildren = getInChildren;
//3D.
if (rbType == RigidbodyType.Rigidbody)
{
List rigidbodies = CollectionCaches.RetrieveList();
if (getInChildren)
{
Rigidbody[] rbs = t.GetComponentsInChildren();
for (int i = 0; i < rbs.Length; i++)
rigidbodies.Add(rbs[i]);
}
else
{
Rigidbody rb = t.GetComponent();
if (rb != null)
rigidbodies.Add(rb);
}
UpdateRigidbodies(rigidbodies);
CollectionCaches.Store(rigidbodies);
}
//2D.
else
{
List rigidbodies = CollectionCaches.RetrieveList();
if (getInChildren)
{
Rigidbody2D[] rbs = t.GetComponentsInChildren();
for (int i = 0; i < rbs.Length; i++)
rigidbodies.Add(rbs[i]);
}
else
{
Rigidbody2D rb = t.GetComponent();
if (rb != null)
rigidbodies.Add(rb);
}
UpdateRigidbodies2D(rigidbodies);
CollectionCaches.Store(rigidbodies);
}
}
///
/// Pauses rigidbodies preventing them from interacting.
///
public void Pause()
{
if (Paused)
return;
Paused = true;
/* Iterate move after pausing.
* This ensures when the children RBs update values
* they are not updating from a new scene, where the root
* may have moved them */
//3D.
if (_rigidbodyType == RigidbodyType.Rigidbody)
{
for (int i = 0; i < _rigidbodyDatas.Count; i++)
{
if (!PauseRigidbody(i))
{
_rigidbodyDatas.RemoveAt(i);
i--;
}
}
//Sets isKinematic status and returns if successful.
bool PauseRigidbody(int index)
{
RigidbodyData rbData = _rigidbodyDatas[index];
Rigidbody rb = rbData.Rigidbody;
if (rb == null)
return false;
rbData.Update(rb);
_rigidbodyDatas[index] = rbData;
rb.collisionDetectionMode = CollisionDetectionMode.Discrete;
rb.isKinematic = true;
//rb.detectCollisions = false;
return true;
}
}
//2D.
else
{
for (int i = 0; i < _rigidbody2dDatas.Count; i++)
{
if (!PauseRigidbody(i))
{
_rigidbody2dDatas.RemoveAt(i);
i--;
}
}
//Sets isKinematic status and returns if successful.
bool PauseRigidbody(int index)
{
Rigidbody2DData rbData = _rigidbody2dDatas[index];
Rigidbody2D rb = rbData.Rigidbody2d;
if (rb == null)
return false;
rbData.Update(rb);
_rigidbody2dDatas[index] = rbData;
rb.collisionDetectionMode = CollisionDetectionMode2D.Discrete;
rb.isKinematic = true;
rb.simulated = false;
return true;
}
}
}
///
/// Unpauses rigidbodies allowing them to interact normally.
///
public void Unpause()
{
if (!Paused)
return;
Paused = false;
//3D.
if (_rigidbodyType == RigidbodyType.Rigidbody)
{
for (int i = 0; i < _rigidbodyDatas.Count; i++)
{
if (!UnpauseRigidbody(i))
{
_rigidbodyDatas.RemoveAt(i);
i--;
}
}
//Sets isKinematic status and returns if successful.
bool UnpauseRigidbody(int index)
{
RigidbodyData rbData = _rigidbodyDatas[index];
Rigidbody rb = rbData.Rigidbody;
if (rb == null)
return false;
/* If data has RB updated as kinematic then
* do not unpause. This means either something else
* is handling the kinematic state of the dev
* made it kinematic. */
if (rbData.IsKinematic)
return true;
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
rb.isKinematic = rbData.IsKinematic;
//rb.detectCollisions = rbData.DetectCollisions;
rb.collisionDetectionMode = rbData.CollisionDetectionMode;
if (!rb.isKinematic)
{
rb.linearVelocity = rbData.Velocity;
rb.angularVelocity = rbData.AngularVelocity;
}
return true;
}
}
//2D.
else
{
for (int i = 0; i < _rigidbody2dDatas.Count; i++)
{
if (!UnpauseRigidbody(i))
{
_rigidbody2dDatas.RemoveAt(i);
i--;
}
}
//Sets isKinematic status and returns if successful.
bool UnpauseRigidbody(int index)
{
Rigidbody2DData rbData = _rigidbody2dDatas[index];
Rigidbody2D rb = rbData.Rigidbody2d;
if (rb == null)
return false;
//Same as RB, only unpause if data is stored in an unpaused state.
if (rbData.IsKinematic || !rbData.Simulated)
return true;
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
rb.isKinematic = rbData.IsKinematic;
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
rb.simulated = rbData.Simulated;
rb.collisionDetectionMode = rbData.CollisionDetectionMode;
if (!rb.isKinematic)
{
rb.linearVelocity = rbData.Velocity;
rb.angularVelocity = rbData.AngularVelocity;
}
return true;
}
}
}
public void ResetState()
{
_rigidbodyDatas.Clear();
_rigidbody2dDatas.Clear();
_getInChildren = default;
_transform = default;
_rigidbodyType = default;
_initialized = default;
Paused = default;
}
public void InitializeState() { }
}
}