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 { [Preserve] [DefaultWriter] public static class PredictionRigidbodySerializers { [DefaultWriter] public static void WriteEntryData(this Writer w, PredictionRigidbody.EntryData value) { PredictionRigidbody.ForceApplicationType appType = value.Type; w.WriteUInt8Unpacked((byte)appType); PredictionRigidbody.AllForceData data = value.Data; switch (appType) { case PredictionRigidbody.ForceApplicationType.AddTorque: case PredictionRigidbody.ForceApplicationType.AddForce: case PredictionRigidbody.ForceApplicationType.AddRelativeTorque: case PredictionRigidbody.ForceApplicationType.AddRelativeForce: w.WriteVector3(data.Vector3Force); w.WriteInt32((byte)data.Mode); break; case PredictionRigidbody.ForceApplicationType.AddExplosiveForce: w.WriteSingle(data.FloatForce); w.WriteVector3(data.Position); w.WriteSingle(data.Radius); w.WriteSingle(data.UpwardsModifier); w.WriteInt32((byte)data.Mode); break; case PredictionRigidbody.ForceApplicationType.AddForceAtPosition: w.WriteVector3(data.Vector3Force); w.WriteVector3(data.Position); w.WriteInt32((byte)data.Mode); break; case PredictionRigidbody.ForceApplicationType.MovePosition: w.WriteVector3(data.Position); break; case PredictionRigidbody.ForceApplicationType.MoveRotation: w.WriteUInt8Unpacked((byte)data.RotationPacking); w.WriteQuaternion(data.Rotation, data.RotationPacking); break; default: w.NetworkManager.LogError($"ForceApplicationType of {appType} is not supported."); break; } } [DefaultReader] public static PredictionRigidbody.EntryData ReadEntryData(this Reader r) { PredictionRigidbody.EntryData fd = new(); PredictionRigidbody.ForceApplicationType appType = (PredictionRigidbody.ForceApplicationType)r.ReadUInt8Unpacked(); fd.Type = appType; PredictionRigidbody.AllForceData data = new(); switch (appType) { case PredictionRigidbody.ForceApplicationType.AddTorque: case PredictionRigidbody.ForceApplicationType.AddForce: case PredictionRigidbody.ForceApplicationType.AddRelativeTorque: case PredictionRigidbody.ForceApplicationType.AddRelativeForce: data.Vector3Force = r.ReadVector3(); data.Mode = (ForceMode)r.ReadInt32(); break; case PredictionRigidbody.ForceApplicationType.AddExplosiveForce: data.FloatForce = r.ReadSingle(); data.Position = r.ReadVector3(); data.Radius = r.ReadSingle(); data.UpwardsModifier = r.ReadSingle(); data.Mode = (ForceMode)r.ReadInt32(); break; case PredictionRigidbody.ForceApplicationType.AddForceAtPosition: data.Vector3Force = r.ReadVector3(); data.Position = r.ReadVector3(); data.Mode = (ForceMode)r.ReadInt32(); break; case PredictionRigidbody.ForceApplicationType.MovePosition: data.Position = r.ReadVector3(); break; case PredictionRigidbody.ForceApplicationType.MoveRotation: AutoPackType apt = (AutoPackType)r.ReadUInt8Unpacked(); data.Rotation = r.ReadQuaternion(apt); break; default: r.NetworkManager.LogError($"ForceApplicationType of {appType} is not supported."); break; } fd.Data = data; return fd; } [DefaultWriter] public static void WritePredictionRigidbody(this Writer w, PredictionRigidbody pr) { w.Write(pr.Rigidbody.GetState(pr.RotationPacking)); /* This used to write pr.GetPendingForces() but is no longer needed, assuming the user properly * reconciles everything that modifies the predictionRigidbody. */ w.WriteList(null); } [DefaultReader] public static PredictionRigidbody ReadPredictionRigidbody(this Reader r) { List lst = CollectionCaches.RetrieveList(); RigidbodyState rs = r.Read(); r.ReadList(ref lst); PredictionRigidbody pr = ResettableObjectCaches.Retrieve(); pr.SetReconcileData(rs, lst); return pr; } [DefaultDeltaWriter] public static bool WriteDeltaEntryData(this Writer w, PredictionRigidbody.EntryData value) { w.WriteEntryData(value); return true; } [DefaultDeltaReader] public static PredictionRigidbody.EntryData ReadDeltaEntryData(this Reader r) => r.ReadEntryData(); [DefaultDeltaWriter] public static bool WriteDeltaPredictionRigidbody(this Writer w, PredictionRigidbody pr) { w.WritePredictionRigidbody(pr); return true; } [DefaultDeltaReader] public static PredictionRigidbody ReadDeltaPredictionRigidbody(this Reader r) => r.ReadPredictionRigidbody(); } [UseGlobalCustomSerializer] [Preserve] public class PredictionRigidbody : IResettable { #region Types. public struct AllForceData { public ForceMode Mode; public Vector3 Vector3Force; public Vector3 Position; public Quaternion Rotation; [ExcludeSerialization] public readonly AutoPackType RotationPacking; public float FloatForce; public float Radius; public float UpwardsModifier; /// /// Used for MovePosition. /// public AllForceData(Vector3 position) : this() { Position = position; } /// /// Used for MoveRotation. /// public AllForceData(Quaternion rotation, AutoPackType rotationPacking) : this() { Rotation = rotation; RotationPacking = rotationPacking; } /// /// Used for Force and Torque. /// public AllForceData(Vector3 force, ForceMode mode) : this() { Vector3Force = force; Mode = mode; } /// /// Used for Position. /// public AllForceData(Vector3 force, Vector3 position, ForceMode mode) : this() { Vector3Force = force; Position = position; Mode = mode; } /// /// Used for Explosive. /// /// /// /// /// /// public AllForceData(float force, Vector3 position, float radius, float upwardsModifier, ForceMode mode) : this() { FloatForce = force; Position = position; Radius = radius; UpwardsModifier = upwardsModifier; Mode = mode; } } public interface IForceData { } // How the force was applied. [System.Flags] public enum ForceApplicationType : byte { AddForceAtPosition = 1 << 0, AddExplosiveForce = 1 << 1, AddForce = 1 << 2, AddRelativeForce = 1 << 3, AddTorque = 1 << 4, AddRelativeTorque = 1 << 5, MovePosition = 1 << 6, MoveRotation = 1 << 7, } [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 Public. /// /// Rigidbody which force is applied. /// public Rigidbody Rigidbody { get; private set; } /// /// Returns if there are any pending forces. /// public bool HasPendingForces => _pendingForces != null && _pendingForces.Count > 0; #endregion #region Internal. /// /// RigidbodyState set only as reconcile data. /// [System.NonSerialized] internal RigidbodyState RigidbodyState; /// /// How much to pack rotation. /// [ExcludeSerialization] internal AutoPackType RotationPacking = AutoPackType.Packed; #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 ~PredictionRigidbody() { if (_pendingForces != null) CollectionCaches.StoreAndDefault(ref _pendingForces); Rigidbody = null; } /// /// Rigidbody which force is applied. /// /// public void Initialize(Rigidbody rb, AutoPackType rotationPacking = AutoPackType.Packed) { Rigidbody = rb; RotationPacking = rotationPacking; if (_pendingForces == null) _pendingForces = CollectionCaches.RetrieveList(); else _pendingForces.Clear(); } /// /// Adds Velocity force to the Rigidbody. /// public void AddForce(Vector3 force, ForceMode mode = ForceMode.Force) { EntryData fd = new(ForceApplicationType.AddForce, new(force, mode)); _pendingForces.Add(fd); } public void AddRelativeForce(Vector3 force, ForceMode mode = ForceMode.Force) { EntryData fd = new(ForceApplicationType.AddRelativeForce, new(force, mode)); _pendingForces.Add(fd); } public void AddTorque(Vector3 force, ForceMode mode = ForceMode.Force) { EntryData fd = new(ForceApplicationType.AddTorque, new(force, mode)); _pendingForces.Add(fd); } public void AddRelativeTorque(Vector3 force, ForceMode mode = ForceMode.Force) { EntryData fd = new(ForceApplicationType.AddRelativeTorque, new(force, mode)); _pendingForces.Add(fd); } public void AddExplosiveForce(float force, Vector3 position, float radius, float upwardsModifier = 0f, ForceMode mode = ForceMode.Force) { EntryData fd = new(ForceApplicationType.AddExplosiveForce, new(force, position, radius, upwardsModifier, mode)); _pendingForces.Add(fd); } public void AddForceAtPosition(Vector3 force, Vector3 position, ForceMode mode = ForceMode.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) { #if UNITY_6000_1_OR_NEWER Rigidbody.linearVelocity = force; #else Rigidbody.velocity = force; #endif RemoveForces(nonAngular: true); } /// /// Sets angularVelocity while clearing pending forces. /// Simulate should still be called normally. /// public void AngularVelocity(Vector3 force) { Rigidbody.angularVelocity = force; RemoveForces(nonAngular: false); } /// /// Moves the kinematic Rigidbody towards position. /// /// Next position. public void MovePosition(Vector3 position) { EntryData fd = new(ForceApplicationType.MovePosition, new(position)); _pendingForces.Add(fd); } /// /// Moves the kinematic Rigidbody towards rotation. /// /// Next position. public void MoveRotation(Quaternion rotation) { EntryData fd = new(ForceApplicationType.MoveRotation, new(rotation, RotationPacking)); _pendingForces.Add(fd); } /// /// 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: Rigidbody.AddTorque(data.Vector3Force, data.Mode); break; case ForceApplicationType.AddForce: Rigidbody.AddForce(data.Vector3Force, data.Mode); break; case ForceApplicationType.AddRelativeTorque: Rigidbody.AddRelativeTorque(data.Vector3Force, data.Mode); break; case ForceApplicationType.AddRelativeForce: Rigidbody.AddRelativeForce(data.Vector3Force, data.Mode); break; case ForceApplicationType.AddExplosiveForce: Rigidbody.AddExplosionForce(data.FloatForce, data.Position, data.Radius, data.UpwardsModifier, data.Mode); break; case ForceApplicationType.AddForceAtPosition: Rigidbody.AddForceAtPosition(data.Vector3Force, data.Position, data.Mode); break; case ForceApplicationType.MovePosition: Rigidbody.MovePosition(data.Position); break; case ForceApplicationType.MoveRotation: Rigidbody.MoveRotation(data.Rotation); break; } } _pendingForces.Clear(); } /// /// Clears current and pending forces for velocity and angularVelocity. /// public void ClearVelocities() { Velocity(Vector3.zero); AngularVelocity(Vector3.zero); } /// /// Clears pending forces for velocity, or angular velocity. /// /// True to clear pending velocity forces, false to clear pending angularVelocity forces. public void ClearPendingForces(bool nonAngular) { RemoveForces(nonAngular); } /// /// Clears pending forces for velocity and angularVelocity. /// public void ClearPendingForces() { _pendingForces.Clear(); } /// /// Reconciles to a state. /// public void Reconcile(PredictionRigidbody pr) { _pendingForces.Clear(); if (pr._pendingForces != null) { foreach (EntryData item in pr._pendingForces) _pendingForces.Add(new(item)); } // Set state. Rigidbody.SetState(pr.RigidbodyState); ResettableObjectCaches.Store(pr); } /// /// Removes forces from pendingForces. /// /// True to remove if velocity, false if to remove angular velocity. private void RemoveForces(bool nonAngular) { if (_pendingForces.Count > 0) { ForceApplicationType velocityApplicationTypes = ForceApplicationType.AddRelativeForce | ForceApplicationType.AddForce | ForceApplicationType.AddExplosiveForce; List datasToKeep = CollectionCaches.RetrieveList(); foreach (EntryData item in _pendingForces) { if (VelocityApplicationTypesContains(item.Type) == !nonAngular || item.Type == ForceApplicationType.MovePosition || item.Type == ForceApplicationType.MoveRotation) datasToKeep.Add(item); } // Add back to _pendingForces if changed. if (datasToKeep.Count != _pendingForces.Count) { _pendingForces.Clear(); foreach (EntryData item in datasToKeep) _pendingForces.Add(item); } CollectionCaches.Store(datasToKeep); bool VelocityApplicationTypesContains(ForceApplicationType apt) { return (velocityApplicationTypes & apt) == apt; } } } internal void SetReconcileData(RigidbodyState rs, List lst) { RigidbodyState = rs; _pendingForces = lst; } public void ResetState() { CollectionCaches.StoreAndDefault(ref _pendingForces); Rigidbody = null; } public void InitializeState() { } } }