using FishNet.CodeGenerating; using FishNet.Component.Prediction; using FishNet.Managing; using FishNet.Serializing; using GameKit.Dependencies.Utilities; using System.Collections.Generic; using UnityEngine; using UnityEngine.Scripting; namespace FishNet.Object.Prediction { public static class PredictionRigidbody2DSerializers { public static void WriteForceData(this Writer w, PredictionRigidbody2D.EntryData value) { PredictionRigidbody2D.ForceApplicationType appType = value.Type; w.WriteUInt8Unpacked((byte)appType); PredictionRigidbody2D.AllForceData data = value.Data; switch (appType) { case PredictionRigidbody2D.ForceApplicationType.AddForce: case PredictionRigidbody2D.ForceApplicationType.AddRelativeForce: w.WriteVector3(data.Vector3Force); w.WriteInt32((byte)data.Mode); break; case PredictionRigidbody2D.ForceApplicationType.AddTorque: w.WriteSingle(data.FloatForce); w.WriteInt32((byte)data.Mode); break; case PredictionRigidbody2D.ForceApplicationType.AddForceAtPosition: w.WriteVector3(data.Vector3Force); w.WriteVector3(data.Position); w.WriteInt32((byte)data.Mode); break; default: NetworkManagerExtensions.LogError($"ForceApplicationType of {appType} is not supported."); break; } } public static PredictionRigidbody2D.EntryData ReadForceData(this Reader r) { PredictionRigidbody2D.EntryData fd = new(); PredictionRigidbody2D.ForceApplicationType appType = (PredictionRigidbody2D.ForceApplicationType)r.ReadUInt8Unpacked(); fd.Type = appType; PredictionRigidbody2D.AllForceData data = new(); switch (appType) { case PredictionRigidbody2D.ForceApplicationType.AddForce: case PredictionRigidbody2D.ForceApplicationType.AddRelativeForce: data.Vector3Force = r.ReadVector3(); data.Mode = (ForceMode2D)r.ReadUInt8Unpacked(); return fd; case PredictionRigidbody2D.ForceApplicationType.AddTorque: data.FloatForce = r.ReadSingle(); data.Mode = (ForceMode2D)r.ReadUInt8Unpacked(); return fd; case PredictionRigidbody2D.ForceApplicationType.AddForceAtPosition: data.Vector3Force = r.ReadVector3(); data.Position = r.ReadVector3(); data.Mode = (ForceMode2D)r.ReadUInt8Unpacked(); return fd; default: NetworkManagerExtensions.LogError($"ForceApplicationType of {appType} is not supported."); return fd; } } public static void WritePredictionRigidbody2D(this Writer w, PredictionRigidbody2D pr) { w.Write(pr.Rigidbody2D.GetState()); w.WriteList(pr.GetPendingForces()); } public static PredictionRigidbody2D ReadPredictionRigidbody2D(this Reader r) { List lst = CollectionCaches.RetrieveList(); Rigidbody2DState rs = r.Read(); r.ReadList(ref lst); PredictionRigidbody2D pr = ResettableObjectCaches.Retrieve(); pr.SetReconcileData(rs, lst); pr.SetPendingForces(lst); return pr; } } [UseGlobalCustomSerializer] [Preserve] public class PredictionRigidbody2D : IResettable { #region Types. //How the force was applied. [System.Flags] public enum ForceApplicationType : byte { AddForceAtPosition = 1, AddForce = 4, AddRelativeForce = 8, AddTorque = 16, } public struct AllForceData { public Vector3 Vector3Force; public float FloatForce; public Vector3 Position; public ForceMode2D Mode; public AllForceData(Vector3 force, ForceMode2D mode) : this() { Vector3Force = force; Mode = mode; } public AllForceData(float force, ForceMode2D mode) : this() { FloatForce = force; Mode = mode; } public AllForceData(Vector3 force, Vector3 position, ForceMode2D mode) : this() { Vector3Force = force; Position = position; Mode = mode; } } [UseGlobalCustomSerializer] public struct EntryData { public ForceApplicationType Type; public AllForceData Data; public EntryData(ForceApplicationType type, AllForceData data) { Type = type; Data = data; } public EntryData(EntryData fd) { Type = fd.Type; Data = fd.Data; } } #endregion #region Internal. /// /// Rigidbody2DState set only as reconcile data. /// [System.NonSerialized] internal Rigidbody2DState Rigidbody2DState; #endregion #region Public. /// /// Rigidbody which force is applied. /// public Rigidbody2D Rigidbody2D { get; private set; } /// /// Returns if there are any pending forces. /// public bool HasPendingForces => (_pendingForces != null && _pendingForces.Count > 0); #endregion #region Private /// /// Forces waiting to be applied. /// [ExcludeSerialization] private List _pendingForces; /// /// Returns current pending forces. /// Modifying this collection could cause undesirable results. /// public List GetPendingForces() => _pendingForces; #endregion ~PredictionRigidbody2D() { if (_pendingForces != null) CollectionCaches.StoreAndDefault(ref _pendingForces); Rigidbody2D = null; } /// /// Rigidbody which force is applied. /// /// public void Initialize(Rigidbody2D rb) { Rigidbody2D = rb; if (_pendingForces == null) _pendingForces = CollectionCaches.RetrieveList(); else _pendingForces.Clear(); } /// /// Adds Velocity force to the Rigidbody. /// public void AddForce(Vector3 force, ForceMode2D mode = ForceMode2D.Force) { EntryData fd = new(ForceApplicationType.AddForce, new(force, mode)); _pendingForces.Add(fd); } public void AddRelativeForce(Vector3 force, ForceMode2D mode = ForceMode2D.Force) { EntryData fd = new(ForceApplicationType.AddRelativeForce, new(force, mode)); _pendingForces.Add(fd); } public void AddTorque(float force, ForceMode2D mode = ForceMode2D.Force) { EntryData fd = new(ForceApplicationType.AddTorque, new(force, mode)); _pendingForces.Add(fd); } public void AddForceAtPosition(Vector3 force, Vector3 position, ForceMode2D mode = ForceMode2D.Force) { EntryData fd = new(ForceApplicationType.AddForceAtPosition, new(force, position, mode)); _pendingForces.Add(fd); } /// /// Sets velocity while clearing pending forces. /// Simulate should still be called normally. /// public void Velocity(Vector3 force) { Rigidbody2D.linearVelocity = force; RemoveForces(true); } /// /// Sets angularVelocity while clearning pending forces. /// Simulate should still be called normally. /// public void AngularVelocity(float force) { Rigidbody2D.angularVelocity = force; RemoveForces(false); } /// /// Applies pending forces to rigidbody in the order they were added. /// public void Simulate() { foreach (EntryData item in _pendingForces) { AllForceData data = item.Data; switch (item.Type) { case ForceApplicationType.AddTorque: Rigidbody2D.AddTorque(data.FloatForce, data.Mode); break; case ForceApplicationType.AddForce: Rigidbody2D.AddForce(data.Vector3Force, data.Mode); break; case ForceApplicationType.AddRelativeForce: Rigidbody2D.AddRelativeForce(data.Vector3Force, data.Mode); break; case ForceApplicationType.AddForceAtPosition: Rigidbody2D.AddForceAtPosition(data.Vector3Force, data.Position, data.Mode); break; } } _pendingForces.Clear(); } /// /// Manually clears pending forces. /// /// True to clear velocities, false to clear angular velocities. public void ClearPendingForces(bool velocity) { RemoveForces(velocity); } /// /// Clears pending velocity and angular velocity forces. /// public void ClearPendingForces() { _pendingForces.Clear(); } /// /// Reconciles to a state. /// public void Reconcile(PredictionRigidbody2D pr) { _pendingForces.Clear(); if (pr._pendingForces != null) { foreach (EntryData item in pr._pendingForces) _pendingForces.Add(new(item)); } Rigidbody2D.SetState(pr.Rigidbody2DState); ResettableObjectCaches.Store(pr); } /// /// Removes forces from pendingForces. /// /// True to remove if velocity, false if to remove angular velocity. private void RemoveForces(bool velocity) { if (_pendingForces.Count > 0) { bool shouldExist = velocity; ForceApplicationType velocityApplicationTypes = (ForceApplicationType.AddRelativeForce | ForceApplicationType.AddForce); List newDatas = CollectionCaches.RetrieveList(); foreach (EntryData item in _pendingForces) { if (VelocityApplicationTypesContains(item.Type) == !velocity) newDatas.Add(item); } //Add back to _pendingForces if changed. if (newDatas.Count != _pendingForces.Count) { _pendingForces.Clear(); foreach (EntryData item in newDatas) _pendingForces.Add(item); } CollectionCaches.Store(newDatas); bool VelocityApplicationTypesContains(ForceApplicationType apt) { return (velocityApplicationTypes & apt) == apt; } } } internal void SetPendingForces(List lst) => _pendingForces = lst; internal void SetReconcileData(Rigidbody2DState rs, List lst) { Rigidbody2DState = rs; _pendingForces = lst; } public void ResetState() { CollectionCaches.StoreAndDefault(ref _pendingForces); Rigidbody2D = null; } public void InitializeState() { } } }