From 3e7c40e5d5225180523c3adc25337f124e218de4 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Sun, 22 Sep 2024 13:55:01 +0200 Subject: [PATCH 01/20] WIP --- .../Processors/VehicleMovementsProcessor.cs | 26 +++ .../VehicleOnPilotModeChangedProcessor.cs | 4 +- .../Drawer/Nitrox/NitroxEntityDrawer.cs | 20 +- NitroxClient/GameLogic/LocalPlayer.cs | 1 + NitroxClient/GameLogic/SimulationOwnership.cs | 57 +++++- .../MonoBehaviours/Cyclops/NitroxCyclops.cs | 1 - .../MonoBehaviours/MovementBroadcaster.cs | 175 ++++++++++++++++++ .../MonoBehaviours/MovementReplicator.cs | 168 +++++++++++++++++ NitroxClient/MonoBehaviours/Multiplayer.cs | 1 + NitroxModel/Packets/VehicleMovements.cs | 22 +++ .../Dynamic/Exosuit_FixedUpdate_Patch.cs | 15 ++ .../Dynamic/FPSCounter_UpdateDisplay_Patch.cs | 1 + .../Dynamic/SeaMoth_FixedUpdate_Patch.cs | 15 ++ NitroxPatcher/PatternMatching/ILExtensions.cs | 48 ++++- .../DefaultServerPacketProcessor.cs | 3 +- 15 files changed, 550 insertions(+), 7 deletions(-) create mode 100644 NitroxClient/Communication/Packets/Processors/VehicleMovementsProcessor.cs create mode 100644 NitroxClient/MonoBehaviours/MovementBroadcaster.cs create mode 100644 NitroxClient/MonoBehaviours/MovementReplicator.cs create mode 100644 NitroxModel/Packets/VehicleMovements.cs create mode 100644 NitroxPatcher/Patches/Dynamic/Exosuit_FixedUpdate_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/SeaMoth_FixedUpdate_Patch.cs diff --git a/NitroxClient/Communication/Packets/Processors/VehicleMovementsProcessor.cs b/NitroxClient/Communication/Packets/Processors/VehicleMovementsProcessor.cs new file mode 100644 index 0000000000..bce26a47af --- /dev/null +++ b/NitroxClient/Communication/Packets/Processors/VehicleMovementsProcessor.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using NitroxClient.Communication.Packets.Processors.Abstract; +using NitroxClient.MonoBehaviours; +using NitroxModel.DataStructures; +using NitroxModel.Packets; + +namespace NitroxClient.Communication.Packets.Processors; + +public class VehicleMovementsProcessor : ClientPacketProcessor +{ + public override void Process(VehicleMovements packet) + { + if (!MovementBroadcaster.Instance) + { + return; + } + + foreach (KeyValuePair pair in packet.Data) + { + if (MovementBroadcaster.Instance.Replicators.TryGetValue(pair.Key, out MovementReplicator movementReplicator)) + { + movementReplicator.AddSnapshot(pair.Value, (float)packet.RealTime); + } + } + } +} diff --git a/NitroxClient/Communication/Packets/Processors/VehicleOnPilotModeChangedProcessor.cs b/NitroxClient/Communication/Packets/Processors/VehicleOnPilotModeChangedProcessor.cs index 7b68515361..925353fae5 100644 --- a/NitroxClient/Communication/Packets/Processors/VehicleOnPilotModeChangedProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/VehicleOnPilotModeChangedProcessor.cs @@ -1,4 +1,4 @@ -using NitroxClient.Communication.Abstract; +using NitroxClient.Communication.Abstract; using NitroxClient.Communication.Packets.Processors.Abstract; using NitroxClient.GameLogic; using NitroxClient.MonoBehaviours; @@ -32,6 +32,8 @@ public override void Process(VehicleOnPilotModeChanged packet) { vehicles.SetOnPilotMode(packet.VehicleId, packet.PlayerId, packet.IsPiloting); } + + // TODO: remake that, it does nothing } } } diff --git a/NitroxClient/Debuggers/Drawer/Nitrox/NitroxEntityDrawer.cs b/NitroxClient/Debuggers/Drawer/Nitrox/NitroxEntityDrawer.cs index 03709b69c3..6fd0e08d7a 100644 --- a/NitroxClient/Debuggers/Drawer/Nitrox/NitroxEntityDrawer.cs +++ b/NitroxClient/Debuggers/Drawer/Nitrox/NitroxEntityDrawer.cs @@ -1,5 +1,7 @@ -using System.Linq; +using System.Linq; +using NitroxClient.GameLogic; using NitroxClient.MonoBehaviours; +using NitroxModel.Core; using NitroxModel.DataStructures; using UnityEngine; @@ -31,5 +33,21 @@ public void Draw(NitroxId nitroxId) NitroxGUILayout.Separator(); GUILayout.TextField(nitroxId == null ? "ID IS NULL!!!" : nitroxId.ToString()); } + + GUILayout.Space(8); + + using (new GUILayout.HorizontalScope()) + { + GUILayout.Label("Simulating state", GUILayout.Width(LABEL_WIDTH)); + NitroxGUILayout.Separator(); + if (NitroxServiceLocator.Cache.Value.TryGetLockType(nitroxId, out SimulationLockType simulationLockType)) + { + GUILayout.TextField(simulationLockType.ToString()); + } + else + { + GUILayout.TextField("NONE"); + } + } } } diff --git a/NitroxClient/GameLogic/LocalPlayer.cs b/NitroxClient/GameLogic/LocalPlayer.cs index 1db0fa51a1..7b810d9e20 100644 --- a/NitroxClient/GameLogic/LocalPlayer.cs +++ b/NitroxClient/GameLogic/LocalPlayer.cs @@ -62,6 +62,7 @@ public void BroadcastLocation(Vector3 location, Vector3 velocity, Quaternion bod if (vehicle.HasValue) { movement = new VehicleMovement(PlayerId.Value, vehicle.Value); + return; } else { diff --git a/NitroxClient/GameLogic/SimulationOwnership.cs b/NitroxClient/GameLogic/SimulationOwnership.cs index aadd9f3d34..7b589764d7 100644 --- a/NitroxClient/GameLogic/SimulationOwnership.cs +++ b/NitroxClient/GameLogic/SimulationOwnership.cs @@ -58,6 +58,7 @@ public void ReceivedSimulationLockResponse(NitroxId id, bool lockAquired, Simula if (lockAquired) { SimulateEntity(id, lockType); + TreatVehicleEntity(id, true); } if (lockRequestsById.TryGetValue(id, out LockRequestBase lockRequest)) @@ -79,7 +80,14 @@ public void StopSimulatingEntity(NitroxId id) public void TreatSimulatedEntity(SimulatedEntity simulatedEntity) { - if (multiplayerSession.Reservation.PlayerId == simulatedEntity.PlayerId) + bool isLocalPlayerNewOwner = multiplayerSession.Reservation.PlayerId == simulatedEntity.PlayerId; + + if (TreatVehicleEntity(simulatedEntity.Id, isLocalPlayerNewOwner)) + { + return; + } + + if (isLocalPlayerNewOwner) { if (simulatedEntity.ChangesPosition) { @@ -111,5 +119,52 @@ public void TreatSimulatedEntity(SimulatedEntity simulatedEntity) GameObject.Destroy(remotelyControlled); } } + + public bool TryGetLockType(NitroxId nitroxId, out SimulationLockType simulationLockType) + { + return simulatedIdsByLockType.TryGetValue(nitroxId, out simulationLockType); + } + + private bool TreatVehicleEntity(NitroxId entityId, bool isLocalPlayerNewOwner) + { + if (!NitroxEntity.TryGetObjectFrom(entityId, out GameObject gameObject) || !IsVehicle(gameObject)) + { + return false; + } + + MovementReplicator movementReplicator = gameObject.GetComponent(); + if (isLocalPlayerNewOwner) + { + if (movementReplicator) + { + GameObject.Destroy(movementReplicator); + } + MovementBroadcaster.RegisterWatched(gameObject, entityId); + } + else + { + if (!movementReplicator) + { + gameObject.AddComponent(); + } + MovementBroadcaster.UnregisterWatched(entityId); + } + + return true; + } + + public bool IsVehicle(GameObject gameObject) + { + if (gameObject.GetComponent()) + { + return true; + } + if (gameObject.TryGetComponent(out SubRoot subRoot) && !subRoot.isBase) + { + return true; + } + + return false; + } } } diff --git a/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs index f056be41e4..2b518ee87a 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs @@ -142,7 +142,6 @@ public void RemovePawnForPlayer(INitroxPlayer player) Pawns.Remove(player); } - // TODO: Use SetBroadcasting and SetReceiving when we finally have a cyclops movements rework public void SetBroadcasting() { worldForces.enabled = true; diff --git a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs new file mode 100644 index 0000000000..310f38c041 --- /dev/null +++ b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; +using NitroxClient.Communication.Abstract; +using NitroxClient.GameLogic; +using NitroxModel.DataStructures; +using NitroxModel.Packets; +using NitroxModel_Subnautica.DataStructures; +using UnityEngine; + +namespace NitroxClient.MonoBehaviours; + +public class MovementBroadcaster : MonoBehaviour +{ + public const int FIXED_TICK_RATE_MS = 50; + public const float TIME_PER_TICK = 1 / FIXED_TICK_RATE_MS; + private float latestUpdateTime; + public static int Tick; + + private readonly Dictionary watchedEntries = []; + public Dictionary Replicators = []; + public static MovementBroadcaster Instance; + + public void Start() + { + if (Instance) + { + Log.Error($"There's already a {nameof(MovementBroadcaster)} Instance alive, destroying the new one."); + Destroy(this); + return; + } + Instance = this; + StartCoroutine(BroadcastLoop()); + //GameLoop().ContinueWithHandleError(Log.Error); + } + + public void OnDestroy() + { + Instance = null; + } + + // TODO: actually no need for ticks: https://www.youtube.com/watch?v=YR6Bc0-6YJA + + private double TickDouble => this.Resolve().RealTimeElapsed / (FIXED_TICK_RATE_MS * 0.001d); + private int CurrentTick => (int)Math.Floor(TickDouble); + + private int TimeBeforeNextTick() + { + // ex: tickDouble = 3487.2 + // tickDouble - Mathf.Floor(tickDouble) = 0.2 + // 1 - 0.2 = 0.8 + double tickDouble = TickDouble; + return (int)Math.Round(1000 * (1 - (tickDouble - Math.Floor(tickDouble))) / FIXED_TICK_RATE_MS); + } + + public async Task GameLoop() + { + while (true) + { + await Task.Delay(TimeBeforeNextTick()); + Tick++; + } + } + + // TODO: if this eventually works, deprecate RemotelyControlled and move everything to this system + public IEnumerator BroadcastLoop() + { + while (true) + { + float time = (float)this.Resolve().RealTimeElapsed; + float deltaTime = time - latestUpdateTime; + + // Happens during loading and freezes + if (deltaTime == 0) + { + yield return null; + continue; + } + + if (deltaTime < TIME_PER_TICK) + { + yield return new WaitForSeconds((float)deltaTime); + } + latestUpdateTime = time; + + BroadcastLocalData(time); + ProcessReplicators(time, deltaTime); + } + } + + public void BroadcastLocalData(float time) + { + Dictionary data = []; + foreach (KeyValuePair entry in watchedEntries) + { + // TODO: Don't broadcast at certain times: while docking, while docked ... + data.Add(entry.Key, entry.Value.GetMovementData()); + } + + if (data.Count > 0) + { + this.Resolve().Send(new VehicleMovements(data, time)); + } + } + + public void ProcessReplicators(float time, float deltaTime) + { + foreach (MovementReplicator movementReplicator in Replicators.Values) + { + //movementReplicator.ReplicatorFixedUpdate(time, deltaTime); + } + } + + public static void RegisterWatched(GameObject gameObject, NitroxId entityId) + { + if (!Instance) + { + return; + } + + if (!Instance.watchedEntries.ContainsKey(entityId)) + { + Instance.watchedEntries.Add(entityId, new(gameObject)); + } + } + + public static void UnregisterWatched(NitroxId entityId) + { + if (Instance) + { + Instance.watchedEntries.Remove(entityId); + } + } + + public static void RegisterReplicator(MovementReplicator movementReplicator) + { + if (Instance) + { + Instance.Replicators.Add(movementReplicator.objectId, movementReplicator); + } + } + + public static void UnregisterReplicator(MovementReplicator movementReplicator) + { + if (Instance) + { + Instance.Replicators.Remove(movementReplicator.objectId); + } + } + + private record struct WatchedEntry + { + private Vehicle vehicle; + private SubRoot subRoot; + private Rigidbody rigidbody; + + public WatchedEntry(GameObject gameObject) + { + if (gameObject.TryGetComponent(out vehicle)) + { + rigidbody = vehicle.GetComponent(); + } + else if (gameObject.TryGetComponent(out SubRoot subRoot)) + { + rigidbody = subRoot.GetComponent(); + } + } + + public MovementData GetMovementData() + { + return new(rigidbody.position.ToDto(), rigidbody.velocity.ToDto(), rigidbody.rotation.ToDto(), rigidbody.angularVelocity.ToDto()); + } + } +} diff --git a/NitroxClient/MonoBehaviours/MovementReplicator.cs b/NitroxClient/MonoBehaviours/MovementReplicator.cs new file mode 100644 index 0000000000..e5df697913 --- /dev/null +++ b/NitroxClient/MonoBehaviours/MovementReplicator.cs @@ -0,0 +1,168 @@ +using System.Collections.Generic; +using System.Linq; +using NitroxClient.MonoBehaviours.Cyclops; +using NitroxModel.DataStructures; +using NitroxModel.Packets; +using NitroxModel_Subnautica.DataStructures; +using UnityEngine; + +namespace NitroxClient.MonoBehaviours; + +public class MovementReplicator : MonoBehaviour +{ + public const float MAX_POSITION_ERROR = 10; + public const float MAX_ROTATION_ERROR = 20; // ° + public const float INTERPOLATION_TIME = 4 * MovementBroadcaster.TIME_PER_TICK; + public const float SNAPSHOT_EXPIRATION_TIME = 5f * INTERPOLATION_TIME; + private const int BUFFER_SIZE = 10; + + private readonly CircularBuffer bufferedSnapshots = new(BUFFER_SIZE); + + public Rigidbody rigidbody; + public NitroxId objectId; + + public void AddSnapshot(MovementData movementData, float time) + { + bufferedSnapshots.Add(new(movementData, time)); + } + + // TODO: add interpolation time (probably like 2 frames) + + public void Start() + { + if (!gameObject.TryGetNitroxId(out objectId)) + { + Log.Error($"Can't start a {nameof(MovementReplicator)} on {name} because it doesn't have an attached: {nameof(NitroxEntity)}"); + return; + } + + if (gameObject.TryGetComponent(out NitroxCyclops nitroxCyclops)) + { + nitroxCyclops.SetReceiving(); + } + else if (gameObject.TryGetComponent(out WorldForces worldForces)) + { + worldForces.enabled = false; + } + rigidbody = GetComponent(); + + MovementBroadcaster.RegisterReplicator(this); + } + + public void OnDestroy() + { + if (gameObject.TryGetComponent(out NitroxCyclops nitroxCyclops)) + { + nitroxCyclops.SetBroadcasting(); + } + else if (gameObject.TryGetComponent(out WorldForces worldForces)) + { + worldForces.enabled = false; + } + + MovementBroadcaster.UnregisterReplicator(this); + } + + public void ReplicatorFixedUpdate(float time, float deltaTime) + { + if (bufferedSnapshots.Count == 0) + { + return; + } + + float renderTime = time - INTERPOLATION_TIME; + + bool isSnapshotExpired(Snapshot snapshot) + { + return snapshot.Time + SNAPSHOT_EXPIRATION_TIME < renderTime; + } + + List orderedSnapshots = [.. bufferedSnapshots.OrderBy(s => s.Time)]; + if (orderedSnapshots.Count == 0) + { + return; + } + if (orderedSnapshots.Count == 1) + { + // We just wait for another snapshot if the only one we got is not treatable yet + if (renderTime < orderedSnapshots[0].Time) + { + return; + } + + // If we've gone past our only snapshot, we'll extrapolate unless the snapshot has expired + // in which case extrapolation might not be relevant + if (isSnapshotExpired(orderedSnapshots[0])) + { + bufferedSnapshots.RemoveAt(0); + return; + } + Extrapolate(orderedSnapshots[0], time, deltaTime); + } + else // At least 2 valid snapshots + { + Snapshot firstBefore = default; + Snapshot firstAfter = default; + foreach (Snapshot snapshot in orderedSnapshots) + { + if (firstBefore == default && renderTime >= snapshot.Time) + { + firstBefore = snapshot; + } + else if (firstAfter == default && renderTime < snapshot.Time) + { + firstAfter = snapshot; + break; + } + } + if (firstBefore == default) + { + + // Do something + return; + } + if (firstAfter == default) + { + // Do something + return; + } + Interpolate(firstBefore, firstAfter, time); + } + } + + private void Interpolate(Snapshot prevSnapshot, Snapshot nextSnapshot, float time) + { + float deltaTime = nextSnapshot.Time - prevSnapshot.Time; + float t = (time - prevSnapshot.Time) / deltaTime; + + rigidbody.position = Vector3.Lerp(prevSnapshot.Data.Position.ToUnity(), nextSnapshot.Data.Position.ToUnity(), t); + } + + private void Extrapolate(Snapshot snapshot, float time, float deltaTime) + { + float movementDeltaTime = (float)(time - snapshot.Time); + Vector3 estimatedPosition = snapshot.Data.Position.ToUnity() + movementDeltaTime * snapshot.Data.Velocity.ToUnity(); + + Vector3 positionError = estimatedPosition - rigidbody.position; + + if (positionError.magnitude > MAX_POSITION_ERROR) + { + rigidbody.position = Vector3.Lerp(rigidbody.position, estimatedPosition, (float)deltaTime * 10); + Log.InGame($"MAX POS ERR: {positionError.magnitude}"); + } + + Quaternion estimatedRotation = snapshot.Data.Rotation.ToUnity() * Quaternion.Euler(snapshot.Data.AngularVelocity.ToUnity() * movementDeltaTime); + + float rotationError = Quaternion.Angle(estimatedRotation, rigidbody.rotation); + if (rotationError > MAX_ROTATION_ERROR) + { + rigidbody.rotation = Quaternion.Lerp(rigidbody.rotation, estimatedRotation, (float)deltaTime * 10); + Log.InGame($"MAX ROT ERR: {rotationError}"); + } + + rigidbody.velocity = snapshot.Data.Velocity.ToUnity(); + rigidbody.angularVelocity = snapshot.Data.AngularVelocity.ToUnity(); + } + + private record struct Snapshot(MovementData Data, float Time); +} diff --git a/NitroxClient/MonoBehaviours/Multiplayer.cs b/NitroxClient/MonoBehaviours/Multiplayer.cs index 890f1d2b75..bc3b8db9da 100644 --- a/NitroxClient/MonoBehaviours/Multiplayer.cs +++ b/NitroxClient/MonoBehaviours/Multiplayer.cs @@ -168,6 +168,7 @@ public void InitMonoBehaviours() gameObject.AddComponent(); gameObject.AddComponent(); gameObject.AddComponent(); + gameObject.AddComponent(); VirtualCyclops.Initialize(); } diff --git a/NitroxModel/Packets/VehicleMovements.cs b/NitroxModel/Packets/VehicleMovements.cs new file mode 100644 index 0000000000..2760e6ebbf --- /dev/null +++ b/NitroxModel/Packets/VehicleMovements.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using NitroxModel.DataStructures; +using NitroxModel.DataStructures.Unity; + +namespace NitroxModel.Packets; + +[Serializable] +public class VehicleMovements : Packet +{ + // TODO: change dictionary to list because it's too heavy right now + public Dictionary Data { get; } + public double RealTime { get; set; } + + public VehicleMovements(Dictionary data, double realTime) + { + Data = data; + RealTime = realTime; + } +} + +public record struct MovementData(NitroxVector3 Position, NitroxVector3 Velocity, NitroxQuaternion Rotation, NitroxVector3 AngularVelocity); diff --git a/NitroxPatcher/Patches/Dynamic/Exosuit_FixedUpdate_Patch.cs b/NitroxPatcher/Patches/Dynamic/Exosuit_FixedUpdate_Patch.cs new file mode 100644 index 0000000000..9d39a3b2ff --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/Exosuit_FixedUpdate_Patch.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using NitroxClient.MonoBehaviours; +using NitroxModel.Helper; + +namespace NitroxPatcher.Patches.Dynamic; + +public sealed partial class Exosuit_FixedUpdate_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((Exosuit t) => t.FixedUpdate()); + + public static bool Prefix(Exosuit __instance) + { + return !__instance.GetComponent(); + } +} diff --git a/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs b/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs index 33b51f6e15..67d1dcff87 100644 --- a/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs @@ -18,6 +18,7 @@ public static void Postfix(FPSCounter __instance) } __instance.strBuffer.Append("Loading entities: ").AppendLine(Resolve().EntitiesToSpawn.Count.ToString()); __instance.strBuffer.Append("Real time elapsed: ").AppendLine(Resolve().RealTimeElapsed.ToString()); + __instance.strBuffer.Append("Tick: ").AppendLine(MovementBroadcaster.Tick.ToString()); __instance.text.SetText(__instance.strBuffer); } } diff --git a/NitroxPatcher/Patches/Dynamic/SeaMoth_FixedUpdate_Patch.cs b/NitroxPatcher/Patches/Dynamic/SeaMoth_FixedUpdate_Patch.cs new file mode 100644 index 0000000000..1468ff7fd7 --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/SeaMoth_FixedUpdate_Patch.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using NitroxClient.MonoBehaviours; +using NitroxModel.Helper; + +namespace NitroxPatcher.Patches.Dynamic; + +public sealed partial class Seamoth_FixedUpdate_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((SeaMoth t) => t.FixedUpdate()); + + public static bool Prefix(SeaMoth __instance) + { + return !__instance.GetComponent(); + } +} diff --git a/NitroxPatcher/PatternMatching/ILExtensions.cs b/NitroxPatcher/PatternMatching/ILExtensions.cs index cedda95fed..d6b6e4ca50 100644 --- a/NitroxPatcher/PatternMatching/ILExtensions.cs +++ b/NitroxPatcher/PatternMatching/ILExtensions.cs @@ -1,15 +1,59 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Text; +using System.Text.RegularExpressions; using HarmonyLib; namespace NitroxPatcher.PatternMatching; internal static class ILExtensions { + private static readonly Regex spaceRegex = new(Regex.Escape(" ")); + + /// + /// Makes a string of an indexed list of instructions, line by line, formatted to have all opcodes and operand aligned in columns. + /// public static string ToPrettyString(this IEnumerable instructions) { - return string.Join(Environment.NewLine, instructions.Select(i => i.ToString())); + List instructionList = [.. instructions]; + if (instructionList.Count == 0) + { + return "No instructions"; + } + int tenPower = 0; + int count = instructionList.Count; + + while (count > 10) + { + count /= 10; + tenPower++; + } + + // if tenPower is 1 (number between 10 and 99), there are 2 numbers to show so we always add 1 to tenPower + string format = $"D{tenPower + 1}"; + + // We need to find the max length of the opcodes to have all of them take the same amount of space + int opcodeMaxLength = 0; + foreach (CodeInstruction instruction in instructionList) + { + int length = instruction.opcode.ToString().Length; + if (length > opcodeMaxLength) + { + opcodeMaxLength = length; + } + } + + StringBuilder builder = new(); + for (int i = 0; i < instructionList.Count; i++) + { + CodeInstruction instruction = instructionList[i]; + // We add 2 so the text is more readable + int spacesRequired = 2 + Math.Max(0, opcodeMaxLength - instruction.opcode.ToString().Length); + string instructionToString = spaceRegex.Replace(instruction.ToString(), new string(' ', spacesRequired), 1); + builder.AppendLine($"{i.ToString(format)} {instructionToString}"); + } + + return builder.ToString(); } /// diff --git a/NitroxServer/Communication/Packets/Processors/DefaultServerPacketProcessor.cs b/NitroxServer/Communication/Packets/Processors/DefaultServerPacketProcessor.cs index cf97cd4f39..8d71700d42 100644 --- a/NitroxServer/Communication/Packets/Processors/DefaultServerPacketProcessor.cs +++ b/NitroxServer/Communication/Packets/Processors/DefaultServerPacketProcessor.cs @@ -22,7 +22,8 @@ public class DefaultServerPacketProcessor : AuthenticatedPacketProcessor typeof(FMODCustomEmitterPacket), typeof(FMODCustomLoopingEmitterPacket), typeof(FMODStudioEmitterPacket), - typeof(PlayerCinematicControllerCall) + typeof(PlayerCinematicControllerCall), + typeof(VehicleMovements) }; /// From 67dbc24726cecb905af6df3a1ae198d833edbbc2 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Sun, 27 Oct 2024 01:26:10 +0200 Subject: [PATCH 02/20] Changed movement replicator's buffer to be a LinkedList (very good optimization), added a setting to adjust movement latency --- .../GameLogic/Settings/NitroxPrefs.cs | 1 + .../Settings/NitroxSettingsManager.cs | 2 + .../MonoBehaviours/MovementBroadcaster.cs | 71 ++------- .../MonoBehaviours/MovementReplicator.cs | 142 +++++++++++------- .../Dynamic/FPSCounter_UpdateDisplay_Patch.cs | 1 - 5 files changed, 99 insertions(+), 118 deletions(-) diff --git a/NitroxClient/GameLogic/Settings/NitroxPrefs.cs b/NitroxClient/GameLogic/Settings/NitroxPrefs.cs index eca094437a..1091cbb120 100644 --- a/NitroxClient/GameLogic/Settings/NitroxPrefs.cs +++ b/NitroxClient/GameLogic/Settings/NitroxPrefs.cs @@ -11,6 +11,7 @@ public class NitroxPrefs public static readonly NitroxPref ChatUsed = new("Nitrox.chatUsed"); public static readonly NitroxPref SafeBuilding = new("Nitrox.safeBuilding", true); public static readonly NitroxPref SafeBuildingLog = new("Nitrox.safeBuildingLog", true); + public static readonly NitroxPref MovementLatency = new("Nitrox.movementLatency", 0.1f); } public abstract class NitroxPref { } diff --git a/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs b/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs index a90acee3e2..32621eb20a 100644 --- a/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs +++ b/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs @@ -51,6 +51,8 @@ private void MakeSettings() AddSetting("Nitrox_BuildingSettings", new Setting("Nitrox_SafeBuilding", NitroxPrefs.SafeBuilding, safe => NitroxPrefs.SafeBuilding.Value = safe)); AddSetting("Nitrox_BuildingSettings", new Setting("Nitrox_SafeBuildingLog", NitroxPrefs.SafeBuildingLog, safeLog => NitroxPrefs.SafeBuildingLog.Value = safeLog)); + + AddSetting("Nitrox_BandwithSettings", new Setting("Nitrox_MovementLatency", NitroxPrefs.MovementLatency, movementLatency => NitroxPrefs.MovementLatency.Value = movementLatency, 0, 1, 0.1f, 0.05f)); } /// Adds a setting to the list under a certain heading diff --git a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs index 310f38c041..37a8185af5 100644 --- a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs +++ b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections; using System.Collections.Generic; -using System.Threading.Tasks; using NitroxClient.Communication.Abstract; -using NitroxClient.GameLogic; using NitroxModel.DataStructures; using NitroxModel.Packets; using NitroxModel_Subnautica.DataStructures; @@ -13,13 +9,12 @@ namespace NitroxClient.MonoBehaviours; public class MovementBroadcaster : MonoBehaviour { - public const int FIXED_TICK_RATE_MS = 50; - public const float TIME_PER_TICK = 1 / FIXED_TICK_RATE_MS; - private float latestUpdateTime; - public static int Tick; + public const int BROADCAST_FREQUENCY = 30; + public const float BROADCAST_PERIOD = 1f / BROADCAST_FREQUENCY; private readonly Dictionary watchedEntries = []; public Dictionary Replicators = []; + private float latestBroadcastTime; public static MovementBroadcaster Instance; public void Start() @@ -31,8 +26,6 @@ public void Start() return; } Instance = this; - StartCoroutine(BroadcastLoop()); - //GameLoop().ContinueWithHandleError(Log.Error); } public void OnDestroy() @@ -40,53 +33,15 @@ public void OnDestroy() Instance = null; } - // TODO: actually no need for ticks: https://www.youtube.com/watch?v=YR6Bc0-6YJA - - private double TickDouble => this.Resolve().RealTimeElapsed / (FIXED_TICK_RATE_MS * 0.001d); - private int CurrentTick => (int)Math.Floor(TickDouble); - - private int TimeBeforeNextTick() - { - // ex: tickDouble = 3487.2 - // tickDouble - Mathf.Floor(tickDouble) = 0.2 - // 1 - 0.2 = 0.8 - double tickDouble = TickDouble; - return (int)Math.Round(1000 * (1 - (tickDouble - Math.Floor(tickDouble))) / FIXED_TICK_RATE_MS); - } - - public async Task GameLoop() - { - while (true) - { - await Task.Delay(TimeBeforeNextTick()); - Tick++; - } - } - - // TODO: if this eventually works, deprecate RemotelyControlled and move everything to this system - public IEnumerator BroadcastLoop() + public void Update() { - while (true) + float currentTime = DayNightCycle.main.timePassedAsFloat; + if (currentTime < latestBroadcastTime + BROADCAST_PERIOD) { - float time = (float)this.Resolve().RealTimeElapsed; - float deltaTime = time - latestUpdateTime; - - // Happens during loading and freezes - if (deltaTime == 0) - { - yield return null; - continue; - } - - if (deltaTime < TIME_PER_TICK) - { - yield return new WaitForSeconds((float)deltaTime); - } - latestUpdateTime = time; - - BroadcastLocalData(time); - ProcessReplicators(time, deltaTime); + return; } + latestBroadcastTime = currentTime; + BroadcastLocalData(currentTime); } public void BroadcastLocalData(float time) @@ -104,14 +59,6 @@ public void BroadcastLocalData(float time) } } - public void ProcessReplicators(float time, float deltaTime) - { - foreach (MovementReplicator movementReplicator in Replicators.Values) - { - //movementReplicator.ReplicatorFixedUpdate(time, deltaTime); - } - } - public static void RegisterWatched(GameObject gameObject, NitroxId entityId) { if (!Instance) diff --git a/NitroxClient/MonoBehaviours/MovementReplicator.cs b/NitroxClient/MonoBehaviours/MovementReplicator.cs index e5df697913..52c5b8c62a 100644 --- a/NitroxClient/MonoBehaviours/MovementReplicator.cs +++ b/NitroxClient/MonoBehaviours/MovementReplicator.cs @@ -1,7 +1,10 @@ using System.Collections.Generic; using System.Linq; +using NitroxClient.GameLogic; +using NitroxClient.GameLogic.Settings; using NitroxClient.MonoBehaviours.Cyclops; using NitroxModel.DataStructures; +using NitroxModel.DataStructures.Unity; using NitroxModel.Packets; using NitroxModel_Subnautica.DataStructures; using UnityEngine; @@ -12,22 +15,20 @@ public class MovementReplicator : MonoBehaviour { public const float MAX_POSITION_ERROR = 10; public const float MAX_ROTATION_ERROR = 20; // ° - public const float INTERPOLATION_TIME = 4 * MovementBroadcaster.TIME_PER_TICK; + public const float INTERPOLATION_TIME = 4 * MovementBroadcaster.BROADCAST_PERIOD; public const float SNAPSHOT_EXPIRATION_TIME = 5f * INTERPOLATION_TIME; private const int BUFFER_SIZE = 10; - private readonly CircularBuffer bufferedSnapshots = new(BUFFER_SIZE); + private readonly LinkedList buffer = new(); public Rigidbody rigidbody; public NitroxId objectId; public void AddSnapshot(MovementData movementData, float time) { - bufferedSnapshots.Add(new(movementData, time)); + buffer.AddLast(new Snapshot(movementData, time + INTERPOLATION_TIME + NitroxPrefs.MovementLatency.Value)); } - // TODO: add interpolation time (probably like 2 frames) - public void Start() { if (!gameObject.TryGetNitroxId(out objectId)) @@ -63,70 +64,96 @@ public void OnDestroy() MovementBroadcaster.UnregisterReplicator(this); } - public void ReplicatorFixedUpdate(float time, float deltaTime) + public void Update() { - if (bufferedSnapshots.Count == 0) + if (buffer.Count == 0) { return; } - float renderTime = time - INTERPOLATION_TIME; + float currentTime = DayNightCycle.main.timePassedAsFloat; + + // Sorting out invalid nodes + while (buffer.First != null && buffer.First.Value.IsExpired(currentTime)) + { + // Log.Debug($"Invalid node: {currentTime} > {buffer.First.Value.Time + SNAPSHOT_EXPIRATION_TIME}"); + buffer.RemoveFirst(); + } - bool isSnapshotExpired(Snapshot snapshot) + LinkedListNode firstNode = buffer.First; + if (firstNode == null) { - return snapshot.Time + SNAPSHOT_EXPIRATION_TIME < renderTime; + // Log.Debug("nothing next"); + return; } - List orderedSnapshots = [.. bufferedSnapshots.OrderBy(s => s.Time)]; - if (orderedSnapshots.Count == 0) + // Current node is not useable yet + if (firstNode.Value.IsOlderThan(currentTime)) { + // Log.Debug($"too early {currentTime} < {firstNode.Value.Time}"); return; } - if (orderedSnapshots.Count == 1) + + while (firstNode.Next != null && !firstNode.Next.Value.IsOlderThan(currentTime)) { - // We just wait for another snapshot if the only one we got is not treatable yet - if (renderTime < orderedSnapshots[0].Time) - { - return; - } - - // If we've gone past our only snapshot, we'll extrapolate unless the snapshot has expired - // in which case extrapolation might not be relevant - if (isSnapshotExpired(orderedSnapshots[0])) - { - bufferedSnapshots.RemoveAt(0); - return; - } - Extrapolate(orderedSnapshots[0], time, deltaTime); + firstNode = firstNode.Next; + buffer.RemoveFirst(); } - else // At least 2 valid snapshots + + LinkedListNode nextNode = firstNode.Next; + + // No next node but current node is fine + if (nextNode == null) { - Snapshot firstBefore = default; - Snapshot firstAfter = default; - foreach (Snapshot snapshot in orderedSnapshots) - { - if (firstBefore == default && renderTime >= snapshot.Time) - { - firstBefore = snapshot; - } - else if (firstAfter == default && renderTime < snapshot.Time) - { - firstAfter = snapshot; - break; - } - } - if (firstBefore == default) - { - - // Do something - return; - } - if (firstAfter == default) - { - // Do something - return; - } - Interpolate(firstBefore, firstAfter, time); + // Log.Debug("waiting for next node"); + return; + } + + // Interpolation + + float t = (currentTime - firstNode.Value.Time) / (nextNode.Value.Time - firstNode.Value.Time); + Vector3 position = Vector3.Lerp(firstNode.Value.Data.Position.ToUnity(), nextNode.Value.Data.Position.ToUnity(), t); + + Quaternion rotation = Quaternion.Lerp(firstNode.Value.Data.Rotation.ToUnity(), nextNode.Value.Data.Rotation.ToUnity(), t); + + transform.position = position; + transform.rotation = rotation; + // TODO: fix remote players being able to go through the object + // Log.Debug($"moved {t} to {nextNode.Value.Data.Position.ToUnity()}"); + } + + public void DebugForward() + { + float currentTime = DayNightCycle.main.timePassedAsFloat; + + int count = 90; + Log.Debug($"Adding {count} snapshots from {currentTime}"); + float delta = 10f / count; + for (int i = 0; i < count; i++) + { + Vector3 result = transform.position + new Vector3(delta * i, 0, 0); + + MovementData movementData = new(result.ToDto(), NitroxVector3.Zero, transform.rotation.ToDto(), NitroxVector3.Zero); + + AddSnapshot(movementData, currentTime + i * MovementBroadcaster.BROADCAST_PERIOD); + } + } + + public void DebugForwardRight() + { + float currentTime = (float)this.Resolve().CurrentTime; + + int count = 90; + float delta = 10f / 90f; + float qDelta = 180f / 90f; + for (int i = 0; i < count; i++) + { + Vector3 offset = new(delta * i, 0, 0); + Quaternion qOffset = Quaternion.AngleAxis(qDelta * i, transform.up); + + MovementData movementData = new((transform.position + offset).ToDto(), NitroxVector3.Zero, (transform.rotation * qOffset).ToDto(), NitroxVector3.Zero); + + AddSnapshot(movementData, currentTime + i * MovementBroadcaster.BROADCAST_PERIOD); } } @@ -164,5 +191,10 @@ private void Extrapolate(Snapshot snapshot, float time, float deltaTime) rigidbody.angularVelocity = snapshot.Data.AngularVelocity.ToUnity(); } - private record struct Snapshot(MovementData Data, float Time); + private record struct Snapshot(MovementData Data, float Time) + { + public bool IsOlderThan(float currentTime) => currentTime < Time; + + public bool IsExpired(float currentTime) => currentTime > Time + SNAPSHOT_EXPIRATION_TIME; + } } diff --git a/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs b/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs index 67d1dcff87..33b51f6e15 100644 --- a/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs @@ -18,7 +18,6 @@ public static void Postfix(FPSCounter __instance) } __instance.strBuffer.Append("Loading entities: ").AppendLine(Resolve().EntitiesToSpawn.Count.ToString()); __instance.strBuffer.Append("Real time elapsed: ").AppendLine(Resolve().RealTimeElapsed.ToString()); - __instance.strBuffer.Append("Tick: ").AppendLine(MovementBroadcaster.Tick.ToString()); __instance.text.SetText(__instance.strBuffer); } } From 7d56989553afb24383e5c2a7e21f4c269e8c85e7 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Sun, 27 Oct 2024 21:50:23 +0100 Subject: [PATCH 03/20] Huge improvements to get a smooth movements for seamoths and exosuits with reduced packet load --- .../Processors/VehicleDockingProcessor.cs | 6 +- .../Processors/VehicleMovementsProcessor.cs | 8 +-- .../Drawer/Nitrox/NitroxEntityDrawer.cs | 22 +++--- NitroxClient/GameLogic/RemotePlayer.cs | 6 +- .../MonoBehaviours/Cyclops/NitroxCyclops.cs | 4 -- .../MonoBehaviours/MovementBroadcaster.cs | 33 ++------- .../MonoBehaviours/MovementReplicator.cs | 69 ++++++------------- NitroxModel/Packets/VehicleMovements.cs | 7 +- 8 files changed, 51 insertions(+), 104 deletions(-) diff --git a/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs b/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs index 5031f2f62b..c7b1576129 100644 --- a/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using NitroxClient.Communication.Abstract; using NitroxClient.Communication.Packets.Processors.Abstract; using NitroxClient.GameLogic; @@ -32,8 +32,8 @@ public override void Process(VehicleDocking packet) using (PacketSuppressor.Suppress()) { Log.Debug($"Set vehicle docked for {vehicleDockingBay.gameObject.name}"); - vehicle.GetComponent().SetPositionVelocityRotation(vehicle.transform.position, Vector3.zero, vehicle.transform.rotation, Vector3.zero); - vehicle.GetComponent().Exit(); + //vehicle.GetComponent().SetPositionVelocityRotation(vehicle.transform.position, Vector3.zero, vehicle.transform.rotation, Vector3.zero); + //vehicle.GetComponent().Exit(); } vehicle.StartCoroutine(DelayAnimationAndDisablePiloting(vehicle, vehicleDockingBay, packet.VehicleId, packet.PlayerId)); } diff --git a/NitroxClient/Communication/Packets/Processors/VehicleMovementsProcessor.cs b/NitroxClient/Communication/Packets/Processors/VehicleMovementsProcessor.cs index bce26a47af..f2701c7fe3 100644 --- a/NitroxClient/Communication/Packets/Processors/VehicleMovementsProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/VehicleMovementsProcessor.cs @@ -1,7 +1,5 @@ -using System.Collections.Generic; using NitroxClient.Communication.Packets.Processors.Abstract; using NitroxClient.MonoBehaviours; -using NitroxModel.DataStructures; using NitroxModel.Packets; namespace NitroxClient.Communication.Packets.Processors; @@ -15,11 +13,11 @@ public override void Process(VehicleMovements packet) return; } - foreach (KeyValuePair pair in packet.Data) + foreach (MovementData movementData in packet.Data) { - if (MovementBroadcaster.Instance.Replicators.TryGetValue(pair.Key, out MovementReplicator movementReplicator)) + if (MovementBroadcaster.Instance.Replicators.TryGetValue(movementData.Id, out MovementReplicator movementReplicator)) { - movementReplicator.AddSnapshot(pair.Value, (float)packet.RealTime); + movementReplicator.AddSnapshot(movementData, (float)packet.RealTime); } } } diff --git a/NitroxClient/Debuggers/Drawer/Nitrox/NitroxEntityDrawer.cs b/NitroxClient/Debuggers/Drawer/Nitrox/NitroxEntityDrawer.cs index 6fd0e08d7a..87e7d50d91 100644 --- a/NitroxClient/Debuggers/Drawer/Nitrox/NitroxEntityDrawer.cs +++ b/NitroxClient/Debuggers/Drawer/Nitrox/NitroxEntityDrawer.cs @@ -23,16 +23,6 @@ public void Draw(NitroxEntity nitroxEntity) NitroxGUILayout.Separator(); GUILayout.TextField(NitroxEntity.GetGameObjects().Count().ToString()); } - } - - public void Draw(NitroxId nitroxId) - { - using (new GUILayout.HorizontalScope()) - { - GUILayout.Label("NitroxId", GUILayout.Width(LABEL_WIDTH)); - NitroxGUILayout.Separator(); - GUILayout.TextField(nitroxId == null ? "ID IS NULL!!!" : nitroxId.ToString()); - } GUILayout.Space(8); @@ -40,7 +30,7 @@ public void Draw(NitroxId nitroxId) { GUILayout.Label("Simulating state", GUILayout.Width(LABEL_WIDTH)); NitroxGUILayout.Separator(); - if (NitroxServiceLocator.Cache.Value.TryGetLockType(nitroxId, out SimulationLockType simulationLockType)) + if (NitroxServiceLocator.Cache.Value.TryGetLockType(nitroxEntity.Id, out SimulationLockType simulationLockType)) { GUILayout.TextField(simulationLockType.ToString()); } @@ -50,4 +40,14 @@ public void Draw(NitroxId nitroxId) } } } + + public void Draw(NitroxId nitroxId) + { + using (new GUILayout.HorizontalScope()) + { + GUILayout.Label("NitroxId", GUILayout.Width(LABEL_WIDTH)); + NitroxGUILayout.Separator(); + GUILayout.TextField(nitroxId == null ? "ID IS NULL!!!" : nitroxId.ToString()); + } + } } diff --git a/NitroxClient/GameLogic/RemotePlayer.cs b/NitroxClient/GameLogic/RemotePlayer.cs index 696f7e4c29..6a0aaf084a 100644 --- a/NitroxClient/GameLogic/RemotePlayer.cs +++ b/NitroxClient/GameLogic/RemotePlayer.cs @@ -284,7 +284,7 @@ public void SetVehicle(Vehicle newVehicle) Detach(); ArmsController.SetWorldIKTarget(null, null); - Vehicle.GetComponent().Exit(); + //Vehicle.GetComponent().Exit(); } if (newVehicle) @@ -298,7 +298,7 @@ public void SetVehicle(Vehicle newVehicle) // When a vehicle is docked since we joined a game and another player undocks him before the local player does, // no MultiplayerVehicleControl can be found on the vehicle because they are only created when receiving VehicleMovement packets // Therefore we need to make sure that the MultiplayerVehicleControl component exists before using it - switch (newVehicle) + /*switch (newVehicle) { case SeaMoth: newVehicle.gameObject.EnsureComponent().Enter(); @@ -306,7 +306,7 @@ public void SetVehicle(Vehicle newVehicle) case Exosuit: newVehicle.gameObject.EnsureComponent().Enter(); break; - } + }*/ } bool isKinematic = newVehicle; diff --git a/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs index 2b518ee87a..8fead8f135 100644 --- a/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs +++ b/NitroxClient/MonoBehaviours/Cyclops/NitroxCyclops.cs @@ -19,7 +19,6 @@ public class NitroxCyclops : MonoBehaviour private WorldForces worldForces; private Stabilizer stabilizer; private CharacterController controller; - private int ballasts; public readonly Dictionary Pawns = []; @@ -32,12 +31,9 @@ public void Start() worldForces = GetComponent(); stabilizer = GetComponent(); controller = cyclopsMotor.controller; - ballasts = GetComponentsInChildren(true).Length; UWE.Utils.SetIsKinematicAndUpdateInterpolation(rigidbody, false, true); - GetComponent().enabled = false; - WorkaroundColliders(); } diff --git a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs index 37a8185af5..34b375560b 100644 --- a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs +++ b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs @@ -9,7 +9,7 @@ namespace NitroxClient.MonoBehaviours; public class MovementBroadcaster : MonoBehaviour { - public const int BROADCAST_FREQUENCY = 30; + public const int BROADCAST_FREQUENCY = 30; // TODO: try with even lower frequency than 30 public const float BROADCAST_PERIOD = 1f / BROADCAST_FREQUENCY; private readonly Dictionary watchedEntries = []; @@ -46,11 +46,11 @@ public void Update() public void BroadcastLocalData(float time) { - Dictionary data = []; + List data = []; foreach (KeyValuePair entry in watchedEntries) { // TODO: Don't broadcast at certain times: while docking, while docked ... - data.Add(entry.Key, entry.Value.GetMovementData()); + data.Add(entry.Value.GetMovementData(entry.Key)); } if (data.Count > 0) @@ -68,7 +68,7 @@ public static void RegisterWatched(GameObject gameObject, NitroxId entityId) if (!Instance.watchedEntries.ContainsKey(entityId)) { - Instance.watchedEntries.Add(entityId, new(gameObject)); + Instance.watchedEntries.Add(entityId, new(gameObject.transform)); } } @@ -96,27 +96,8 @@ public static void UnregisterReplicator(MovementReplicator movementReplicator) } } - private record struct WatchedEntry + private record struct WatchedEntry(Transform transform) { - private Vehicle vehicle; - private SubRoot subRoot; - private Rigidbody rigidbody; - - public WatchedEntry(GameObject gameObject) - { - if (gameObject.TryGetComponent(out vehicle)) - { - rigidbody = vehicle.GetComponent(); - } - else if (gameObject.TryGetComponent(out SubRoot subRoot)) - { - rigidbody = subRoot.GetComponent(); - } - } - - public MovementData GetMovementData() - { - return new(rigidbody.position.ToDto(), rigidbody.velocity.ToDto(), rigidbody.rotation.ToDto(), rigidbody.angularVelocity.ToDto()); - } - } + public MovementData GetMovementData(NitroxId id) => new(id, transform.position.ToDto(), transform.rotation.ToDto()); + } } diff --git a/NitroxClient/MonoBehaviours/MovementReplicator.cs b/NitroxClient/MonoBehaviours/MovementReplicator.cs index 52c5b8c62a..1285b97c0f 100644 --- a/NitroxClient/MonoBehaviours/MovementReplicator.cs +++ b/NitroxClient/MonoBehaviours/MovementReplicator.cs @@ -1,10 +1,8 @@ using System.Collections.Generic; -using System.Linq; using NitroxClient.GameLogic; using NitroxClient.GameLogic.Settings; using NitroxClient.MonoBehaviours.Cyclops; using NitroxModel.DataStructures; -using NitroxModel.DataStructures.Unity; using NitroxModel.Packets; using NitroxModel_Subnautica.DataStructures; using UnityEngine; @@ -13,8 +11,6 @@ namespace NitroxClient.MonoBehaviours; public class MovementReplicator : MonoBehaviour { - public const float MAX_POSITION_ERROR = 10; - public const float MAX_ROTATION_ERROR = 20; // ° public const float INTERPOLATION_TIME = 4 * MovementBroadcaster.BROADCAST_PERIOD; public const float SNAPSHOT_EXPIRATION_TIME = 5f * INTERPOLATION_TIME; private const int BUFFER_SIZE = 10; @@ -37,15 +33,20 @@ public void Start() return; } + rigidbody = GetComponent(); if (gameObject.TryGetComponent(out NitroxCyclops nitroxCyclops)) { nitroxCyclops.SetReceiving(); } - else if (gameObject.TryGetComponent(out WorldForces worldForces)) + else { - worldForces.enabled = false; + if (gameObject.TryGetComponent(out WorldForces worldForces)) + { + worldForces.enabled = false; + } + rigidbody.isKinematic = true; + rigidbody.interpolation = RigidbodyInterpolation.Interpolate; } - rigidbody = GetComponent(); MovementBroadcaster.RegisterReplicator(this); } @@ -56,9 +57,14 @@ public void OnDestroy() { nitroxCyclops.SetBroadcasting(); } - else if (gameObject.TryGetComponent(out WorldForces worldForces)) + else { - worldForces.enabled = false; + if (gameObject.TryGetComponent(out WorldForces worldForces)) + { + worldForces.enabled = true; + } + rigidbody.isKinematic = false; + rigidbody.interpolation = RigidbodyInterpolation.None; } MovementBroadcaster.UnregisterReplicator(this); @@ -73,7 +79,7 @@ public void Update() float currentTime = DayNightCycle.main.timePassedAsFloat; - // Sorting out invalid nodes + // Sorting out expired nodes while (buffer.First != null && buffer.First.Value.IsExpired(currentTime)) { // Log.Debug($"Invalid node: {currentTime} > {buffer.First.Value.Time + SNAPSHOT_EXPIRATION_TIME}"); @@ -119,6 +125,7 @@ public void Update() transform.position = position; transform.rotation = rotation; // TODO: fix remote players being able to go through the object + // Log.Debug($"moved {t} to {nextNode.Value.Data.Position.ToUnity()}"); } @@ -128,12 +135,12 @@ public void DebugForward() int count = 90; Log.Debug($"Adding {count} snapshots from {currentTime}"); - float delta = 10f / count; + float delta = 20f / count; for (int i = 0; i < count; i++) { Vector3 result = transform.position + new Vector3(delta * i, 0, 0); - MovementData movementData = new(result.ToDto(), NitroxVector3.Zero, transform.rotation.ToDto(), NitroxVector3.Zero); + MovementData movementData = new(null, result.ToDto(), transform.rotation.ToDto()); AddSnapshot(movementData, currentTime + i * MovementBroadcaster.BROADCAST_PERIOD); } @@ -144,53 +151,19 @@ public void DebugForwardRight() float currentTime = (float)this.Resolve().CurrentTime; int count = 90; - float delta = 10f / 90f; + float delta = 20f / 90f; float qDelta = 180f / 90f; for (int i = 0; i < count; i++) { Vector3 offset = new(delta * i, 0, 0); Quaternion qOffset = Quaternion.AngleAxis(qDelta * i, transform.up); - MovementData movementData = new((transform.position + offset).ToDto(), NitroxVector3.Zero, (transform.rotation * qOffset).ToDto(), NitroxVector3.Zero); + MovementData movementData = new(null, (transform.position + offset).ToDto(), (transform.rotation * qOffset).ToDto()); AddSnapshot(movementData, currentTime + i * MovementBroadcaster.BROADCAST_PERIOD); } } - private void Interpolate(Snapshot prevSnapshot, Snapshot nextSnapshot, float time) - { - float deltaTime = nextSnapshot.Time - prevSnapshot.Time; - float t = (time - prevSnapshot.Time) / deltaTime; - - rigidbody.position = Vector3.Lerp(prevSnapshot.Data.Position.ToUnity(), nextSnapshot.Data.Position.ToUnity(), t); - } - - private void Extrapolate(Snapshot snapshot, float time, float deltaTime) - { - float movementDeltaTime = (float)(time - snapshot.Time); - Vector3 estimatedPosition = snapshot.Data.Position.ToUnity() + movementDeltaTime * snapshot.Data.Velocity.ToUnity(); - - Vector3 positionError = estimatedPosition - rigidbody.position; - - if (positionError.magnitude > MAX_POSITION_ERROR) - { - rigidbody.position = Vector3.Lerp(rigidbody.position, estimatedPosition, (float)deltaTime * 10); - Log.InGame($"MAX POS ERR: {positionError.magnitude}"); - } - - Quaternion estimatedRotation = snapshot.Data.Rotation.ToUnity() * Quaternion.Euler(snapshot.Data.AngularVelocity.ToUnity() * movementDeltaTime); - - float rotationError = Quaternion.Angle(estimatedRotation, rigidbody.rotation); - if (rotationError > MAX_ROTATION_ERROR) - { - rigidbody.rotation = Quaternion.Lerp(rigidbody.rotation, estimatedRotation, (float)deltaTime * 10); - Log.InGame($"MAX ROT ERR: {rotationError}"); - } - - rigidbody.velocity = snapshot.Data.Velocity.ToUnity(); - rigidbody.angularVelocity = snapshot.Data.AngularVelocity.ToUnity(); - } - private record struct Snapshot(MovementData Data, float Time) { public bool IsOlderThan(float currentTime) => currentTime < Time; diff --git a/NitroxModel/Packets/VehicleMovements.cs b/NitroxModel/Packets/VehicleMovements.cs index 2760e6ebbf..f347ab8677 100644 --- a/NitroxModel/Packets/VehicleMovements.cs +++ b/NitroxModel/Packets/VehicleMovements.cs @@ -8,15 +8,14 @@ namespace NitroxModel.Packets; [Serializable] public class VehicleMovements : Packet { - // TODO: change dictionary to list because it's too heavy right now - public Dictionary Data { get; } + public List Data { get; } public double RealTime { get; set; } - public VehicleMovements(Dictionary data, double realTime) + public VehicleMovements(List data, double realTime) { Data = data; RealTime = realTime; } } -public record struct MovementData(NitroxVector3 Position, NitroxVector3 Velocity, NitroxQuaternion Rotation, NitroxVector3 AngularVelocity); +public record struct MovementData(NitroxId Id, NitroxVector3 Position, NitroxQuaternion Rotation); From e3c364f878b7cdfa3b63a443c2b3c97626c57b8e Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Wed, 30 Oct 2024 01:33:49 +0100 Subject: [PATCH 04/20] Reimplement channels into LiteNetLib --- .../NetworkingLayer/LiteNetLib/LiteNetLibClient.cs | 5 ++++- NitroxModel/Networking/NitroxDeliveryMethod.cs | 8 +++++++- NitroxModel/Packets/Packet.cs | 10 ++++------ NitroxModel/Packets/PlayerInCyclopsMovement.cs | 2 +- NitroxModel/Packets/PlayerMovement.cs | 4 ++-- NitroxModel/Packets/PlayerStats.cs | 3 --- NitroxModel/Packets/VehicleMovement.cs | 4 ++-- NitroxModel/Packets/VehicleMovements.cs | 2 ++ 8 files changed, 22 insertions(+), 16 deletions(-) diff --git a/NitroxClient/Communication/NetworkingLayer/LiteNetLib/LiteNetLibClient.cs b/NitroxClient/Communication/NetworkingLayer/LiteNetLib/LiteNetLibClient.cs index faaa337510..a00580f830 100644 --- a/NitroxClient/Communication/NetworkingLayer/LiteNetLib/LiteNetLibClient.cs +++ b/NitroxClient/Communication/NetworkingLayer/LiteNetLib/LiteNetLibClient.cs @@ -31,9 +31,12 @@ public LiteNetLibClient(PacketReceiver packetReceiver, INetworkDebugger networkD listener.PeerDisconnectedEvent += Disconnected; listener.NetworkReceiveEvent += ReceivedNetworkData; + byte channelsCount = (byte)typeof(Packet.UdpChannelId).GetEnumValues().Length; + client = new NetManager(listener) { UpdateTime = 15, + ChannelsCount = channelsCount, #if DEBUG DisconnectTimeout = 300000 //Disables Timeout (for 5 min) for debug purpose (like if you jump though the server code) #endif @@ -64,7 +67,7 @@ public void Send(Packet packet) dataWriter.Put(packetData); networkDebugger?.PacketSent(packet, dataWriter.Length); - client.SendToAll(dataWriter, NitroxDeliveryMethod.ToLiteNetLib(packet.DeliveryMethod)); + client.SendToAll(dataWriter, (byte)packet.UdpChannel, NitroxDeliveryMethod.ToLiteNetLib(packet.DeliveryMethod)); } public void Stop() diff --git a/NitroxModel/Networking/NitroxDeliveryMethod.cs b/NitroxModel/Networking/NitroxDeliveryMethod.cs index 90e08ba52b..b00c69f47a 100644 --- a/NitroxModel/Networking/NitroxDeliveryMethod.cs +++ b/NitroxModel/Networking/NitroxDeliveryMethod.cs @@ -1,11 +1,17 @@ -namespace NitroxModel.Networking +namespace NitroxModel.Networking { public class NitroxDeliveryMethod { public enum DeliveryMethod { + /// + /// + /// UNRELIABLE_SEQUENCED, + /// + /// + /// RELIABLE_ORDERED } diff --git a/NitroxModel/Packets/Packet.cs b/NitroxModel/Packets/Packet.cs index b49142aa3f..b91c8653cf 100644 --- a/NitroxModel/Packets/Packet.cs +++ b/NitroxModel/Packets/Packet.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -73,13 +73,11 @@ static IEnumerable FindUnionBaseTypes() => FindTypesInModelAssemblies() [IgnoredMember] public UdpChannelId UdpChannel { get; protected set; } = UdpChannelId.DEFAULT; - - public enum UdpChannelId + + public enum UdpChannelId : byte { DEFAULT = 0, - PLAYER_MOVEMENT = 1, - VEHICLE_MOVEMENT = 2, - PLAYER_STATS = 3 + MOVEMENTS = 1, } public byte[] Serialize() diff --git a/NitroxModel/Packets/PlayerInCyclopsMovement.cs b/NitroxModel/Packets/PlayerInCyclopsMovement.cs index 8284704fd4..74265e5b3a 100644 --- a/NitroxModel/Packets/PlayerInCyclopsMovement.cs +++ b/NitroxModel/Packets/PlayerInCyclopsMovement.cs @@ -17,6 +17,6 @@ public PlayerInCyclopsMovement(ushort playerId, NitroxVector3 localPosition, Nit LocalPosition = localPosition; LocalRotation = localRotation; DeliveryMethod = NitroxDeliveryMethod.DeliveryMethod.UNRELIABLE_SEQUENCED; - UdpChannel = UdpChannelId.PLAYER_MOVEMENT; + UdpChannel = UdpChannelId.MOVEMENTS; } } diff --git a/NitroxModel/Packets/PlayerMovement.cs b/NitroxModel/Packets/PlayerMovement.cs index 094ae31784..5e45e5670c 100644 --- a/NitroxModel/Packets/PlayerMovement.cs +++ b/NitroxModel/Packets/PlayerMovement.cs @@ -1,4 +1,4 @@ -using System; +using System; using NitroxModel.DataStructures.Unity; using NitroxModel.Networking; @@ -21,7 +21,7 @@ public PlayerMovement(ushort playerId, NitroxVector3 position, NitroxVector3 vel BodyRotation = bodyRotation; AimingRotation = aimingRotation; DeliveryMethod = NitroxDeliveryMethod.DeliveryMethod.UNRELIABLE_SEQUENCED; - UdpChannel = UdpChannelId.PLAYER_MOVEMENT; + UdpChannel = UdpChannelId.MOVEMENTS; } } } diff --git a/NitroxModel/Packets/PlayerStats.cs b/NitroxModel/Packets/PlayerStats.cs index 0c0158e654..a54b21386b 100644 --- a/NitroxModel/Packets/PlayerStats.cs +++ b/NitroxModel/Packets/PlayerStats.cs @@ -1,5 +1,4 @@ using System; -using NitroxModel.Networking; namespace NitroxModel.Packets; @@ -23,7 +22,5 @@ public PlayerStats(ushort playerId, float oxygen, float maxOxygen, float health, Food = food; Water = water; InfectionAmount = infectionAmount; - DeliveryMethod = NitroxDeliveryMethod.DeliveryMethod.UNRELIABLE_SEQUENCED; - UdpChannel = UdpChannelId.PLAYER_STATS; } } diff --git a/NitroxModel/Packets/VehicleMovement.cs b/NitroxModel/Packets/VehicleMovement.cs index 5f1a9498ee..5d880b405a 100644 --- a/NitroxModel/Packets/VehicleMovement.cs +++ b/NitroxModel/Packets/VehicleMovement.cs @@ -1,4 +1,4 @@ -using System; +using System; using BinaryPack.Attributes; using NitroxModel.DataStructures.GameLogic; using NitroxModel.DataStructures.Unity; @@ -26,7 +26,7 @@ public VehicleMovement(ushort playerId, VehicleMovementData vehicleMovementData) PlayerId = playerId; VehicleMovementData = vehicleMovementData; DeliveryMethod = NitroxDeliveryMethod.DeliveryMethod.UNRELIABLE_SEQUENCED; - UdpChannel = UdpChannelId.VEHICLE_MOVEMENT; + UdpChannel = UdpChannelId.MOVEMENTS; } } } diff --git a/NitroxModel/Packets/VehicleMovements.cs b/NitroxModel/Packets/VehicleMovements.cs index f347ab8677..92e46913db 100644 --- a/NitroxModel/Packets/VehicleMovements.cs +++ b/NitroxModel/Packets/VehicleMovements.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using NitroxModel.DataStructures; using NitroxModel.DataStructures.Unity; +using NitroxModel.Networking; namespace NitroxModel.Packets; @@ -15,6 +16,7 @@ public VehicleMovements(List data, double realTime) { Data = data; RealTime = realTime; + DeliveryMethod = NitroxDeliveryMethod.DeliveryMethod.UNRELIABLE_SEQUENCED; } } From e762c9c8b653e1f6ddff497cc10c1cb647ae07d2 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Wed, 30 Oct 2024 01:37:28 +0100 Subject: [PATCH 05/20] First attempt at implementing a variable latency adaptor --- NitroxClient/Debuggers/NetworkDebugger.cs | 2 +- .../Settings/NitroxSettingsManager.cs | 2 +- .../MonoBehaviours/MovementBroadcaster.cs | 3 +- .../MonoBehaviours/MovementReplicator.cs | 143 ++++++++++++++++-- .../Dynamic/FPSCounter_UpdateDisplay_Patch.cs | 2 + .../DefaultServerPacketProcessor.cs | 1 - .../VehicleMovementsPacketProcessor.cs | 41 +++++ 7 files changed, 179 insertions(+), 15 deletions(-) create mode 100644 NitroxServer/Communication/Packets/Processors/VehicleMovementsPacketProcessor.cs diff --git a/NitroxClient/Debuggers/NetworkDebugger.cs b/NitroxClient/Debuggers/NetworkDebugger.cs index abd37c73b1..f66904df90 100644 --- a/NitroxClient/Debuggers/NetworkDebugger.cs +++ b/NitroxClient/Debuggers/NetworkDebugger.cs @@ -19,7 +19,7 @@ public class NetworkDebugger : BaseDebugger, INetworkDebugger { nameof(PlayerMovement), nameof(EntityTransformUpdates), nameof(PlayerStats), nameof(SpawnEntities), nameof(VehicleMovement), nameof(PlayerCinematicControllerCall), nameof(FMODAssetPacket), nameof(FMODEventInstancePacket), nameof(FMODCustomEmitterPacket), nameof(FMODStudioEmitterPacket), nameof(FMODCustomLoopingEmitterPacket), - nameof(SimulationOwnershipChange), nameof(CellVisibilityChanged), nameof(PlayerInCyclopsMovement) + nameof(SimulationOwnershipChange), nameof(CellVisibilityChanged), nameof(PlayerInCyclopsMovement), nameof(VehicleMovements), }; private readonly List packets = new List(PACKET_STORED_COUNT); diff --git a/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs b/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs index 32621eb20a..0f1014d7a0 100644 --- a/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs +++ b/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs @@ -52,7 +52,7 @@ private void MakeSettings() AddSetting("Nitrox_BuildingSettings", new Setting("Nitrox_SafeBuilding", NitroxPrefs.SafeBuilding, safe => NitroxPrefs.SafeBuilding.Value = safe)); AddSetting("Nitrox_BuildingSettings", new Setting("Nitrox_SafeBuildingLog", NitroxPrefs.SafeBuildingLog, safeLog => NitroxPrefs.SafeBuildingLog.Value = safeLog)); - AddSetting("Nitrox_BandwithSettings", new Setting("Nitrox_MovementLatency", NitroxPrefs.MovementLatency, movementLatency => NitroxPrefs.MovementLatency.Value = movementLatency, 0, 1, 0.1f, 0.05f)); + // AddSetting("Nitrox_BandwithSettings", new Setting("Nitrox_MovementLatency", NitroxPrefs.MovementLatency, movementLatency => NitroxPrefs.MovementLatency.Value = movementLatency * 0.01f, 0, 1000, 100, 50)); } /// Adds a setting to the list under a certain heading diff --git a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs index 34b375560b..527adfe5c6 100644 --- a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs +++ b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using NitroxClient.Communication.Abstract; +using NitroxClient.GameLogic; using NitroxModel.DataStructures; using NitroxModel.Packets; using NitroxModel_Subnautica.DataStructures; @@ -35,7 +36,7 @@ public void OnDestroy() public void Update() { - float currentTime = DayNightCycle.main.timePassedAsFloat; + float currentTime = (float)this.Resolve().RealTimeElapsed; if (currentTime < latestBroadcastTime + BROADCAST_PERIOD) { return; diff --git a/NitroxClient/MonoBehaviours/MovementReplicator.cs b/NitroxClient/MonoBehaviours/MovementReplicator.cs index 1285b97c0f..f28d0deef4 100644 --- a/NitroxClient/MonoBehaviours/MovementReplicator.cs +++ b/NitroxClient/MonoBehaviours/MovementReplicator.cs @@ -1,8 +1,11 @@ using System.Collections.Generic; +using System.IO; +using System.Text; using NitroxClient.GameLogic; using NitroxClient.GameLogic.Settings; using NitroxClient.MonoBehaviours.Cyclops; using NitroxModel.DataStructures; +using NitroxModel.Helper; using NitroxModel.Packets; using NitroxModel_Subnautica.DataStructures; using UnityEngine; @@ -13,16 +16,109 @@ public class MovementReplicator : MonoBehaviour { public const float INTERPOLATION_TIME = 4 * MovementBroadcaster.BROADCAST_PERIOD; public const float SNAPSHOT_EXPIRATION_TIME = 5f * INTERPOLATION_TIME; - private const int BUFFER_SIZE = 10; private readonly LinkedList buffer = new(); + private float variableLatency; + private float latestLatencyBumpTime; - public Rigidbody rigidbody; + private float maxLatencyDetectedRecently = NitroxPrefs.MovementLatency.Value; + + private Rigidbody rigidbody; public NitroxId objectId; + /// + /// Current time must be based on real time to avoid effects from time changes/speed. + /// + private float CurrentTime => (float)this.Resolve().RealTimeElapsed; + + public static float R, V; + + private const float SAFETY_LATENCY_MARGIN = 0.05f; // 50ms is a safe margin to avoid future smaller bumps + + private string path; + private StringBuilder csv; + private bool writingCsv; + private float timeStartCSV; + private float dataGatherDuration = 30f; + + private void InitCSV() + { + path = Path.Combine(NitroxUser.LauncherPath, $"latency-{objectId}.csv"); + CSVStart(); + } + + public void CSVStart() + { + writingCsv = true; + csv = new StringBuilder(); + csv.AppendLine("Real Time;Real Latency;Max Latency Allowed"); + timeStartCSV = (float)this.Resolve().RealTimeElapsed; + Log.Debug("CSV Start"); + } + + public void CSVSave() + { + if (!writingCsv) + { + return; + } + writingCsv = false; + File.WriteAllText(path, csv.ToString()); + Log.Debug("CSV Saved"); + } + public void AddSnapshot(MovementData movementData, float time) { - buffer.AddLast(new Snapshot(movementData, time + INTERPOLATION_TIME + NitroxPrefs.MovementLatency.Value)); + float currentTime = CurrentTime; + float latency = currentTime - time; + R = latency; + + if (latency > variableLatency) + { + variableLatency = latency + SAFETY_LATENCY_MARGIN; + latestLatencyBumpTime = currentTime; + maxLatencyDetectedRecently = 0; + Log.InGame($"Latency [+]: {variableLatency*1000:F3}ms"); + Log.Debug($"Latency [+]: {variableLatency*1000:F3}ms"); + } + else + { + maxLatencyDetectedRecently = UnityEngine.Mathf.Max(latency, maxLatencyDetectedRecently); + + if (currentTime - latestLatencyBumpTime >= 4f) // 4s is arbitrary + { + if (maxLatencyDetectedRecently < variableLatency - 2 * SAFETY_LATENCY_MARGIN) // If max latency is in the safety range + { + variableLatency = maxLatencyDetectedRecently + SAFETY_LATENCY_MARGIN; // regular gameplay latency variation + Log.InGame($"Latency [-]: {variableLatency * 1000:F3}ms"); + Log.Debug($"Latency [-]: {variableLatency * 1000:F3}ms"); + } + latestLatencyBumpTime = currentTime; + maxLatencyDetectedRecently = 0; + } + } + V = variableLatency; + if (writingCsv) + { + if (currentTime > timeStartCSV + dataGatherDuration) + { + CSVSave(); + } + else + { + csv.AppendLine($"{currentTime};{latency};{variableLatency}"); + } + } + + float occurenceTime = time + INTERPOLATION_TIME + variableLatency; + + // Cleaning any previous value change that would occur later than the newly received snapshot + while (buffer.Last != null && buffer.Last.Value.IsOlderThan(occurenceTime)) + { + buffer.RemoveLast(); + } + + buffer.AddLast(new Snapshot(movementData, occurenceTime)); } public void Start() @@ -44,10 +140,11 @@ public void Start() { worldForces.enabled = false; } - rigidbody.isKinematic = true; - rigidbody.interpolation = RigidbodyInterpolation.Interpolate; + rigidbody.isKinematic = false;// true; + //rigidbody.interpolation = RigidbodyInterpolation.Interpolate; } - + + InitCSV(); MovementBroadcaster.RegisterReplicator(this); } @@ -63,8 +160,8 @@ public void OnDestroy() { worldForces.enabled = true; } - rigidbody.isKinematic = false; - rigidbody.interpolation = RigidbodyInterpolation.None; + //rigidbody.isKinematic = false; + //rigidbody.interpolation = RigidbodyInterpolation.None; } MovementBroadcaster.UnregisterReplicator(this); @@ -77,7 +174,7 @@ public void Update() return; } - float currentTime = DayNightCycle.main.timePassedAsFloat; + float currentTime = CurrentTime; // Sorting out expired nodes while (buffer.First != null && buffer.First.Value.IsExpired(currentTime)) @@ -131,7 +228,7 @@ public void Update() public void DebugForward() { - float currentTime = DayNightCycle.main.timePassedAsFloat; + float currentTime = CurrentTime; int count = 90; Log.Debug($"Adding {count} snapshots from {currentTime}"); @@ -148,7 +245,7 @@ public void DebugForward() public void DebugForwardRight() { - float currentTime = (float)this.Resolve().CurrentTime; + float currentTime = CurrentTime; int count = 90; float delta = 20f / 90f; @@ -164,6 +261,30 @@ public void DebugForwardRight() } } + public void DebugLatencyVariation() + { + float currentTime = CurrentTime; + + float delta = 10f / 60f; + for (int i = 0; i < 60; i++) + { + Vector3 result = transform.position + new Vector3(delta * i, 0, 0); + + MovementData movementData = new(null, result.ToDto(), transform.rotation.ToDto()); + + AddSnapshot(movementData, currentTime + i * MovementBroadcaster.BROADCAST_PERIOD); + } + + for (int i = 0; i < 60; i++) + { + Vector3 result = transform.position + new Vector3(10 + delta * i, 0, 0); + + MovementData movementData = new(null, result.ToDto(), transform.rotation.ToDto()); + + AddSnapshot(movementData, currentTime + i * MovementBroadcaster.BROADCAST_PERIOD); + } + } + private record struct Snapshot(MovementData Data, float Time) { public bool IsOlderThan(float currentTime) => currentTime < Time; diff --git a/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs b/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs index 33b51f6e15..36407ef3a2 100644 --- a/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs @@ -18,6 +18,8 @@ public static void Postfix(FPSCounter __instance) } __instance.strBuffer.Append("Loading entities: ").AppendLine(Resolve().EntitiesToSpawn.Count.ToString()); __instance.strBuffer.Append("Real time elapsed: ").AppendLine(Resolve().RealTimeElapsed.ToString()); + __instance.strBuffer.Append("R LAT: ").AppendLine(MovementReplicator.R.ToString()); + __instance.strBuffer.Append("V LAT: ").AppendLine(MovementReplicator.V.ToString()); __instance.text.SetText(__instance.strBuffer); } } diff --git a/NitroxServer/Communication/Packets/Processors/DefaultServerPacketProcessor.cs b/NitroxServer/Communication/Packets/Processors/DefaultServerPacketProcessor.cs index 8d71700d42..96e2fe5e77 100644 --- a/NitroxServer/Communication/Packets/Processors/DefaultServerPacketProcessor.cs +++ b/NitroxServer/Communication/Packets/Processors/DefaultServerPacketProcessor.cs @@ -23,7 +23,6 @@ public class DefaultServerPacketProcessor : AuthenticatedPacketProcessor typeof(FMODCustomLoopingEmitterPacket), typeof(FMODStudioEmitterPacket), typeof(PlayerCinematicControllerCall), - typeof(VehicleMovements) }; /// diff --git a/NitroxServer/Communication/Packets/Processors/VehicleMovementsPacketProcessor.cs b/NitroxServer/Communication/Packets/Processors/VehicleMovementsPacketProcessor.cs new file mode 100644 index 0000000000..70c18cb3d6 --- /dev/null +++ b/NitroxServer/Communication/Packets/Processors/VehicleMovementsPacketProcessor.cs @@ -0,0 +1,41 @@ +using NitroxModel.DataStructures.GameLogic.Entities; +using NitroxModel.Packets; +using NitroxServer.Communication.Packets.Processors.Abstract; +using NitroxServer.GameLogic; +using NitroxServer.GameLogic.Entities; + +namespace NitroxServer.Communication.Packets.Processors; + +public class VehicleMovementsPacketProcessor : AuthenticatedPacketProcessor +{ + private readonly PlayerManager playerManager; + private readonly EntityRegistry entityRegistry; + + public VehicleMovementsPacketProcessor(PlayerManager playerManager, EntityRegistry entityRegistry) + { + this.playerManager = playerManager; + this.entityRegistry = entityRegistry; + } + + public override void Process(VehicleMovements packet, Player player) + { + foreach (MovementData movementData in packet.Data) + { + if (entityRegistry.TryGetEntityById(movementData.Id, out WorldEntity worldEntity)) + { + worldEntity.Transform.Position = movementData.Position; + worldEntity.Transform.Rotation = movementData.Rotation; + } + } + + // TODO: sync driving player movement without adding much more data (maybe have a nullable DriverPosition field in there) + + + /*if (player.Id == packet.PlayerId) + { + player.Position = packet.VehicleMovementData.DriverPosition ?? packet.Position; + }*/ + + playerManager.SendPacketToOtherPlayers(packet, player); + } +} From ee9b93751eb6db8c382194f1a6f1c95acae30c4d Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Wed, 30 Oct 2024 12:14:06 +0100 Subject: [PATCH 06/20] Fix channels (again), add two settings for better control of movements latency managing, and files cleanup --- .../LiteNetLib/LiteNetLibClient.cs | 5 +- .../GameLogic/Settings/NitroxPrefs.cs | 9 +- .../Settings/NitroxSettingsManager.cs | 14 +- .../MonoBehaviours/MovementBroadcaster.cs | 2 +- .../MonoBehaviours/MovementReplicator.cs | 173 ++++-------------- NitroxModel/Packets/VehicleMovements.cs | 1 + .../Dynamic/FPSCounter_UpdateDisplay_Patch.cs | 2 - .../uGUI_OptionsPanel_AddTabs_Patch.cs | 2 +- .../LiteNetLib/LiteNetLibServer.cs | 3 +- 9 files changed, 59 insertions(+), 152 deletions(-) diff --git a/NitroxClient/Communication/NetworkingLayer/LiteNetLib/LiteNetLibClient.cs b/NitroxClient/Communication/NetworkingLayer/LiteNetLib/LiteNetLibClient.cs index a00580f830..64eb10ea5d 100644 --- a/NitroxClient/Communication/NetworkingLayer/LiteNetLib/LiteNetLibClient.cs +++ b/NitroxClient/Communication/NetworkingLayer/LiteNetLib/LiteNetLibClient.cs @@ -31,12 +31,11 @@ public LiteNetLibClient(PacketReceiver packetReceiver, INetworkDebugger networkD listener.PeerDisconnectedEvent += Disconnected; listener.NetworkReceiveEvent += ReceivedNetworkData; - byte channelsCount = (byte)typeof(Packet.UdpChannelId).GetEnumValues().Length; - + client = new NetManager(listener) { UpdateTime = 15, - ChannelsCount = channelsCount, + ChannelsCount = (byte)typeof(Packet.UdpChannelId).GetEnumValues().Length, #if DEBUG DisconnectTimeout = 300000 //Disables Timeout (for 5 min) for debug purpose (like if you jump though the server code) #endif diff --git a/NitroxClient/GameLogic/Settings/NitroxPrefs.cs b/NitroxClient/GameLogic/Settings/NitroxPrefs.cs index 1091cbb120..eec921e2e8 100644 --- a/NitroxClient/GameLogic/Settings/NitroxPrefs.cs +++ b/NitroxClient/GameLogic/Settings/NitroxPrefs.cs @@ -11,7 +11,14 @@ public class NitroxPrefs public static readonly NitroxPref ChatUsed = new("Nitrox.chatUsed"); public static readonly NitroxPref SafeBuilding = new("Nitrox.safeBuilding", true); public static readonly NitroxPref SafeBuildingLog = new("Nitrox.safeBuildingLog", true); - public static readonly NitroxPref MovementLatency = new("Nitrox.movementLatency", 0.1f); + /// + /// In seconds. + /// + public static readonly NitroxPref LatencyUpdatePeriod = new("Nitrox.latencyUpdatePeriod", 10); + /// + /// In milliseconds. + /// + public static readonly NitroxPref SafetyLatencyMargin = new("Nitrox.safetyLatencyMargin", 0.05f); } public abstract class NitroxPref { } diff --git a/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs b/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs index 0f1014d7a0..9393aabf5d 100644 --- a/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs +++ b/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs @@ -52,7 +52,8 @@ private void MakeSettings() AddSetting("Nitrox_BuildingSettings", new Setting("Nitrox_SafeBuilding", NitroxPrefs.SafeBuilding, safe => NitroxPrefs.SafeBuilding.Value = safe)); AddSetting("Nitrox_BuildingSettings", new Setting("Nitrox_SafeBuildingLog", NitroxPrefs.SafeBuildingLog, safeLog => NitroxPrefs.SafeBuildingLog.Value = safeLog)); - // AddSetting("Nitrox_BandwithSettings", new Setting("Nitrox_MovementLatency", NitroxPrefs.MovementLatency, movementLatency => NitroxPrefs.MovementLatency.Value = movementLatency * 0.01f, 0, 1000, 100, 50)); + AddSetting("Nitrox_BandwidthSettings", new Setting("Nitrox_LatencyUpdatePeriod", NitroxPrefs.LatencyUpdatePeriod, latencyUpdatePeriod => NitroxPrefs.LatencyUpdatePeriod.Value = (int)latencyUpdatePeriod, 1, 60, NitroxPrefs.LatencyUpdatePeriod.DefaultValue, 1, SliderLabelMode.Int, tooltip: "Nitrox_HigherForUnstable")); + AddSetting("Nitrox_BandwidthSettings", new Setting("Nitrox_SafetyLatencyMargin", NitroxPrefs.SafetyLatencyMargin, safetyLatencyMargin => NitroxPrefs.SafetyLatencyMargin.Value = safetyLatencyMargin, 0.01f, 0.5f, NitroxPrefs.SafetyLatencyMargin.DefaultValue, 0.01f, SliderLabelMode.Float, "0.00", "Nitrox_HigherForUnstable")); } /// Adds a setting to the list under a certain heading @@ -82,6 +83,12 @@ public class Setting public readonly float SliderMaxValue; public readonly float SliderDefaultValue; public readonly float SliderStep; + public readonly SliderLabelMode LabelMode; + /// + /// Examples: "0", "0.00" + /// + public string FloatFormat; + public readonly string Tooltip; // List specifics public readonly string[] ListItems; @@ -107,12 +114,15 @@ public Setting(string label, UnityAction callback) public Setting(string label, NitroxPref nitroxPref, UnityAction callback) : this(SettingType.TOGGLE, label, nitroxPref, callback) { } /// Constructor for a Slider setting - public Setting(string label, NitroxPref nitroxPref, UnityAction callback, float sliderMinValue, float sliderMaxValue, float sliderDefaultValue, float sliderStep) : this(SettingType.SLIDER, label, nitroxPref, callback) + public Setting(string label, NitroxPref nitroxPref, UnityAction callback, float sliderMinValue, float sliderMaxValue, float sliderDefaultValue, float sliderStep, SliderLabelMode labelMode, string floatFormat = "0", string tooltip = null) : this(SettingType.SLIDER, label, nitroxPref, callback) { SliderMinValue = sliderMinValue; SliderMaxValue = sliderMaxValue; SliderDefaultValue = sliderDefaultValue; SliderStep = sliderStep; + LabelMode = labelMode; + FloatFormat = floatFormat; + Tooltip = tooltip; } /// Constructor for a List setting diff --git a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs index 527adfe5c6..69378ef280 100644 --- a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs +++ b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs @@ -10,7 +10,7 @@ namespace NitroxClient.MonoBehaviours; public class MovementBroadcaster : MonoBehaviour { - public const int BROADCAST_FREQUENCY = 30; // TODO: try with even lower frequency than 30 + public const int BROADCAST_FREQUENCY = 30; public const float BROADCAST_PERIOD = 1f / BROADCAST_FREQUENCY; private readonly Dictionary watchedEntries = []; diff --git a/NitroxClient/MonoBehaviours/MovementReplicator.cs b/NitroxClient/MonoBehaviours/MovementReplicator.cs index f28d0deef4..b0b7d99268 100644 --- a/NitroxClient/MonoBehaviours/MovementReplicator.cs +++ b/NitroxClient/MonoBehaviours/MovementReplicator.cs @@ -1,11 +1,8 @@ using System.Collections.Generic; -using System.IO; -using System.Text; using NitroxClient.GameLogic; using NitroxClient.GameLogic.Settings; using NitroxClient.MonoBehaviours.Cyclops; using NitroxModel.DataStructures; -using NitroxModel.Helper; using NitroxModel.Packets; using NitroxModel_Subnautica.DataStructures; using UnityEngine; @@ -18,10 +15,24 @@ public class MovementReplicator : MonoBehaviour public const float SNAPSHOT_EXPIRATION_TIME = 5f * INTERPOLATION_TIME; private readonly LinkedList buffer = new(); - private float variableLatency; + /// + /// To ensure a smooth experience, we need a max allowed latency value which should top the incoming latencies at all times. + /// Big increments and any decrements of this value will likely cause stutter, so we try to avoid changing this value too much. + /// But it is required that after a lag spike, we eventually lower down that value, which is done periodically . + /// + private float maxAllowedLatency; + private float latestLatencyBumpTime; + private float maxLatencyDetectedRecently; + + /// + /// When encountering a latency bump, we must expect worse happening right after, so we add this margin to our new . + /// After each periodical latency update (), we only want to lower the latency if it's way smaller than the current variable latency. + /// The safety threshold is defined + /// + private float SafetyLatencyMargin => NitroxPrefs.SafetyLatencyMargin.Value; - private float maxLatencyDetectedRecently = NitroxPrefs.MovementLatency.Value; + private float LatencyUpdatePeriod => NitroxPrefs.LatencyUpdatePeriod.Value; private Rigidbody rigidbody; public NitroxId objectId; @@ -31,94 +42,41 @@ public class MovementReplicator : MonoBehaviour /// private float CurrentTime => (float)this.Resolve().RealTimeElapsed; - public static float R, V; - - private const float SAFETY_LATENCY_MARGIN = 0.05f; // 50ms is a safe margin to avoid future smaller bumps - - private string path; - private StringBuilder csv; - private bool writingCsv; - private float timeStartCSV; - private float dataGatherDuration = 30f; - - private void InitCSV() - { - path = Path.Combine(NitroxUser.LauncherPath, $"latency-{objectId}.csv"); - CSVStart(); - } - - public void CSVStart() - { - writingCsv = true; - csv = new StringBuilder(); - csv.AppendLine("Real Time;Real Latency;Max Latency Allowed"); - timeStartCSV = (float)this.Resolve().RealTimeElapsed; - Log.Debug("CSV Start"); - } - - public void CSVSave() - { - if (!writingCsv) - { - return; - } - writingCsv = false; - File.WriteAllText(path, csv.ToString()); - Log.Debug("CSV Saved"); - } - public void AddSnapshot(MovementData movementData, float time) { float currentTime = CurrentTime; float latency = currentTime - time; - R = latency; - if (latency > variableLatency) + if (latency > maxAllowedLatency) { - variableLatency = latency + SAFETY_LATENCY_MARGIN; + maxAllowedLatency = latency + SafetyLatencyMargin; latestLatencyBumpTime = currentTime; maxLatencyDetectedRecently = 0; - Log.InGame($"Latency [+]: {variableLatency*1000:F3}ms"); - Log.Debug($"Latency [+]: {variableLatency*1000:F3}ms"); } else { - maxLatencyDetectedRecently = UnityEngine.Mathf.Max(latency, maxLatencyDetectedRecently); + maxLatencyDetectedRecently = Mathf.Max(latency, maxLatencyDetectedRecently); - if (currentTime - latestLatencyBumpTime >= 4f) // 4s is arbitrary + if (currentTime - latestLatencyBumpTime >= LatencyUpdatePeriod) { - if (maxLatencyDetectedRecently < variableLatency - 2 * SAFETY_LATENCY_MARGIN) // If max latency is in the safety range + if (maxLatencyDetectedRecently < maxAllowedLatency - 2 * SafetyLatencyMargin) { - variableLatency = maxLatencyDetectedRecently + SAFETY_LATENCY_MARGIN; // regular gameplay latency variation - Log.InGame($"Latency [-]: {variableLatency * 1000:F3}ms"); - Log.Debug($"Latency [-]: {variableLatency * 1000:F3}ms"); + maxAllowedLatency = maxLatencyDetectedRecently + SafetyLatencyMargin; // regular gameplay latency variation } latestLatencyBumpTime = currentTime; maxLatencyDetectedRecently = 0; } } - V = variableLatency; - if (writingCsv) - { - if (currentTime > timeStartCSV + dataGatherDuration) - { - CSVSave(); - } - else - { - csv.AppendLine($"{currentTime};{latency};{variableLatency}"); - } - } - float occurenceTime = time + INTERPOLATION_TIME + variableLatency; + float occurrenceTime = time + INTERPOLATION_TIME + maxAllowedLatency; // Cleaning any previous value change that would occur later than the newly received snapshot - while (buffer.Last != null && buffer.Last.Value.IsOlderThan(occurenceTime)) + while (buffer.Last != null && buffer.Last.Value.IsOlderThan(occurrenceTime)) { buffer.RemoveLast(); } - buffer.AddLast(new Snapshot(movementData, occurenceTime)); + buffer.AddLast(new Snapshot(movementData, occurrenceTime)); } public void Start() @@ -140,11 +98,9 @@ public void Start() { worldForces.enabled = false; } - rigidbody.isKinematic = false;// true; - //rigidbody.interpolation = RigidbodyInterpolation.Interpolate; + rigidbody.isKinematic = false; } - InitCSV(); MovementBroadcaster.RegisterReplicator(this); } @@ -160,8 +116,6 @@ public void OnDestroy() { worldForces.enabled = true; } - //rigidbody.isKinematic = false; - //rigidbody.interpolation = RigidbodyInterpolation.None; } MovementBroadcaster.UnregisterReplicator(this); @@ -179,24 +133,22 @@ public void Update() // Sorting out expired nodes while (buffer.First != null && buffer.First.Value.IsExpired(currentTime)) { - // Log.Debug($"Invalid node: {currentTime} > {buffer.First.Value.Time + SNAPSHOT_EXPIRATION_TIME}"); buffer.RemoveFirst(); } LinkedListNode firstNode = buffer.First; if (firstNode == null) { - // Log.Debug("nothing next"); return; } // Current node is not useable yet if (firstNode.Value.IsOlderThan(currentTime)) { - // Log.Debug($"too early {currentTime} < {firstNode.Value.Time}"); return; } - + + // Purging the next nodes if they should have already happened (we still have an expiration margin for the first node so it's fine) while (firstNode.Next != null && !firstNode.Next.Value.IsOlderThan(currentTime)) { firstNode = firstNode.Next; @@ -204,11 +156,10 @@ public void Update() } LinkedListNode nextNode = firstNode.Next; - - // No next node but current node is fine + + // Current node is fine but there's no next node (waiting for it without dropping current) if (nextNode == null) { - // Log.Debug("waiting for next node"); return; } @@ -221,68 +172,8 @@ public void Update() transform.position = position; transform.rotation = rotation; - // TODO: fix remote players being able to go through the object - - // Log.Debug($"moved {t} to {nextNode.Value.Data.Position.ToUnity()}"); - } - - public void DebugForward() - { - float currentTime = CurrentTime; - - int count = 90; - Log.Debug($"Adding {count} snapshots from {currentTime}"); - float delta = 20f / count; - for (int i = 0; i < count; i++) - { - Vector3 result = transform.position + new Vector3(delta * i, 0, 0); - - MovementData movementData = new(null, result.ToDto(), transform.rotation.ToDto()); - - AddSnapshot(movementData, currentTime + i * MovementBroadcaster.BROADCAST_PERIOD); - } - } - - public void DebugForwardRight() - { - float currentTime = CurrentTime; - - int count = 90; - float delta = 20f / 90f; - float qDelta = 180f / 90f; - for (int i = 0; i < count; i++) - { - Vector3 offset = new(delta * i, 0, 0); - Quaternion qOffset = Quaternion.AngleAxis(qDelta * i, transform.up); - - MovementData movementData = new(null, (transform.position + offset).ToDto(), (transform.rotation * qOffset).ToDto()); - - AddSnapshot(movementData, currentTime + i * MovementBroadcaster.BROADCAST_PERIOD); - } - } - - public void DebugLatencyVariation() - { - float currentTime = CurrentTime; - - float delta = 10f / 60f; - for (int i = 0; i < 60; i++) - { - Vector3 result = transform.position + new Vector3(delta * i, 0, 0); - - MovementData movementData = new(null, result.ToDto(), transform.rotation.ToDto()); - - AddSnapshot(movementData, currentTime + i * MovementBroadcaster.BROADCAST_PERIOD); - } - - for (int i = 0; i < 60; i++) - { - Vector3 result = transform.position + new Vector3(10 + delta * i, 0, 0); - - MovementData movementData = new(null, result.ToDto(), transform.rotation.ToDto()); - - AddSnapshot(movementData, currentTime + i * MovementBroadcaster.BROADCAST_PERIOD); - } + + // TODO: fix remote players being able to go through the object (ex: cyclops) } private record struct Snapshot(MovementData Data, float Time) diff --git a/NitroxModel/Packets/VehicleMovements.cs b/NitroxModel/Packets/VehicleMovements.cs index 92e46913db..6714aa9361 100644 --- a/NitroxModel/Packets/VehicleMovements.cs +++ b/NitroxModel/Packets/VehicleMovements.cs @@ -17,6 +17,7 @@ public VehicleMovements(List data, double realTime) Data = data; RealTime = realTime; DeliveryMethod = NitroxDeliveryMethod.DeliveryMethod.UNRELIABLE_SEQUENCED; + UdpChannel = UdpChannelId.MOVEMENTS; } } diff --git a/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs b/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs index 36407ef3a2..33b51f6e15 100644 --- a/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/FPSCounter_UpdateDisplay_Patch.cs @@ -18,8 +18,6 @@ public static void Postfix(FPSCounter __instance) } __instance.strBuffer.Append("Loading entities: ").AppendLine(Resolve().EntitiesToSpawn.Count.ToString()); __instance.strBuffer.Append("Real time elapsed: ").AppendLine(Resolve().RealTimeElapsed.ToString()); - __instance.strBuffer.Append("R LAT: ").AppendLine(MovementReplicator.R.ToString()); - __instance.strBuffer.Append("V LAT: ").AppendLine(MovementReplicator.V.ToString()); __instance.text.SetText(__instance.strBuffer); } } diff --git a/NitroxPatcher/Patches/Persistent/uGUI_OptionsPanel_AddTabs_Patch.cs b/NitroxPatcher/Patches/Persistent/uGUI_OptionsPanel_AddTabs_Patch.cs index 518510395f..01c774d3d4 100644 --- a/NitroxPatcher/Patches/Persistent/uGUI_OptionsPanel_AddTabs_Patch.cs +++ b/NitroxPatcher/Patches/Persistent/uGUI_OptionsPanel_AddTabs_Patch.cs @@ -26,7 +26,7 @@ public static void Postfix(uGUI_OptionsPanel __instance) __instance.AddToggleOption(tabIndex, setting.Label, setting.GetValue(), (UnityAction)setting.Callback); break; case NitroxSettingsManager.SettingType.SLIDER: - __instance.AddSliderOption(tabIndex, setting.Label, setting.GetValue(), setting.SliderMinValue, setting.SliderMaxValue, setting.SliderDefaultValue, setting.SliderStep, (UnityAction)setting.Callback, SliderLabelMode.Percent, "0"); + __instance.AddSliderOption(tabIndex, setting.Label, setting.GetValue(), setting.SliderMinValue, setting.SliderMaxValue, setting.SliderDefaultValue, setting.SliderStep, (UnityAction)setting.Callback, setting.LabelMode, setting.FloatFormat, setting.Tooltip); break; case NitroxSettingsManager.SettingType.LIST: __instance.AddChoiceOption(tabIndex, setting.Label, setting.ListItems, setting.GetValue(), (UnityAction)setting.Callback); diff --git a/NitroxServer/Communication/LiteNetLib/LiteNetLibServer.cs b/NitroxServer/Communication/LiteNetLib/LiteNetLibServer.cs index c8d45f0190..57e8d7fae6 100644 --- a/NitroxServer/Communication/LiteNetLib/LiteNetLibServer.cs +++ b/NitroxServer/Communication/LiteNetLib/LiteNetLibServer.cs @@ -1,4 +1,4 @@ -using System.Buffers; +using System.Buffers; using System.Threading; using System.Threading.Tasks; using LiteNetLib; @@ -31,6 +31,7 @@ public override bool Start() listener.NetworkReceiveEvent += NetworkDataReceived; listener.ConnectionRequestEvent += OnConnectionRequest; + server.ChannelsCount = (byte)typeof(Packet.UdpChannelId).GetEnumValues().Length; server.BroadcastReceiveEnabled = true; server.UnconnectedMessagesEnabled = true; server.UpdateTime = 15; From f8569e0cfbbfd9fd60b66843f9d9faaabed0ed0e Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:32:11 +0100 Subject: [PATCH 07/20] Deprecate the current vehicle animation system and replace it by a new one integrated in the new movement system --- .../Processors/VehicleMovementProcessor.cs | 27 ---- .../Processors/VehicleMovementsProcessor.cs | 1 + .../VehicleOnPilotModeChangedProcessor.cs | 35 ++--- NitroxClient/Debuggers/NetworkDebugger.cs | 4 +- NitroxClient/GameLogic/LocalPlayer.cs | 15 +- NitroxClient/GameLogic/MobileVehicleBay.cs | 5 +- NitroxClient/GameLogic/RemotePlayer.cs | 33 ++-- .../GameLogic/Settings/NitroxPrefs.cs | 4 +- .../Settings/NitroxSettingsManager.cs | 4 +- NitroxClient/GameLogic/SimulationOwnership.cs | 2 +- NitroxClient/GameLogic/Vehicles.cs | 143 ++++-------------- .../Cyclops/MultiplayerCyclops.cs | 62 -------- .../MonoBehaviours/MovementBroadcaster.cs | 58 ++++++- .../MonoBehaviours/MovementReplicator.cs | 37 ++++- .../MonoBehaviours/MultiplayerExosuit.cs | 105 ------------- .../MonoBehaviours/MultiplayerSeaMoth.cs | 80 ---------- .../PlayerMovementBroadcaster.cs | 130 +--------------- .../Vehicles/CyclopsMovementReplicator.cs | 75 +++++++++ .../Vehicles/ExosuitMovementReplicator.cs | 128 ++++++++++++++++ .../Vehicles/SeaMothMovementReplicator.cs | 115 ++++++++++++++ .../Vehicles/VehicleMovementReplicator.cs | 9 ++ .../GameLogic/ExoSuitMovementData.cs | 38 ----- .../Helper/VehicleMovementFactory.cs | 22 --- .../GameLogic/BasicVehicleMovementData.cs | 16 -- .../GameLogic/VehicleMovementData.cs | 80 ---------- NitroxModel/Packets/VehicleMovement.cs | 32 ---- NitroxModel/Packets/VehicleMovements.cs | 6 +- .../Packets/VehicleOnPilotModeChanged.cs | 27 ++-- .../CyclopsSonarButton_Update_Patch.cs | 8 +- .../PilotingChair_OnHandClick_Patch.cs | 6 +- .../PilotingChair_OnPlayerDeath_Patch.cs | 3 +- .../Dynamic/PilotingChair_ReleaseBy_Patch.cs | 3 +- .../Dynamic/Vehicle_OnPilotModeBegin_Patch.cs | 4 +- .../Dynamic/Vehicle_OnPilotModeEnd_Patch.cs | 4 +- .../SubnauticaServerProtoBufSerializer.cs | 7 - .../DefaultServerPacketProcessor.cs | 1 - .../VehicleMovementPacketProcessor.cs | 40 ----- .../VehicleMovementsPacketProcessor.cs | 41 +++-- 38 files changed, 561 insertions(+), 849 deletions(-) delete mode 100644 NitroxClient/Communication/Packets/Processors/VehicleMovementProcessor.cs delete mode 100644 NitroxClient/MonoBehaviours/Cyclops/MultiplayerCyclops.cs delete mode 100644 NitroxClient/MonoBehaviours/MultiplayerExosuit.cs delete mode 100644 NitroxClient/MonoBehaviours/MultiplayerSeaMoth.cs create mode 100644 NitroxClient/MonoBehaviours/Vehicles/CyclopsMovementReplicator.cs create mode 100644 NitroxClient/MonoBehaviours/Vehicles/ExosuitMovementReplicator.cs create mode 100644 NitroxClient/MonoBehaviours/Vehicles/SeaMothMovementReplicator.cs create mode 100644 NitroxClient/MonoBehaviours/Vehicles/VehicleMovementReplicator.cs delete mode 100644 NitroxModel-Subnautica/DataStructures/GameLogic/ExoSuitMovementData.cs delete mode 100644 NitroxModel-Subnautica/Helper/VehicleMovementFactory.cs delete mode 100644 NitroxModel/DataStructures/GameLogic/BasicVehicleMovementData.cs delete mode 100644 NitroxModel/DataStructures/GameLogic/VehicleMovementData.cs delete mode 100644 NitroxModel/Packets/VehicleMovement.cs delete mode 100644 NitroxServer/Communication/Packets/Processors/VehicleMovementPacketProcessor.cs diff --git a/NitroxClient/Communication/Packets/Processors/VehicleMovementProcessor.cs b/NitroxClient/Communication/Packets/Processors/VehicleMovementProcessor.cs deleted file mode 100644 index 2963d91a2c..0000000000 --- a/NitroxClient/Communication/Packets/Processors/VehicleMovementProcessor.cs +++ /dev/null @@ -1,27 +0,0 @@ -using NitroxClient.Communication.Packets.Processors.Abstract; -using NitroxClient.GameLogic; -using NitroxModel.DataStructures.GameLogic; -using NitroxModel.DataStructures.Util; -using NitroxModel.Packets; - -namespace NitroxClient.Communication.Packets.Processors -{ - public class VehicleMovementProcessor : ClientPacketProcessor - { - private readonly PlayerManager remotePlayerManager; - private readonly Vehicles vehicles; - - public VehicleMovementProcessor(PlayerManager remotePlayerManager, Vehicles vehicles) - { - this.remotePlayerManager = remotePlayerManager; - this.vehicles = vehicles; - } - - public override void Process(VehicleMovement vehicleMovement) - { - VehicleMovementData vehicleModel = vehicleMovement.VehicleMovementData; - Optional player = remotePlayerManager.Find(vehicleMovement.PlayerId); - vehicles.UpdateVehiclePosition(vehicleModel, player); - } - } -} diff --git a/NitroxClient/Communication/Packets/Processors/VehicleMovementsProcessor.cs b/NitroxClient/Communication/Packets/Processors/VehicleMovementsProcessor.cs index f2701c7fe3..77e9c149c4 100644 --- a/NitroxClient/Communication/Packets/Processors/VehicleMovementsProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/VehicleMovementsProcessor.cs @@ -1,5 +1,6 @@ using NitroxClient.Communication.Packets.Processors.Abstract; using NitroxClient.MonoBehaviours; +using NitroxClient.MonoBehaviours.Vehicles; using NitroxModel.Packets; namespace NitroxClient.Communication.Packets.Processors; diff --git a/NitroxClient/Communication/Packets/Processors/VehicleOnPilotModeChangedProcessor.cs b/NitroxClient/Communication/Packets/Processors/VehicleOnPilotModeChangedProcessor.cs index 925353fae5..74591d94f6 100644 --- a/NitroxClient/Communication/Packets/Processors/VehicleOnPilotModeChangedProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/VehicleOnPilotModeChangedProcessor.cs @@ -2,38 +2,35 @@ using NitroxClient.Communication.Packets.Processors.Abstract; using NitroxClient.GameLogic; using NitroxClient.MonoBehaviours; -using NitroxClient.Unity.Helper; using NitroxModel.Packets; -using UnityEngine; -namespace NitroxClient.Communication.Packets.Processors +namespace NitroxClient.Communication.Packets.Processors; + +public class VehicleOnPilotModeChangedProcessor : ClientPacketProcessor { - public class VehicleOnPilotModeChangedProcessor : ClientPacketProcessor - { - private readonly IPacketSender packetSender; - private readonly Vehicles vehicles; + private readonly IPacketSender packetSender; + private readonly Vehicles vehicles; + private readonly PlayerManager playerManager; - public VehicleOnPilotModeChangedProcessor(IPacketSender packetSender, Vehicles vehicles) - { - this.packetSender = packetSender; - this.vehicles = vehicles; - } + public VehicleOnPilotModeChangedProcessor(IPacketSender packetSender, Vehicles vehicles, PlayerManager playerManager) + { + this.packetSender = packetSender; + this.vehicles = vehicles; + this.playerManager = playerManager; + } - public override void Process(VehicleOnPilotModeChanged packet) + public override void Process(VehicleOnPilotModeChanged packet) + { + if (NitroxEntity.TryGetComponentFrom(packet.VehicleId, out Vehicle vehicle)) { - GameObject vehicleGo = NitroxEntity.RequireObjectFrom(packet.VehicleId); - Vehicle vehicle = vehicleGo.RequireComponent(); - // If the vehicle is docked, then we will manually set the piloting mode // once the animations complete. This prevents weird behaviour such as the // player existing the vehicle while it is about to dock (the event fires // before the animation completes on the remote player.) if (!vehicle.docked) { - vehicles.SetOnPilotMode(packet.VehicleId, packet.PlayerId, packet.IsPiloting); + vehicles.SetOnPilotMode(vehicle.gameObject, packet.PlayerId, packet.IsPiloting); } - - // TODO: remake that, it does nothing } } } diff --git a/NitroxClient/Debuggers/NetworkDebugger.cs b/NitroxClient/Debuggers/NetworkDebugger.cs index f66904df90..8f7a58ca2b 100644 --- a/NitroxClient/Debuggers/NetworkDebugger.cs +++ b/NitroxClient/Debuggers/NetworkDebugger.cs @@ -17,9 +17,9 @@ public class NetworkDebugger : BaseDebugger, INetworkDebugger private readonly List filter = new() { - nameof(PlayerMovement), nameof(EntityTransformUpdates), nameof(PlayerStats), nameof(SpawnEntities), nameof(VehicleMovement), nameof(PlayerCinematicControllerCall), + nameof(PlayerMovement), nameof(EntityTransformUpdates), nameof(PlayerStats), nameof(SpawnEntities), nameof(VehicleMovements), nameof(PlayerCinematicControllerCall), nameof(FMODAssetPacket), nameof(FMODEventInstancePacket), nameof(FMODCustomEmitterPacket), nameof(FMODStudioEmitterPacket), nameof(FMODCustomLoopingEmitterPacket), - nameof(SimulationOwnershipChange), nameof(CellVisibilityChanged), nameof(PlayerInCyclopsMovement), nameof(VehicleMovements), + nameof(SimulationOwnershipChange), nameof(CellVisibilityChanged), nameof(PlayerInCyclopsMovement), }; private readonly List packets = new List(PACKET_STORED_COUNT); diff --git a/NitroxClient/GameLogic/LocalPlayer.cs b/NitroxClient/GameLogic/LocalPlayer.cs index 7b810d9e20..1ae5d0b1a2 100644 --- a/NitroxClient/GameLogic/LocalPlayer.cs +++ b/NitroxClient/GameLogic/LocalPlayer.cs @@ -51,25 +51,16 @@ public LocalPlayer(IMultiplayerSession multiplayerSession, IPacketSender packetS Permissions = Perms.PLAYER; } - public void BroadcastLocation(Vector3 location, Vector3 velocity, Quaternion bodyRotation, Quaternion aimingRotation, Optional vehicle) + public void BroadcastLocation(Vector3 location, Vector3 velocity, Quaternion bodyRotation, Quaternion aimingRotation) { if (!PlayerId.HasValue) { return; } - Movement movement; - if (vehicle.HasValue) - { - movement = new VehicleMovement(PlayerId.Value, vehicle.Value); - return; - } - else - { - movement = new PlayerMovement(PlayerId.Value, location.ToDto(), velocity.ToDto(), bodyRotation.ToDto(), aimingRotation.ToDto()); - } + PlayerMovement playerMovement = new(PlayerId.Value, location.ToDto(), velocity.ToDto(), bodyRotation.ToDto(), aimingRotation.ToDto()); - packetSender.Send(movement); + packetSender.Send(playerMovement); } public void AnimationChange(AnimChangeType type, AnimChangeState state) diff --git a/NitroxClient/GameLogic/MobileVehicleBay.cs b/NitroxClient/GameLogic/MobileVehicleBay.cs index b48f5a61a1..bb54cb7284 100644 --- a/NitroxClient/GameLogic/MobileVehicleBay.cs +++ b/NitroxClient/GameLogic/MobileVehicleBay.cs @@ -43,7 +43,8 @@ public void BeginCrafting(ConstructorInput constructor, GameObject constructedOb VehicleWorldEntity vehicleEntity = Vehicles.BuildVehicleWorldEntity(constructedObject, constructedObjectId, techType, constructorId); packetSender.Send(new EntitySpawnedByClient(vehicleEntity)); - - constructor.StartCoroutine(vehicles.UpdateVehiclePositionAfterSpawn(constructedObjectId, techType, constructedObject, duration + 10.0f)); + // TODO: Fix remote players treating the SimulationOwnership change on the vehicle (they can't find it) even tho they're still in the + // process of spawning the said vehicle because it's done over multiple frames, while the SimulationOwnership packet is received + // right after the spawning started (so the processor won't find its target) } } diff --git a/NitroxClient/GameLogic/RemotePlayer.cs b/NitroxClient/GameLogic/RemotePlayer.cs index 6a0aaf084a..758d4e10ac 100644 --- a/NitroxClient/GameLogic/RemotePlayer.cs +++ b/NitroxClient/GameLogic/RemotePlayer.cs @@ -7,6 +7,7 @@ using NitroxClient.MonoBehaviours; using NitroxClient.MonoBehaviours.Cyclops; using NitroxClient.MonoBehaviours.Gui.HUD; +using NitroxClient.MonoBehaviours.Vehicles; using NitroxClient.Unity.Helper; using NitroxModel.GameLogic.FMOD; using NitroxModel.MultiplayerSession; @@ -186,7 +187,7 @@ public void SetPilotingChair(PilotingChair newPilotingChair) { PilotingChair = newPilotingChair; - MultiplayerCyclops mpCyclops = null; + CyclopsMovementReplicator cyclopsMovementReplicator = null; // For unexpected and expected cases, for example when a player is driving a cyclops but the cyclops is destroyed if (!SubRoot) @@ -195,7 +196,7 @@ public void SetPilotingChair(PilotingChair newPilotingChair) } else { - mpCyclops = SubRoot.GetComponent(); + cyclopsMovementReplicator = SubRoot.GetComponent(); } if (PilotingChair) @@ -203,8 +204,10 @@ public void SetPilotingChair(PilotingChair newPilotingChair) Attach(PilotingChair.sittingPosition.transform); ArmsController.SetWorldIKTarget(PilotingChair.leftHandPlug, PilotingChair.rightHandPlug); - mpCyclops.CurrentPlayer = this; - mpCyclops.Enter(); + if (cyclopsMovementReplicator) + { + cyclopsMovementReplicator.Enter(this); + } if (SubRoot) { @@ -216,10 +219,9 @@ public void SetPilotingChair(PilotingChair newPilotingChair) SetSubRoot(SubRoot, true); ArmsController.SetWorldIKTarget(null, null); - if (mpCyclops) + if (cyclopsMovementReplicator) { - mpCyclops.CurrentPlayer = null; - mpCyclops.Exit(); + cyclopsMovementReplicator.Exit(); } } @@ -284,7 +286,10 @@ public void SetVehicle(Vehicle newVehicle) Detach(); ArmsController.SetWorldIKTarget(null, null); - //Vehicle.GetComponent().Exit(); + if (Vehicle.TryGetComponent(out VehicleMovementReplicator vehicleMovementReplicator)) + { + vehicleMovementReplicator.Exit(); + } } if (newVehicle) @@ -296,17 +301,17 @@ public void SetVehicle(Vehicle newVehicle) // From here, a basic issue can happen. // When a vehicle is docked since we joined a game and another player undocks him before the local player does, - // no MultiplayerVehicleControl can be found on the vehicle because they are only created when receiving VehicleMovement packets - // Therefore we need to make sure that the MultiplayerVehicleControl component exists before using it - /*switch (newVehicle) + // no VehicleMovementReplicator can be found on the vehicle because they are only created when receiving SimulationOwnership packets + // Therefore we need to make sure that the VehicleMovementReplicator component exists before using it + switch (newVehicle) { case SeaMoth: - newVehicle.gameObject.EnsureComponent().Enter(); + newVehicle.gameObject.EnsureComponent().Enter(this); break; case Exosuit: - newVehicle.gameObject.EnsureComponent().Enter(); + newVehicle.gameObject.EnsureComponent().Enter(this); break; - }*/ + } } bool isKinematic = newVehicle; diff --git a/NitroxClient/GameLogic/Settings/NitroxPrefs.cs b/NitroxClient/GameLogic/Settings/NitroxPrefs.cs index eec921e2e8..6b8f4d882a 100644 --- a/NitroxClient/GameLogic/Settings/NitroxPrefs.cs +++ b/NitroxClient/GameLogic/Settings/NitroxPrefs.cs @@ -12,11 +12,11 @@ public class NitroxPrefs public static readonly NitroxPref SafeBuilding = new("Nitrox.safeBuilding", true); public static readonly NitroxPref SafeBuildingLog = new("Nitrox.safeBuildingLog", true); /// - /// In seconds. + /// In seconds. /// public static readonly NitroxPref LatencyUpdatePeriod = new("Nitrox.latencyUpdatePeriod", 10); /// - /// In milliseconds. + /// In milliseconds. /// public static readonly NitroxPref SafetyLatencyMargin = new("Nitrox.safetyLatencyMargin", 0.05f); } diff --git a/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs b/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs index 9393aabf5d..da702fbb16 100644 --- a/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs +++ b/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs @@ -52,8 +52,8 @@ private void MakeSettings() AddSetting("Nitrox_BuildingSettings", new Setting("Nitrox_SafeBuilding", NitroxPrefs.SafeBuilding, safe => NitroxPrefs.SafeBuilding.Value = safe)); AddSetting("Nitrox_BuildingSettings", new Setting("Nitrox_SafeBuildingLog", NitroxPrefs.SafeBuildingLog, safeLog => NitroxPrefs.SafeBuildingLog.Value = safeLog)); - AddSetting("Nitrox_BandwidthSettings", new Setting("Nitrox_LatencyUpdatePeriod", NitroxPrefs.LatencyUpdatePeriod, latencyUpdatePeriod => NitroxPrefs.LatencyUpdatePeriod.Value = (int)latencyUpdatePeriod, 1, 60, NitroxPrefs.LatencyUpdatePeriod.DefaultValue, 1, SliderLabelMode.Int, tooltip: "Nitrox_HigherForUnstable")); - AddSetting("Nitrox_BandwidthSettings", new Setting("Nitrox_SafetyLatencyMargin", NitroxPrefs.SafetyLatencyMargin, safetyLatencyMargin => NitroxPrefs.SafetyLatencyMargin.Value = safetyLatencyMargin, 0.01f, 0.5f, NitroxPrefs.SafetyLatencyMargin.DefaultValue, 0.01f, SliderLabelMode.Float, "0.00", "Nitrox_HigherForUnstable")); + AddSetting("NitroxBandwidthSettings", new Setting("NitroxSettingsLatencyUpdatePeriod", NitroxPrefs.LatencyUpdatePeriod, latencyUpdatePeriod => NitroxPrefs.LatencyUpdatePeriod.Value = (int)latencyUpdatePeriod, 1, 60, NitroxPrefs.LatencyUpdatePeriod.DefaultValue, 1, SliderLabelMode.Int, tooltip: "NitroxHigherForUnstable_Tooltip")); + AddSetting("NitroxBandwidthSettings", new Setting("NitroxSettingsSafetyLatencyMargin", NitroxPrefs.SafetyLatencyMargin, safetyLatencyMargin => NitroxPrefs.SafetyLatencyMargin.Value = safetyLatencyMargin, 0.01f, 0.5f, NitroxPrefs.SafetyLatencyMargin.DefaultValue, 0.01f, SliderLabelMode.Float, "0.00", "NitroxHigherForUnstable_Tooltip")); } /// Adds a setting to the list under a certain heading diff --git a/NitroxClient/GameLogic/SimulationOwnership.cs b/NitroxClient/GameLogic/SimulationOwnership.cs index 7b589764d7..b8caa5b182 100644 --- a/NitroxClient/GameLogic/SimulationOwnership.cs +++ b/NitroxClient/GameLogic/SimulationOwnership.cs @@ -145,7 +145,7 @@ private bool TreatVehicleEntity(NitroxId entityId, bool isLocalPlayerNewOwner) { if (!movementReplicator) { - gameObject.AddComponent(); + MovementReplicator.AddReplicatorToObject(gameObject); } MovementBroadcaster.UnregisterWatched(entityId); } diff --git a/NitroxClient/GameLogic/Vehicles.cs b/NitroxClient/GameLogic/Vehicles.cs index bcba85f5dc..5cdaf105b9 100644 --- a/NitroxClient/GameLogic/Vehicles.cs +++ b/NitroxClient/GameLogic/Vehicles.cs @@ -4,15 +4,11 @@ using NitroxClient.Communication.Abstract; using NitroxClient.GameLogic.Helper; using NitroxClient.MonoBehaviours; -using NitroxClient.MonoBehaviours.Cyclops; using NitroxClient.Unity.Helper; using NitroxModel.DataStructures; -using NitroxModel.DataStructures.GameLogic; using NitroxModel.DataStructures.GameLogic.Entities; -using NitroxModel.DataStructures.Util; using NitroxModel.Packets; using NitroxModel_Subnautica.DataStructures; -using NitroxModel_Subnautica.DataStructures.GameLogic; using UnityEngine; namespace NitroxClient.GameLogic; @@ -21,6 +17,8 @@ public class Vehicles { private readonly IPacketSender packetSender; private readonly IMultiplayerSession multiplayerSession; + private readonly PlayerManager playerManager; + private readonly Dictionary pilotingChairByTechType = []; public Vehicles(IPacketSender packetSender, IMultiplayerSession multiplayerSession) @@ -29,88 +27,6 @@ public Vehicles(IPacketSender packetSender, IMultiplayerSession multiplayerSessi this.multiplayerSession = multiplayerSession; } - public void UpdateVehiclePosition(VehicleMovementData vehicleModel, Optional player) - { - Optional opGameObject = NitroxEntity.GetObjectFrom(vehicleModel.Id); - Vehicle vehicle = null; - SubRoot subRoot = null; - - if (opGameObject.HasValue) - { - Rocket rocket = opGameObject.Value.GetComponent(); - vehicle = opGameObject.Value.GetComponent(); - subRoot = opGameObject.Value.GetComponent(); - - MultiplayerVehicleControl mvc = null; - - if (subRoot) - { - mvc = subRoot.gameObject.EnsureComponent(); - } - else if (vehicle) - { - if (vehicle.docked) - { - Log.Debug($"For vehicle {vehicleModel.Id} position update while docked, will not execute"); - return; - } - - switch (vehicle) - { - case SeaMoth seamoth: - { - mvc = seamoth.gameObject.EnsureComponent(); - break; - } - case Exosuit exosuit: - { - mvc = exosuit.gameObject.EnsureComponent(); - - if (vehicleModel is ExosuitMovementData exoSuitMovement) - { - mvc.SetArmPositions(exoSuitMovement.LeftAimTarget.ToUnity(), exoSuitMovement.RightAimTarget.ToUnity()); - } - else - { - Log.Error($"{nameof(Vehicles)}: Got exosuit vehicle but no ExosuitMovementData"); - } - - break; - } - } - } - else if (rocket) - { - rocket.transform.position = vehicleModel.Position.ToUnity(); - rocket.transform.rotation = vehicleModel.Rotation.ToUnity(); - } - - if (mvc) - { - mvc.SetPositionVelocityRotation( - vehicleModel.Position.ToUnity(), - vehicleModel.Velocity.ToUnity(), - vehicleModel.Rotation.ToUnity(), - vehicleModel.AngularVelocity.ToUnity() - ); - mvc.SetThrottle(vehicleModel.AppliedThrottle); - mvc.SetSteeringWheel(vehicleModel.SteeringWheelYaw, vehicleModel.SteeringWheelPitch); - } - } - - if (player.HasValue) - { - RemotePlayer playerInstance = player.Value; - playerInstance.SetVehicle(vehicle); - if (subRoot) - { - playerInstance.SetSubRoot(subRoot); - } - playerInstance.SetPilotingChair(FindPilotingChairWithCache(opGameObject.Value, vehicleModel.TechType.ToUnity())); - playerInstance.AnimationController.UpdatePlayerAnimations = false; - } - } - private PilotingChair FindPilotingChairWithCache(GameObject parent, TechType techType) { if (!parent) @@ -164,8 +80,8 @@ public void BroadcastVehicleDocking(VehicleDockingBay dockingBay, Vehicle vehicl packetSender.Send(packet); PacketSuppressor playerMovementSuppressor = PacketSuppressor.Suppress(); - PacketSuppressor vehicleMovementSuppressor = PacketSuppressor.Suppress(); - vehicle.StartCoroutine(AllowMovementPacketsAfterDockingAnimation(playerMovementSuppressor, vehicleMovementSuppressor)); + // TODO: Properly prevent the vehicle from sending position update as long as it's not free from the animation + vehicle.StartCoroutine(AllowMovementPacketsAfterDockingAnimation(playerMovementSuppressor)); } public void BroadcastVehicleUndocking(VehicleDockingBay dockingBay, Vehicle vehicle, bool undockingStart) @@ -180,11 +96,10 @@ public void BroadcastVehicleUndocking(VehicleDockingBay dockingBay, Vehicle vehi } PacketSuppressor movementSuppressor = PacketSuppressor.Suppress(); - PacketSuppressor vehicleMovementSuppressor = PacketSuppressor.Suppress(); + // TODO: Properly prevent the vehicle from sending position update as long as it's not free from the animation if (!undockingStart) { movementSuppressor.Dispose(); - vehicleMovementSuppressor.Dispose(); } VehicleUndocking packet = new VehicleUndocking(vehicleId, dockId, multiplayerSession.Reservation.PlayerId, undockingStart); @@ -204,50 +119,44 @@ and it will show them sitting outside the vehicle during the docking animation. place until after the player exits the vehicle. This causes the player body to strech to the current cyclops position. */ - public IEnumerator AllowMovementPacketsAfterDockingAnimation(PacketSuppressor playerMovementSuppressor, PacketSuppressor vehicleMovementSuppressor) + public IEnumerator AllowMovementPacketsAfterDockingAnimation(PacketSuppressor playerMovementSuppressor) { yield return Yielders.WaitFor3Seconds; playerMovementSuppressor.Dispose(); - vehicleMovementSuppressor.Dispose(); } - public IEnumerator UpdateVehiclePositionAfterSpawn(NitroxId id, TechType techType, GameObject gameObject, float cooldown) + public void BroadcastOnPilotModeChanged(GameObject gameObject, bool isPiloting) { - yield return new WaitForSeconds(cooldown); - - VehicleMovementData vehicleMovementData = new BasicVehicleMovementData(techType.ToDto(), id, gameObject.transform.position.ToDto(), gameObject.transform.rotation.ToDto()); - ushort playerId = ushort.MaxValue; - - packetSender.Send(new VehicleMovement(playerId, vehicleMovementData)); + if (gameObject.TryGetIdOrWarn(out NitroxId vehicleId)) + { + VehicleOnPilotModeChanged packet = new(vehicleId, multiplayerSession.Reservation.PlayerId, isPiloting); + packetSender.Send(packet); + } } - public void BroadcastOnPilotModeChanged(Vehicle vehicle, bool isPiloting) + public void SetOnPilotMode(GameObject gameObject, ushort playerId, bool isPiloting) { - if (!vehicle.TryGetIdOrWarn(out NitroxId vehicleId)) + if (playerManager.TryFind(playerId, out RemotePlayer remotePlayer)) { - return; + if (gameObject.TryGetComponent(out Vehicle vehicle)) + { + remotePlayer.SetVehicle(isPiloting ? vehicle : null); + } + else if (gameObject.GetComponent()) + { + PilotingChair pilotingChair = FindPilotingChairWithCache(gameObject, TechType.Cyclops); + remotePlayer.SetPilotingChair(pilotingChair); + } + // TODO: [FUTURE] For any mods adding new vehicle with a piloting chair, there should be something done right here } - - VehicleOnPilotModeChanged packet = new(vehicleId, multiplayerSession.Reservation.PlayerId, isPiloting); - packetSender.Send(packet); } public void SetOnPilotMode(NitroxId vehicleId, ushort playerId, bool isPiloting) { - Optional opVehicle = NitroxEntity.GetObjectFrom(vehicleId); - if (!opVehicle.HasValue) + if (NitroxEntity.TryGetObjectFrom(vehicleId, out GameObject vehicleObject)) { - return; + SetOnPilotMode(vehicleObject, playerId, isPiloting); } - - GameObject gameObject = opVehicle.Value; - Vehicle vehicle = gameObject.GetComponent(); - if (!vehicle) - { - return; - } - - vehicle.pilotId = isPiloting ? playerId.ToString() : string.Empty; } /// diff --git a/NitroxClient/MonoBehaviours/Cyclops/MultiplayerCyclops.cs b/NitroxClient/MonoBehaviours/Cyclops/MultiplayerCyclops.cs deleted file mode 100644 index 1c2509fc42..0000000000 --- a/NitroxClient/MonoBehaviours/Cyclops/MultiplayerCyclops.cs +++ /dev/null @@ -1,62 +0,0 @@ -using NitroxClient.GameLogic; - -namespace NitroxClient.MonoBehaviours.Cyclops; - -public class MultiplayerCyclops : MultiplayerVehicleControl -{ - private ISubTurnHandler[] subTurnHandlers; - private ISubThrottleHandler[] subThrottleHandlers; - private float previousAbsYaw; - - public RemotePlayer CurrentPlayer { get; set; } - - protected override void Awake() - { - SubControl subControl = GetComponent(); - WheelYawSetter = value => subControl.steeringWheelYaw = value; - WheelPitchSetter = value => subControl.steeringWheelPitch = value; - - subTurnHandlers = subControl.turnHandlers; - subThrottleHandlers = subControl.throttleHandlers; - base.Awake(); - } - - protected override void FixedUpdate() - { - base.FixedUpdate(); - if (CurrentPlayer != null) - { - // These values are set by the game code, but they do not seem to have any impact on animations. - CurrentPlayer.AnimationController.SetFloat("cyclops_yaw", SmoothYaw.SmoothValue); - CurrentPlayer.AnimationController.SetFloat("cyclops_pitch", SmoothPitch.SmoothValue); - } - } - - internal override void SetSteeringWheel(float yaw, float pitch) - { - base.SetSteeringWheel(yaw, pitch); - - ShipSide useShipSide = yaw > 0 ? ShipSide.Port : ShipSide.Starboard; - yaw = UnityEngine.Mathf.Abs(yaw); - if (yaw > .1f && yaw >= previousAbsYaw) - { - subTurnHandlers?.ForEach(turnHandler => turnHandler.OnSubTurn(useShipSide)); - } - - previousAbsYaw = yaw; - } - - internal override void SetThrottle(bool isOn) - { - if (isOn) - { - subThrottleHandlers?.ForEach(throttleHandlers => throttleHandlers.OnSubAppliedThrottle()); - } - } - - public override void Exit() - { - base.Exit(); - CurrentPlayer = null; - } -} diff --git a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs index 69378ef280..964df9da07 100644 --- a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs +++ b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs @@ -97,8 +97,62 @@ public static void UnregisterReplicator(MovementReplicator movementReplicator) } } - private record struct WatchedEntry(Transform transform) + private readonly record struct WatchedEntry { - public MovementData GetMovementData(NitroxId id) => new(id, transform.position.ToDto(), transform.rotation.ToDto()); + // TODO: eventually add a detector for multiple broadcast in a row where the watched entry has almost not moved + // in this case, only send the data once every 5 second or as soon as new movement is detected + private readonly Transform transform; + private readonly Vehicle vehicle; + private readonly SubControl subControl; + + public WatchedEntry(Transform transform) + { + this.transform = transform; + vehicle = transform.GetComponent(); + subControl = transform.GetComponent(); + } + + public MovementData GetMovementData(NitroxId id) + { + // Packets should be filled with more data if the vehicle is being driven by the local player + if (vehicle && Player.main.currentMountedVehicle == vehicle) + { + // Those two values are set between -1 and 1 so we can easily scale them up while still in range for sbyte + sbyte steeringWheelYaw = (sbyte)(vehicle.steeringWheelYaw * 70f); + sbyte steeringWheelPitch = (sbyte)(vehicle.steeringWheelPitch * 45f); + + bool throttleApplied = false; + + Vector3 input = AvatarInputHandler.main.IsEnabled() ? GameInput.GetMoveDirection() : Vector3.zero; + // See SeaMoth.UpdateSounds + if (vehicle is SeaMoth) + { + throttleApplied = input.magnitude > 0f; + } + // See Exosuit.Update + else if (vehicle is Exosuit) + { + throttleApplied = input.y > 0f; + } + + return new DrivenVehicleMovementData(id, transform.position.ToDto(), transform.rotation.ToDto(), steeringWheelYaw, steeringWheelPitch, throttleApplied); + } + + // TODO: find out if this is enough to ensure local player is piloting the said cyclops + if (subControl && Player.main.currentSub == subControl.sub && Player.main.mode == Player.Mode.Piloting) + { + // Cyclop steering wheel's yaw and pitch are between -90 and 90 so they're already in range for sbyte + sbyte steeringWheelYaw = (sbyte)subControl.steeringWheelYaw; + sbyte steeringWheelPitch = (sbyte)subControl.steeringWheelPitch; + + // See SubControl.Update + bool throttleApplied = subControl.throttle.magnitude > 0.0001f; + + return new DrivenVehicleMovementData(id, transform.position.ToDto(), transform.rotation.ToDto(), steeringWheelYaw, steeringWheelPitch, throttleApplied); + } + + // Normal case in which the vehicule isn't driven by the local player + return new SimpleMovementData(id, transform.position.ToDto(), transform.rotation.ToDto()); + } } } diff --git a/NitroxClient/MonoBehaviours/MovementReplicator.cs b/NitroxClient/MonoBehaviours/MovementReplicator.cs index b0b7d99268..c6442bd1f6 100644 --- a/NitroxClient/MonoBehaviours/MovementReplicator.cs +++ b/NitroxClient/MonoBehaviours/MovementReplicator.cs @@ -2,6 +2,7 @@ using NitroxClient.GameLogic; using NitroxClient.GameLogic.Settings; using NitroxClient.MonoBehaviours.Cyclops; +using NitroxClient.MonoBehaviours.Vehicles; using NitroxModel.DataStructures; using NitroxModel.Packets; using NitroxModel_Subnautica.DataStructures; @@ -9,7 +10,7 @@ namespace NitroxClient.MonoBehaviours; -public class MovementReplicator : MonoBehaviour +public abstract class MovementReplicator : MonoBehaviour { public const float INTERPOLATION_TIME = 4 * MovementBroadcaster.BROADCAST_PERIOD; public const float SNAPSHOT_EXPIRATION_TIME = 5f * INTERPOLATION_TIME; @@ -28,7 +29,7 @@ public class MovementReplicator : MonoBehaviour /// /// When encountering a latency bump, we must expect worse happening right after, so we add this margin to our new . /// After each periodical latency update (), we only want to lower the latency if it's way smaller than the current variable latency. - /// The safety threshold is defined + /// The safety threshold is defined by this value. /// private float SafetyLatencyMargin => NitroxPrefs.SafetyLatencyMargin.Value; @@ -165,21 +166,45 @@ public void Update() // Interpolation + MovementData prevData = firstNode.Value.Data; + MovementData nextData = nextNode.Value.Data; + float t = (currentTime - firstNode.Value.Time) / (nextNode.Value.Time - firstNode.Value.Time); - Vector3 position = Vector3.Lerp(firstNode.Value.Data.Position.ToUnity(), nextNode.Value.Data.Position.ToUnity(), t); + Vector3 position = Vector3.Lerp(prevData.Position.ToUnity(), nextData.Position.ToUnity(), t); - Quaternion rotation = Quaternion.Lerp(firstNode.Value.Data.Rotation.ToUnity(), nextNode.Value.Data.Rotation.ToUnity(), t); + Quaternion rotation = Quaternion.Lerp(prevData.Rotation.ToUnity(), nextData.Rotation.ToUnity(), t); transform.position = position; transform.rotation = rotation; - + + ApplyNewMovementData(nextData); + // TODO: fix remote players being able to go through the object (ex: cyclops) } - private record struct Snapshot(MovementData Data, float Time) + public abstract void ApplyNewMovementData(MovementData newMovementData); + + public record struct Snapshot(MovementData Data, float Time) { public bool IsOlderThan(float currentTime) => currentTime < Time; public bool IsExpired(float currentTime) => currentTime > Time + SNAPSHOT_EXPIRATION_TIME; } + + public static MovementReplicator AddReplicatorToObject(GameObject gameObject) + { + if (gameObject.GetComponent()) + { + return gameObject.AddComponent(); + } + if (gameObject.GetComponent()) + { + return gameObject.AddComponent(); + } + if (gameObject.GetComponent()) + { + return gameObject.AddComponent(); + } + return gameObject.AddComponent(); + } } diff --git a/NitroxClient/MonoBehaviours/MultiplayerExosuit.cs b/NitroxClient/MonoBehaviours/MultiplayerExosuit.cs deleted file mode 100644 index 4052a2c452..0000000000 --- a/NitroxClient/MonoBehaviours/MultiplayerExosuit.cs +++ /dev/null @@ -1,105 +0,0 @@ -using NitroxClient.GameLogic.FMOD; -using NitroxClient.Unity.Smoothing; -using NitroxModel.GameLogic.FMOD; -using UnityEngine; - -namespace NitroxClient.MonoBehaviours; - -public class MultiplayerExosuit : MultiplayerVehicleControl -{ - private float jetLoopingSoundDistance; - - private bool lastThrottle; - private float timeJetsChanged; - private Exosuit exosuit; - - protected override void Awake() - { - exosuit = GetComponent(); - WheelYawSetter = value => exosuit.steeringWheelYaw = value; - WheelPitchSetter = value => exosuit.steeringWheelPitch = value; - base.Awake(); - SmoothRotation = new ExosuitSmoothRotation(gameObject.transform.rotation); - - this.Resolve().TryGetSoundData(exosuit.loopingJetSound.asset.path, out SoundData jetSoundData); - jetLoopingSoundDistance = jetSoundData.Radius; - } - - internal override void Enter() - { - GetComponent().freezeRotation = false; - exosuit.SetIKEnabled(true); - exosuit.thrustIntensity = 0; - base.Enter(); - } - - public override void Exit() - { - GetComponent().freezeRotation = true; - exosuit.SetIKEnabled(false); - exosuit.loopingJetSound.Stop(); - exosuit.fxcontrol.Stop(0); - base.Exit(); - } - - internal override void SetThrottle(bool isOn) - { - if (timeJetsChanged + 0.3f <= Time.time && lastThrottle != isOn) - { - timeJetsChanged = Time.time; - lastThrottle = isOn; - if (isOn) - { - exosuit.loopingJetSound.Play(); - exosuit.fxcontrol.Play(0); - exosuit.areFXPlaying = true; - } - else - { - exosuit.loopingJetSound.Stop(); - exosuit.fxcontrol.Stop(0); - exosuit.areFXPlaying = false; - } - } - } - - private void Update() - { - if (exosuit.loopingJetSound.playing) - { - if (exosuit.loopingJetSound.evt.hasHandle()) - { - float volume = FMODSystem.CalculateVolume(transform.position, Player.main.transform.position, jetLoopingSoundDistance, 1f); - exosuit.loopingJetSound.evt.setVolume(volume); - } - } - else - { - if (exosuit.loopingJetSound.evtStop.hasHandle()) - { - float volume = FMODSystem.CalculateVolume(transform.position, Player.main.transform.position, jetLoopingSoundDistance, 1f); - exosuit.loopingJetSound.evtStop.setVolume(volume); - } - } - } - - internal override void SetArmPositions(Vector3 leftArmPosition, Vector3 rightArmPosition) - { - base.SetArmPositions(leftArmPosition, rightArmPosition); - Transform leftAim = exosuit.aimTargetLeft; - Transform rightAim = exosuit.aimTargetRight; - if (leftAim) - { - Vector3 leftAimPosition = leftAim.localPosition; - leftAimPosition = new Vector3(leftAimPosition.x, leftArmPosition.y, leftAimPosition.z); - leftAim.localPosition = leftAimPosition; - } - - if (rightAim) - { - Vector3 rightAimPosition = rightAim.localPosition; - rightAimPosition = new Vector3(rightAimPosition.x, rightArmPosition.y, rightAimPosition.z); - rightAim.localPosition = rightAimPosition; - } - } -} diff --git a/NitroxClient/MonoBehaviours/MultiplayerSeaMoth.cs b/NitroxClient/MonoBehaviours/MultiplayerSeaMoth.cs deleted file mode 100644 index cf492147c5..0000000000 --- a/NitroxClient/MonoBehaviours/MultiplayerSeaMoth.cs +++ /dev/null @@ -1,80 +0,0 @@ -using FMOD.Studio; -using NitroxModel.GameLogic.FMOD; -using UnityEngine; - -namespace NitroxClient.MonoBehaviours; - -public class MultiplayerSeaMoth : MultiplayerVehicleControl -{ - private bool lastThrottle; - private SeaMoth seamoth; - - private FMOD_CustomLoopingEmitter rpmSound; - private FMOD_CustomEmitter revSound; - private float radiusRpmSound; - private float radiusRevSound; - - protected override void Awake() - { - seamoth = GetComponent(); - WheelYawSetter = value => seamoth.steeringWheelYaw = value; - WheelPitchSetter = value => seamoth.steeringWheelPitch = value; - - SetupSound(); - base.Awake(); - } - - protected void Update() - { - float distanceToPlayer = Vector3.Distance(Player.main.transform.position, transform.position); - float volumeRpmSound = SoundHelper.CalculateVolume(distanceToPlayer, radiusRpmSound, 1f); - float volumeRevSound = SoundHelper.CalculateVolume(distanceToPlayer, radiusRevSound, 1f); - rpmSound.GetEventInstance().setVolume(volumeRpmSound); - revSound.GetEventInstance().setVolume(volumeRevSound); - - if (lastThrottle) - { - seamoth.engineSound.AccelerateInput(); - } - } - - public override void Exit() - { - seamoth.bubbles.Stop(); - base.Exit(); - } - - internal override void SetThrottle(bool isOn) - { - if (isOn != lastThrottle) - { - if (isOn) - { - seamoth.bubbles.Play(); - } - else - { - seamoth.bubbles.Stop(); - } - - lastThrottle = isOn; - } - } - - private void SetupSound() - { - rpmSound = seamoth.engineSound.engineRpmSFX; - revSound = seamoth.engineSound.engineRevUp; - - rpmSound.followParent = true; - revSound.followParent = true; - - this.Resolve().IsWhitelisted(rpmSound.asset.path, out radiusRpmSound); - this.Resolve().IsWhitelisted(revSound.asset.path, out radiusRevSound); - - rpmSound.GetEventInstance().setProperty(EVENT_PROPERTY.MINIMUM_DISTANCE, 1f); - revSound.GetEventInstance().setProperty(EVENT_PROPERTY.MINIMUM_DISTANCE, 1f); - rpmSound.GetEventInstance().setProperty(EVENT_PROPERTY.MAXIMUM_DISTANCE, radiusRpmSound); - revSound.GetEventInstance().setProperty(EVENT_PROPERTY.MAXIMUM_DISTANCE, radiusRevSound); - } -} diff --git a/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs b/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs index 35cc31af92..f8c2420769 100644 --- a/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs +++ b/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs @@ -1,11 +1,7 @@ using NitroxClient.Communication.Abstract; using NitroxClient.GameLogic; -using NitroxModel_Subnautica.DataStructures; -using NitroxModel_Subnautica.Helper; -using NitroxModel.DataStructures; -using NitroxModel.DataStructures.GameLogic; -using NitroxModel.DataStructures.Util; using NitroxModel.Packets; +using NitroxModel_Subnautica.DataStructures; using UnityEngine; namespace NitroxClient.MonoBehaviours; @@ -34,6 +30,11 @@ public void Update() return; } + if (Player.main.isPiloting) + { + return; + } + Vector3 currentPosition = Player.main.transform.position; Vector3 playerVelocity = Player.main.playerController.velocity; @@ -41,7 +42,6 @@ public void Update() Quaternion bodyRotation = MainCameraControl.main.viewModel.transform.rotation; Quaternion aimingRotation = Player.main.camRoot.GetAimingTransform().rotation; - Optional vehicle = GetVehicleMovement(); SubRoot subRoot = Player.main.GetCurrentSub(); // If in a subroot the position will be relative to the subroot @@ -55,15 +55,9 @@ public void Update() bodyRotation = undoVehicleAngle * bodyRotation; aimingRotation = undoVehicleAngle * aimingRotation; currentPosition = subRootTransform.TransformPoint(currentPosition); - - if (Player.main.isPiloting && subRoot.isCyclops) - { - // In case you're driving the cyclops, the currentPosition is the real position of the player, so we need to send it to the server - vehicle.Value.DriverPosition = currentPosition.ToDto(); - } } - localPlayer.BroadcastLocation(currentPosition, playerVelocity, bodyRotation, aimingRotation, vehicle); + localPlayer.BroadcastLocation(currentPosition, playerVelocity, bodyRotation, aimingRotation); } private bool BroadcastPlayerInCyclopsMovement() @@ -77,114 +71,4 @@ private bool BroadcastPlayerInCyclopsMovement() } return false; } - - private Optional GetVehicleMovement() - { - Vehicle vehicle = Player.main.GetVehicle(); - SubRoot sub = Player.main.GetCurrentSub(); - - NitroxId id; - Vector3 position; - Quaternion rotation; - Vector3 velocity; - Vector3 angularVelocity; - TechType techType; - bool appliedThrottle = false; - Vector3 leftArmPosition = new(0, 0, 0); - Vector3 rightArmPosition = new(0, 0, 0); - float steeringWheelYaw, steeringWheelPitch; - - if (vehicle) - { - //TODO: We should cache this (and other MBs) somehow because the method is called very frequently - if (!vehicle.TryGetIdOrWarn(out id)) - { - return Optional.Empty; - } - - Transform vehicleTransform = vehicle.transform; - position = vehicleTransform.position; - rotation = vehicleTransform.rotation; - techType = CraftData.GetTechType(vehicle.gameObject); - - Rigidbody rigidbody = vehicle.GetComponent(); - - velocity = rigidbody.velocity; - angularVelocity = rigidbody.angularVelocity; - - // Required because vehicle is either a SeaMoth or an Exosuit, both types which can't see the fields either. - steeringWheelYaw = vehicle.steeringWheelYaw; - steeringWheelPitch = vehicle.steeringWheelPitch; - - // Vehicles (or the SeaMoth at least) do not have special throttle animations. Instead, these animations are always playing because the player can't even see them (unlike the cyclops which has cameras). - // So, we need to hack in and try to figure out when thrust needs to be applied. - if (AvatarInputHandler.main.IsEnabled()) - { - if (techType == TechType.Seamoth) - { - bool flag = position.y < Ocean.GetOceanLevel() && position.y < vehicle.worldForces.waterDepth && !vehicle.precursorOutOfWater; - appliedThrottle = flag && GameInput.GetMoveDirection().sqrMagnitude > .1f; - } - else if (techType == TechType.Exosuit) - { - Exosuit exosuit = vehicle as Exosuit; - if (exosuit) - { - appliedThrottle = exosuit._jetsActive && exosuit.thrustPower > 0f; - - Transform leftAim = exosuit.aimTargetLeft; - Transform rightAim = exosuit.aimTargetRight; - - Vector3 eulerAngles = exosuit.transform.eulerAngles; - eulerAngles.x = MainCamera.camera.transform.eulerAngles.x; - Quaternion quaternion = Quaternion.Euler(eulerAngles.x, eulerAngles.y, eulerAngles.z); - - Vector3 mainCameraPosition = MainCamera.camera.transform.position; - leftArmPosition = leftAim.transform.position = mainCameraPosition + quaternion * Vector3.forward * 100f; - rightArmPosition = rightAim.transform.position = mainCameraPosition + quaternion * Vector3.forward * 100f; - } - } - } - } - else if (sub && Player.main.isPiloting) - { - if (!sub.TryGetIdOrWarn(out id)) - { - return Optional.Empty; - } - - Transform subTransform = sub.transform; - position = subTransform.position; - rotation = subTransform.rotation; - Rigidbody rigidbody = sub.GetComponent(); - velocity = rigidbody.velocity; - angularVelocity = rigidbody.angularVelocity; - techType = TechType.Cyclops; - - SubControl subControl = sub.GetComponent(); - steeringWheelYaw = subControl.steeringWheelYaw; - steeringWheelPitch = subControl.steeringWheelPitch; - appliedThrottle = subControl.appliedThrottle && subControl.canAccel; - } - else - { - return Optional.Empty; - } - - return Optional.Of( - VehicleMovementFactory.GetVehicleMovementData( - techType, - id, - position, - rotation, - velocity, - angularVelocity, - steeringWheelYaw, - steeringWheelPitch, - appliedThrottle, - leftArmPosition, - rightArmPosition - ) - ); - } } diff --git a/NitroxClient/MonoBehaviours/Vehicles/CyclopsMovementReplicator.cs b/NitroxClient/MonoBehaviours/Vehicles/CyclopsMovementReplicator.cs new file mode 100644 index 0000000000..66c2502fa1 --- /dev/null +++ b/NitroxClient/MonoBehaviours/Vehicles/CyclopsMovementReplicator.cs @@ -0,0 +1,75 @@ +using NitroxClient.GameLogic; +using NitroxModel.Packets; +using UnityEngine; + +namespace NitroxClient.MonoBehaviours.Vehicles; + +public class CyclopsMovementReplicator : VehicleMovementReplicator +{ + private SubControl subControl; + + private RemotePlayer drivingPlayer; + + public void Awake() + { + subControl = GetComponent(); + } + + public override void ApplyNewMovementData(MovementData newMovementData) + { + if (newMovementData is not DrivenVehicleMovementData vehicleMovementData) + { + return; + } + + float steeringWheelYaw = vehicleMovementData.SteeringWheelYaw; + float steeringWheelPitch = vehicleMovementData.SteeringWheelPitch; + + // See SubControl.UpdateAnimation + subControl.steeringWheelYaw = steeringWheelYaw; + subControl.steeringWheelPitch = steeringWheelPitch; + if (subControl.mainAnimator) + { + subControl.mainAnimator.SetFloat("view_yaw", subControl.steeringWheelYaw); + subControl.mainAnimator.SetFloat("view_pitch", subControl.steeringWheelPitch); + + drivingPlayer.AnimationController.SetFloat("cyclops_yaw", subControl.steeringWheelYaw); + drivingPlayer.AnimationController.SetFloat("cyclops_pitch", subControl.steeringWheelPitch); + } + + if (Mathf.Abs(steeringWheelYaw) > 0.1f) + { + ShipSide shipSide = steeringWheelYaw > 0 ? ShipSide.Port : ShipSide.Starboard; + for (int i = 0; i < subControl.turnHandlers.Length; i++) + { + subControl.turnHandlers[i].OnSubTurn(shipSide); + } + } + + if (subControl.canAccel && vehicleMovementData.ThrottleApplied) + { + // See SubControl.Update + var topClamp = subControl.useThrottleIndex switch + { + 1 => 0.66f, + 2 => 1f, + _ => 0.33f, + }; + subControl.engineRPMManager.AccelerateInput(topClamp); + for (int i = 0; i < subControl.throttleHandlers.Length; i++) + { + subControl.throttleHandlers[i].OnSubAppliedThrottle(); + } + } + } + + public override void Enter(RemotePlayer drivingPlayer) + { + this.drivingPlayer = drivingPlayer; + } + + public override void Exit() + { + drivingPlayer = null; + } +} diff --git a/NitroxClient/MonoBehaviours/Vehicles/ExosuitMovementReplicator.cs b/NitroxClient/MonoBehaviours/Vehicles/ExosuitMovementReplicator.cs new file mode 100644 index 0000000000..81353ef302 --- /dev/null +++ b/NitroxClient/MonoBehaviours/Vehicles/ExosuitMovementReplicator.cs @@ -0,0 +1,128 @@ +using NitroxClient.GameLogic; +using NitroxClient.GameLogic.FMOD; +using NitroxModel.GameLogic.FMOD; +using NitroxModel.Packets; +using UnityEngine; + +namespace NitroxClient.MonoBehaviours.Vehicles; + +public class ExosuitMovementReplicator : VehicleMovementReplicator +{ + private Exosuit exosuit; + + public Vector3 velocity; + public Vector3 angularVelocity; + private float jetLoopingSoundDistance; + private float timeJetsChanged; + private bool lastThrottle; + + public void Awake() + { + exosuit = GetComponent(); + SetupSound(); + } + + public new void Update() + { + Vector3 positionBefore = transform.position; + Vector3 rotationBefore = transform.rotation.eulerAngles; + base.Update(); + Vector3 positionAfter = transform.position; + Vector3 rotationAfter = transform.rotation.eulerAngles; + + velocity = (positionAfter - positionBefore) / Time.deltaTime; + angularVelocity = (rotationAfter - rotationBefore) / Time.deltaTime; + + if (exosuit.loopingJetSound.playing) + { + if (exosuit.loopingJetSound.evt.hasHandle()) + { + float volume = FMODSystem.CalculateVolume(transform.position, Player.main.transform.position, jetLoopingSoundDistance, 1f); + exosuit.loopingJetSound.evt.setVolume(volume); + } + } + else + { + if (exosuit.loopingJetSound.evtStop.hasHandle()) + { + float volume = FMODSystem.CalculateVolume(transform.position, Player.main.transform.position, jetLoopingSoundDistance, 1f); + exosuit.loopingJetSound.evtStop.setVolume(volume); + } + } + + // TODO: find out if this is necessary + exosuit.ambienceSound.SetParameterValue(exosuit.fmodIndexSpeed, velocity.magnitude); + exosuit.ambienceSound.SetParameterValue(exosuit.fmodIndexRotate, angularVelocity.magnitude); + } + + public override void ApplyNewMovementData(MovementData newMovementData) + { + if (newMovementData is not DrivenVehicleMovementData vehicleMovementData) + { + return; + } + float steeringWheelYaw = vehicleMovementData.SteeringWheelYaw; + float steeringWheelPitch = vehicleMovementData.SteeringWheelPitch; + + // See Vehicle.Update (reverse operation for vehicle.steeringWheel... = ...) + exosuit.steeringWheelYaw = steeringWheelPitch / 70f; + exosuit.steeringWheelPitch = steeringWheelPitch / 45f; + + if (exosuit.mainAnimator) + { + exosuit.mainAnimator.SetFloat("view_yaw", steeringWheelYaw); + exosuit.mainAnimator.SetFloat("view_pitch", steeringWheelPitch); + } + + // See Exosuit.Update + if (timeJetsChanged + 0.3f <= Time.time && lastThrottle != vehicleMovementData.ThrottleApplied) + { + timeJetsChanged = Time.time; + lastThrottle = vehicleMovementData.ThrottleApplied; + if (vehicleMovementData.ThrottleApplied) + { + exosuit.loopingJetSound.Play(); + exosuit.fxcontrol.Play(0); + exosuit.areFXPlaying = true; + } + else + { + exosuit.loopingJetSound.Stop(); + exosuit.fxcontrol.Stop(0); + exosuit.areFXPlaying = false; + } + } + } + + private void SetupSound() + { + this.Resolve().TryGetSoundData(exosuit.loopingJetSound.asset.path, out SoundData jetSoundData); + jetLoopingSoundDistance = jetSoundData.Radius; + + if (FMODUWE.IsInvalidParameterId(exosuit.fmodIndexSpeed)) + { + exosuit.fmodIndexSpeed = exosuit.ambienceSound.GetParameterIndex("speed"); + } + if (FMODUWE.IsInvalidParameterId(exosuit.fmodIndexRotate)) + { + exosuit.fmodIndexRotate = exosuit.ambienceSound.GetParameterIndex("rotate"); + } + } + + public override void Enter(RemotePlayer remotePlayer) + { + exosuit.mainAnimator.SetBool("player_in", true); + // TODO: see if required: GetComponent().freezeRotation = false; + exosuit.SetIKEnabled(true); + exosuit.thrustIntensity = 0; + } + + public override void Exit() + { + exosuit.mainAnimator.SetBool("player_in", false); + // TODO: see if required: GetComponent().freezeRotation = true; + exosuit.SetIKEnabled(false); + exosuit.loopingJetSound.Stop(); + exosuit.fxcontrol.Stop(0); + } +} diff --git a/NitroxClient/MonoBehaviours/Vehicles/SeaMothMovementReplicator.cs b/NitroxClient/MonoBehaviours/Vehicles/SeaMothMovementReplicator.cs new file mode 100644 index 0000000000..9daf8d0829 --- /dev/null +++ b/NitroxClient/MonoBehaviours/Vehicles/SeaMothMovementReplicator.cs @@ -0,0 +1,115 @@ +using FMOD.Studio; +using NitroxClient.GameLogic; +using NitroxModel.GameLogic.FMOD; +using NitroxModel.Packets; +using UnityEngine; + +namespace NitroxClient.MonoBehaviours.Vehicles; + +public class SeamothMovementReplicator : VehicleMovementReplicator +{ + private SeaMoth seaMoth; + public Vector3 velocity; + + private FMOD_CustomLoopingEmitter rpmSound; + private FMOD_CustomEmitter revSound; + private float radiusRpmSound; + private float radiusRevSound; + private bool throttleApplied; + + public void Awake() + { + seaMoth = GetComponent(); + SetupSound(); + } + + public new void Update() + { + Vector3 positionBefore = transform.position; + base.Update(); + Vector3 positionAfter = transform.position; + velocity = (positionAfter - positionBefore) / Time.deltaTime; + + // TODO: find out if this is necessary + if (seaMoth.ambienceSound && seaMoth.ambienceSound.GetIsPlaying()) + { + seaMoth.ambienceSound.SetParameterValue(seaMoth.fmodIndexSpeed, velocity.magnitude); + } + + if (throttleApplied) + { + seaMoth.engineSound.AccelerateInput(1); + } + } + + public override void ApplyNewMovementData(MovementData newMovementData) + { + if (newMovementData is not DrivenVehicleMovementData vehicleMovementData) + { + return; + } + float steeringWheelYaw = vehicleMovementData.SteeringWheelYaw; + float steeringWheelPitch = vehicleMovementData.SteeringWheelPitch; + + // See Vehicle.Update (reverse operation for vehicle.steeringWheel... = ...) + seaMoth.steeringWheelYaw = steeringWheelPitch / 70f; + seaMoth.steeringWheelPitch = steeringWheelPitch / 45f; + + if (seaMoth.mainAnimator) + { + seaMoth.mainAnimator.SetFloat("view_yaw", steeringWheelYaw); + seaMoth.mainAnimator.SetFloat("view_pitch", steeringWheelPitch); + } + + // Adjusting volume for the engine Sound + float distanceToPlayer = Vector3.Distance(Player.main.transform.position, transform.position); + float volumeRpmSound = SoundHelper.CalculateVolume(distanceToPlayer, radiusRpmSound, 1f); + float volumeRevSound = SoundHelper.CalculateVolume(distanceToPlayer, radiusRevSound, 1f); + rpmSound.GetEventInstance().setVolume(volumeRpmSound); + revSound.GetEventInstance().setVolume(volumeRevSound); + + throttleApplied = vehicleMovementData.ThrottleApplied; + } + + private void SetupSound() + { + rpmSound = seaMoth.engineSound.engineRpmSFX; + revSound = seaMoth.engineSound.engineRevUp; + + rpmSound.followParent = true; + revSound.followParent = true; + + this.Resolve().IsWhitelisted(rpmSound.asset.path, out radiusRpmSound); + this.Resolve().IsWhitelisted(revSound.asset.path, out radiusRevSound); + + rpmSound.GetEventInstance().setProperty(EVENT_PROPERTY.MINIMUM_DISTANCE, 1f); + revSound.GetEventInstance().setProperty(EVENT_PROPERTY.MINIMUM_DISTANCE, 1f); + rpmSound.GetEventInstance().setProperty(EVENT_PROPERTY.MAXIMUM_DISTANCE, radiusRpmSound); + revSound.GetEventInstance().setProperty(EVENT_PROPERTY.MAXIMUM_DISTANCE, radiusRevSound); + + if (FMODUWE.IsInvalidParameterId(seaMoth.fmodIndexSpeed)) + { + seaMoth.fmodIndexSpeed = seaMoth.ambienceSound.GetParameterIndex("speed"); + } + } + + public override void Enter(RemotePlayer remotePlayer) + { + seaMoth.mainAnimator.SetBool("player_in", true); + seaMoth.bubbles.Play(); + if (seaMoth.enterSeamoth) + { + seaMoth.enterSeamoth.Play(); // TODO: find out if this is required + } + seaMoth.ambienceSound.PlayUI(); // TODO: find out if this is required + } + + public override void Exit() + { + seaMoth.mainAnimator.SetBool("player_in", false); + seaMoth.bubbles.Stop(); + seaMoth.ambienceSound.Stop(true); // TODO: find out if this is required + + throttleApplied = false; + } +} diff --git a/NitroxClient/MonoBehaviours/Vehicles/VehicleMovementReplicator.cs b/NitroxClient/MonoBehaviours/Vehicles/VehicleMovementReplicator.cs new file mode 100644 index 0000000000..56671703a7 --- /dev/null +++ b/NitroxClient/MonoBehaviours/Vehicles/VehicleMovementReplicator.cs @@ -0,0 +1,9 @@ +using NitroxClient.GameLogic; + +namespace NitroxClient.MonoBehaviours.Vehicles; + +public abstract class VehicleMovementReplicator : MovementReplicator +{ + public abstract void Enter(RemotePlayer remotePlayer); + public abstract void Exit(); +} diff --git a/NitroxModel-Subnautica/DataStructures/GameLogic/ExoSuitMovementData.cs b/NitroxModel-Subnautica/DataStructures/GameLogic/ExoSuitMovementData.cs deleted file mode 100644 index 30c2439329..0000000000 --- a/NitroxModel-Subnautica/DataStructures/GameLogic/ExoSuitMovementData.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Runtime.Serialization; -using BinaryPack.Attributes; -using NitroxModel.DataStructures; -using NitroxModel.DataStructures.GameLogic; -using NitroxModel.DataStructures.Unity; - -namespace NitroxModel_Subnautica.DataStructures.GameLogic -{ - [Serializable] - [DataContract] - public class ExosuitMovementData : VehicleMovementData - { - [DataMember(Order = 1)] - public NitroxVector3 LeftAimTarget { get; } - - [DataMember(Order = 2)] - public NitroxVector3 RightAimTarget { get; } - - [IgnoreConstructor] - protected ExosuitMovementData() - { - // Constructor for serialization. Has to be "protected" for json serialization. - } - - public ExosuitMovementData(NitroxTechType techType, NitroxId id, NitroxVector3 position, NitroxQuaternion rotation, NitroxVector3 velocity, NitroxVector3 angularVelocity, float steeringWheelYaw, float steeringWheelPitch, bool appliedThrottle, NitroxVector3 leftAimTarget, NitroxVector3 rightAimTarget, NitroxVector3? driverPosition = null) - : base(techType, id, position, rotation, velocity, angularVelocity, steeringWheelYaw, steeringWheelPitch, appliedThrottle, driverPosition) - { - LeftAimTarget = leftAimTarget; - RightAimTarget = rightAimTarget; - } - - public override string ToString() - { - return $"[ExosuitMovementData - {base.ToString()}, LeftAimTarget: {LeftAimTarget}, RightAimTarget: {RightAimTarget}]"; - } - } -} diff --git a/NitroxModel-Subnautica/Helper/VehicleMovementFactory.cs b/NitroxModel-Subnautica/Helper/VehicleMovementFactory.cs deleted file mode 100644 index e075a9bc2f..0000000000 --- a/NitroxModel-Subnautica/Helper/VehicleMovementFactory.cs +++ /dev/null @@ -1,22 +0,0 @@ -using NitroxModel.DataStructures; -using NitroxModel.DataStructures.GameLogic; -using NitroxModel_Subnautica.DataStructures; -using NitroxModel_Subnautica.DataStructures.GameLogic; -using UnityEngine; - -namespace NitroxModel_Subnautica.Helper -{ - public static class VehicleMovementFactory - { - public static VehicleMovementData GetVehicleMovementData(TechType techType, NitroxId id, Vector3 position, Quaternion rotation, Vector3 velocity, Vector3 angularVelocity, float steeringWheelYaw, float steeringWheelPitch, bool appliedThrottle, Vector3 leftAimTarget, Vector3 rightAimTarget) - { - switch (techType) - { - case TechType.Exosuit: - return new ExosuitMovementData(techType.ToDto(), id, position.ToDto(), rotation.ToDto(), velocity.ToDto(), angularVelocity.ToDto(), steeringWheelYaw, steeringWheelPitch, appliedThrottle, leftAimTarget.ToDto(), rightAimTarget.ToDto()); - default: - return new BasicVehicleMovementData(techType.ToDto(), id, position.ToDto(), rotation.ToDto(), velocity.ToDto(), angularVelocity.ToDto(), steeringWheelYaw, steeringWheelPitch, appliedThrottle); - } - } - } -} diff --git a/NitroxModel/DataStructures/GameLogic/BasicVehicleMovementData.cs b/NitroxModel/DataStructures/GameLogic/BasicVehicleMovementData.cs deleted file mode 100644 index aa073a2f40..0000000000 --- a/NitroxModel/DataStructures/GameLogic/BasicVehicleMovementData.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Runtime.Serialization; -using NitroxModel.DataStructures.Unity; - -namespace NitroxModel.DataStructures.GameLogic; - -[Serializable] -[DataContract] -public class BasicVehicleMovementData : VehicleMovementData -{ - public BasicVehicleMovementData(NitroxTechType techType, NitroxId id, NitroxVector3 position, NitroxQuaternion rotation) : - base(techType, id, position, rotation) { } - - public BasicVehicleMovementData(NitroxTechType techType, NitroxId id, NitroxVector3 position, NitroxQuaternion rotation, NitroxVector3 velocity, NitroxVector3 angularVelocity, float steeringWheelYaw, float steeringWheelPitch, bool appliedThrottle, NitroxVector3? driverPosition = null) : - base(techType, id, position, rotation, velocity, angularVelocity, steeringWheelYaw, steeringWheelPitch, appliedThrottle, driverPosition) { } -} diff --git a/NitroxModel/DataStructures/GameLogic/VehicleMovementData.cs b/NitroxModel/DataStructures/GameLogic/VehicleMovementData.cs deleted file mode 100644 index 959aeb9afe..0000000000 --- a/NitroxModel/DataStructures/GameLogic/VehicleMovementData.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Runtime.Serialization; -using BinaryPack.Attributes; -using NitroxModel.DataStructures.Unity; - -namespace NitroxModel.DataStructures.GameLogic -{ - [Serializable] - [DataContract] - public abstract class VehicleMovementData - { - [DataMember(Order = 1)] - public NitroxTechType TechType { get; } - - [DataMember(Order = 2)] - public NitroxId Id { get; set; } - - [DataMember(Order = 3)] - public NitroxVector3 Position { get; } - - [DataMember(Order = 4)] - public NitroxQuaternion Rotation { get; } - - [DataMember(Order = 5)] - public NitroxVector3 Velocity { get; } - - [DataMember(Order = 6)] - public NitroxVector3 AngularVelocity { get; } - - [DataMember(Order = 7)] - public float SteeringWheelYaw { get; } - - [DataMember(Order = 8)] - public float SteeringWheelPitch { get; } - - [DataMember(Order = 9)] - public bool AppliedThrottle { get; } - - [DataMember(Order = 10)] - public NitroxVector3? DriverPosition { get; set; } - - [IgnoreConstructor] - protected VehicleMovementData() - { - // Constructor for serialization. Has to be "protected" for json serialization. - } - - public VehicleMovementData(NitroxTechType techType, NitroxId id, NitroxVector3 position, NitroxQuaternion rotation, NitroxVector3 velocity, NitroxVector3 angularVelocity, float steeringWheelYaw, float steeringWheelPitch, bool appliedThrottle, NitroxVector3? driverPosition = null) - { - TechType = techType; - Id = id; - Position = position; - Rotation = rotation; - Velocity = velocity; - AngularVelocity = angularVelocity; - SteeringWheelYaw = steeringWheelYaw; - SteeringWheelPitch = steeringWheelPitch; - AppliedThrottle = appliedThrottle; - DriverPosition = driverPosition; - } - - public VehicleMovementData(NitroxTechType techType, NitroxId id, NitroxVector3 position, NitroxQuaternion rotation) - { - TechType = techType; - Id = id; - Position = position; - Rotation = rotation; - Velocity = NitroxVector3.Zero; - AngularVelocity = NitroxVector3.Zero; - SteeringWheelYaw = 0f; - SteeringWheelPitch = 0f; - AppliedThrottle = false; - } - - public override string ToString() - { - return $"[VehicleMovementData - TechType: {TechType}, Id: {Id}, Position: {Position}, Rotation: {Rotation}, Velocity: {Velocity}, AngularVelocity: {AngularVelocity}, SteeringWheelYaw: {SteeringWheelYaw}, SteeringWheelPitch: {SteeringWheelPitch}, AppliedThrottle: {AppliedThrottle}]"; - } - } -} diff --git a/NitroxModel/Packets/VehicleMovement.cs b/NitroxModel/Packets/VehicleMovement.cs deleted file mode 100644 index 5d880b405a..0000000000 --- a/NitroxModel/Packets/VehicleMovement.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using BinaryPack.Attributes; -using NitroxModel.DataStructures.GameLogic; -using NitroxModel.DataStructures.Unity; -using NitroxModel.Networking; - -namespace NitroxModel.Packets -{ - [Serializable] - public class VehicleMovement : Movement - { - public override ushort PlayerId { get; } - public VehicleMovementData VehicleMovementData { get; } - - [IgnoredMember] - public override NitroxVector3 Position => VehicleMovementData.Position; - [IgnoredMember] - public override NitroxVector3 Velocity => VehicleMovementData.Velocity; - [IgnoredMember] - public override NitroxQuaternion BodyRotation => VehicleMovementData.Rotation; - [IgnoredMember] - public override NitroxQuaternion AimingRotation => VehicleMovementData.Rotation; - - public VehicleMovement(ushort playerId, VehicleMovementData vehicleMovementData) - { - PlayerId = playerId; - VehicleMovementData = vehicleMovementData; - DeliveryMethod = NitroxDeliveryMethod.DeliveryMethod.UNRELIABLE_SEQUENCED; - UdpChannel = UdpChannelId.MOVEMENTS; - } - } -} diff --git a/NitroxModel/Packets/VehicleMovements.cs b/NitroxModel/Packets/VehicleMovements.cs index 6714aa9361..59823b6183 100644 --- a/NitroxModel/Packets/VehicleMovements.cs +++ b/NitroxModel/Packets/VehicleMovements.cs @@ -21,4 +21,8 @@ public VehicleMovements(List data, double realTime) } } -public record struct MovementData(NitroxId Id, NitroxVector3 Position, NitroxQuaternion Rotation); +public abstract record class MovementData(NitroxId Id, NitroxVector3 Position, NitroxQuaternion Rotation) { } + +public record class SimpleMovementData(NitroxId Id, NitroxVector3 Position, NitroxQuaternion Rotation) : MovementData(Id, Position, Rotation) { } + +public record class DrivenVehicleMovementData(NitroxId Id, NitroxVector3 Position, NitroxQuaternion Rotation, sbyte SteeringWheelYaw, sbyte SteeringWheelPitch, bool ThrottleApplied) : MovementData(Id, Position, Rotation) { } diff --git a/NitroxModel/Packets/VehicleOnPilotModeChanged.cs b/NitroxModel/Packets/VehicleOnPilotModeChanged.cs index 1c9cc8e2cd..07ba472d48 100644 --- a/NitroxModel/Packets/VehicleOnPilotModeChanged.cs +++ b/NitroxModel/Packets/VehicleOnPilotModeChanged.cs @@ -1,20 +1,19 @@ -using System; +using System; using NitroxModel.DataStructures; -namespace NitroxModel.Packets +namespace NitroxModel.Packets; + +[Serializable] +public class VehicleOnPilotModeChanged : Packet { - [Serializable] - public class VehicleOnPilotModeChanged : Packet - { - public NitroxId VehicleId { get; } - public ushort PlayerId { get; } - public bool IsPiloting { get; } + public NitroxId VehicleId { get; } + public ushort PlayerId { get; } + public bool IsPiloting { get; } - public VehicleOnPilotModeChanged(NitroxId vehicleId, ushort playerId, bool isPiloting) - { - VehicleId = vehicleId; - PlayerId = playerId; - IsPiloting = isPiloting; - } + public VehicleOnPilotModeChanged(NitroxId vehicleId, ushort playerId, bool isPiloting) + { + VehicleId = vehicleId; + PlayerId = playerId; + IsPiloting = isPiloting; } } diff --git a/NitroxPatcher/Patches/Dynamic/CyclopsSonarButton_Update_Patch.cs b/NitroxPatcher/Patches/Dynamic/CyclopsSonarButton_Update_Patch.cs index 3f0ed47208..b6c49b8023 100644 --- a/NitroxPatcher/Patches/Dynamic/CyclopsSonarButton_Update_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/CyclopsSonarButton_Update_Patch.cs @@ -4,6 +4,7 @@ using System.Reflection.Emit; using HarmonyLib; using NitroxClient.MonoBehaviours.Cyclops; +using NitroxClient.MonoBehaviours.Vehicles; using NitroxModel.Helper; namespace NitroxPatcher.Patches.Dynamic; @@ -55,12 +56,9 @@ public static IEnumerable Transpiler(MethodBase methodBase, IEn } } + /// true (sonar should be turned off) if local player is simulating the cyclops (there's no replicator in this case) public static bool ShouldTurnoff(CyclopsSonarButton cyclopsSonarButton) { - if (cyclopsSonarButton.subRoot.TryGetComponent(out MultiplayerCyclops multiplayerCyclops)) - { - return !multiplayerCyclops.enabled; - } - return true; + return !cyclopsSonarButton.subRoot.GetComponent(); } } diff --git a/NitroxPatcher/Patches/Dynamic/PilotingChair_OnHandClick_Patch.cs b/NitroxPatcher/Patches/Dynamic/PilotingChair_OnHandClick_Patch.cs index 75dc908286..47f080d294 100644 --- a/NitroxPatcher/Patches/Dynamic/PilotingChair_OnHandClick_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/PilotingChair_OnHandClick_Patch.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using NitroxClient.GameLogic; using NitroxClient.GameLogic.Simulation; using NitroxClient.MonoBehaviours.Gui.HUD; @@ -50,6 +50,10 @@ private static void ReceivedSimulationLockResponse(NitroxId id, bool lockAquired { skipPrefix = true; pilotingChair.OnHandClick(context.GuiHand); + if (pilotingChair.subRoot) + { + Resolve().BroadcastOnPilotModeChanged(pilotingChair.subRoot.gameObject, true); + } skipPrefix = false; } else diff --git a/NitroxPatcher/Patches/Dynamic/PilotingChair_OnPlayerDeath_Patch.cs b/NitroxPatcher/Patches/Dynamic/PilotingChair_OnPlayerDeath_Patch.cs index 95ff53903a..65a62a0f79 100644 --- a/NitroxPatcher/Patches/Dynamic/PilotingChair_OnPlayerDeath_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/PilotingChair_OnPlayerDeath_Patch.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using NitroxClient.GameLogic; using NitroxModel.DataStructures; using NitroxModel.Helper; @@ -18,6 +18,7 @@ public static void Postfix(PilotingChair __instance) { // Request to be downgraded to a transient lock so we can still simulate the positioning. Resolve().RequestSimulationLock(id, SimulationLockType.TRANSIENT); + Resolve().BroadcastOnPilotModeChanged(__instance.gameObject, false); } } } diff --git a/NitroxPatcher/Patches/Dynamic/PilotingChair_ReleaseBy_Patch.cs b/NitroxPatcher/Patches/Dynamic/PilotingChair_ReleaseBy_Patch.cs index 6f30e5684a..f62b307cc9 100644 --- a/NitroxPatcher/Patches/Dynamic/PilotingChair_ReleaseBy_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/PilotingChair_ReleaseBy_Patch.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using NitroxClient.GameLogic; using NitroxModel.DataStructures; using NitroxModel.Helper; @@ -18,6 +18,7 @@ public static void Postfix(PilotingChair __instance) { // Request to be downgraded to a transient lock so we can still simulate the positioning. Resolve().RequestSimulationLock(id, SimulationLockType.TRANSIENT); + Resolve().BroadcastOnPilotModeChanged(__instance.gameObject, false); } } } diff --git a/NitroxPatcher/Patches/Dynamic/Vehicle_OnPilotModeBegin_Patch.cs b/NitroxPatcher/Patches/Dynamic/Vehicle_OnPilotModeBegin_Patch.cs index 73711d9c10..aee15d8244 100644 --- a/NitroxPatcher/Patches/Dynamic/Vehicle_OnPilotModeBegin_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/Vehicle_OnPilotModeBegin_Patch.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using NitroxClient.GameLogic; using NitroxModel.Helper; @@ -10,6 +10,6 @@ public sealed partial class Vehicle_OnPilotModeBegin_Patch : NitroxPatch, IDynam public static void Prefix(Vehicle __instance) { - Resolve().BroadcastOnPilotModeChanged(__instance, true); + Resolve().BroadcastOnPilotModeChanged(__instance.gameObject, true); } } diff --git a/NitroxPatcher/Patches/Dynamic/Vehicle_OnPilotModeEnd_Patch.cs b/NitroxPatcher/Patches/Dynamic/Vehicle_OnPilotModeEnd_Patch.cs index a86d788319..0d4cb6540c 100644 --- a/NitroxPatcher/Patches/Dynamic/Vehicle_OnPilotModeEnd_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/Vehicle_OnPilotModeEnd_Patch.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using NitroxClient.GameLogic; using NitroxClient.MonoBehaviours; using NitroxModel.DataStructures; @@ -12,7 +12,7 @@ public sealed partial class Vehicle_OnPilotModeEnd_Patch : NitroxPatch, IDynamic public static void Prefix(Vehicle __instance) { - Resolve().BroadcastOnPilotModeChanged(__instance, false); + Resolve().BroadcastOnPilotModeChanged(__instance.gameObject, false); // Fixes instances of vehicles stuck on nothing by forcing the workaround (let another player enter and leave the vehicle) if (__instance.TryGetComponent(out MultiplayerVehicleControl mvc)) { diff --git a/NitroxServer-Subnautica/Serialization/SubnauticaServerProtoBufSerializer.cs b/NitroxServer-Subnautica/Serialization/SubnauticaServerProtoBufSerializer.cs index 584ad341de..46632d69bf 100644 --- a/NitroxServer-Subnautica/Serialization/SubnauticaServerProtoBufSerializer.cs +++ b/NitroxServer-Subnautica/Serialization/SubnauticaServerProtoBufSerializer.cs @@ -1,9 +1,6 @@ -using NitroxModel.DataStructures.GameLogic; using NitroxModel.DataStructures.Unity; -using NitroxModel_Subnautica.DataStructures.GameLogic; using NitroxModel_Subnautica.DataStructures.Surrogates; using NitroxServer.Serialization; -using ProtoBufNet.Meta; using UnityEngine; namespace NitroxServer_Subnautica.Serialization @@ -28,10 +25,6 @@ private void RegisterHardCodedTypes() Model.Add(typeof(NitroxQuaternion), false).SetSurrogate(typeof(QuaternionSurrogate)); Model.Add(typeof(Transform), false).SetSurrogate(typeof(NitroxTransform)); Model.Add(typeof(GameObject), false).SetSurrogate(typeof(NitroxServer.UnityStubs.GameObject)); - - MetaType movementData = Model.Add(typeof(VehicleMovementData), false); - movementData.AddSubType(100, typeof(BasicVehicleMovementData)); - movementData.AddSubType(200, typeof(ExosuitMovementData)); } } } diff --git a/NitroxServer/Communication/Packets/Processors/DefaultServerPacketProcessor.cs b/NitroxServer/Communication/Packets/Processors/DefaultServerPacketProcessor.cs index 96e2fe5e77..226ae8b82d 100644 --- a/NitroxServer/Communication/Packets/Processors/DefaultServerPacketProcessor.cs +++ b/NitroxServer/Communication/Packets/Processors/DefaultServerPacketProcessor.cs @@ -14,7 +14,6 @@ public class DefaultServerPacketProcessor : AuthenticatedPacketProcessor { typeof(AnimationChangeEvent), typeof(PlayerMovement), - typeof(VehicleMovement), typeof(ItemPosition), typeof(PlayerStats), typeof(StoryGoalExecuted), diff --git a/NitroxServer/Communication/Packets/Processors/VehicleMovementPacketProcessor.cs b/NitroxServer/Communication/Packets/Processors/VehicleMovementPacketProcessor.cs deleted file mode 100644 index 9e0fd1de1e..0000000000 --- a/NitroxServer/Communication/Packets/Processors/VehicleMovementPacketProcessor.cs +++ /dev/null @@ -1,40 +0,0 @@ -using NitroxModel.DataStructures.GameLogic; -using NitroxModel.DataStructures.GameLogic.Entities; -using NitroxModel.DataStructures.Util; -using NitroxModel.Packets; -using NitroxServer.Communication.Packets.Processors.Abstract; -using NitroxServer.GameLogic; -using NitroxServer.GameLogic.Entities; - -namespace NitroxServer.Communication.Packets.Processors -{ - class VehicleMovementPacketProcessor : AuthenticatedPacketProcessor - { - private readonly PlayerManager playerManager; - private readonly EntityRegistry entityRegistry; - - public VehicleMovementPacketProcessor(PlayerManager playerManager, EntityRegistry entityRegistry) - { - this.playerManager = playerManager; - this.entityRegistry = entityRegistry; - } - - public override void Process(VehicleMovement packet, Player player) - { - Optional vehicle = entityRegistry.GetEntityById(packet.VehicleMovementData.Id); - - if (vehicle.HasValue && vehicle.Value is WorldEntity worldVehicle) - { - worldVehicle.Transform.Position = packet.VehicleMovementData.Position; - worldVehicle.Transform.Rotation = packet.VehicleMovementData.Rotation; - } - - if (player.Id == packet.PlayerId) - { - player.Position = packet.VehicleMovementData.DriverPosition ?? packet.Position; - } - - playerManager.SendPacketToOtherPlayers(packet, player); - } - } -} diff --git a/NitroxServer/Communication/Packets/Processors/VehicleMovementsPacketProcessor.cs b/NitroxServer/Communication/Packets/Processors/VehicleMovementsPacketProcessor.cs index 70c18cb3d6..98c5e461b3 100644 --- a/NitroxServer/Communication/Packets/Processors/VehicleMovementsPacketProcessor.cs +++ b/NitroxServer/Communication/Packets/Processors/VehicleMovementsPacketProcessor.cs @@ -1,4 +1,5 @@ using NitroxModel.DataStructures.GameLogic.Entities; +using NitroxModel.DataStructures.Unity; using NitroxModel.Packets; using NitroxServer.Communication.Packets.Processors.Abstract; using NitroxServer.GameLogic; @@ -10,32 +11,52 @@ public class VehicleMovementsPacketProcessor : AuthenticatedPacketProcessor= 0; i--) { + MovementData movementData = packet.Data[i]; + if (simulationOwnershipData.GetPlayerForLock(movementData.Id) != player) + { + packet.Data.RemoveAt(i); + Log.WarnOnce($"Player {player.Name} tried updating {movementData.Id}'s position but they don't have the lock on it"); + continue; + } + if (entityRegistry.TryGetEntityById(movementData.Id, out WorldEntity worldEntity)) { worldEntity.Transform.Position = movementData.Position; worldEntity.Transform.Rotation = movementData.Rotation; + + if (movementData is DrivenVehicleMovementData) + { + // Cyclops' driving wheel is not at relative 0,0,0 + if (worldEntity.TechType.Name.Equals("Cyclops")) + { + player.Entity.Transform.LocalPosition = NitroxVector3.Zero; // TODO: set the right position offset in the cyclops + player.Position = player.Entity.Transform.Position; + } + else + { + player.Position = movementData.Position; + player.Rotation = movementData.Rotation; + } + } } } - // TODO: sync driving player movement without adding much more data (maybe have a nullable DriverPosition field in there) - - - /*if (player.Id == packet.PlayerId) + if (packet.Data.Count > 0) { - player.Position = packet.VehicleMovementData.DriverPosition ?? packet.Position; - }*/ - - playerManager.SendPacketToOtherPlayers(packet, player); + playerManager.SendPacketToOtherPlayers(packet, player); + } } } From fa4b1ae2f8b77a48babebbb45155e6ddadcd8829 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Fri, 1 Nov 2024 18:07:48 +0100 Subject: [PATCH 08/20] Fix seamoth enter sound playing all over the map, cyclops driver not being set correctly --- .../Resources/SoundWhitelist_Subnautica.csv | 2 +- .../Processors/VehicleMovementsProcessor.cs | 1 - .../VehicleOnPilotModeChangedProcessor.cs | 14 ++-- NitroxClient/GameLogic/PlayerManager.cs | 5 ++ .../RemotePlayerManagerExtensions.cs | 15 ----- NitroxClient/GameLogic/Vehicles.cs | 3 +- .../Vehicles/CyclopsMovementReplicator.cs | 64 +++++++++++-------- .../Vehicles/SeaMothMovementReplicator.cs | 31 ++++++--- NitroxModel/Packets/FootstepPacket.cs | 3 + .../PilotingChair_OnHandClick_Patch.cs | 4 -- .../PilotingChair_OnPlayerDeath_Patch.cs | 2 +- .../PilotingChair_OnSteeringStart_Patch.cs | 18 ++++++ .../Dynamic/PilotingChair_ReleaseBy_Patch.cs | 2 +- .../VehicleMovementsPacketProcessor.cs | 9 +-- 14 files changed, 103 insertions(+), 70 deletions(-) delete mode 100644 NitroxClient/GameLogic/RemotePlayerManagerExtensions.cs create mode 100644 NitroxPatcher/Patches/Dynamic/PilotingChair_OnSteeringStart_Patch.cs diff --git a/Nitrox.Assets.Subnautica/Resources/SoundWhitelist_Subnautica.csv b/Nitrox.Assets.Subnautica/Resources/SoundWhitelist_Subnautica.csv index 865ae5be4a..6282a6fa6e 100644 --- a/Nitrox.Assets.Subnautica/Resources/SoundWhitelist_Subnautica.csv +++ b/Nitrox.Assets.Subnautica/Resources/SoundWhitelist_Subnautica.csv @@ -678,7 +678,7 @@ event:/sub/seamoth/crush_depth_warning;false;false;0 event:/sub/seamoth/depth_update;false;false;0 event:/sub/seamoth/dock;false;false;0 event:/sub/seamoth/dock_seamoth_cyclops;false;false;0 -event:/sub/seamoth/enter_seamoth;false;false;0 +event:/sub/seamoth/enter_seamoth;false;false;50 event:/sub/seamoth/impact_solid_hard;false;false;0 event:/sub/seamoth/impact_solid_medium;false;false;0 event:/sub/seamoth/impact_solid_soft;false;false;0 diff --git a/NitroxClient/Communication/Packets/Processors/VehicleMovementsProcessor.cs b/NitroxClient/Communication/Packets/Processors/VehicleMovementsProcessor.cs index 77e9c149c4..f2701c7fe3 100644 --- a/NitroxClient/Communication/Packets/Processors/VehicleMovementsProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/VehicleMovementsProcessor.cs @@ -1,6 +1,5 @@ using NitroxClient.Communication.Packets.Processors.Abstract; using NitroxClient.MonoBehaviours; -using NitroxClient.MonoBehaviours.Vehicles; using NitroxModel.Packets; namespace NitroxClient.Communication.Packets.Processors; diff --git a/NitroxClient/Communication/Packets/Processors/VehicleOnPilotModeChangedProcessor.cs b/NitroxClient/Communication/Packets/Processors/VehicleOnPilotModeChangedProcessor.cs index 74591d94f6..427902753c 100644 --- a/NitroxClient/Communication/Packets/Processors/VehicleOnPilotModeChangedProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/VehicleOnPilotModeChangedProcessor.cs @@ -1,36 +1,36 @@ -using NitroxClient.Communication.Abstract; using NitroxClient.Communication.Packets.Processors.Abstract; using NitroxClient.GameLogic; using NitroxClient.MonoBehaviours; using NitroxModel.Packets; +using UnityEngine; namespace NitroxClient.Communication.Packets.Processors; public class VehicleOnPilotModeChangedProcessor : ClientPacketProcessor { - private readonly IPacketSender packetSender; private readonly Vehicles vehicles; private readonly PlayerManager playerManager; - public VehicleOnPilotModeChangedProcessor(IPacketSender packetSender, Vehicles vehicles, PlayerManager playerManager) + public VehicleOnPilotModeChangedProcessor(Vehicles vehicles, PlayerManager playerManager) { - this.packetSender = packetSender; this.vehicles = vehicles; this.playerManager = playerManager; } public override void Process(VehicleOnPilotModeChanged packet) { - if (NitroxEntity.TryGetComponentFrom(packet.VehicleId, out Vehicle vehicle)) + if (NitroxEntity.TryGetObjectFrom(packet.VehicleId, out GameObject gameObject)) { // If the vehicle is docked, then we will manually set the piloting mode // once the animations complete. This prevents weird behaviour such as the // player existing the vehicle while it is about to dock (the event fires // before the animation completes on the remote player.) - if (!vehicle.docked) + if (gameObject.TryGetComponent(out Vehicle vehicle) && vehicle.docked) { - vehicles.SetOnPilotMode(vehicle.gameObject, packet.PlayerId, packet.IsPiloting); + return; } + + vehicles.SetOnPilotMode(gameObject, packet.PlayerId, packet.IsPiloting); } } } diff --git a/NitroxClient/GameLogic/PlayerManager.cs b/NitroxClient/GameLogic/PlayerManager.cs index 994c331a9e..26cf93700c 100644 --- a/NitroxClient/GameLogic/PlayerManager.cs +++ b/NitroxClient/GameLogic/PlayerManager.cs @@ -35,6 +35,11 @@ public Optional Find(ushort playerId) return Optional.OfNullable(player); } + public bool TryFind(ushort playerId, out RemotePlayer remotePlayer) + { + return playersById.TryGetValue(playerId, out remotePlayer); + } + public Optional Find(NitroxId playerNitroxId) { RemotePlayer remotePlayer = playersById.Select(idToPlayer => idToPlayer.Value) diff --git a/NitroxClient/GameLogic/RemotePlayerManagerExtensions.cs b/NitroxClient/GameLogic/RemotePlayerManagerExtensions.cs deleted file mode 100644 index 8221c77053..0000000000 --- a/NitroxClient/GameLogic/RemotePlayerManagerExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using NitroxModel.DataStructures.Util; - -namespace NitroxClient.GameLogic -{ - public static class RemotePlayerManagerExtensions - { - public static bool TryFind(this PlayerManager playerManager, ushort playerId, out RemotePlayer remotePlayer) - { - Optional optional = playerManager.Find(playerId); - remotePlayer = optional.HasValue ? optional.Value : null; - - return optional.HasValue; - } - } -} diff --git a/NitroxClient/GameLogic/Vehicles.cs b/NitroxClient/GameLogic/Vehicles.cs index 5cdaf105b9..048435db0c 100644 --- a/NitroxClient/GameLogic/Vehicles.cs +++ b/NitroxClient/GameLogic/Vehicles.cs @@ -21,10 +21,11 @@ public class Vehicles private readonly Dictionary pilotingChairByTechType = []; - public Vehicles(IPacketSender packetSender, IMultiplayerSession multiplayerSession) + public Vehicles(IPacketSender packetSender, IMultiplayerSession multiplayerSession, PlayerManager playerManager) { this.packetSender = packetSender; this.multiplayerSession = multiplayerSession; + this.playerManager = playerManager; } private PilotingChair FindPilotingChairWithCache(GameObject parent, TechType techType) diff --git a/NitroxClient/MonoBehaviours/Vehicles/CyclopsMovementReplicator.cs b/NitroxClient/MonoBehaviours/Vehicles/CyclopsMovementReplicator.cs index 66c2502fa1..0d5fbef3dc 100644 --- a/NitroxClient/MonoBehaviours/Vehicles/CyclopsMovementReplicator.cs +++ b/NitroxClient/MonoBehaviours/Vehicles/CyclopsMovementReplicator.cs @@ -9,12 +9,44 @@ public class CyclopsMovementReplicator : VehicleMovementReplicator private SubControl subControl; private RemotePlayer drivingPlayer; + private bool throttleApplied; + private float steeringWheelYaw; public void Awake() { subControl = GetComponent(); } + public new void Update() + { + base.Update(); + + if (subControl.canAccel && throttleApplied) + { + // See SubControl.Update + var topClamp = subControl.useThrottleIndex switch + { + 1 => 0.66f, + 2 => 1f, + _ => 0.33f, + }; + subControl.engineRPMManager.AccelerateInput(topClamp); + for (int i = 0; i < subControl.throttleHandlers.Length; i++) + { + subControl.throttleHandlers[i].OnSubAppliedThrottle(); + } + } + + if (Mathf.Abs(steeringWheelYaw) > 0.1f) + { + ShipSide shipSide = steeringWheelYaw > 0 ? ShipSide.Port : ShipSide.Starboard; + for (int i = 0; i < subControl.turnHandlers.Length; i++) + { + subControl.turnHandlers[i].OnSubTurn(shipSide); + } + } + } + public override void ApplyNewMovementData(MovementData newMovementData) { if (newMovementData is not DrivenVehicleMovementData vehicleMovementData) @@ -22,7 +54,7 @@ public override void ApplyNewMovementData(MovementData newMovementData) return; } - float steeringWheelYaw = vehicleMovementData.SteeringWheelYaw; + steeringWheelYaw = vehicleMovementData.SteeringWheelYaw; float steeringWheelPitch = vehicleMovementData.SteeringWheelPitch; // See SubControl.UpdateAnimation @@ -33,34 +65,15 @@ public override void ApplyNewMovementData(MovementData newMovementData) subControl.mainAnimator.SetFloat("view_yaw", subControl.steeringWheelYaw); subControl.mainAnimator.SetFloat("view_pitch", subControl.steeringWheelPitch); - drivingPlayer.AnimationController.SetFloat("cyclops_yaw", subControl.steeringWheelYaw); - drivingPlayer.AnimationController.SetFloat("cyclops_pitch", subControl.steeringWheelPitch); - } - - if (Mathf.Abs(steeringWheelYaw) > 0.1f) - { - ShipSide shipSide = steeringWheelYaw > 0 ? ShipSide.Port : ShipSide.Starboard; - for (int i = 0; i < subControl.turnHandlers.Length; i++) + if (drivingPlayer != null) { - subControl.turnHandlers[i].OnSubTurn(shipSide); + drivingPlayer.AnimationController.SetFloat("cyclops_yaw", subControl.steeringWheelYaw); + drivingPlayer.AnimationController.SetFloat("cyclops_pitch", subControl.steeringWheelPitch); } } - if (subControl.canAccel && vehicleMovementData.ThrottleApplied) - { - // See SubControl.Update - var topClamp = subControl.useThrottleIndex switch - { - 1 => 0.66f, - 2 => 1f, - _ => 0.33f, - }; - subControl.engineRPMManager.AccelerateInput(topClamp); - for (int i = 0; i < subControl.throttleHandlers.Length; i++) - { - subControl.throttleHandlers[i].OnSubAppliedThrottle(); - } - } + throttleApplied = vehicleMovementData.ThrottleApplied; + } public override void Enter(RemotePlayer drivingPlayer) @@ -71,5 +84,6 @@ public override void Enter(RemotePlayer drivingPlayer) public override void Exit() { drivingPlayer = null; + throttleApplied = false; } } diff --git a/NitroxClient/MonoBehaviours/Vehicles/SeaMothMovementReplicator.cs b/NitroxClient/MonoBehaviours/Vehicles/SeaMothMovementReplicator.cs index 9daf8d0829..e3b8af5a7d 100644 --- a/NitroxClient/MonoBehaviours/Vehicles/SeaMothMovementReplicator.cs +++ b/NitroxClient/MonoBehaviours/Vehicles/SeaMothMovementReplicator.cs @@ -13,8 +13,12 @@ public class SeamothMovementReplicator : VehicleMovementReplicator private FMOD_CustomLoopingEmitter rpmSound; private FMOD_CustomEmitter revSound; + private FMOD_CustomEmitter enterSeamoth; + private float radiusRpmSound; private float radiusRevSound; + private float radiusEnterSound; + private bool throttleApplied; public void Awake() @@ -30,12 +34,6 @@ public void Awake() Vector3 positionAfter = transform.position; velocity = (positionAfter - positionBefore) / Time.deltaTime; - // TODO: find out if this is necessary - if (seaMoth.ambienceSound && seaMoth.ambienceSound.GetIsPlaying()) - { - seaMoth.ambienceSound.SetParameterValue(seaMoth.fmodIndexSpeed, velocity.magnitude); - } - if (throttleApplied) { seaMoth.engineSound.AccelerateInput(1); @@ -75,18 +73,24 @@ private void SetupSound() { rpmSound = seaMoth.engineSound.engineRpmSFX; revSound = seaMoth.engineSound.engineRevUp; + enterSeamoth = seaMoth.enterSeamoth; rpmSound.followParent = true; revSound.followParent = true; this.Resolve().IsWhitelisted(rpmSound.asset.path, out radiusRpmSound); this.Resolve().IsWhitelisted(revSound.asset.path, out radiusRevSound); + this.Resolve().IsWhitelisted(seaMoth.enterSeamoth.asset.path, out radiusEnterSound); rpmSound.GetEventInstance().setProperty(EVENT_PROPERTY.MINIMUM_DISTANCE, 1f); revSound.GetEventInstance().setProperty(EVENT_PROPERTY.MINIMUM_DISTANCE, 1f); + rpmSound.GetEventInstance().setProperty(EVENT_PROPERTY.MAXIMUM_DISTANCE, radiusRpmSound); revSound.GetEventInstance().setProperty(EVENT_PROPERTY.MAXIMUM_DISTANCE, radiusRevSound); + enterSeamoth.GetEventInstance().setProperty(EVENT_PROPERTY.MINIMUM_DISTANCE, 1f); + enterSeamoth.GetEventInstance().setProperty(EVENT_PROPERTY.MAXIMUM_DISTANCE, radiusEnterSound); + if (FMODUWE.IsInvalidParameterId(seaMoth.fmodIndexSpeed)) { seaMoth.fmodIndexSpeed = seaMoth.ambienceSound.GetParameterIndex("speed"); @@ -97,18 +101,25 @@ public override void Enter(RemotePlayer remotePlayer) { seaMoth.mainAnimator.SetBool("player_in", true); seaMoth.bubbles.Play(); - if (seaMoth.enterSeamoth) + if (enterSeamoth) { - seaMoth.enterSeamoth.Play(); // TODO: find out if this is required + // After first run, this sound will still be in "playing" mode so we need to release it by hand + enterSeamoth.Stop(); + enterSeamoth.ReleaseEvent(); + enterSeamoth.CacheEventInstance(); + + float distanceToPlayer = Vector3.Distance(Player.main.transform.position, transform.position); + float sound = SoundHelper.CalculateVolume(distanceToPlayer, radiusEnterSound, 1f); + enterSeamoth.evt.setVolume(sound); + + enterSeamoth.Play(); } - seaMoth.ambienceSound.PlayUI(); // TODO: find out if this is required } public override void Exit() { seaMoth.mainAnimator.SetBool("player_in", false); seaMoth.bubbles.Stop(); - seaMoth.ambienceSound.Stop(true); // TODO: find out if this is required throttleApplied = false; } diff --git a/NitroxModel/Packets/FootstepPacket.cs b/NitroxModel/Packets/FootstepPacket.cs index 8b21656b76..743e06b81f 100644 --- a/NitroxModel/Packets/FootstepPacket.cs +++ b/NitroxModel/Packets/FootstepPacket.cs @@ -12,6 +12,9 @@ public FootstepPacket(ushort playerID, StepSounds assetIndex) { PlayerID = playerID; AssetIndex = assetIndex; + + DeliveryMethod = Networking.NitroxDeliveryMethod.DeliveryMethod.UNRELIABLE_SEQUENCED; + UdpChannel = UdpChannelId.MOVEMENTS; } public enum StepSounds : byte diff --git a/NitroxPatcher/Patches/Dynamic/PilotingChair_OnHandClick_Patch.cs b/NitroxPatcher/Patches/Dynamic/PilotingChair_OnHandClick_Patch.cs index 47f080d294..8bc491bddf 100644 --- a/NitroxPatcher/Patches/Dynamic/PilotingChair_OnHandClick_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/PilotingChair_OnHandClick_Patch.cs @@ -50,10 +50,6 @@ private static void ReceivedSimulationLockResponse(NitroxId id, bool lockAquired { skipPrefix = true; pilotingChair.OnHandClick(context.GuiHand); - if (pilotingChair.subRoot) - { - Resolve().BroadcastOnPilotModeChanged(pilotingChair.subRoot.gameObject, true); - } skipPrefix = false; } else diff --git a/NitroxPatcher/Patches/Dynamic/PilotingChair_OnPlayerDeath_Patch.cs b/NitroxPatcher/Patches/Dynamic/PilotingChair_OnPlayerDeath_Patch.cs index 65a62a0f79..875bf592cd 100644 --- a/NitroxPatcher/Patches/Dynamic/PilotingChair_OnPlayerDeath_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/PilotingChair_OnPlayerDeath_Patch.cs @@ -18,7 +18,7 @@ public static void Postfix(PilotingChair __instance) { // Request to be downgraded to a transient lock so we can still simulate the positioning. Resolve().RequestSimulationLock(id, SimulationLockType.TRANSIENT); - Resolve().BroadcastOnPilotModeChanged(__instance.gameObject, false); + Resolve().BroadcastOnPilotModeChanged(__instance.subRoot.gameObject, false); } } } diff --git a/NitroxPatcher/Patches/Dynamic/PilotingChair_OnSteeringStart_Patch.cs b/NitroxPatcher/Patches/Dynamic/PilotingChair_OnSteeringStart_Patch.cs new file mode 100644 index 0000000000..df61760dd0 --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/PilotingChair_OnSteeringStart_Patch.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using NitroxClient.GameLogic; +using NitroxModel.Helper; + +namespace NitroxPatcher.Patches.Dynamic; + +public sealed partial class PilotingChair_OnSteeringStart_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((PilotingChair t) => t.OnSteeringStart(default)); + + public static void Postfix(PilotingChair __instance) + { + if (Player.main.currChair == __instance && __instance.subRoot) + { + Resolve().BroadcastOnPilotModeChanged(__instance.subRoot.gameObject, true); + } + } +} diff --git a/NitroxPatcher/Patches/Dynamic/PilotingChair_ReleaseBy_Patch.cs b/NitroxPatcher/Patches/Dynamic/PilotingChair_ReleaseBy_Patch.cs index f62b307cc9..557e940278 100644 --- a/NitroxPatcher/Patches/Dynamic/PilotingChair_ReleaseBy_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/PilotingChair_ReleaseBy_Patch.cs @@ -18,7 +18,7 @@ public static void Postfix(PilotingChair __instance) { // Request to be downgraded to a transient lock so we can still simulate the positioning. Resolve().RequestSimulationLock(id, SimulationLockType.TRANSIENT); - Resolve().BroadcastOnPilotModeChanged(__instance.gameObject, false); + Resolve().BroadcastOnPilotModeChanged(__instance.subRoot.gameObject, false); } } } diff --git a/NitroxServer/Communication/Packets/Processors/VehicleMovementsPacketProcessor.cs b/NitroxServer/Communication/Packets/Processors/VehicleMovementsPacketProcessor.cs index 98c5e461b3..9ac0f52649 100644 --- a/NitroxServer/Communication/Packets/Processors/VehicleMovementsPacketProcessor.cs +++ b/NitroxServer/Communication/Packets/Processors/VehicleMovementsPacketProcessor.cs @@ -9,6 +9,8 @@ namespace NitroxServer.Communication.Packets.Processors; public class VehicleMovementsPacketProcessor : AuthenticatedPacketProcessor { + private static readonly NitroxVector3 CyclopsSteeringWheelRelativePosition = new(-0.05f, 0.97f, -23.54f); + private readonly PlayerManager playerManager; private readonly EntityRegistry entityRegistry; private readonly SimulationOwnershipData simulationOwnershipData; @@ -27,9 +29,8 @@ public override void Process(VehicleMovements packet, Player player) MovementData movementData = packet.Data[i]; if (simulationOwnershipData.GetPlayerForLock(movementData.Id) != player) { - packet.Data.RemoveAt(i); Log.WarnOnce($"Player {player.Name} tried updating {movementData.Id}'s position but they don't have the lock on it"); - continue; + // In the future, add "packet.Data.RemoveAt(i);" and "continue;" to prevent those abnormal situations } if (entityRegistry.TryGetEntityById(movementData.Id, out WorldEntity worldEntity)) @@ -39,10 +40,10 @@ public override void Process(VehicleMovements packet, Player player) if (movementData is DrivenVehicleMovementData) { - // Cyclops' driving wheel is not at relative 0,0,0 + // Cyclops' driving wheel is at a known position so we need to adapt the position of the player accordingly if (worldEntity.TechType.Name.Equals("Cyclops")) { - player.Entity.Transform.LocalPosition = NitroxVector3.Zero; // TODO: set the right position offset in the cyclops + player.Entity.Transform.LocalPosition = CyclopsSteeringWheelRelativePosition; player.Position = player.Entity.Transform.Position; } else From 8852d26d95a50af26fb49214378252c2ce19ebc0 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Fri, 1 Nov 2024 18:14:17 +0100 Subject: [PATCH 09/20] Restore driver state of other players when connecting to a server --- .../GlobalRootInitialSyncProcessor.cs | 37 ++++++++++++++++++- .../MultiplayerSession/PlayerContext.cs | 6 ++- .../VehicleOnPilotModeChangedProcessor.cs | 22 +++++++++++ NitroxServer/GameLogic/PlayerManager.cs | 2 +- 4 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 NitroxServer/Communication/Packets/Processors/VehicleOnPilotModeChangedProcessor.cs diff --git a/NitroxClient/GameLogic/InitialSync/GlobalRootInitialSyncProcessor.cs b/NitroxClient/GameLogic/InitialSync/GlobalRootInitialSyncProcessor.cs index 1703b9306b..063709d80d 100644 --- a/NitroxClient/GameLogic/InitialSync/GlobalRootInitialSyncProcessor.cs +++ b/NitroxClient/GameLogic/InitialSync/GlobalRootInitialSyncProcessor.cs @@ -1,7 +1,9 @@ using System.Collections; using NitroxClient.GameLogic.Bases; using NitroxClient.GameLogic.InitialSync.Abstract; +using NitroxClient.MonoBehaviours; using NitroxClient.MonoBehaviours.Cyclops; +using NitroxModel.MultiplayerSession; using NitroxModel.Packets; using UnityEngine; @@ -13,14 +15,19 @@ namespace NitroxClient.GameLogic.InitialSync; /// /// This allows for:
/// - vehicles to use equipment +/// - other players to be set as drivers of some vehicle ///
public class GlobalRootInitialSyncProcessor : InitialSyncProcessor { private readonly Entities entities; + private readonly Vehicles vehicles; + private readonly PlayerManager playerManager; - public GlobalRootInitialSyncProcessor(Entities entities) + public GlobalRootInitialSyncProcessor(Entities entities, Vehicles vehicles, PlayerManager playerManager) { this.entities = entities; + this.vehicles = vehicles; + this.playerManager = playerManager; // As we migrate systems over to entities, we want to ensure the required components are in place to spawn these entities. // For example, migrating inventories to the entity system requires players are spawned in the world before we try to add @@ -28,9 +35,13 @@ public GlobalRootInitialSyncProcessor(Entities entities) AddDependency(); AddDependency(); AddDependency(); + + AddStep(WorldSettledForBuildings); + AddStep(SpawnEntities); + AddStep(RestoreDrivers); } - public override IEnumerator Process(InitialPlayerSync packet, WaitScreen.ManualWaitItem waitScreenItem) + public IEnumerator WorldSettledForBuildings(InitialPlayerSync packet) { yield return new WaitUntil(LargeWorldStreamer.main.IsWorldSettled); // Make sure all building-related prefabs are fully loaded (happen to bug when launching multiple clients locally) @@ -40,8 +51,30 @@ public override IEnumerator Process(InitialPlayerSync packet, WaitScreen.ManualW yield return VirtualCyclops.InitializeConstructablesCache(); BuildingHandler.Main.InitializeOperations(packet.BuildOperationIds); + } + public IEnumerator SpawnEntities(InitialPlayerSync packet) + { Log.Info($"Received initial sync packet with {packet.GlobalRootEntities.Count} global root entities"); yield return entities.SpawnBatchAsync(packet.GlobalRootEntities); } + + public void RestoreDrivers(InitialPlayerSync packet) + { + // At this step, vehicles have been spawned already (by SpawnEntities) + foreach (PlayerContext playerContext in packet.OtherPlayers) + { + if (playerContext.DrivingVehicle != null) + { + Log.Info($"Restoring driver state of {playerContext.PlayerName} in {playerContext.DrivingVehicle}"); + vehicles.SetOnPilotMode(playerContext.DrivingVehicle, playerContext.PlayerId, true); + if (playerManager.TryFind(playerContext.PlayerId, out RemotePlayer remotePlayer)) + { + // As remote players are still driving, they aren't updating their IsUnderwater state so AnimationSender.Update + // isn't going to send a packet. Therefore we need to set this by hand + remotePlayer.UpdateAnimationAndCollider(AnimChangeType.UNDERWATER, AnimChangeState.OFF); + } + } + } + } } diff --git a/NitroxModel/MultiplayerSession/PlayerContext.cs b/NitroxModel/MultiplayerSession/PlayerContext.cs index ece774fb9e..4577275def 100644 --- a/NitroxModel/MultiplayerSession/PlayerContext.cs +++ b/NitroxModel/MultiplayerSession/PlayerContext.cs @@ -14,8 +14,9 @@ public class PlayerContext public PlayerSettings PlayerSettings { get; } public bool IsMuted { get; set; } public NitroxGameMode GameMode { get; set; } + public NitroxId DrivingVehicle { get; set; } - public PlayerContext(string playerName, ushort playerId, NitroxId playerNitroxId, bool wasBrandNewPlayer, PlayerSettings playerSettings, bool isMuted, NitroxGameMode gameMode) + public PlayerContext(string playerName, ushort playerId, NitroxId playerNitroxId, bool wasBrandNewPlayer, PlayerSettings playerSettings, bool isMuted, NitroxGameMode gameMode, NitroxId drivingVehicle) { PlayerName = playerName; PlayerId = playerId; @@ -24,10 +25,11 @@ public PlayerContext(string playerName, ushort playerId, NitroxId playerNitroxId PlayerSettings = playerSettings; IsMuted = isMuted; GameMode = gameMode; + DrivingVehicle = drivingVehicle; } public override string ToString() { - return $"[PlayerContext - PlayerName: {PlayerName}, PlayerId: {PlayerId}, PlayerNitroxId: {PlayerNitroxId}, WasBrandNewPlayer: {WasBrandNewPlayer}, PlayerSettings: {PlayerSettings}, GameMode: {GameMode}]"; + return $"[PlayerContext - PlayerName: {PlayerName}, PlayerId: {PlayerId}, PlayerNitroxId: {PlayerNitroxId}, WasBrandNewPlayer: {WasBrandNewPlayer}, PlayerSettings: {PlayerSettings}, GameMode: {GameMode}, DrivingVehicle: {DrivingVehicle}]"; } } diff --git a/NitroxServer/Communication/Packets/Processors/VehicleOnPilotModeChangedProcessor.cs b/NitroxServer/Communication/Packets/Processors/VehicleOnPilotModeChangedProcessor.cs new file mode 100644 index 0000000000..a6617a86f7 --- /dev/null +++ b/NitroxServer/Communication/Packets/Processors/VehicleOnPilotModeChangedProcessor.cs @@ -0,0 +1,22 @@ +using NitroxModel.Packets; +using NitroxServer.Communication.Packets.Processors.Abstract; +using NitroxServer.GameLogic; + +namespace NitroxServer.Communication.Packets.Processors; + +public class VehicleOnPilotModeChangedProcessor : AuthenticatedPacketProcessor +{ + private readonly PlayerManager playerManager; + + public VehicleOnPilotModeChangedProcessor(PlayerManager playerManager) + { + this.playerManager = playerManager; + } + + public override void Process(VehicleOnPilotModeChanged packet, Player player) + { + player.PlayerContext.DrivingVehicle = packet.IsPiloting ? packet.VehicleId : null; + + playerManager.SendPacketToOtherPlayers(packet, player); + } +} diff --git a/NitroxServer/GameLogic/PlayerManager.cs b/NitroxServer/GameLogic/PlayerManager.cs index 65055a912f..e4e0111bed 100644 --- a/NitroxServer/GameLogic/PlayerManager.cs +++ b/NitroxServer/GameLogic/PlayerManager.cs @@ -124,7 +124,7 @@ public MultiplayerSessionReservation ReservePlayerContext( NitroxGameMode gameMode = hasSeenPlayerBefore ? player.GameMode : serverConfig.GameMode; // TODO: At some point, store the muted state of a player - PlayerContext playerContext = new(playerName, playerId, playerNitroxId, !hasSeenPlayerBefore, playerSettings, false, gameMode); + PlayerContext playerContext = new(playerName, playerId, playerNitroxId, !hasSeenPlayerBefore, playerSettings, false, gameMode, null); string reservationKey = Guid.NewGuid().ToString(); reservations.Add(reservationKey, playerContext); From e5bc3a825c35e2f1c131518bf326e733fabbc471 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Sat, 2 Nov 2024 20:57:42 +0100 Subject: [PATCH 10/20] Fix remotely driven exosuits' animations --- .../Vehicles/CyclopsMovementReplicator.cs | 1 - .../Vehicles/ExosuitMovementReplicator.cs | 75 +++++++++++-------- .../Vehicles/SeaMothMovementReplicator.cs | 2 - .../Dynamic/Exosuit_GetVelocity_Patch.cs | 24 ++++++ .../Dynamic/Exosuit_UpdateAnimations_Patch.cs | 35 +++++++++ 5 files changed, 104 insertions(+), 33 deletions(-) create mode 100644 NitroxPatcher/Patches/Dynamic/Exosuit_GetVelocity_Patch.cs create mode 100644 NitroxPatcher/Patches/Dynamic/Exosuit_UpdateAnimations_Patch.cs diff --git a/NitroxClient/MonoBehaviours/Vehicles/CyclopsMovementReplicator.cs b/NitroxClient/MonoBehaviours/Vehicles/CyclopsMovementReplicator.cs index 0d5fbef3dc..060be03429 100644 --- a/NitroxClient/MonoBehaviours/Vehicles/CyclopsMovementReplicator.cs +++ b/NitroxClient/MonoBehaviours/Vehicles/CyclopsMovementReplicator.cs @@ -73,7 +73,6 @@ public override void ApplyNewMovementData(MovementData newMovementData) } throttleApplied = vehicleMovementData.ThrottleApplied; - } public override void Enter(RemotePlayer drivingPlayer) diff --git a/NitroxClient/MonoBehaviours/Vehicles/ExosuitMovementReplicator.cs b/NitroxClient/MonoBehaviours/Vehicles/ExosuitMovementReplicator.cs index 81353ef302..368d923946 100644 --- a/NitroxClient/MonoBehaviours/Vehicles/ExosuitMovementReplicator.cs +++ b/NitroxClient/MonoBehaviours/Vehicles/ExosuitMovementReplicator.cs @@ -1,3 +1,4 @@ +using FMOD.Studio; using NitroxClient.GameLogic; using NitroxClient.GameLogic.FMOD; using NitroxModel.GameLogic.FMOD; @@ -11,10 +12,10 @@ public class ExosuitMovementReplicator : VehicleMovementReplicator private Exosuit exosuit; public Vector3 velocity; - public Vector3 angularVelocity; private float jetLoopingSoundDistance; - private float timeJetsChanged; - private bool lastThrottle; + private float thrustPower; + private bool jetsActive; + private float timeJetsActiveChanged; public void Awake() { @@ -25,13 +26,10 @@ public void Awake() public new void Update() { Vector3 positionBefore = transform.position; - Vector3 rotationBefore = transform.rotation.eulerAngles; base.Update(); Vector3 positionAfter = transform.position; - Vector3 rotationAfter = transform.rotation.eulerAngles; velocity = (positionAfter - positionBefore) / Time.deltaTime; - angularVelocity = (rotationAfter - rotationBefore) / Time.deltaTime; if (exosuit.loopingJetSound.playing) { @@ -50,9 +48,40 @@ public void Awake() } } - // TODO: find out if this is necessary - exosuit.ambienceSound.SetParameterValue(exosuit.fmodIndexSpeed, velocity.magnitude); - exosuit.ambienceSound.SetParameterValue(exosuit.fmodIndexRotate, angularVelocity.magnitude); + // See Exosuit.Update, thrust power simulation + + if (jetsActive) + { + thrustPower = Mathf.Clamp01(thrustPower - Time.deltaTime * exosuit.thrustConsumption); + exosuit.thrustIntensity += Time.deltaTime / exosuit.timeForFullVirbation; + } + else + { + float num = Time.deltaTime * exosuit.thrustConsumption * 0.7f; + if (exosuit.onGround) + { + num = Time.deltaTime * exosuit.thrustConsumption * 4f; + } + thrustPower = Mathf.Clamp01(thrustPower + num); + exosuit.thrustIntensity -= Time.deltaTime * 10f; + } + exosuit.thrustIntensity = Mathf.Clamp01(exosuit.thrustIntensity); + + if (timeJetsActiveChanged + 0.3f <= DayNightCycle.main.timePassedAsFloat) + { + if (jetsActive && thrustPower > 0f) + { + exosuit.loopingJetSound.Play(); + exosuit.fxcontrol.Play(0); + exosuit.areFXPlaying = true; + } + else + { + exosuit.loopingJetSound.Stop(); + exosuit.fxcontrol.Stop(0); + exosuit.areFXPlaying = false; + } + } } public override void ApplyNewMovementData(MovementData newMovementData) @@ -74,23 +103,11 @@ public override void ApplyNewMovementData(MovementData newMovementData) exosuit.mainAnimator.SetFloat("view_pitch", steeringWheelPitch); } - // See Exosuit.Update - if (timeJetsChanged + 0.3f <= Time.time && lastThrottle != vehicleMovementData.ThrottleApplied) + // See Exosuit.jetsActive setter + if (jetsActive != vehicleMovementData.ThrottleApplied) { - timeJetsChanged = Time.time; - lastThrottle = vehicleMovementData.ThrottleApplied; - if (vehicleMovementData.ThrottleApplied) - { - exosuit.loopingJetSound.Play(); - exosuit.fxcontrol.Play(0); - exosuit.areFXPlaying = true; - } - else - { - exosuit.loopingJetSound.Stop(); - exosuit.fxcontrol.Stop(0); - exosuit.areFXPlaying = false; - } + jetsActive = vehicleMovementData.ThrottleApplied; + timeJetsActiveChanged = DayNightCycle.main.timePassedAsFloat; } } @@ -111,18 +128,16 @@ private void SetupSound() public override void Enter(RemotePlayer remotePlayer) { - exosuit.mainAnimator.SetBool("player_in", true); - // TODO: see if required: GetComponent().freezeRotation = false; exosuit.SetIKEnabled(true); exosuit.thrustIntensity = 0; } public override void Exit() { - exosuit.mainAnimator.SetBool("player_in", false); - // TODO: see if required: GetComponent().freezeRotation = true; exosuit.SetIKEnabled(false); - exosuit.loopingJetSound.Stop(); + exosuit.loopingJetSound.Stop(STOP_MODE.ALLOWFADEOUT); exosuit.fxcontrol.Stop(0); + + jetsActive = false; } } diff --git a/NitroxClient/MonoBehaviours/Vehicles/SeaMothMovementReplicator.cs b/NitroxClient/MonoBehaviours/Vehicles/SeaMothMovementReplicator.cs index e3b8af5a7d..06132dede1 100644 --- a/NitroxClient/MonoBehaviours/Vehicles/SeaMothMovementReplicator.cs +++ b/NitroxClient/MonoBehaviours/Vehicles/SeaMothMovementReplicator.cs @@ -99,7 +99,6 @@ private void SetupSound() public override void Enter(RemotePlayer remotePlayer) { - seaMoth.mainAnimator.SetBool("player_in", true); seaMoth.bubbles.Play(); if (enterSeamoth) { @@ -118,7 +117,6 @@ public override void Enter(RemotePlayer remotePlayer) public override void Exit() { - seaMoth.mainAnimator.SetBool("player_in", false); seaMoth.bubbles.Stop(); throttleApplied = false; diff --git a/NitroxPatcher/Patches/Dynamic/Exosuit_GetVelocity_Patch.cs b/NitroxPatcher/Patches/Dynamic/Exosuit_GetVelocity_Patch.cs new file mode 100644 index 0000000000..ff921f5f50 --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/Exosuit_GetVelocity_Patch.cs @@ -0,0 +1,24 @@ +using System.Reflection; +using NitroxClient.MonoBehaviours.Vehicles; +using UnityEngine; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Gives the real velocity data for to play the right step sounds. +/// +public sealed partial class Exosuit_GetVelocity_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = typeof(Exosuit).GetMethod("IGroundMoveable.GetVelocity", BindingFlags.NonPublic | BindingFlags.Instance); + + public static bool Prefix(Exosuit __instance, ref Vector3 __result) + { + if (__instance.TryGetComponent(out ExosuitMovementReplicator exosuitMovementReplicator)) + { + __result = exosuitMovementReplicator.velocity; + return false; + } + + return true; + } +} diff --git a/NitroxPatcher/Patches/Dynamic/Exosuit_UpdateAnimations_Patch.cs b/NitroxPatcher/Patches/Dynamic/Exosuit_UpdateAnimations_Patch.cs new file mode 100644 index 0000000000..174e03ed95 --- /dev/null +++ b/NitroxPatcher/Patches/Dynamic/Exosuit_UpdateAnimations_Patch.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using NitroxClient.MonoBehaviours.Vehicles; +using NitroxModel.Helper; +using UnityEngine; + +namespace NitroxPatcher.Patches.Dynamic; + +/// +/// Provide exosuits driven by remote players with the right velocity for the animations to use +/// +public sealed partial class Exosuit_UpdateAnimations_Patch : NitroxPatch, IDynamicPatch +{ + private static readonly MethodInfo TARGET_METHOD = Reflect.Method((Exosuit t) => t.UpdateAnimations()); + + public static void Prefix(Exosuit __instance, out Vector3? __state) + { + if (__instance.TryGetComponent(out ExosuitMovementReplicator exosuitMovementReplicator)) + { + __state = __instance.useRigidbody.velocity; + __instance.useRigidbody.velocity = exosuitMovementReplicator.velocity; + } + else + { + __state = null; + } + } + + public static void Postfix(Exosuit __instance, Vector3? __state) + { + if (__state.HasValue) + { + __instance.useRigidbody.velocity = __state.Value; + } + } +} From 0d4b2d3251467dacbb97656c7bbb55ab5cb55b3d Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Sat, 2 Nov 2024 20:59:13 +0100 Subject: [PATCH 11/20] Fix a possible divide by zero from TimeManager.DeltaTime --- NitroxClient/GameLogic/TimeManager.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/NitroxClient/GameLogic/TimeManager.cs b/NitroxClient/GameLogic/TimeManager.cs index 1ca0eb334e..0cd88091eb 100644 --- a/NitroxClient/GameLogic/TimeManager.cs +++ b/NitroxClient/GameLogic/TimeManager.cs @@ -111,10 +111,11 @@ public void ProcessUpdate(TimeChange packet) latestRegistrationTime = DateTimeOffset.FromUnixTimeMilliseconds(packet.UpdateTime); latestRegisteredTime = packet.CurrentTime; - + + // We don't want to have a big DeltaTime when processing a time skip so we calculate it beforehands + float deltaTimeBefore = DeltaTime; DayNightCycle.main.timePassedAsDouble = CalculateCurrentTime(); - // We don't want to have a big DeltaTime when processing a time skip - DeltaTime = 0; + DeltaTime = deltaTimeBefore; DayNightCycle.main.StopSkipTimeMode(); } @@ -127,6 +128,11 @@ public double CalculateCurrentTime() { double currentTime = CurrentTime; DeltaTime = (float)(currentTime - DayNightCycle.main.timePassedAsDouble); + // DeltaTime = 0 might end up causing a divide by 0 => NaN in some scripts + if (DeltaTime == 0f) + { + DeltaTime = 0.00001f; + } return currentTime; } From 5eed7ed0d34f90ac7f0fab15dd94a864fffaa9f7 Mon Sep 17 00:00:00 2001 From: Jannify <23176718+Jannify@users.noreply.github.com> Date: Mon, 28 Oct 2024 21:03:49 +0100 Subject: [PATCH 12/20] Refactor docking code to bring it up to new standard --- Nitrox.sln.DotSettings | 1 + .../Processors/VehicleDockingProcessor.cs | 90 +++++++----- .../Processors/VehicleUndockingProcessor.cs | 134 ++++++++---------- NitroxClient/GameLogic/Vehicles.cs | 11 +- NitroxModel/Packets/VehicleDocking.cs | 25 ++-- NitroxModel/Packets/VehicleUndocking.cs | 29 ++-- ...ckedVehicleHandTarget_OnHandClick_Patch.cs | 10 +- .../VehicleDockingBay_OnTriggerEnter.cs | 21 +-- ...cleDockingBay_OnUndockingComplete_Patch.cs | 7 +- 9 files changed, 167 insertions(+), 161 deletions(-) diff --git a/Nitrox.sln.DotSettings b/Nitrox.sln.DotSettings index 2acdc6c8c4..37ee7e9f40 100644 --- a/Nitrox.sln.DotSettings +++ b/Nitrox.sln.DotSettings @@ -127,6 +127,7 @@ True True True + True True True True diff --git a/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs b/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs index c7b1576129..a55c6d4b21 100644 --- a/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs @@ -1,5 +1,4 @@ using System.Collections; -using NitroxClient.Communication.Abstract; using NitroxClient.Communication.Packets.Processors.Abstract; using NitroxClient.GameLogic; using NitroxClient.MonoBehaviours; @@ -8,49 +7,72 @@ using NitroxModel.Packets; using UnityEngine; -namespace NitroxClient.Communication.Packets.Processors +namespace NitroxClient.Communication.Packets.Processors; + +public class VehicleDockingProcessor : ClientPacketProcessor { - public class VehicleDockingProcessor : ClientPacketProcessor + private readonly Vehicles vehicles; + + public VehicleDockingProcessor(Vehicles vehicles) + { + this.vehicles = vehicles; + } + + public override void Process(VehicleDocking packet) { - private readonly IPacketSender packetSender; - private readonly Vehicles vehicles; + if (!NitroxEntity.TryGetComponentFrom(packet.VehicleId, out Vehicle vehicle)) + { + Log.Error($"[{nameof(VehicleDockingProcessor)}] could not find Vehicle component on {packet.VehicleId}"); + return; + } + + if (!NitroxEntity.TryGetComponentFrom(packet.DockId, out VehicleDockingBay dockingBay)) + { + Log.Error($"[{nameof(VehicleDockingProcessor)}] could not find VehicleDockingBay component on {packet.DockId}"); + return; + } - public VehicleDockingProcessor(IPacketSender packetSender, Vehicles vehicles) + if (vehicle.TryGetComponent(out MovementReplicator vehicleMovementReplicator)) { - this.packetSender = packetSender; - this.vehicles = vehicles; + Object.Destroy(vehicleMovementReplicator); + Log.Debug($"[{nameof(VehicleDockingProcessor)}] Destroyed MovementReplicator on {packet.VehicleId}"); } - public override void Process(VehicleDocking packet) + //vehicleMovementReplicator.enabled = false; + DockRemoteVehicle(dockingBay, vehicle); + //vehicle.StartCoroutine(DelayAnimationAndDisablePiloting(vehicle, dockingBay, packet.VehicleId, packet.PlayerId)); + vehicles.SetOnPilotMode(packet.VehicleId, packet.PlayerId, false); + } + + /// Copy of without the player centric bits + private void DockRemoteVehicle(VehicleDockingBay bay, Vehicle vehicle) + { + bay.dockedVehicle = vehicle; + LargeWorldStreamer.main.cellManager.UnregisterEntity(bay.dockedVehicle.gameObject); + bay.dockedVehicle.transform.parent = bay.GetSubRoot().transform; + vehicle.docked = true; + bay.vehicle_docked_param = true; + bay.GetSubRoot().BroadcastMessage("UnlockDoors", SendMessageOptions.DontRequireReceiver); + + if (false) // TODO: Should be executed when sym lock on vehicle or cyclops or both, idk { - GameObject vehicleGo = NitroxEntity.RequireObjectFrom(packet.VehicleId); - GameObject vehicleDockingBayGo = NitroxEntity.RequireObjectFrom(packet.DockId); - - Vehicle vehicle = vehicleGo.RequireComponent(); - VehicleDockingBay vehicleDockingBay = vehicleDockingBayGo.RequireComponent(); - - using (PacketSuppressor.Suppress()) - { - Log.Debug($"Set vehicle docked for {vehicleDockingBay.gameObject.name}"); - //vehicle.GetComponent().SetPositionVelocityRotation(vehicle.transform.position, Vector3.zero, vehicle.transform.rotation, Vector3.zero); - //vehicle.GetComponent().Exit(); - } - vehicle.StartCoroutine(DelayAnimationAndDisablePiloting(vehicle, vehicleDockingBay, packet.VehicleId, packet.PlayerId)); + bay.CancelInvoke("RepairVehicle"); + bay.InvokeRepeating("RepairVehicle", 0.0f, 5f); } + } - IEnumerator DelayAnimationAndDisablePiloting(Vehicle vehicle, VehicleDockingBay vehicleDockingBay, NitroxId vehicleId, ushort playerId) + IEnumerator DelayAnimationAndDisablePiloting(Vehicle vehicle, VehicleDockingBay vehicleDockingBay, NitroxId vehicleId, ushort playerId) + { + yield return Yielders.WaitFor1Second; + // DockVehicle sets the rigid body kinematic of the vehicle to true, we don't want that behaviour + // Therefore disable kinematic (again) to remove the bouncing behavior + vehicleDockingBay.DockVehicle(vehicle); + vehicle.useRigidbody.isKinematic = false; + yield return Yielders.WaitFor2Seconds; + vehicles.SetOnPilotMode(vehicleId, playerId, false); + if (!vehicle.docked) { - yield return Yielders.WaitFor1Second; - // DockVehicle sets the rigid body kinematic of the vehicle to true, we don't want that behaviour - // Therefore disable kinematic (again) to remove the bouncing behavior - vehicleDockingBay.DockVehicle(vehicle); - vehicle.useRigidbody.isKinematic = false; - yield return Yielders.WaitFor2Seconds; - vehicles.SetOnPilotMode(vehicleId, playerId, false); - if (!vehicle.docked) - { - Log.Error($"Vehicle {vehicleId} not docked after docking process"); - } + Log.Error($"Vehicle {vehicleId} not docked after docking process"); } } } diff --git a/NitroxClient/Communication/Packets/Processors/VehicleUndockingProcessor.cs b/NitroxClient/Communication/Packets/Processors/VehicleUndockingProcessor.cs index 2f216ab825..271ed361ee 100644 --- a/NitroxClient/Communication/Packets/Processors/VehicleUndockingProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/VehicleUndockingProcessor.cs @@ -1,97 +1,89 @@ using System.Collections; -using NitroxClient.Communication.Abstract; using NitroxClient.Communication.Packets.Processors.Abstract; using NitroxClient.GameLogic; using NitroxClient.MonoBehaviours; using NitroxClient.Unity.Helper; -using NitroxModel.DataStructures.Util; using NitroxModel.Packets; using UnityEngine; -namespace NitroxClient.Communication.Packets.Processors +namespace NitroxClient.Communication.Packets.Processors; + +public class VehicleUndockingProcessor : ClientPacketProcessor { - public class VehicleUndockingProcessor : ClientPacketProcessor - { - private readonly IPacketSender packetSender; - private readonly Vehicles vehicles; - private readonly PlayerManager remotePlayerManager; + private readonly Vehicles vehicles; + private readonly PlayerManager remotePlayerManager; - public VehicleUndockingProcessor(IPacketSender packetSender, Vehicles vehicles, PlayerManager playerManager) - { - this.packetSender = packetSender; - this.vehicles = vehicles; - remotePlayerManager = playerManager; - } + public VehicleUndockingProcessor(Vehicles vehicles, PlayerManager remotePlayerManager) + { + this.vehicles = vehicles; + this.remotePlayerManager = remotePlayerManager; + } - public override void Process(VehicleUndocking packet) - { - GameObject vehicleGo = NitroxEntity.RequireObjectFrom(packet.VehicleId); - GameObject vehicleDockingBayGo = NitroxEntity.RequireObjectFrom(packet.DockId); + public override void Process(VehicleUndocking packet) + { + GameObject vehicleGo = NitroxEntity.RequireObjectFrom(packet.VehicleId); + GameObject vehicleDockingBayGo = NitroxEntity.RequireObjectFrom(packet.DockId); - Vehicle vehicle = vehicleGo.RequireComponent(); - VehicleDockingBay vehicleDockingBay = vehicleDockingBayGo.RequireComponent(); + Vehicle vehicle = vehicleGo.RequireComponent(); + VehicleDockingBay vehicleDockingBay = vehicleDockingBayGo.RequireComponent(); - using (PacketSuppressor.Suppress()) + using (PacketSuppressor.Suppress()) + { + if (packet.UndockingStart) { - if (packet.UndockingStart) - { - StartVehicleUndocking(packet, vehicleGo, vehicle, vehicleDockingBay); - } - else - { - FinishVehicleUndocking(packet, vehicle, vehicleDockingBay); - } + StartVehicleUndocking(packet, vehicleGo, vehicle, vehicleDockingBay); } - } - - private void StartVehicleUndocking(VehicleUndocking packet, GameObject vehicleGo, Vehicle vehicle, VehicleDockingBay vehicleDockingBay) - { - Optional player = remotePlayerManager.Find(packet.PlayerId); - vehicleDockingBay.subRoot.BroadcastMessage("OnLaunchBayOpening", SendMessageOptions.DontRequireReceiver); - SkyEnvironmentChanged.Broadcast(vehicleGo, (GameObject)null); - - if (player.HasValue) + else { - RemotePlayer playerInstance = player.Value; - vehicle.mainAnimator.SetBool("player_in", true); - playerInstance.Attach(vehicle.playerPosition.transform); - // It can happen that the player turns in circles around himself in the vehicle. This stops it. - playerInstance.RigidBody.angularVelocity = Vector3.zero; - playerInstance.ArmsController.SetWorldIKTarget(vehicle.leftHandPlug, vehicle.rightHandPlug); - playerInstance.AnimationController["in_seamoth"] = vehicle is SeaMoth; - playerInstance.AnimationController["in_exosuit"] = playerInstance.AnimationController["using_mechsuit"] = vehicle is Exosuit; - vehicles.SetOnPilotMode(packet.VehicleId, packet.PlayerId, true); - playerInstance.AnimationController.UpdatePlayerAnimations = false; + FinishVehicleUndocking(packet, vehicle, vehicleDockingBay); } - vehicleDockingBay.StartCoroutine(StartUndockingAnimation(vehicleDockingBay)); } + } - public IEnumerator StartUndockingAnimation(VehicleDockingBay vehicleDockingBay) + private void StartVehicleUndocking(VehicleUndocking packet, GameObject vehicleGo, Vehicle vehicle, VehicleDockingBay vehicleDockingBay) + { + vehicleDockingBay.subRoot.BroadcastMessage("OnLaunchBayOpening", SendMessageOptions.DontRequireReceiver); + SkyEnvironmentChanged.Broadcast(vehicleGo, (GameObject)null); + + if (remotePlayerManager.TryFind(packet.PlayerId, out RemotePlayer player)) { - yield return Yielders.WaitFor2Seconds; - vehicleDockingBay.vehicle_docked_param = false; + vehicle.mainAnimator.SetBool("player_in", true); + player.Attach(vehicle.playerPosition.transform); + // It can happen that the player turns in circles around himself in the vehicle. This stops it. + player.RigidBody.angularVelocity = Vector3.zero; + player.ArmsController.SetWorldIKTarget(vehicle.leftHandPlug, vehicle.rightHandPlug); + player.AnimationController["in_seamoth"] = vehicle is SeaMoth; + player.AnimationController["in_exosuit"] = player.AnimationController["using_mechsuit"] = vehicle is Exosuit; + vehicles.SetOnPilotMode(packet.VehicleId, packet.PlayerId, true); + player.AnimationController.UpdatePlayerAnimations = false; } + vehicleDockingBay.StartCoroutine(StartUndockingAnimation(vehicleDockingBay)); + } - private void FinishVehicleUndocking(VehicleUndocking packet, Vehicle vehicle, VehicleDockingBay vehicleDockingBay) + private static IEnumerator StartUndockingAnimation(VehicleDockingBay vehicleDockingBay) + { + yield return Yielders.WaitFor2Seconds; + vehicleDockingBay.vehicle_docked_param = false; + } + + private void FinishVehicleUndocking(VehicleUndocking packet, Vehicle vehicle, VehicleDockingBay vehicleDockingBay) + { + if (vehicleDockingBay.GetSubRoot().isCyclops) { - if (vehicleDockingBay.GetSubRoot().isCyclops) - { - vehicleDockingBay.SetVehicleUndocked(); - } - vehicleDockingBay.dockedVehicle = null; - vehicleDockingBay.CancelInvoke("RepairVehicle"); - vehicle.docked = false; - Optional player = remotePlayerManager.Find(packet.PlayerId); - if (player.HasValue) - { - // Sometimes the player is not set accordingly which stretches the player's model instead of putting them in place - // after undocking. This fixes it (the player rigid body seems to not be set right sometimes) - player.Value.SetSubRoot(null); - player.Value.SetVehicle(null); - player.Value.SetVehicle(vehicle); - } - vehicles.SetOnPilotMode(packet.VehicleId, packet.PlayerId, true); - Log.Debug($"Set vehicle undocking complete"); + vehicleDockingBay.SetVehicleUndocked(); + } + vehicleDockingBay.dockedVehicle = null; + vehicleDockingBay.CancelInvoke(nameof(VehicleDockingBay.RepairVehicle)); + vehicle.docked = false; + if (remotePlayerManager.TryFind(packet.PlayerId, out RemotePlayer player)) + { + // Sometimes the player is not set accordingly which stretches the player's model instead of putting them in place + // after undocking. This fixes it (the player rigid body seems to not be set right sometimes) + player.SetSubRoot(null); + player.SetVehicle(null); + player.SetVehicle(vehicle); } + vehicles.SetOnPilotMode(packet.VehicleId, packet.PlayerId, true); + Log.Debug("Set vehicle undocking complete"); } } diff --git a/NitroxClient/GameLogic/Vehicles.cs b/NitroxClient/GameLogic/Vehicles.cs index 048435db0c..fa48dbe618 100644 --- a/NitroxClient/GameLogic/Vehicles.cs +++ b/NitroxClient/GameLogic/Vehicles.cs @@ -45,14 +45,7 @@ private PilotingChair FindPilotingChairWithCache(GameObject parent, TechType tec else { PilotingChair chair = parent.GetComponentInChildren(true); - if (chair) - { - pilotingChairByTechType.Add(techType, chair.gameObject.GetHierarchyPath(parent)); - } - else - { - pilotingChairByTechType.Add(techType, string.Empty); - } + pilotingChairByTechType.Add(techType, chair ? chair.gameObject.GetHierarchyPath(parent) : string.Empty); return chair; } } @@ -176,7 +169,7 @@ public static void RemoveNitroxEntitiesTagging(GameObject constructedObject) foreach (NitroxEntity nitroxEntity in nitroxEntities) { nitroxEntity.Remove(); - UnityEngine.Object.DestroyImmediate(nitroxEntity); + Object.DestroyImmediate(nitroxEntity); } } diff --git a/NitroxModel/Packets/VehicleDocking.cs b/NitroxModel/Packets/VehicleDocking.cs index e9005bed6d..3757aa4835 100644 --- a/NitroxModel/Packets/VehicleDocking.cs +++ b/NitroxModel/Packets/VehicleDocking.cs @@ -1,20 +1,19 @@ using System; using NitroxModel.DataStructures; -namespace NitroxModel.Packets +namespace NitroxModel.Packets; + +[Serializable] +public class VehicleDocking : Packet { - [Serializable] - public class VehicleDocking : Packet - { - public NitroxId VehicleId { get; } - public NitroxId DockId { get; } - public ushort PlayerId { get; } + public NitroxId VehicleId { get; } + public NitroxId DockId { get; } + public ushort PlayerId { get; } - public VehicleDocking(NitroxId vehicleId, NitroxId dockId, ushort playerId) - { - VehicleId = vehicleId; - DockId = dockId; - PlayerId = playerId; - } + public VehicleDocking(NitroxId vehicleId, NitroxId dockId, ushort playerId) + { + VehicleId = vehicleId; + DockId = dockId; + PlayerId = playerId; } } diff --git a/NitroxModel/Packets/VehicleUndocking.cs b/NitroxModel/Packets/VehicleUndocking.cs index 7b533495d2..e73740e3b7 100644 --- a/NitroxModel/Packets/VehicleUndocking.cs +++ b/NitroxModel/Packets/VehicleUndocking.cs @@ -1,22 +1,21 @@ using System; using NitroxModel.DataStructures; -namespace NitroxModel.Packets +namespace NitroxModel.Packets; + +[Serializable] +public class VehicleUndocking : Packet { - [Serializable] - public class VehicleUndocking : Packet - { - public NitroxId VehicleId { get; } - public NitroxId DockId { get; } - public ushort PlayerId { get; } - public bool UndockingStart { get; } + public NitroxId VehicleId { get; } + public NitroxId DockId { get; } + public ushort PlayerId { get; } + public bool UndockingStart { get; } - public VehicleUndocking(NitroxId vehicleId, NitroxId dockId, ushort playerId, bool undockingStart) - { - VehicleId = vehicleId; - DockId = dockId; - PlayerId = playerId; - UndockingStart = undockingStart; - } + public VehicleUndocking(NitroxId vehicleId, NitroxId dockId, ushort playerId, bool undockingStart) + { + VehicleId = vehicleId; + DockId = dockId; + PlayerId = playerId; + UndockingStart = undockingStart; } } diff --git a/NitroxPatcher/Patches/Dynamic/DockedVehicleHandTarget_OnHandClick_Patch.cs b/NitroxPatcher/Patches/Dynamic/DockedVehicleHandTarget_OnHandClick_Patch.cs index 95ff1cb051..bb394a2216 100644 --- a/NitroxPatcher/Patches/Dynamic/DockedVehicleHandTarget_OnHandClick_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/DockedVehicleHandTarget_OnHandClick_Patch.cs @@ -1,7 +1,6 @@ using System.Reflection; using NitroxClient.GameLogic; using NitroxClient.GameLogic.Simulation; -using NitroxModel.Core; using NitroxModel.DataStructures; using NitroxModel.Helper; @@ -9,7 +8,7 @@ namespace NitroxPatcher.Patches.Dynamic; public sealed partial class DockedVehicleHandTarget_OnHandClick_Patch : NitroxPatch, IDynamicPatch { - public static readonly MethodInfo TARGET_METHOD = Reflect.Method((DockedVehicleHandTarget t) => t.OnHandClick(default(GUIHand))); + private static readonly MethodInfo targetMethod = Reflect.Method((DockedVehicleHandTarget t) => t.OnHandClick(default(GUIHand))); private static bool skipPrefix; @@ -30,18 +29,17 @@ public static bool Prefix(DockedVehicleHandTarget __instance, GUIHand hand) HandInteraction context = new(__instance, hand); LockRequest> lockRequest = new(id, SimulationLockType.EXCLUSIVE, ReceivedSimulationLockResponse, context); - Resolve().RequestSimulationLock(lockRequest); return false; } - private static void ReceivedSimulationLockResponse(NitroxId id, bool lockAquired, HandInteraction context) + private static void ReceivedSimulationLockResponse(NitroxId id, bool lockAcquired, HandInteraction context) { - if (lockAquired) + if (lockAcquired) { VehicleDockingBay dockingBay = context.Target.dockingBay; - NitroxServiceLocator.LocateService().BroadcastVehicleUndocking(dockingBay, dockingBay.GetDockedVehicle(), true); + Resolve().BroadcastVehicleUndocking(dockingBay, dockingBay.GetDockedVehicle(), true); skipPrefix = true; context.Target.OnHandClick(context.GuiHand); diff --git a/NitroxPatcher/Patches/Dynamic/VehicleDockingBay_OnTriggerEnter.cs b/NitroxPatcher/Patches/Dynamic/VehicleDockingBay_OnTriggerEnter.cs index 20626f3650..e5ce97f26c 100644 --- a/NitroxPatcher/Patches/Dynamic/VehicleDockingBay_OnTriggerEnter.cs +++ b/NitroxPatcher/Patches/Dynamic/VehicleDockingBay_OnTriggerEnter.cs @@ -1,38 +1,41 @@ using System.Reflection; +using NitroxClient.Communication.Abstract; using NitroxClient.GameLogic; using NitroxModel.DataStructures; using NitroxModel.DataStructures.Util; using NitroxModel.Helper; +using NitroxModel.Packets; using UnityEngine; namespace NitroxPatcher.Patches.Dynamic; public sealed partial class VehicleDockingBay_OnTriggerEnter : NitroxPatch, IDynamicPatch { - private static readonly MethodInfo TARGET_METHOD = Reflect.Method((VehicleDockingBay t) => t.OnTriggerEnter(default(Collider))); - private static Vehicle prevInterpolatingVehicle; + private static readonly MethodInfo targetMethod = Reflect.Method((VehicleDockingBay t) => t.OnTriggerEnter(default(Collider))); - public static bool Prefix(VehicleDockingBay __instance, Collider other) + public static bool Prefix(VehicleDockingBay __instance, Collider other, ref Vehicle __state) { Vehicle vehicle = other.GetComponentInParent(); - prevInterpolatingVehicle = __instance.interpolatingVehicle; + __state = __instance.interpolatingVehicle; Optional opVehicleId = vehicle.GetId(); return !vehicle || (opVehicleId.HasValue && Resolve().HasAnyLockType(opVehicleId.Value)); } - public static void Postfix(VehicleDockingBay __instance) + public static void Postfix(VehicleDockingBay __instance, ref Vehicle __state) { Vehicle interpolatingVehicle = __instance.interpolatingVehicle; // Only send data, when interpolatingVehicle changes to avoid multiple packages send - if (!interpolatingVehicle || interpolatingVehicle == prevInterpolatingVehicle) + if (!interpolatingVehicle || interpolatingVehicle == __state) { return; } - if (interpolatingVehicle.TryGetIdOrWarn(out NitroxId id) && Resolve().HasAnyLockType(id)) + if (__instance.gameObject.TryGetIdOrWarn(out NitroxId dockId) && + interpolatingVehicle.TryGetIdOrWarn(out NitroxId vehicleId) && + Resolve().HasAnyLockType(vehicleId)) { - Log.Debug($"Will send vehicle docking for {id}"); - Resolve().BroadcastVehicleDocking(__instance, interpolatingVehicle); + Log.Debug($"Will send vehicle docking for {vehicleId}"); //TODO: Debug logging + Resolve().Send(new VehicleDocking(vehicleId, dockId, Resolve().Reservation.PlayerId)); } } } diff --git a/NitroxPatcher/Patches/Dynamic/VehicleDockingBay_OnUndockingComplete_Patch.cs b/NitroxPatcher/Patches/Dynamic/VehicleDockingBay_OnUndockingComplete_Patch.cs index b15ef775c4..40d80c013e 100644 --- a/NitroxPatcher/Patches/Dynamic/VehicleDockingBay_OnUndockingComplete_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/VehicleDockingBay_OnUndockingComplete_Patch.cs @@ -6,11 +6,10 @@ namespace NitroxPatcher.Patches.Dynamic; public sealed partial class VehicleDockingBay_OnUndockingComplete_Patch : NitroxPatch, IDynamicPatch { - private static readonly MethodInfo TARGET_METHOD = Reflect.Method((VehicleDockingBay t) => t.OnUndockingComplete(default(Player))); + private static readonly MethodInfo targetMethod = Reflect.Method((VehicleDockingBay t) => t.OnUndockingComplete(default(Player))); - public static void Prefix(VehicleDockingBay __instance, Player player) + public static void Prefix(VehicleDockingBay __instance) { - Vehicle vehicle = __instance.GetDockedVehicle(); - Resolve().BroadcastVehicleUndocking(__instance, vehicle, false); + Resolve().BroadcastVehicleUndocking(__instance, __instance.GetDockedVehicle(), false); } } From a4c41ef66cb59e8987f62f8ae08743d0ba91684e Mon Sep 17 00:00:00 2001 From: Jannify <23176718+Jannify@users.noreply.github.com> Date: Sat, 2 Nov 2024 15:29:44 +0100 Subject: [PATCH 13/20] Decouple docking patches from Vehicles.cs --- NitroxClient/GameLogic/Vehicles.cs | 75 ++++++------------- ...ckedVehicleHandTarget_OnHandClick_Patch.cs | 22 ++++-- .../VehicleDockingBay_OnTriggerEnter.cs | 1 + ...cleDockingBay_OnUndockingComplete_Patch.cs | 12 ++- 4 files changed, 48 insertions(+), 62 deletions(-) diff --git a/NitroxClient/GameLogic/Vehicles.cs b/NitroxClient/GameLogic/Vehicles.cs index fa48dbe618..6ba771f115 100644 --- a/NitroxClient/GameLogic/Vehicles.cs +++ b/NitroxClient/GameLogic/Vehicles.cs @@ -59,64 +59,31 @@ public void BroadcastDestroyedVehicle(NitroxId id) } } - public void BroadcastVehicleDocking(VehicleDockingBay dockingBay, Vehicle vehicle) + public static void EngagePlayerMovementProcessor(Vehicle vehicle) { - if (!dockingBay.gameObject.TryGetIdOrWarn(out NitroxId dockId)) - { - return; - } - if (!vehicle.gameObject.TryGetIdOrWarn(out NitroxId vehicleId)) - { - return; - } - - VehicleDocking packet = new VehicleDocking(vehicleId, dockId, multiplayerSession.Reservation.PlayerId); - packetSender.Send(packet); - - PacketSuppressor playerMovementSuppressor = PacketSuppressor.Suppress(); // TODO: Properly prevent the vehicle from sending position update as long as it's not free from the animation - vehicle.StartCoroutine(AllowMovementPacketsAfterDockingAnimation(playerMovementSuppressor)); - } - - public void BroadcastVehicleUndocking(VehicleDockingBay dockingBay, Vehicle vehicle, bool undockingStart) - { - if (!dockingBay.TryGetIdOrWarn(out NitroxId dockId)) - { - return; - } - if (!vehicle.TryGetIdOrWarn(out NitroxId vehicleId)) - { - return; - } - - PacketSuppressor movementSuppressor = PacketSuppressor.Suppress(); - // TODO: Properly prevent the vehicle from sending position update as long as it's not free from the animation - if (!undockingStart) + PacketSuppressor playerMovementSuppressor = PacketSuppressor.Suppress(); + vehicle.StartCoroutine(AllowMovementPacketsAfterDockingAnimation()); + return; + + /* + A poorly timed movement packet will cause major problems when docking because the remote + player will think that the player is no longer in a vehicle. Unfortunately, the game calls + the vehicle exit code before the animation completes so we need to suppress any side effects. + Two thing we want to protect against: + + 1) If a movement packet is received when docking, the player might exit the vehicle early, + and it will show them sitting outside the vehicle during the docking animation. + + 2) If a movement packet is received when undocking, the player game object will be stuck in + place until after the player exits the vehicle. This causes the player body to stretch to + the current cyclops position. + */ + IEnumerator AllowMovementPacketsAfterDockingAnimation() { - movementSuppressor.Dispose(); + yield return Yielders.WaitFor3Seconds; + playerMovementSuppressor.Dispose(); } - - VehicleUndocking packet = new VehicleUndocking(vehicleId, dockId, multiplayerSession.Reservation.PlayerId, undockingStart); - packetSender.Send(packet); - } - - /* - A poorly timed movement packet will cause major problems when docking because the remote - player will think that the player is no longer in a vehicle. Unfortunetly, the game calls - the vehicle exit code before the animation completes so we need to suppress any side affects. - Two thing we want to protect against: - - 1) If a movement packet is received when docking, the player might exit the vehicle early - and it will show them sitting outside the vehicle during the docking animation. - - 2) If a movement packet is received when undocking, the player game object will be stuck in - place until after the player exits the vehicle. This causes the player body to strech to - the current cyclops position. - */ - public IEnumerator AllowMovementPacketsAfterDockingAnimation(PacketSuppressor playerMovementSuppressor) - { - yield return Yielders.WaitFor3Seconds; - playerMovementSuppressor.Dispose(); } public void BroadcastOnPilotModeChanged(GameObject gameObject, bool isPiloting) diff --git a/NitroxPatcher/Patches/Dynamic/DockedVehicleHandTarget_OnHandClick_Patch.cs b/NitroxPatcher/Patches/Dynamic/DockedVehicleHandTarget_OnHandClick_Patch.cs index bb394a2216..49fd736259 100644 --- a/NitroxPatcher/Patches/Dynamic/DockedVehicleHandTarget_OnHandClick_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/DockedVehicleHandTarget_OnHandClick_Patch.cs @@ -1,8 +1,10 @@ using System.Reflection; +using NitroxClient.Communication.Abstract; using NitroxClient.GameLogic; using NitroxClient.GameLogic.Simulation; using NitroxModel.DataStructures; using NitroxModel.Helper; +using NitroxModel.Packets; namespace NitroxPatcher.Patches.Dynamic; @@ -16,30 +18,38 @@ public static bool Prefix(DockedVehicleHandTarget __instance, GUIHand hand) { Vehicle vehicle = __instance.dockingBay.GetDockedVehicle(); - if (skipPrefix || !vehicle.TryGetIdOrWarn(out NitroxId id)) + if (skipPrefix || !vehicle.TryGetIdOrWarn(out NitroxId vehicleId)) { return true; } - if (Resolve().HasExclusiveLock(id)) + if (Resolve().HasExclusiveLock(vehicleId)) { - Log.Debug($"Already have an exclusive lock on this vehicle: {id}"); + Log.Debug($"Already have an exclusive lock on this vehicle: {vehicleId}"); return true; } HandInteraction context = new(__instance, hand); - LockRequest> lockRequest = new(id, SimulationLockType.EXCLUSIVE, ReceivedSimulationLockResponse, context); + LockRequest> lockRequest = new(vehicleId, SimulationLockType.EXCLUSIVE, ReceivedSimulationLockResponse, context); Resolve().RequestSimulationLock(lockRequest); return false; } - private static void ReceivedSimulationLockResponse(NitroxId id, bool lockAcquired, HandInteraction context) + private static void ReceivedSimulationLockResponse(NitroxId vehicleId, bool lockAcquired, HandInteraction context) { if (lockAcquired) { VehicleDockingBay dockingBay = context.Target.dockingBay; - Resolve().BroadcastVehicleUndocking(dockingBay, dockingBay.GetDockedVehicle(), true); + Vehicle vehicle = dockingBay.GetDockedVehicle(); + + if (!dockingBay.TryGetIdOrWarn(out NitroxId dockId)) + { + return; + } + + Vehicles.EngagePlayerMovementProcessor(vehicle); + Resolve().Send(new VehicleUndocking(vehicleId, dockId, Resolve().Reservation.PlayerId, true)); skipPrefix = true; context.Target.OnHandClick(context.GuiHand); diff --git a/NitroxPatcher/Patches/Dynamic/VehicleDockingBay_OnTriggerEnter.cs b/NitroxPatcher/Patches/Dynamic/VehicleDockingBay_OnTriggerEnter.cs index e5ce97f26c..6b2ebffab4 100644 --- a/NitroxPatcher/Patches/Dynamic/VehicleDockingBay_OnTriggerEnter.cs +++ b/NitroxPatcher/Patches/Dynamic/VehicleDockingBay_OnTriggerEnter.cs @@ -35,6 +35,7 @@ public static void Postfix(VehicleDockingBay __instance, ref Vehicle __state) Resolve().HasAnyLockType(vehicleId)) { Log.Debug($"Will send vehicle docking for {vehicleId}"); //TODO: Debug logging + Vehicles.EngagePlayerMovementProcessor(interpolatingVehicle); Resolve().Send(new VehicleDocking(vehicleId, dockId, Resolve().Reservation.PlayerId)); } } diff --git a/NitroxPatcher/Patches/Dynamic/VehicleDockingBay_OnUndockingComplete_Patch.cs b/NitroxPatcher/Patches/Dynamic/VehicleDockingBay_OnUndockingComplete_Patch.cs index 40d80c013e..e8824fd7e1 100644 --- a/NitroxPatcher/Patches/Dynamic/VehicleDockingBay_OnUndockingComplete_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/VehicleDockingBay_OnUndockingComplete_Patch.cs @@ -1,6 +1,8 @@ using System.Reflection; -using NitroxClient.GameLogic; +using NitroxClient.Communication.Abstract; +using NitroxModel.DataStructures; using NitroxModel.Helper; +using NitroxModel.Packets; namespace NitroxPatcher.Patches.Dynamic; @@ -10,6 +12,12 @@ public sealed partial class VehicleDockingBay_OnUndockingComplete_Patch : Nitrox public static void Prefix(VehicleDockingBay __instance) { - Resolve().BroadcastVehicleUndocking(__instance, __instance.GetDockedVehicle(), false); + if (!__instance.TryGetIdOrWarn(out NitroxId dockId) || + !__instance.GetDockedVehicle().TryGetIdOrWarn(out NitroxId vehicleId)) + { + return; + } + + Resolve().Send(new VehicleUndocking(vehicleId, dockId, Resolve().Reservation.PlayerId, false)); } } From f145902125922b9d697b73e0330572ad54779aea Mon Sep 17 00:00:00 2001 From: Jannify <23176718+Jannify@users.noreply.github.com> Date: Sat, 2 Nov 2024 15:57:20 +0100 Subject: [PATCH 14/20] Supress MovementReplicator during (un)docking --- .../Packets/Processors/VehicleDockingProcessor.cs | 6 +++--- .../Packets/Processors/VehicleUndockingProcessor.cs | 13 +++++++++++++ NitroxClient/MonoBehaviours/MovementReplicator.cs | 8 +++++--- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs b/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs index a55c6d4b21..65c50586d1 100644 --- a/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs @@ -34,11 +34,11 @@ public override void Process(VehicleDocking packet) if (vehicle.TryGetComponent(out MovementReplicator vehicleMovementReplicator)) { - Object.Destroy(vehicleMovementReplicator); - Log.Debug($"[{nameof(VehicleDockingProcessor)}] Destroyed MovementReplicator on {packet.VehicleId}"); + vehicleMovementReplicator.enabled = false; + Log.Debug($"[{nameof(VehicleDockingProcessor)}] Disabled MovementReplicator on {packet.VehicleId}"); } - //vehicleMovementReplicator.enabled = false; + //DockRemoteVehicle(dockingBay, vehicle); DockRemoteVehicle(dockingBay, vehicle); //vehicle.StartCoroutine(DelayAnimationAndDisablePiloting(vehicle, dockingBay, packet.VehicleId, packet.PlayerId)); vehicles.SetOnPilotMode(packet.VehicleId, packet.PlayerId, false); diff --git a/NitroxClient/Communication/Packets/Processors/VehicleUndockingProcessor.cs b/NitroxClient/Communication/Packets/Processors/VehicleUndockingProcessor.cs index 271ed361ee..0a7a212aef 100644 --- a/NitroxClient/Communication/Packets/Processors/VehicleUndockingProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/VehicleUndockingProcessor.cs @@ -58,6 +58,12 @@ private void StartVehicleUndocking(VehicleUndocking packet, GameObject vehicleGo player.AnimationController.UpdatePlayerAnimations = false; } vehicleDockingBay.StartCoroutine(StartUndockingAnimation(vehicleDockingBay)); + + if (vehicle.TryGetComponent(out MovementReplicator vehicleMovementReplicator)) + { + vehicleMovementReplicator.ClearBuffer(); + Log.Debug($"[{nameof(VehicleDockingProcessor)}] Clear MovementReplicator on {packet.VehicleId}"); + } } private static IEnumerator StartUndockingAnimation(VehicleDockingBay vehicleDockingBay) @@ -84,6 +90,13 @@ private void FinishVehicleUndocking(VehicleUndocking packet, Vehicle vehicle, Ve player.SetVehicle(vehicle); } vehicles.SetOnPilotMode(packet.VehicleId, packet.PlayerId, true); + + if (vehicle.TryGetComponent(out MovementReplicator vehicleMovementReplicator)) + { + vehicleMovementReplicator.enabled = true; + Log.Debug($"[{nameof(VehicleDockingProcessor)}] Enabled MovementReplicator on {packet.VehicleId}"); + } + Log.Debug("Set vehicle undocking complete"); } } diff --git a/NitroxClient/MonoBehaviours/MovementReplicator.cs b/NitroxClient/MonoBehaviours/MovementReplicator.cs index c6442bd1f6..c717ebe911 100644 --- a/NitroxClient/MonoBehaviours/MovementReplicator.cs +++ b/NitroxClient/MonoBehaviours/MovementReplicator.cs @@ -80,6 +80,8 @@ public void AddSnapshot(MovementData movementData, float time) buffer.AddLast(new Snapshot(movementData, occurrenceTime)); } + public void ClearBuffer() => buffer.Clear(); + public void Start() { if (!gameObject.TryGetNitroxId(out objectId)) @@ -148,7 +150,7 @@ public void Update() { return; } - + // Purging the next nodes if they should have already happened (we still have an expiration margin for the first node so it's fine) while (firstNode.Next != null && !firstNode.Next.Value.IsOlderThan(currentTime)) { @@ -157,7 +159,7 @@ public void Update() } LinkedListNode nextNode = firstNode.Next; - + // Current node is fine but there's no next node (waiting for it without dropping current) if (nextNode == null) { @@ -187,7 +189,7 @@ public void Update() public record struct Snapshot(MovementData Data, float Time) { public bool IsOlderThan(float currentTime) => currentTime < Time; - + public bool IsExpired(float currentTime) => currentTime > Time + SNAPSHOT_EXPIRATION_TIME; } From d9ed3dbcbdba5a0e032b9618426b1c77f3708e32 Mon Sep 17 00:00:00 2001 From: Jannify <23176718+Jannify@users.noreply.github.com> Date: Sat, 2 Nov 2024 16:04:14 +0100 Subject: [PATCH 15/20] Fix docking processor --- .../Processors/VehicleDockingProcessor.cs | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs b/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs index 65c50586d1..f3695c4c18 100644 --- a/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs @@ -38,14 +38,22 @@ public override void Process(VehicleDocking packet) Log.Debug($"[{nameof(VehicleDockingProcessor)}] Disabled MovementReplicator on {packet.VehicleId}"); } - //DockRemoteVehicle(dockingBay, vehicle); - DockRemoteVehicle(dockingBay, vehicle); - //vehicle.StartCoroutine(DelayAnimationAndDisablePiloting(vehicle, dockingBay, packet.VehicleId, packet.PlayerId)); - vehicles.SetOnPilotMode(packet.VehicleId, packet.PlayerId, false); + vehicle.StartCoroutine(DelayAnimationAndDisablePiloting(vehicle, dockingBay, packet.VehicleId, packet.PlayerId)); + } + + private IEnumerator DelayAnimationAndDisablePiloting(Vehicle vehicle, VehicleDockingBay vehicleDockingBay, NitroxId vehicleId, ushort playerId) + { + yield return Yielders.WaitFor1Second; + // DockVehicle sets the rigid body kinematic of the vehicle to true, we don't want that behaviour + // Therefore disable kinematic (again) to remove the bouncing behavior + DockRemoteVehicle(vehicleDockingBay, vehicle); + vehicle.useRigidbody.isKinematic = false; + yield return Yielders.WaitFor2Seconds; + vehicles.SetOnPilotMode(vehicleId, playerId, false); } /// Copy of without the player centric bits - private void DockRemoteVehicle(VehicleDockingBay bay, Vehicle vehicle) + private static void DockRemoteVehicle(VehicleDockingBay bay, Vehicle vehicle) { bay.dockedVehicle = vehicle; LargeWorldStreamer.main.cellManager.UnregisterEntity(bay.dockedVehicle.gameObject); @@ -60,19 +68,4 @@ private void DockRemoteVehicle(VehicleDockingBay bay, Vehicle vehicle) bay.InvokeRepeating("RepairVehicle", 0.0f, 5f); } } - - IEnumerator DelayAnimationAndDisablePiloting(Vehicle vehicle, VehicleDockingBay vehicleDockingBay, NitroxId vehicleId, ushort playerId) - { - yield return Yielders.WaitFor1Second; - // DockVehicle sets the rigid body kinematic of the vehicle to true, we don't want that behaviour - // Therefore disable kinematic (again) to remove the bouncing behavior - vehicleDockingBay.DockVehicle(vehicle); - vehicle.useRigidbody.isKinematic = false; - yield return Yielders.WaitFor2Seconds; - vehicles.SetOnPilotMode(vehicleId, playerId, false); - if (!vehicle.docked) - { - Log.Error($"Vehicle {vehicleId} not docked after docking process"); - } - } } From 8f38ff0bae62f05b7dc36fbef0bbfe655dc91ce6 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Sun, 3 Nov 2024 13:18:35 +0100 Subject: [PATCH 16/20] Few corrections for docking and simulation ownership --- .../Processors/SpawnEntitiesProcessor.cs | 13 ++++++++- .../Processors/VehicleDockingProcessor.cs | 29 ++++++++++++++----- .../Processors/VehicleUndockingProcessor.cs | 8 +---- NitroxClient/GameLogic/Entities.cs | 5 ++++ NitroxClient/GameLogic/RemotePlayer.cs | 4 +++ NitroxClient/GameLogic/SimulationOwnership.cs | 29 +++++++++++++++++-- .../MonoBehaviours/MovementReplicator.cs | 2 +- NitroxModel/DataStructures/SimulatedEntity.cs | 4 +-- NitroxModel/Packets/SpawnEntities.cs | 23 +++++---------- .../EntitySpawnedByClientProcessor.cs | 5 ++-- .../GameLogic/Entities/EntitySimulation.cs | 6 +++- NitroxServer/GameLogic/SimulationOwnership.cs | 12 ++++++-- 12 files changed, 97 insertions(+), 43 deletions(-) diff --git a/NitroxClient/Communication/Packets/Processors/SpawnEntitiesProcessor.cs b/NitroxClient/Communication/Packets/Processors/SpawnEntitiesProcessor.cs index 4fc26b996e..5fe475cde6 100644 --- a/NitroxClient/Communication/Packets/Processors/SpawnEntitiesProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/SpawnEntitiesProcessor.cs @@ -1,5 +1,6 @@ using NitroxClient.Communication.Packets.Processors.Abstract; using NitroxClient.GameLogic; +using NitroxModel.DataStructures; using NitroxModel.Packets; namespace NitroxClient.Communication.Packets.Processors; @@ -7,10 +8,12 @@ namespace NitroxClient.Communication.Packets.Processors; public class SpawnEntitiesProcessor : ClientPacketProcessor { private readonly Entities entities; + private readonly SimulationOwnership simulationOwnership; - public SpawnEntitiesProcessor(Entities entities) + public SpawnEntitiesProcessor(Entities entities, SimulationOwnership simulationOwnership) { this.entities = entities; + this.simulationOwnership = simulationOwnership; } public override void Process(SpawnEntities packet) @@ -22,6 +25,14 @@ public override void Process(SpawnEntities packet) if (packet.Entities.Count > 0) { + if (packet.Simulations != null) + { + foreach (SimulatedEntity simulatedEntity in packet.Simulations) + { + simulationOwnership.RegisterNewerSimulation(simulatedEntity.Id, simulatedEntity); + } + } + // Packet processing is done in the main thread so there's no issue calling this entities.EnqueueEntitiesToSpawn(packet.Entities); } diff --git a/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs b/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs index f3695c4c18..83efbb45cd 100644 --- a/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs @@ -2,6 +2,7 @@ using NitroxClient.Communication.Packets.Processors.Abstract; using NitroxClient.GameLogic; using NitroxClient.MonoBehaviours; +using NitroxClient.MonoBehaviours.Vehicles; using NitroxClient.Unity.Helper; using NitroxModel.DataStructures; using NitroxModel.Packets; @@ -43,29 +44,41 @@ public override void Process(VehicleDocking packet) private IEnumerator DelayAnimationAndDisablePiloting(Vehicle vehicle, VehicleDockingBay vehicleDockingBay, NitroxId vehicleId, ushort playerId) { - yield return Yielders.WaitFor1Second; + // Consider the vehicle movement latency (we don't teleport the vehicle to the docking position) + if (vehicle.TryGetComponent(out VehicleMovementReplicator vehicleMovementReplicator)) + { + // NB: We don't have a lifetime ahead of us + float waitTime = Mathf.Clamp(vehicleMovementReplicator.maxAllowedLatency, 0f, 2f); + yield return new WaitForSeconds(waitTime); + } + else + { + yield return Yielders.WaitFor1Second; + } + // DockVehicle sets the rigid body kinematic of the vehicle to true, we don't want that behaviour // Therefore disable kinematic (again) to remove the bouncing behavior DockRemoteVehicle(vehicleDockingBay, vehicle); vehicle.useRigidbody.isKinematic = false; + yield return Yielders.WaitFor2Seconds; vehicles.SetOnPilotMode(vehicleId, playerId, false); } /// Copy of without the player centric bits - private static void DockRemoteVehicle(VehicleDockingBay bay, Vehicle vehicle) + private void DockRemoteVehicle(VehicleDockingBay bay, Vehicle vehicle) { bay.dockedVehicle = vehicle; LargeWorldStreamer.main.cellManager.UnregisterEntity(bay.dockedVehicle.gameObject); bay.dockedVehicle.transform.parent = bay.GetSubRoot().transform; vehicle.docked = true; bay.vehicle_docked_param = true; + SkyEnvironmentChanged.Broadcast(vehicle.gameObject, bay.subRoot); bay.GetSubRoot().BroadcastMessage("UnlockDoors", SendMessageOptions.DontRequireReceiver); - - if (false) // TODO: Should be executed when sym lock on vehicle or cyclops or both, idk - { - bay.CancelInvoke("RepairVehicle"); - bay.InvokeRepeating("RepairVehicle", 0.0f, 5f); - } + + // We are only actually adding the health if we have a lock on the vehicle so we're fine to keep this routine going on. + // If vehicle ownership changes then it'll still be fine because the verification will still be on the vehicle ownership. + bay.CancelInvoke(nameof(VehicleDockingBay.RepairVehicle)); + bay.InvokeRepeating(nameof(VehicleDockingBay.RepairVehicle), 0.0f, 5f); } } diff --git a/NitroxClient/Communication/Packets/Processors/VehicleUndockingProcessor.cs b/NitroxClient/Communication/Packets/Processors/VehicleUndockingProcessor.cs index 0a7a212aef..741fd31827 100644 --- a/NitroxClient/Communication/Packets/Processors/VehicleUndockingProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/VehicleUndockingProcessor.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using NitroxClient.Communication.Packets.Processors.Abstract; using NitroxClient.GameLogic; using NitroxClient.MonoBehaviours; @@ -47,15 +47,9 @@ private void StartVehicleUndocking(VehicleUndocking packet, GameObject vehicleGo if (remotePlayerManager.TryFind(packet.PlayerId, out RemotePlayer player)) { - vehicle.mainAnimator.SetBool("player_in", true); - player.Attach(vehicle.playerPosition.transform); // It can happen that the player turns in circles around himself in the vehicle. This stops it. player.RigidBody.angularVelocity = Vector3.zero; - player.ArmsController.SetWorldIKTarget(vehicle.leftHandPlug, vehicle.rightHandPlug); - player.AnimationController["in_seamoth"] = vehicle is SeaMoth; - player.AnimationController["in_exosuit"] = player.AnimationController["using_mechsuit"] = vehicle is Exosuit; vehicles.SetOnPilotMode(packet.VehicleId, packet.PlayerId, true); - player.AnimationController.UpdatePlayerAnimations = false; } vehicleDockingBay.StartCoroutine(StartUndockingAnimation(vehicleDockingBay)); diff --git a/NitroxClient/GameLogic/Entities.cs b/NitroxClient/GameLogic/Entities.cs index ffa21a4ef8..9c7e41296b 100644 --- a/NitroxClient/GameLogic/Entities.cs +++ b/NitroxClient/GameLogic/Entities.cs @@ -29,6 +29,7 @@ public class Entities private readonly IPacketSender packetSender; private readonly ThrottledPacketSender throttledPacketSender; private readonly EntityMetadataManager entityMetadataManager; + private readonly SimulationOwnership simulationOwnership; private readonly Dictionary spawnedAsType = new(); private readonly Dictionary> pendingParentEntitiesByParentId = new Dictionary>(); @@ -39,12 +40,14 @@ public class Entities private bool spawningEntities; private readonly HashSet deletedEntitiesIds = new(); + private readonly List pendingSimulatedEntities = new(); public Entities(IPacketSender packetSender, ThrottledPacketSender throttledPacketSender, EntityMetadataManager entityMetadataManager, PlayerManager playerManager, ILocalNitroxPlayer localPlayer, LiveMixinManager liveMixinManager, TimeManager timeManager, SimulationOwnership simulationOwnership) { this.packetSender = packetSender; this.throttledPacketSender = throttledPacketSender; this.entityMetadataManager = entityMetadataManager; + this.simulationOwnership = simulationOwnership; EntitiesToSpawn = new(); entitySpawnersByType[typeof(PrefabChildEntity)] = new PrefabChildEntitySpawner(); @@ -133,6 +136,7 @@ private IEnumerator SpawnNewEntities() { entityMetadataManager.ClearNewerMetadata(); deletedEntitiesIds.Clear(); + simulationOwnership.ClearNewerSimulations(); } } @@ -209,6 +213,7 @@ public IEnumerator SpawnBatchAsync(List batch, bool forceRespawn = false } entityMetadataManager.ApplyMetadata(entityResult.Get().Value, entity.Metadata); + simulationOwnership.ApplyNewerSimulation(entity.Id); MarkAsSpawned(entity); diff --git a/NitroxClient/GameLogic/RemotePlayer.cs b/NitroxClient/GameLogic/RemotePlayer.cs index 758d4e10ac..2b0a968331 100644 --- a/NitroxClient/GameLogic/RemotePlayer.cs +++ b/NitroxClient/GameLogic/RemotePlayer.cs @@ -213,6 +213,8 @@ public void SetPilotingChair(PilotingChair newPilotingChair) { SkyEnvironmentChanged.Broadcast(Body, SubRoot); } + + AnimationController.UpdatePlayerAnimations = false; } else { @@ -312,6 +314,8 @@ public void SetVehicle(Vehicle newVehicle) newVehicle.gameObject.EnsureComponent().Enter(this); break; } + + AnimationController.UpdatePlayerAnimations = false; } bool isKinematic = newVehicle; diff --git a/NitroxClient/GameLogic/SimulationOwnership.cs b/NitroxClient/GameLogic/SimulationOwnership.cs index b8caa5b182..003aaad080 100644 --- a/NitroxClient/GameLogic/SimulationOwnership.cs +++ b/NitroxClient/GameLogic/SimulationOwnership.cs @@ -15,6 +15,8 @@ public class SimulationOwnership private readonly Dictionary simulatedIdsByLockType = new Dictionary(); private readonly Dictionary lockRequestsById = new Dictionary(); + private readonly Dictionary newerSimulationById = []; + public SimulationOwnership(IMultiplayerSession muliplayerSession, IPacketSender packetSender) { this.multiplayerSession = muliplayerSession; @@ -58,7 +60,7 @@ public void ReceivedSimulationLockResponse(NitroxId id, bool lockAquired, Simula if (lockAquired) { SimulateEntity(id, lockType); - TreatVehicleEntity(id, true); + TreatVehicleEntity(id, true, lockType); } if (lockRequestsById.TryGetValue(id, out LockRequestBase lockRequest)) @@ -82,7 +84,7 @@ public void TreatSimulatedEntity(SimulatedEntity simulatedEntity) { bool isLocalPlayerNewOwner = multiplayerSession.Reservation.PlayerId == simulatedEntity.PlayerId; - if (TreatVehicleEntity(simulatedEntity.Id, isLocalPlayerNewOwner)) + if (TreatVehicleEntity(simulatedEntity.Id, isLocalPlayerNewOwner, simulatedEntity.LockType)) { return; } @@ -125,7 +127,7 @@ public bool TryGetLockType(NitroxId nitroxId, out SimulationLockType simulationL return simulatedIdsByLockType.TryGetValue(nitroxId, out simulationLockType); } - private bool TreatVehicleEntity(NitroxId entityId, bool isLocalPlayerNewOwner) + private bool TreatVehicleEntity(NitroxId entityId, bool isLocalPlayerNewOwner, SimulationLockType simulationLockType) { if (!NitroxEntity.TryGetObjectFrom(entityId, out GameObject gameObject) || !IsVehicle(gameObject)) { @@ -140,6 +142,7 @@ private bool TreatVehicleEntity(NitroxId entityId, bool isLocalPlayerNewOwner) GameObject.Destroy(movementReplicator); } MovementBroadcaster.RegisterWatched(gameObject, entityId); + SimulateEntity(entityId, simulationLockType); } else { @@ -148,6 +151,7 @@ private bool TreatVehicleEntity(NitroxId entityId, bool isLocalPlayerNewOwner) MovementReplicator.AddReplicatorToObject(gameObject); } MovementBroadcaster.UnregisterWatched(entityId); + StopSimulatingEntity(entityId); } return true; @@ -166,5 +170,24 @@ public bool IsVehicle(GameObject gameObject) return false; } + + public void RegisterNewerSimulation(NitroxId entityId, SimulatedEntity simulatedEntity) + { + newerSimulationById[entityId] = simulatedEntity; + } + + public void ApplyNewerSimulation(NitroxId nitroxId) + { + if (newerSimulationById.TryGetValue(nitroxId, out SimulatedEntity simulatedEntity)) + { + TreatSimulatedEntity(simulatedEntity); + newerSimulationById.Remove(nitroxId); + } + } + + public void ClearNewerSimulations() + { + newerSimulationById.Clear(); + } } } diff --git a/NitroxClient/MonoBehaviours/MovementReplicator.cs b/NitroxClient/MonoBehaviours/MovementReplicator.cs index c717ebe911..c7976f91a8 100644 --- a/NitroxClient/MonoBehaviours/MovementReplicator.cs +++ b/NitroxClient/MonoBehaviours/MovementReplicator.cs @@ -21,7 +21,7 @@ public abstract class MovementReplicator : MonoBehaviour /// Big increments and any decrements of this value will likely cause stutter, so we try to avoid changing this value too much. /// But it is required that after a lag spike, we eventually lower down that value, which is done periodically . ///
- private float maxAllowedLatency; + public float maxAllowedLatency; private float latestLatencyBumpTime; private float maxLatencyDetectedRecently; diff --git a/NitroxModel/DataStructures/SimulatedEntity.cs b/NitroxModel/DataStructures/SimulatedEntity.cs index a49e503751..8511353f8f 100644 --- a/NitroxModel/DataStructures/SimulatedEntity.cs +++ b/NitroxModel/DataStructures/SimulatedEntity.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace NitroxModel.DataStructures { @@ -27,7 +27,7 @@ public SimulatedEntity(NitroxId id, ushort playerId, bool changesPosition, Simul public override string ToString() { - return $"[SimulatedEntity Id: '{Id}' PlayerId: {PlayerId} IsEntity: {ChangesPosition} LockType: {LockType}]"; + return $"[SimulatedEntity Id: {Id}, PlayerId: {PlayerId}, ChangesPosition: {ChangesPosition}, LockType: {LockType}]"; } } } diff --git a/NitroxModel/Packets/SpawnEntities.cs b/NitroxModel/Packets/SpawnEntities.cs index e9b43ab020..a74c269593 100644 --- a/NitroxModel/Packets/SpawnEntities.cs +++ b/NitroxModel/Packets/SpawnEntities.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using NitroxModel.DataStructures; using NitroxModel.DataStructures.GameLogic; namespace NitroxModel.Packets @@ -8,6 +9,7 @@ namespace NitroxModel.Packets public class SpawnEntities : Packet { public List Entities { get; } + public List Simulations { get; } public bool ForceRespawn { get; } @@ -17,31 +19,20 @@ public SpawnEntities(List entities) ForceRespawn = false; } - public SpawnEntities(Entity entity) + public SpawnEntities(Entity entity, bool forceRespawn = false, SimulatedEntity simulatedEntity = null) { - Entities = new List - { - entity - }; - - ForceRespawn = false; - } - - public SpawnEntities(Entity entity, bool forceRespawn) - { - Entities = new List - { - entity - }; + Entities = [entity]; + Simulations = [simulatedEntity]; ForceRespawn = forceRespawn; } // Constructor for serialization. - public SpawnEntities(List entities, bool forceRespawn) + public SpawnEntities(List entities, bool forceRespawn, List simulations) { Entities = entities; ForceRespawn = forceRespawn; + Simulations = simulations; } } } diff --git a/NitroxServer/Communication/Packets/Processors/EntitySpawnedByClientProcessor.cs b/NitroxServer/Communication/Packets/Processors/EntitySpawnedByClientProcessor.cs index 1da54ba7f8..bd00e18acc 100644 --- a/NitroxServer/Communication/Packets/Processors/EntitySpawnedByClientProcessor.cs +++ b/NitroxServer/Communication/Packets/Processors/EntitySpawnedByClientProcessor.cs @@ -31,20 +31,21 @@ public override void Process(EntitySpawnedByClient packet, Player playerWhoSpawn // may have an item in their inventory (that the registry knows about) then wants to spawn it into the world. entityRegistry.AddOrUpdate(entity); + SimulatedEntity simulatedEntity = null; if (entity is WorldEntity worldEntity) { worldEntityManager.TrackEntityInTheWorld(worldEntity); if (packet.RequireSimulation) { - SimulatedEntity simulatedEntity = entitySimulation.AssignNewEntityToPlayer(entity, playerWhoSpawned); + simulatedEntity = entitySimulation.AssignNewEntityToPlayer(entity, playerWhoSpawned); SimulationOwnershipChange ownershipChangePacket = new SimulationOwnershipChange(simulatedEntity); playerManager.SendPacketToAllPlayers(ownershipChangePacket); } } - SpawnEntities spawnEntities = new(entity, packet.RequireRespawn); + SpawnEntities spawnEntities = new(entity, packet.RequireRespawn, simulatedEntity); foreach (Player player in playerManager.GetConnectedPlayers()) { bool isOtherPlayer = player != playerWhoSpawned; diff --git a/NitroxServer/GameLogic/Entities/EntitySimulation.cs b/NitroxServer/GameLogic/Entities/EntitySimulation.cs index 52be3cda49..fb286d17d4 100644 --- a/NitroxServer/GameLogic/Entities/EntitySimulation.cs +++ b/NitroxServer/GameLogic/Entities/EntitySimulation.cs @@ -88,8 +88,12 @@ public List AssignGlobalRootEntitiesAndGetData(Player player) foreach (GlobalRootEntity entity in worldEntityManager.GetGlobalRootEntities()) { simulationOwnershipData.TryToAcquire(entity.Id, player, SimulationLockType.TRANSIENT); + if (!simulationOwnershipData.TryGetLock(entity.Id, out SimulationOwnershipData.PlayerLock playerLock)) + { + continue; + } bool doesEntityMove = ShouldSimulateEntityMovement(entity); - SimulatedEntity simulatedEntity = new(entity.Id, simulationOwnershipData.GetPlayerForLock(entity.Id).Id, doesEntityMove, SimulationLockType.TRANSIENT); + SimulatedEntity simulatedEntity = new(entity.Id, playerLock.Player.Id, doesEntityMove, playerLock.LockType); simulatedEntities.Add(simulatedEntity); } return simulatedEntities; diff --git a/NitroxServer/GameLogic/SimulationOwnership.cs b/NitroxServer/GameLogic/SimulationOwnership.cs index efaa9e4d8b..97f38a50f4 100644 --- a/NitroxServer/GameLogic/SimulationOwnership.cs +++ b/NitroxServer/GameLogic/SimulationOwnership.cs @@ -1,11 +1,11 @@ -using System.Collections.Generic; +using System.Collections.Generic; using NitroxModel.DataStructures; namespace NitroxServer.GameLogic { public class SimulationOwnershipData { - struct PlayerLock + public struct PlayerLock { public Player Player { get; } public SimulationLockType LockType { get; set; } @@ -107,5 +107,13 @@ public Player GetPlayerForLock(NitroxId id) } return null; } + + public bool TryGetLock(NitroxId id, out PlayerLock playerLock) + { + lock (playerLocksById) + { + return playerLocksById.TryGetValue(id, out playerLock); + } + } } } From cbdad2936a2f9711f4cf061aa1a8d43f71acf106 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Sun, 10 Nov 2024 21:01:34 +0100 Subject: [PATCH 17/20] Refactors and little corrections here and there --- .../Processors/VehicleDockingProcessor.cs | 10 +++---- NitroxClient/GameLogic/RemotePlayer.cs | 3 --- .../Settings/NitroxSettingsManager.cs | 4 +-- NitroxClient/GameLogic/SimulationOwnership.cs | 8 +++--- NitroxClient/GameLogic/Vehicles.cs | 2 +- .../MonoBehaviours/AnimationController.cs | 7 ++++- .../MonoBehaviours/MovementBroadcaster.cs | 13 ++++----- .../MonoBehaviours/MovementReplicator.cs | 20 +++++++------- .../Vehicles/CyclopsMovementReplicator.cs | 11 +++++--- .../Vehicles/ExosuitMovementReplicator.cs | 27 +++++++------------ .../Vehicles/SeaMothMovementReplicator.cs | 14 ++++------ .../Vehicles/VehicleMovementReplicator.cs | 4 +++ .../MultiplayerSession/PlayerContext.cs | 3 +++ .../Networking/NitroxDeliveryMethod.cs | 15 +++++++---- NitroxModel/Packets/PlayerStats.cs | 3 +++ NitroxModel/Packets/SpawnEntities.cs | 12 ++++++--- ...ckedVehicleHandTarget_OnHandClick_Patch.cs | 4 +-- .../Dynamic/Exosuit_FixedUpdate_Patch.cs | 5 +++- .../Dynamic/Exosuit_GetVelocity_Patch.cs | 3 ++- .../Dynamic/SeaMoth_FixedUpdate_Patch.cs | 5 +++- .../VehicleDockingBay_OnTriggerEnter.cs | 5 ++-- .../LiteNetLib/LiteNetLibConnection.cs | 2 +- .../EntitySpawnedByClientProcessor.cs | 2 +- .../Processors/ModuleAddedProcessor.cs | 2 +- .../Processors/ModuleRemovedProcessor.cs | 2 +- .../Processors/PickupItemPacketProcessor.cs | 2 +- .../VehicleMovementsPacketProcessor.cs | 2 +- 27 files changed, 104 insertions(+), 86 deletions(-) diff --git a/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs b/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs index 83efbb45cd..7bb5a9229f 100644 --- a/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/VehicleDockingProcessor.cs @@ -33,19 +33,19 @@ public override void Process(VehicleDocking packet) return; } - if (vehicle.TryGetComponent(out MovementReplicator vehicleMovementReplicator)) + if (vehicle.TryGetComponent(out VehicleMovementReplicator vehicleMovementReplicator)) { vehicleMovementReplicator.enabled = false; - Log.Debug($"[{nameof(VehicleDockingProcessor)}] Disabled MovementReplicator on {packet.VehicleId}"); + Log.Debug($"[{nameof(VehicleDockingProcessor)}] Disabled VehicleMovementReplicator on {packet.VehicleId}"); } - vehicle.StartCoroutine(DelayAnimationAndDisablePiloting(vehicle, dockingBay, packet.VehicleId, packet.PlayerId)); + vehicle.StartCoroutine(DelayAnimationAndDisablePiloting(vehicle, vehicleMovementReplicator, dockingBay, packet.VehicleId, packet.PlayerId)); } - private IEnumerator DelayAnimationAndDisablePiloting(Vehicle vehicle, VehicleDockingBay vehicleDockingBay, NitroxId vehicleId, ushort playerId) + private IEnumerator DelayAnimationAndDisablePiloting(Vehicle vehicle, VehicleMovementReplicator vehicleMovementReplicator, VehicleDockingBay vehicleDockingBay, NitroxId vehicleId, ushort playerId) { // Consider the vehicle movement latency (we don't teleport the vehicle to the docking position) - if (vehicle.TryGetComponent(out VehicleMovementReplicator vehicleMovementReplicator)) + if (vehicleMovementReplicator) { // NB: We don't have a lifetime ahead of us float waitTime = Mathf.Clamp(vehicleMovementReplicator.maxAllowedLatency, 0f, 2f); diff --git a/NitroxClient/GameLogic/RemotePlayer.cs b/NitroxClient/GameLogic/RemotePlayer.cs index 2b0a968331..b1c720a50e 100644 --- a/NitroxClient/GameLogic/RemotePlayer.cs +++ b/NitroxClient/GameLogic/RemotePlayer.cs @@ -176,9 +176,6 @@ public void UpdatePositionInCyclops(Vector3 localPosition, Quaternion localRotat Pawn.Handle.transform.localPosition = localPosition; Pawn.Handle.transform.localRotation = localRotation; - - AnimationController.UpdatePlayerAnimations = true; - AnimationController.AimingRotation = localRotation; } public void SetPilotingChair(PilotingChair newPilotingChair) diff --git a/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs b/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs index da702fbb16..dbfc428635 100644 --- a/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs +++ b/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs @@ -52,8 +52,8 @@ private void MakeSettings() AddSetting("Nitrox_BuildingSettings", new Setting("Nitrox_SafeBuilding", NitroxPrefs.SafeBuilding, safe => NitroxPrefs.SafeBuilding.Value = safe)); AddSetting("Nitrox_BuildingSettings", new Setting("Nitrox_SafeBuildingLog", NitroxPrefs.SafeBuildingLog, safeLog => NitroxPrefs.SafeBuildingLog.Value = safeLog)); - AddSetting("NitroxBandwidthSettings", new Setting("NitroxSettingsLatencyUpdatePeriod", NitroxPrefs.LatencyUpdatePeriod, latencyUpdatePeriod => NitroxPrefs.LatencyUpdatePeriod.Value = (int)latencyUpdatePeriod, 1, 60, NitroxPrefs.LatencyUpdatePeriod.DefaultValue, 1, SliderLabelMode.Int, tooltip: "NitroxHigherForUnstable_Tooltip")); - AddSetting("NitroxBandwidthSettings", new Setting("NitroxSettingsSafetyLatencyMargin", NitroxPrefs.SafetyLatencyMargin, safetyLatencyMargin => NitroxPrefs.SafetyLatencyMargin.Value = safetyLatencyMargin, 0.01f, 0.5f, NitroxPrefs.SafetyLatencyMargin.DefaultValue, 0.01f, SliderLabelMode.Float, "0.00", "NitroxHigherForUnstable_Tooltip")); + AddSetting("Nitrox_BandwidthSettings", new Setting("Nitrox_Settings_LatencyUpdatePeriod", NitroxPrefs.LatencyUpdatePeriod, latencyUpdatePeriod => NitroxPrefs.LatencyUpdatePeriod.Value = (int)latencyUpdatePeriod, 1, 60, NitroxPrefs.LatencyUpdatePeriod.DefaultValue, 1, SliderLabelMode.Int, tooltip: "Nitrox_Settings_HigherForUnstable_Tooltip")); + AddSetting("Nitrox_BandwidthSettings", new Setting("Nitrox_Settings_SafetyLatencyMargin", NitroxPrefs.SafetyLatencyMargin, safetyLatencyMargin => NitroxPrefs.SafetyLatencyMargin.Value = safetyLatencyMargin, 0.01f, 0.5f, NitroxPrefs.SafetyLatencyMargin.DefaultValue, 0.01f, SliderLabelMode.Float, "0.00", "Nitrox_Settings_HigherForUnstable_Tooltip")); } /// Adds a setting to the list under a certain heading diff --git a/NitroxClient/GameLogic/SimulationOwnership.cs b/NitroxClient/GameLogic/SimulationOwnership.cs index 003aaad080..174fce8bcf 100644 --- a/NitroxClient/GameLogic/SimulationOwnership.cs +++ b/NitroxClient/GameLogic/SimulationOwnership.cs @@ -12,8 +12,8 @@ public class SimulationOwnership { private readonly IMultiplayerSession multiplayerSession; private readonly IPacketSender packetSender; - private readonly Dictionary simulatedIdsByLockType = new Dictionary(); - private readonly Dictionary lockRequestsById = new Dictionary(); + private readonly Dictionary simulatedIdsByLockType = []; + private readonly Dictionary lockRequestsById = []; private readonly Dictionary newerSimulationById = []; @@ -118,7 +118,7 @@ public void TreatSimulatedEntity(SimulatedEntity simulatedEntity) // Avoid keeping artifacts of the entity's previous ChangesPosition state if (!simulatedEntity.ChangesPosition && NitroxEntity.TryGetComponentFrom(simulatedEntity.Id, out RemotelyControlled remotelyControlled)) { - GameObject.Destroy(remotelyControlled); + Object.Destroy(remotelyControlled); } } @@ -139,7 +139,7 @@ private bool TreatVehicleEntity(NitroxId entityId, bool isLocalPlayerNewOwner, S { if (movementReplicator) { - GameObject.Destroy(movementReplicator); + Object.Destroy(movementReplicator); } MovementBroadcaster.RegisterWatched(gameObject, entityId); SimulateEntity(entityId, simulationLockType); diff --git a/NitroxClient/GameLogic/Vehicles.cs b/NitroxClient/GameLogic/Vehicles.cs index 6ba771f115..268899a6d9 100644 --- a/NitroxClient/GameLogic/Vehicles.cs +++ b/NitroxClient/GameLogic/Vehicles.cs @@ -59,7 +59,7 @@ public void BroadcastDestroyedVehicle(NitroxId id) } } - public static void EngagePlayerMovementProcessor(Vehicle vehicle) + public static void EngagePlayerMovementSuppressor(Vehicle vehicle) { // TODO: Properly prevent the vehicle from sending position update as long as it's not free from the animation PacketSuppressor playerMovementSuppressor = PacketSuppressor.Suppress(); diff --git a/NitroxClient/MonoBehaviours/AnimationController.cs b/NitroxClient/MonoBehaviours/AnimationController.cs index 1d4cf130a7..1e0ee1c56d 100644 --- a/NitroxClient/MonoBehaviours/AnimationController.cs +++ b/NitroxClient/MonoBehaviours/AnimationController.cs @@ -27,7 +27,7 @@ public void FixedUpdate() { if (UpdatePlayerAnimations) { - Vector3 rotationCorrectedVelocity = gameObject.transform.rotation.GetInverse() * Velocity; + Vector3 rotationCorrectedVelocity = transform.rotation.GetInverse() * Velocity; smoothedVelocity = UWE.Utils.SlerpVector(smoothedVelocity, rotationCorrectedVelocity, Vector3.Normalize(rotationCorrectedVelocity - smoothedVelocity) * SMOOTHING_SPEED * Time.fixedDeltaTime); @@ -59,6 +59,11 @@ internal void SetFloat(string name, float value) animator.SetFloat(name, value); } + internal void SetFloat(int id, float value) + { + animator.SetFloat(id, value); + } + public void Reset() { animator.Rebind(); diff --git a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs index 964df9da07..ef2dda1450 100644 --- a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs +++ b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs @@ -13,10 +13,11 @@ public class MovementBroadcaster : MonoBehaviour public const int BROADCAST_FREQUENCY = 30; public const float BROADCAST_PERIOD = 1f / BROADCAST_FREQUENCY; - private readonly Dictionary watchedEntries = []; + public static MovementBroadcaster Instance; + public Dictionary Replicators = []; + private readonly Dictionary watchedEntries = []; private float latestBroadcastTime; - public static MovementBroadcaster Instance; public void Start() { @@ -118,8 +119,8 @@ public MovementData GetMovementData(NitroxId id) if (vehicle && Player.main.currentMountedVehicle == vehicle) { // Those two values are set between -1 and 1 so we can easily scale them up while still in range for sbyte - sbyte steeringWheelYaw = (sbyte)(vehicle.steeringWheelYaw * 70f); - sbyte steeringWheelPitch = (sbyte)(vehicle.steeringWheelPitch * 45f); + sbyte steeringWheelYaw = (sbyte)(Mathf.Clamp(vehicle.steeringWheelYaw, -1, 1) * 70f); + sbyte steeringWheelPitch = (sbyte)(Mathf.Clamp(vehicle.steeringWheelPitch, -1, 1) * 45f); bool throttleApplied = false; @@ -142,8 +143,8 @@ public MovementData GetMovementData(NitroxId id) if (subControl && Player.main.currentSub == subControl.sub && Player.main.mode == Player.Mode.Piloting) { // Cyclop steering wheel's yaw and pitch are between -90 and 90 so they're already in range for sbyte - sbyte steeringWheelYaw = (sbyte)subControl.steeringWheelYaw; - sbyte steeringWheelPitch = (sbyte)subControl.steeringWheelPitch; + sbyte steeringWheelYaw = (sbyte)Mathf.Clamp(subControl.steeringWheelYaw, -90, 90); + sbyte steeringWheelPitch = (sbyte)Mathf.Clamp(subControl.steeringWheelPitch, -90, 90); // See SubControl.Update bool throttleApplied = subControl.throttle.magnitude > 0.0001f; diff --git a/NitroxClient/MonoBehaviours/MovementReplicator.cs b/NitroxClient/MonoBehaviours/MovementReplicator.cs index c7976f91a8..da426fe938 100644 --- a/NitroxClient/MonoBehaviours/MovementReplicator.cs +++ b/NitroxClient/MonoBehaviours/MovementReplicator.cs @@ -36,7 +36,7 @@ public abstract class MovementReplicator : MonoBehaviour private float LatencyUpdatePeriod => NitroxPrefs.LatencyUpdatePeriod.Value; private Rigidbody rigidbody; - public NitroxId objectId; + public NitroxId objectId { get; private set; } /// /// Current time must be based on real time to avoid effects from time changes/speed. @@ -72,7 +72,7 @@ public void AddSnapshot(MovementData movementData, float time) float occurrenceTime = time + INTERPOLATION_TIME + maxAllowedLatency; // Cleaning any previous value change that would occur later than the newly received snapshot - while (buffer.Last != null && buffer.Last.Value.IsOlderThan(occurrenceTime)) + while (buffer.Last != null && buffer.Last.Value.IsSnapshotNewer(occurrenceTime)) { buffer.RemoveLast(); } @@ -84,11 +84,13 @@ public void AddSnapshot(MovementData movementData, float time) public void Start() { - if (!gameObject.TryGetNitroxId(out objectId)) + if (!gameObject.TryGetNitroxId(out NitroxId _objectId)) { Log.Error($"Can't start a {nameof(MovementReplicator)} on {name} because it doesn't have an attached: {nameof(NitroxEntity)}"); + Destroy(this); return; } + objectId = _objectId; rigidbody = GetComponent(); if (gameObject.TryGetComponent(out NitroxCyclops nitroxCyclops)) @@ -146,13 +148,13 @@ public void Update() } // Current node is not useable yet - if (firstNode.Value.IsOlderThan(currentTime)) + if (firstNode.Value.IsSnapshotNewer(currentTime)) { return; } // Purging the next nodes if they should have already happened (we still have an expiration margin for the first node so it's fine) - while (firstNode.Next != null && !firstNode.Next.Value.IsOlderThan(currentTime)) + while (firstNode.Next != null && !firstNode.Next.Value.IsSnapshotNewer(currentTime)) { firstNode = firstNode.Next; buffer.RemoveFirst(); @@ -172,12 +174,10 @@ public void Update() MovementData nextData = nextNode.Value.Data; float t = (currentTime - firstNode.Value.Time) / (nextNode.Value.Time - firstNode.Value.Time); - Vector3 position = Vector3.Lerp(prevData.Position.ToUnity(), nextData.Position.ToUnity(), t); - Quaternion rotation = Quaternion.Lerp(prevData.Rotation.ToUnity(), nextData.Rotation.ToUnity(), t); + transform.position = Vector3.Lerp(prevData.Position.ToUnity(), nextData.Position.ToUnity(), t); - transform.position = position; - transform.rotation = rotation; + transform.rotation = Quaternion.Lerp(prevData.Rotation.ToUnity(), nextData.Rotation.ToUnity(), t); ApplyNewMovementData(nextData); @@ -188,7 +188,7 @@ public void Update() public record struct Snapshot(MovementData Data, float Time) { - public bool IsOlderThan(float currentTime) => currentTime < Time; + public bool IsSnapshotNewer(float currentTime) => currentTime < Time; public bool IsExpired(float currentTime) => currentTime > Time + SNAPSHOT_EXPIRATION_TIME; } diff --git a/NitroxClient/MonoBehaviours/Vehicles/CyclopsMovementReplicator.cs b/NitroxClient/MonoBehaviours/Vehicles/CyclopsMovementReplicator.cs index 060be03429..334e11c5d5 100644 --- a/NitroxClient/MonoBehaviours/Vehicles/CyclopsMovementReplicator.cs +++ b/NitroxClient/MonoBehaviours/Vehicles/CyclopsMovementReplicator.cs @@ -6,6 +6,9 @@ namespace NitroxClient.MonoBehaviours.Vehicles; public class CyclopsMovementReplicator : VehicleMovementReplicator { + protected static readonly int CYCLOPS_YAW = Animator.StringToHash("cyclops_yaw"); + protected static readonly int CYCLOPS_PITCH = Animator.StringToHash("cyclops_pitch"); + private SubControl subControl; private RemotePlayer drivingPlayer; @@ -62,13 +65,13 @@ public override void ApplyNewMovementData(MovementData newMovementData) subControl.steeringWheelPitch = steeringWheelPitch; if (subControl.mainAnimator) { - subControl.mainAnimator.SetFloat("view_yaw", subControl.steeringWheelYaw); - subControl.mainAnimator.SetFloat("view_pitch", subControl.steeringWheelPitch); + subControl.mainAnimator.SetFloat(VIEW_YAW, subControl.steeringWheelYaw); + subControl.mainAnimator.SetFloat(VIEW_PITCH, subControl.steeringWheelPitch); if (drivingPlayer != null) { - drivingPlayer.AnimationController.SetFloat("cyclops_yaw", subControl.steeringWheelYaw); - drivingPlayer.AnimationController.SetFloat("cyclops_pitch", subControl.steeringWheelPitch); + drivingPlayer.AnimationController.SetFloat(CYCLOPS_YAW, subControl.steeringWheelYaw); + drivingPlayer.AnimationController.SetFloat(CYCLOPS_PITCH, subControl.steeringWheelPitch); } } diff --git a/NitroxClient/MonoBehaviours/Vehicles/ExosuitMovementReplicator.cs b/NitroxClient/MonoBehaviours/Vehicles/ExosuitMovementReplicator.cs index 368d923946..e71298c157 100644 --- a/NitroxClient/MonoBehaviours/Vehicles/ExosuitMovementReplicator.cs +++ b/NitroxClient/MonoBehaviours/Vehicles/ExosuitMovementReplicator.cs @@ -31,21 +31,12 @@ public void Awake() velocity = (positionAfter - positionBefore) / Time.deltaTime; - if (exosuit.loopingJetSound.playing) - { - if (exosuit.loopingJetSound.evt.hasHandle()) - { - float volume = FMODSystem.CalculateVolume(transform.position, Player.main.transform.position, jetLoopingSoundDistance, 1f); - exosuit.loopingJetSound.evt.setVolume(volume); - } - } - else + + float volume = FMODSystem.CalculateVolume(transform.position, Player.main.transform.position, jetLoopingSoundDistance, 1f); + EventInstance soundHandle = exosuit.loopingJetSound.playing ? exosuit.loopingJetSound.evt : exosuit.loopingJetSound.evtStop; + if (soundHandle.hasHandle()) { - if (exosuit.loopingJetSound.evtStop.hasHandle()) - { - float volume = FMODSystem.CalculateVolume(transform.position, Player.main.transform.position, jetLoopingSoundDistance, 1f); - exosuit.loopingJetSound.evtStop.setVolume(volume); - } + soundHandle.setVolume(volume); } // See Exosuit.Update, thrust power simulation @@ -67,7 +58,7 @@ public void Awake() } exosuit.thrustIntensity = Mathf.Clamp01(exosuit.thrustIntensity); - if (timeJetsActiveChanged + 0.3f <= DayNightCycle.main.timePassedAsFloat) + if (timeJetsActiveChanged + 0.3f <= Time.time) { if (jetsActive && thrustPower > 0f) { @@ -99,15 +90,15 @@ public override void ApplyNewMovementData(MovementData newMovementData) if (exosuit.mainAnimator) { - exosuit.mainAnimator.SetFloat("view_yaw", steeringWheelYaw); - exosuit.mainAnimator.SetFloat("view_pitch", steeringWheelPitch); + exosuit.mainAnimator.SetFloat(VIEW_YAW, steeringWheelYaw); + exosuit.mainAnimator.SetFloat(VIEW_PITCH, steeringWheelPitch); } // See Exosuit.jetsActive setter if (jetsActive != vehicleMovementData.ThrottleApplied) { jetsActive = vehicleMovementData.ThrottleApplied; - timeJetsActiveChanged = DayNightCycle.main.timePassedAsFloat; + timeJetsActiveChanged = Time.time; } } diff --git a/NitroxClient/MonoBehaviours/Vehicles/SeaMothMovementReplicator.cs b/NitroxClient/MonoBehaviours/Vehicles/SeaMothMovementReplicator.cs index 06132dede1..0a4af74a32 100644 --- a/NitroxClient/MonoBehaviours/Vehicles/SeaMothMovementReplicator.cs +++ b/NitroxClient/MonoBehaviours/Vehicles/SeaMothMovementReplicator.cs @@ -9,7 +9,6 @@ namespace NitroxClient.MonoBehaviours.Vehicles; public class SeamothMovementReplicator : VehicleMovementReplicator { private SeaMoth seaMoth; - public Vector3 velocity; private FMOD_CustomLoopingEmitter rpmSound; private FMOD_CustomEmitter revSound; @@ -29,10 +28,7 @@ public void Awake() public new void Update() { - Vector3 positionBefore = transform.position; base.Update(); - Vector3 positionAfter = transform.position; - velocity = (positionAfter - positionBefore) / Time.deltaTime; if (throttleApplied) { @@ -50,13 +46,13 @@ public override void ApplyNewMovementData(MovementData newMovementData) float steeringWheelPitch = vehicleMovementData.SteeringWheelPitch; // See Vehicle.Update (reverse operation for vehicle.steeringWheel... = ...) - seaMoth.steeringWheelYaw = steeringWheelPitch / 70f; + seaMoth.steeringWheelYaw = steeringWheelYaw / 70f; seaMoth.steeringWheelPitch = steeringWheelPitch / 45f; if (seaMoth.mainAnimator) { - seaMoth.mainAnimator.SetFloat("view_yaw", steeringWheelYaw); - seaMoth.mainAnimator.SetFloat("view_pitch", steeringWheelPitch); + seaMoth.mainAnimator.SetFloat(VIEW_YAW, steeringWheelYaw); + seaMoth.mainAnimator.SetFloat(VIEW_PITCH, steeringWheelPitch); } // Adjusting volume for the engine Sound @@ -84,13 +80,13 @@ private void SetupSound() rpmSound.GetEventInstance().setProperty(EVENT_PROPERTY.MINIMUM_DISTANCE, 1f); revSound.GetEventInstance().setProperty(EVENT_PROPERTY.MINIMUM_DISTANCE, 1f); + enterSeamoth.GetEventInstance().setProperty(EVENT_PROPERTY.MINIMUM_DISTANCE, 1f); rpmSound.GetEventInstance().setProperty(EVENT_PROPERTY.MAXIMUM_DISTANCE, radiusRpmSound); revSound.GetEventInstance().setProperty(EVENT_PROPERTY.MAXIMUM_DISTANCE, radiusRevSound); - - enterSeamoth.GetEventInstance().setProperty(EVENT_PROPERTY.MINIMUM_DISTANCE, 1f); enterSeamoth.GetEventInstance().setProperty(EVENT_PROPERTY.MAXIMUM_DISTANCE, radiusEnterSound); + if (FMODUWE.IsInvalidParameterId(seaMoth.fmodIndexSpeed)) { seaMoth.fmodIndexSpeed = seaMoth.ambienceSound.GetParameterIndex("speed"); diff --git a/NitroxClient/MonoBehaviours/Vehicles/VehicleMovementReplicator.cs b/NitroxClient/MonoBehaviours/Vehicles/VehicleMovementReplicator.cs index 56671703a7..0eb0a0bb3f 100644 --- a/NitroxClient/MonoBehaviours/Vehicles/VehicleMovementReplicator.cs +++ b/NitroxClient/MonoBehaviours/Vehicles/VehicleMovementReplicator.cs @@ -1,9 +1,13 @@ using NitroxClient.GameLogic; +using UnityEngine; namespace NitroxClient.MonoBehaviours.Vehicles; public abstract class VehicleMovementReplicator : MovementReplicator { + protected static readonly int VIEW_YAW = Animator.StringToHash("view_yaw"); + protected static readonly int VIEW_PITCH = Animator.StringToHash("view_pitch"); + public abstract void Enter(RemotePlayer remotePlayer); public abstract void Exit(); } diff --git a/NitroxModel/MultiplayerSession/PlayerContext.cs b/NitroxModel/MultiplayerSession/PlayerContext.cs index 4577275def..713211755a 100644 --- a/NitroxModel/MultiplayerSession/PlayerContext.cs +++ b/NitroxModel/MultiplayerSession/PlayerContext.cs @@ -14,6 +14,9 @@ public class PlayerContext public PlayerSettings PlayerSettings { get; } public bool IsMuted { get; set; } public NitroxGameMode GameMode { get; set; } + /// + /// Not null if the player is currently driving a vehicle. + /// public NitroxId DrivingVehicle { get; set; } public PlayerContext(string playerName, ushort playerId, NitroxId playerNitroxId, bool wasBrandNewPlayer, PlayerSettings playerSettings, bool isMuted, NitroxGameMode gameMode, NitroxId drivingVehicle) diff --git a/NitroxModel/Networking/NitroxDeliveryMethod.cs b/NitroxModel/Networking/NitroxDeliveryMethod.cs index b00c69f47a..6f74f4fbd1 100644 --- a/NitroxModel/Networking/NitroxDeliveryMethod.cs +++ b/NitroxModel/Networking/NitroxDeliveryMethod.cs @@ -3,16 +3,20 @@ namespace NitroxModel.Networking public class NitroxDeliveryMethod { - public enum DeliveryMethod + public enum DeliveryMethod : byte { /// /// /// - UNRELIABLE_SEQUENCED, + UNRELIABLE_SEQUENCED = LiteNetLib.DeliveryMethod.Sequenced, /// /// /// - RELIABLE_ORDERED + RELIABLE_ORDERED = LiteNetLib.DeliveryMethod.ReliableOrdered, + /// + /// + /// + RELIABLE_ORDERED_LAST = LiteNetLib.DeliveryMethod.ReliableSequenced, } public static LiteNetLib.DeliveryMethod ToLiteNetLib(DeliveryMethod deliveryMethod) @@ -20,9 +24,10 @@ public static LiteNetLib.DeliveryMethod ToLiteNetLib(DeliveryMethod deliveryMeth switch (deliveryMethod) { case DeliveryMethod.UNRELIABLE_SEQUENCED: - return LiteNetLib.DeliveryMethod.Sequenced; + case DeliveryMethod.RELIABLE_ORDERED_LAST: case DeliveryMethod.RELIABLE_ORDERED: - return LiteNetLib.DeliveryMethod.ReliableOrdered; + return (LiteNetLib.DeliveryMethod)deliveryMethod; + default: return LiteNetLib.DeliveryMethod.ReliableOrdered; } diff --git a/NitroxModel/Packets/PlayerStats.cs b/NitroxModel/Packets/PlayerStats.cs index a54b21386b..bde2a9fc94 100644 --- a/NitroxModel/Packets/PlayerStats.cs +++ b/NitroxModel/Packets/PlayerStats.cs @@ -1,4 +1,5 @@ using System; +using NitroxModel.Networking; namespace NitroxModel.Packets; @@ -22,5 +23,7 @@ public PlayerStats(ushort playerId, float oxygen, float maxOxygen, float health, Food = food; Water = water; InfectionAmount = infectionAmount; + + DeliveryMethod = NitroxDeliveryMethod.DeliveryMethod.RELIABLE_ORDERED_LAST; } } diff --git a/NitroxModel/Packets/SpawnEntities.cs b/NitroxModel/Packets/SpawnEntities.cs index a74c269593..de1b13a3ee 100644 --- a/NitroxModel/Packets/SpawnEntities.cs +++ b/NitroxModel/Packets/SpawnEntities.cs @@ -19,20 +19,24 @@ public SpawnEntities(List entities) ForceRespawn = false; } - public SpawnEntities(Entity entity, bool forceRespawn = false, SimulatedEntity simulatedEntity = null) + public SpawnEntities(Entity entity, SimulatedEntity simulatedEntity = null, bool forceRespawn = false) { Entities = [entity]; - Simulations = [simulatedEntity]; + Simulations = []; + if (simulatedEntity != null) + { + Simulations.Add(simulatedEntity); + } ForceRespawn = forceRespawn; } // Constructor for serialization. - public SpawnEntities(List entities, bool forceRespawn, List simulations) + public SpawnEntities(List entities, List simulations, bool forceRespawn) { Entities = entities; - ForceRespawn = forceRespawn; Simulations = simulations; + ForceRespawn = forceRespawn; } } } diff --git a/NitroxPatcher/Patches/Dynamic/DockedVehicleHandTarget_OnHandClick_Patch.cs b/NitroxPatcher/Patches/Dynamic/DockedVehicleHandTarget_OnHandClick_Patch.cs index 49fd736259..97aa35361f 100644 --- a/NitroxPatcher/Patches/Dynamic/DockedVehicleHandTarget_OnHandClick_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/DockedVehicleHandTarget_OnHandClick_Patch.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using NitroxClient.Communication.Abstract; using NitroxClient.GameLogic; using NitroxClient.GameLogic.Simulation; @@ -48,7 +48,7 @@ private static void ReceivedSimulationLockResponse(NitroxId vehicleId, bool lock return; } - Vehicles.EngagePlayerMovementProcessor(vehicle); + Vehicles.EngagePlayerMovementSuppressor(vehicle); Resolve().Send(new VehicleUndocking(vehicleId, dockId, Resolve().Reservation.PlayerId, true)); skipPrefix = true; diff --git a/NitroxPatcher/Patches/Dynamic/Exosuit_FixedUpdate_Patch.cs b/NitroxPatcher/Patches/Dynamic/Exosuit_FixedUpdate_Patch.cs index 9d39a3b2ff..bf217bf5b7 100644 --- a/NitroxPatcher/Patches/Dynamic/Exosuit_FixedUpdate_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/Exosuit_FixedUpdate_Patch.cs @@ -4,9 +4,12 @@ namespace NitroxPatcher.Patches.Dynamic; +/// +/// Disables for not simulated Exosuits. +/// public sealed partial class Exosuit_FixedUpdate_Patch : NitroxPatch, IDynamicPatch { - private static readonly MethodInfo TARGET_METHOD = Reflect.Method((Exosuit t) => t.FixedUpdate()); + private static readonly MethodInfo targetMethod = Reflect.Method((Exosuit t) => t.FixedUpdate()); public static bool Prefix(Exosuit __instance) { diff --git a/NitroxPatcher/Patches/Dynamic/Exosuit_GetVelocity_Patch.cs b/NitroxPatcher/Patches/Dynamic/Exosuit_GetVelocity_Patch.cs index ff921f5f50..7f319bb817 100644 --- a/NitroxPatcher/Patches/Dynamic/Exosuit_GetVelocity_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/Exosuit_GetVelocity_Patch.cs @@ -1,5 +1,6 @@ using System.Reflection; using NitroxClient.MonoBehaviours.Vehicles; +using NitroxModel.Helper; using UnityEngine; namespace NitroxPatcher.Patches.Dynamic; @@ -9,7 +10,7 @@ namespace NitroxPatcher.Patches.Dynamic; /// public sealed partial class Exosuit_GetVelocity_Patch : NitroxPatch, IDynamicPatch { - private static readonly MethodInfo TARGET_METHOD = typeof(Exosuit).GetMethod("IGroundMoveable.GetVelocity", BindingFlags.NonPublic | BindingFlags.Instance); + private static readonly MethodInfo targetMethod = Reflect.Method((Exosuit t) => ((IGroundMoveable)t).GetVelocity()); public static bool Prefix(Exosuit __instance, ref Vector3 __result) { diff --git a/NitroxPatcher/Patches/Dynamic/SeaMoth_FixedUpdate_Patch.cs b/NitroxPatcher/Patches/Dynamic/SeaMoth_FixedUpdate_Patch.cs index 1468ff7fd7..c28b9738d6 100644 --- a/NitroxPatcher/Patches/Dynamic/SeaMoth_FixedUpdate_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/SeaMoth_FixedUpdate_Patch.cs @@ -4,9 +4,12 @@ namespace NitroxPatcher.Patches.Dynamic; +/// +/// Disables for not simulated Seamoths. +/// public sealed partial class Seamoth_FixedUpdate_Patch : NitroxPatch, IDynamicPatch { - private static readonly MethodInfo TARGET_METHOD = Reflect.Method((SeaMoth t) => t.FixedUpdate()); + private static readonly MethodInfo targetMethod = Reflect.Method((SeaMoth t) => t.FixedUpdate()); public static bool Prefix(SeaMoth __instance) { diff --git a/NitroxPatcher/Patches/Dynamic/VehicleDockingBay_OnTriggerEnter.cs b/NitroxPatcher/Patches/Dynamic/VehicleDockingBay_OnTriggerEnter.cs index 6b2ebffab4..9ff5d1eb2c 100644 --- a/NitroxPatcher/Patches/Dynamic/VehicleDockingBay_OnTriggerEnter.cs +++ b/NitroxPatcher/Patches/Dynamic/VehicleDockingBay_OnTriggerEnter.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using NitroxClient.Communication.Abstract; using NitroxClient.GameLogic; using NitroxModel.DataStructures; @@ -34,8 +34,7 @@ public static void Postfix(VehicleDockingBay __instance, ref Vehicle __state) interpolatingVehicle.TryGetIdOrWarn(out NitroxId vehicleId) && Resolve().HasAnyLockType(vehicleId)) { - Log.Debug($"Will send vehicle docking for {vehicleId}"); //TODO: Debug logging - Vehicles.EngagePlayerMovementProcessor(interpolatingVehicle); + Vehicles.EngagePlayerMovementSuppressor(interpolatingVehicle); Resolve().Send(new VehicleDocking(vehicleId, dockId, Resolve().Reservation.PlayerId)); } } diff --git a/NitroxServer/Communication/LiteNetLib/LiteNetLibConnection.cs b/NitroxServer/Communication/LiteNetLib/LiteNetLibConnection.cs index 910201e833..b7695c2f5c 100644 --- a/NitroxServer/Communication/LiteNetLib/LiteNetLibConnection.cs +++ b/NitroxServer/Communication/LiteNetLib/LiteNetLibConnection.cs @@ -29,7 +29,7 @@ public void SendPacket(Packet packet) dataWriter.Put(packetData.Length); dataWriter.Put(packetData); - peer.Send(dataWriter, NitroxDeliveryMethod.ToLiteNetLib(packet.DeliveryMethod)); + peer.Send(dataWriter, (byte)packet.UdpChannel, NitroxDeliveryMethod.ToLiteNetLib(packet.DeliveryMethod)); } else { diff --git a/NitroxServer/Communication/Packets/Processors/EntitySpawnedByClientProcessor.cs b/NitroxServer/Communication/Packets/Processors/EntitySpawnedByClientProcessor.cs index bd00e18acc..ad4f038b63 100644 --- a/NitroxServer/Communication/Packets/Processors/EntitySpawnedByClientProcessor.cs +++ b/NitroxServer/Communication/Packets/Processors/EntitySpawnedByClientProcessor.cs @@ -45,7 +45,7 @@ public override void Process(EntitySpawnedByClient packet, Player playerWhoSpawn } } - SpawnEntities spawnEntities = new(entity, packet.RequireRespawn, simulatedEntity); + SpawnEntities spawnEntities = new(entity, simulatedEntity, packet.RequireRespawn); foreach (Player player in playerManager.GetConnectedPlayers()) { bool isOtherPlayer = player != playerWhoSpawned; diff --git a/NitroxServer/Communication/Packets/Processors/ModuleAddedProcessor.cs b/NitroxServer/Communication/Packets/Processors/ModuleAddedProcessor.cs index 1e0da60471..cba532e97e 100644 --- a/NitroxServer/Communication/Packets/Processors/ModuleAddedProcessor.cs +++ b/NitroxServer/Communication/Packets/Processors/ModuleAddedProcessor.cs @@ -37,7 +37,7 @@ public override void Process(ModuleAdded packet, Player player) entityRegistry.AddOrUpdate(moduleEntity); // Have other players respawn the item inside the inventory. - playerManager.SendPacketToOtherPlayers(new SpawnEntities(moduleEntity, true), player); + playerManager.SendPacketToOtherPlayers(new SpawnEntities(moduleEntity, forceRespawn: true), player); } } } diff --git a/NitroxServer/Communication/Packets/Processors/ModuleRemovedProcessor.cs b/NitroxServer/Communication/Packets/Processors/ModuleRemovedProcessor.cs index dc46babb6c..2c5b4ccfa7 100644 --- a/NitroxServer/Communication/Packets/Processors/ModuleRemovedProcessor.cs +++ b/NitroxServer/Communication/Packets/Processors/ModuleRemovedProcessor.cs @@ -37,7 +37,7 @@ public override void Process(ModuleRemoved packet, Player player) entityRegistry.AddOrUpdate(inventoryEntity); // Have other players respawn the item inside the inventory. - playerManager.SendPacketToOtherPlayers(new SpawnEntities(inventoryEntity, true), player); + playerManager.SendPacketToOtherPlayers(new SpawnEntities(inventoryEntity, forceRespawn: true), player); } } } diff --git a/NitroxServer/Communication/Packets/Processors/PickupItemPacketProcessor.cs b/NitroxServer/Communication/Packets/Processors/PickupItemPacketProcessor.cs index 2e8794c868..18b3928fc4 100644 --- a/NitroxServer/Communication/Packets/Processors/PickupItemPacketProcessor.cs +++ b/NitroxServer/Communication/Packets/Processors/PickupItemPacketProcessor.cs @@ -38,7 +38,7 @@ public override void Process(PickupItem packet, Player player) entityRegistry.AddOrUpdate(packet.Item); // Have other players respawn the item inside the inventory. - playerManager.SendPacketToOtherPlayers(new SpawnEntities(packet.Item, true), player); + playerManager.SendPacketToOtherPlayers(new SpawnEntities(packet.Item, forceRespawn: true), player); } private void StopTrackingExistingWorldEntity(NitroxId id) diff --git a/NitroxServer/Communication/Packets/Processors/VehicleMovementsPacketProcessor.cs b/NitroxServer/Communication/Packets/Processors/VehicleMovementsPacketProcessor.cs index 9ac0f52649..217b74cf98 100644 --- a/NitroxServer/Communication/Packets/Processors/VehicleMovementsPacketProcessor.cs +++ b/NitroxServer/Communication/Packets/Processors/VehicleMovementsPacketProcessor.cs @@ -30,7 +30,7 @@ public override void Process(VehicleMovements packet, Player player) if (simulationOwnershipData.GetPlayerForLock(movementData.Id) != player) { Log.WarnOnce($"Player {player.Name} tried updating {movementData.Id}'s position but they don't have the lock on it"); - // In the future, add "packet.Data.RemoveAt(i);" and "continue;" to prevent those abnormal situations + // TODO: In the future, add "packet.Data.RemoveAt(i);" and "continue;" to prevent those abnormal situations } if (entityRegistry.TryGetEntityById(movementData.Id, out WorldEntity worldEntity)) From 32117988fd02792efecc696c4f5870533a549552 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Sun, 10 Nov 2024 21:03:34 +0100 Subject: [PATCH 18/20] Add a movement broadcast rate limiter --- .../Settings/NitroxSettingsManager.cs | 4 +- .../MonoBehaviours/MovementBroadcaster.cs | 68 +-------- .../MonoBehaviours/Vehicles/WatchedEntry.cs | 138 ++++++++++++++++++ 3 files changed, 146 insertions(+), 64 deletions(-) create mode 100644 NitroxClient/MonoBehaviours/Vehicles/WatchedEntry.cs diff --git a/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs b/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs index dbfc428635..e5a8c345c2 100644 --- a/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs +++ b/NitroxClient/GameLogic/Settings/NitroxSettingsManager.cs @@ -52,8 +52,8 @@ private void MakeSettings() AddSetting("Nitrox_BuildingSettings", new Setting("Nitrox_SafeBuilding", NitroxPrefs.SafeBuilding, safe => NitroxPrefs.SafeBuilding.Value = safe)); AddSetting("Nitrox_BuildingSettings", new Setting("Nitrox_SafeBuildingLog", NitroxPrefs.SafeBuildingLog, safeLog => NitroxPrefs.SafeBuildingLog.Value = safeLog)); - AddSetting("Nitrox_BandwidthSettings", new Setting("Nitrox_Settings_LatencyUpdatePeriod", NitroxPrefs.LatencyUpdatePeriod, latencyUpdatePeriod => NitroxPrefs.LatencyUpdatePeriod.Value = (int)latencyUpdatePeriod, 1, 60, NitroxPrefs.LatencyUpdatePeriod.DefaultValue, 1, SliderLabelMode.Int, tooltip: "Nitrox_Settings_HigherForUnstable_Tooltip")); - AddSetting("Nitrox_BandwidthSettings", new Setting("Nitrox_Settings_SafetyLatencyMargin", NitroxPrefs.SafetyLatencyMargin, safetyLatencyMargin => NitroxPrefs.SafetyLatencyMargin.Value = safetyLatencyMargin, 0.01f, 0.5f, NitroxPrefs.SafetyLatencyMargin.DefaultValue, 0.01f, SliderLabelMode.Float, "0.00", "Nitrox_Settings_HigherForUnstable_Tooltip")); + AddSetting("Nitrox_Settings_Bandwidth", new Setting("Nitrox_Settings_LatencyUpdatePeriod", NitroxPrefs.LatencyUpdatePeriod, latencyUpdatePeriod => NitroxPrefs.LatencyUpdatePeriod.Value = (int)latencyUpdatePeriod, 1, 60, NitroxPrefs.LatencyUpdatePeriod.DefaultValue, 1, SliderLabelMode.Int, tooltip: "Nitrox_Settings_HigherForUnstable_Tooltip")); + AddSetting("Nitrox_Settings_Bandwidth", new Setting("Nitrox_Settings_SafetyLatencyMargin", NitroxPrefs.SafetyLatencyMargin, safetyLatencyMargin => NitroxPrefs.SafetyLatencyMargin.Value = safetyLatencyMargin, 0.01f, 0.5f, NitroxPrefs.SafetyLatencyMargin.DefaultValue, 0.01f, SliderLabelMode.Float, "0.00", "Nitrox_Settings_HigherForUnstable_Tooltip")); } /// Adds a setting to the list under a certain heading diff --git a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs index ef2dda1450..d58bb99aad 100644 --- a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs +++ b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; using NitroxClient.Communication.Abstract; using NitroxClient.GameLogic; +using NitroxClient.MonoBehaviours.Vehicles; using NitroxModel.DataStructures; using NitroxModel.Packets; -using NitroxModel_Subnautica.DataStructures; using UnityEngine; namespace NitroxClient.MonoBehaviours; @@ -51,8 +51,11 @@ public void BroadcastLocalData(float time) List data = []; foreach (KeyValuePair entry in watchedEntries) { - // TODO: Don't broadcast at certain times: while docking, while docked ... - data.Add(entry.Value.GetMovementData(entry.Key)); + if (entry.Value.ShouldBroadcastMovement()) + { + data.Add(entry.Value.GetMovementData(entry.Key)); + entry.Value.OnBroadcastPosition(); + } } if (data.Count > 0) @@ -97,63 +100,4 @@ public static void UnregisterReplicator(MovementReplicator movementReplicator) Instance.Replicators.Remove(movementReplicator.objectId); } } - - private readonly record struct WatchedEntry - { - // TODO: eventually add a detector for multiple broadcast in a row where the watched entry has almost not moved - // in this case, only send the data once every 5 second or as soon as new movement is detected - private readonly Transform transform; - private readonly Vehicle vehicle; - private readonly SubControl subControl; - - public WatchedEntry(Transform transform) - { - this.transform = transform; - vehicle = transform.GetComponent(); - subControl = transform.GetComponent(); - } - - public MovementData GetMovementData(NitroxId id) - { - // Packets should be filled with more data if the vehicle is being driven by the local player - if (vehicle && Player.main.currentMountedVehicle == vehicle) - { - // Those two values are set between -1 and 1 so we can easily scale them up while still in range for sbyte - sbyte steeringWheelYaw = (sbyte)(Mathf.Clamp(vehicle.steeringWheelYaw, -1, 1) * 70f); - sbyte steeringWheelPitch = (sbyte)(Mathf.Clamp(vehicle.steeringWheelPitch, -1, 1) * 45f); - - bool throttleApplied = false; - - Vector3 input = AvatarInputHandler.main.IsEnabled() ? GameInput.GetMoveDirection() : Vector3.zero; - // See SeaMoth.UpdateSounds - if (vehicle is SeaMoth) - { - throttleApplied = input.magnitude > 0f; - } - // See Exosuit.Update - else if (vehicle is Exosuit) - { - throttleApplied = input.y > 0f; - } - - return new DrivenVehicleMovementData(id, transform.position.ToDto(), transform.rotation.ToDto(), steeringWheelYaw, steeringWheelPitch, throttleApplied); - } - - // TODO: find out if this is enough to ensure local player is piloting the said cyclops - if (subControl && Player.main.currentSub == subControl.sub && Player.main.mode == Player.Mode.Piloting) - { - // Cyclop steering wheel's yaw and pitch are between -90 and 90 so they're already in range for sbyte - sbyte steeringWheelYaw = (sbyte)Mathf.Clamp(subControl.steeringWheelYaw, -90, 90); - sbyte steeringWheelPitch = (sbyte)Mathf.Clamp(subControl.steeringWheelPitch, -90, 90); - - // See SubControl.Update - bool throttleApplied = subControl.throttle.magnitude > 0.0001f; - - return new DrivenVehicleMovementData(id, transform.position.ToDto(), transform.rotation.ToDto(), steeringWheelYaw, steeringWheelPitch, throttleApplied); - } - - // Normal case in which the vehicule isn't driven by the local player - return new SimpleMovementData(id, transform.position.ToDto(), transform.rotation.ToDto()); - } - } } diff --git a/NitroxClient/MonoBehaviours/Vehicles/WatchedEntry.cs b/NitroxClient/MonoBehaviours/Vehicles/WatchedEntry.cs new file mode 100644 index 0000000000..376737a985 --- /dev/null +++ b/NitroxClient/MonoBehaviours/Vehicles/WatchedEntry.cs @@ -0,0 +1,138 @@ +using NitroxModel.DataStructures; +using NitroxModel.Packets; +using NitroxModel_Subnautica.DataStructures; +using UnityEngine; + +namespace NitroxClient.MonoBehaviours.Vehicles; + +public class WatchedEntry +{ + /// + /// In unity position units. Refer to for use infos. + /// + private const float MINIMAL_MOVEMENT_TRESHOLD = 0.05f; + /// + /// In degrees (°). Refer to for use infos. + /// + private const float MINIMAL_ROTATION_TRESHOLD = 0.05f; + /// + /// In seconds. Refer to for use infos. + /// + private const float MAX_TIME_WITHOUT_BROADCAST = 5f; + /// + private const float SAFETY_BROADCAST_WINDOW = 0.2f; + + private readonly Transform transform; + private readonly Vehicle vehicle; + private readonly SubControl subControl; + + private float latestBroadcastTime; + private Vector3 latestLocalPositionSent; + private Quaternion latestLocalRotationSent; + + public WatchedEntry(Transform transform) + { + this.transform = transform; + vehicle = transform.GetComponent(); + subControl = transform.GetComponent(); + } + + private bool IsDrivenVehicle() + { + return vehicle && Player.main.currentMountedVehicle == vehicle; + } + + private bool IsDrivenCyclops() + { + return subControl && Player.main.currentSub == subControl.sub && Player.main.mode == Player.Mode.Piloting; + } + + public MovementData GetMovementData(NitroxId id) + { + // Packets should be filled with more data if the vehicle is being driven by the local player + if (IsDrivenVehicle()) + { + // Those two values are set between -1 and 1 so we can easily scale them up while still in range for sbyte + sbyte steeringWheelYaw = (sbyte)(Mathf.Clamp(vehicle.steeringWheelYaw, -1, 1) * 70f); + sbyte steeringWheelPitch = (sbyte)(Mathf.Clamp(vehicle.steeringWheelPitch, -1, 1) * 45f); + + bool throttleApplied = false; + + Vector3 input = AvatarInputHandler.main.IsEnabled() ? GameInput.GetMoveDirection() : Vector3.zero; + // See SeaMoth.UpdateSounds + if (vehicle is SeaMoth) + { + throttleApplied = input.magnitude > 0f; + } + // See Exosuit.Update + else if (vehicle is Exosuit) + { + throttleApplied = input.y > 0f; + } + + return new DrivenVehicleMovementData(id, transform.position.ToDto(), transform.rotation.ToDto(), steeringWheelYaw, steeringWheelPitch, throttleApplied); + } + + if (IsDrivenCyclops()) + { + // Cyclop steering wheel's yaw and pitch are between -90 and 90 so they're already in range for sbyte + sbyte steeringWheelYaw = (sbyte)Mathf.Clamp(subControl.steeringWheelYaw, -90, 90); + sbyte steeringWheelPitch = (sbyte)Mathf.Clamp(subControl.steeringWheelPitch, -90, 90); + + // See SubControl.Update + bool throttleApplied = subControl.throttle.magnitude > 0.0001f; + + return new DrivenVehicleMovementData(id, transform.position.ToDto(), transform.rotation.ToDto(), steeringWheelYaw, steeringWheelPitch, throttleApplied); + } + + // Normal case in which the vehicule isn't driven by the local player + return new SimpleMovementData(id, transform.position.ToDto(), transform.rotation.ToDto()); + } + + public void OnBroadcastPosition() + { + latestLocalPositionSent = transform.localPosition; + latestLocalRotationSent = transform.localRotation; + } + + private bool HasVehicleMoved() + { + return Vector3.Distance(latestLocalPositionSent, transform.localPosition) > MINIMAL_MOVEMENT_TRESHOLD || + Quaternion.Angle(latestLocalRotationSent, transform.localRotation) > MINIMAL_ROTATION_TRESHOLD; + } + + /// + /// Rate limiter which prevents all non-moving vehicles from sending too many packets following some rules: + /// - the driven vehicle is not rate limited + /// - position changes less than are ignored + /// - rotation changes less than are ignored + /// - every period of , there's a + /// during which movements packets are sent to avoid any packet drop's bad effect, regardless of + /// + /// + /// is not updated during the so we can recognize this window + /// + public bool ShouldBroadcastMovement() + { + float deltaTimeSinceBroadcast = DayNightCycle.main.timePassedAsFloat - latestBroadcastTime; + + if (IsDrivenCyclops() || IsDrivenVehicle() || deltaTimeSinceBroadcast < 0 || HasVehicleMoved()) + { + // As long as the vehicle has moved, we can reset the broadcast timer + latestBroadcastTime = DayNightCycle.main.timePassedAsFloat; + return true; + } + + if (deltaTimeSinceBroadcast > MAX_TIME_WITHOUT_BROADCAST) + { + if (deltaTimeSinceBroadcast > MAX_TIME_WITHOUT_BROADCAST + SAFETY_BROADCAST_WINDOW) + { + // only reset the broadcast timer after the safety window has elapsed + latestBroadcastTime = DayNightCycle.main.timePassedAsFloat; + } + return true; + } + + return false; + } +} From b6b86ca2d86d249249b0e9fb5f43d6f0bf521c26 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Tue, 12 Nov 2024 00:49:37 +0100 Subject: [PATCH 19/20] Fix bugs: not being able to drive a vehicle, rate limiter entries not being deleted when the vehicle is gone, being too far from the piloting chair, destroying a cyclop's objects would bug for players outside the cyclops, remote player wrong rotation in bases --- NitroxClient/GameLogic/RemotePlayer.cs | 5 ++--- NitroxClient/GameLogic/SimulationOwnership.cs | 7 ++++--- NitroxClient/GameLogic/Vehicles.cs | 5 +++++ NitroxClient/MonoBehaviours/MovementBroadcaster.cs | 2 +- NitroxClient/MonoBehaviours/Vehicles/WatchedEntry.cs | 11 ++++++++++- .../Constructable_ProgressDeconstruction_Patch.cs | 4 ++-- .../Dynamic/PropulsionCannon_GrabObject_Patch.cs | 8 ++++++-- 7 files changed, 30 insertions(+), 12 deletions(-) diff --git a/NitroxClient/GameLogic/RemotePlayer.cs b/NitroxClient/GameLogic/RemotePlayer.cs index b1c720a50e..c72db621ff 100644 --- a/NitroxClient/GameLogic/RemotePlayer.cs +++ b/NitroxClient/GameLogic/RemotePlayer.cs @@ -147,7 +147,7 @@ public void UpdatePosition(Vector3 position, Vector3 velocity, Quaternion bodyRo AnimationController.Velocity = MovementHelper.GetCorrectedVelocity(position, velocity, Body, Time.fixedDeltaTime); // If in a subroot the position will be relative to the subroot - if (SubRoot && !SubRoot.isBase) + if (SubRoot && SubRoot.isBase) { Quaternion vehicleAngle = SubRoot.transform.rotation; position = vehicleAngle * position; @@ -162,13 +162,12 @@ public void UpdatePosition(Vector3 position, Vector3 velocity, Quaternion bodyRo public void UpdatePositionInCyclops(Vector3 localPosition, Quaternion localRotation) { - if (Pawn == null) + if (Pawn == null || PilotingChair) { return; } SetVehicle(null); - SetPilotingChair(null); AnimationController.AimingRotation = localRotation; AnimationController.UpdatePlayerAnimations = true; diff --git a/NitroxClient/GameLogic/SimulationOwnership.cs b/NitroxClient/GameLogic/SimulationOwnership.cs index 174fce8bcf..476a8880eb 100644 --- a/NitroxClient/GameLogic/SimulationOwnership.cs +++ b/NitroxClient/GameLogic/SimulationOwnership.cs @@ -84,7 +84,8 @@ public void TreatSimulatedEntity(SimulatedEntity simulatedEntity) { bool isLocalPlayerNewOwner = multiplayerSession.Reservation.PlayerId == simulatedEntity.PlayerId; - if (TreatVehicleEntity(simulatedEntity.Id, isLocalPlayerNewOwner, simulatedEntity.LockType)) + if (TreatVehicleEntity(simulatedEntity.Id, isLocalPlayerNewOwner, simulatedEntity.LockType) || + newerSimulationById.ContainsKey(simulatedEntity.Id)) { return; } @@ -127,7 +128,7 @@ public bool TryGetLockType(NitroxId nitroxId, out SimulationLockType simulationL return simulatedIdsByLockType.TryGetValue(nitroxId, out simulationLockType); } - private bool TreatVehicleEntity(NitroxId entityId, bool isLocalPlayerNewOwner, SimulationLockType simulationLockType) + public bool TreatVehicleEntity(NitroxId entityId, bool isLocalPlayerNewOwner, SimulationLockType simulationLockType) { if (!NitroxEntity.TryGetObjectFrom(entityId, out GameObject gameObject) || !IsVehicle(gameObject)) { @@ -180,8 +181,8 @@ public void ApplyNewerSimulation(NitroxId nitroxId) { if (newerSimulationById.TryGetValue(nitroxId, out SimulatedEntity simulatedEntity)) { - TreatSimulatedEntity(simulatedEntity); newerSimulationById.Remove(nitroxId); + TreatSimulatedEntity(simulatedEntity); } } diff --git a/NitroxClient/GameLogic/Vehicles.cs b/NitroxClient/GameLogic/Vehicles.cs index 268899a6d9..cc5c2d0b4b 100644 --- a/NitroxClient/GameLogic/Vehicles.cs +++ b/NitroxClient/GameLogic/Vehicles.cs @@ -105,6 +105,11 @@ public void SetOnPilotMode(GameObject gameObject, ushort playerId, bool isPiloti } else if (gameObject.GetComponent()) { + if (!isPiloting) + { + remotePlayer.SetPilotingChair(null); + return; + } PilotingChair pilotingChair = FindPilotingChairWithCache(gameObject, TechType.Cyclops); remotePlayer.SetPilotingChair(pilotingChair); } diff --git a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs index d58bb99aad..f9b46805d9 100644 --- a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs +++ b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs @@ -73,7 +73,7 @@ public static void RegisterWatched(GameObject gameObject, NitroxId entityId) if (!Instance.watchedEntries.ContainsKey(entityId)) { - Instance.watchedEntries.Add(entityId, new(gameObject.transform)); + Instance.watchedEntries.Add(entityId, new(entityId, gameObject.transform)); } } diff --git a/NitroxClient/MonoBehaviours/Vehicles/WatchedEntry.cs b/NitroxClient/MonoBehaviours/Vehicles/WatchedEntry.cs index 376737a985..4c0c6e179d 100644 --- a/NitroxClient/MonoBehaviours/Vehicles/WatchedEntry.cs +++ b/NitroxClient/MonoBehaviours/Vehicles/WatchedEntry.cs @@ -22,6 +22,7 @@ public class WatchedEntry /// private const float SAFETY_BROADCAST_WINDOW = 0.2f; + private readonly NitroxId Id; private readonly Transform transform; private readonly Vehicle vehicle; private readonly SubControl subControl; @@ -30,8 +31,9 @@ public class WatchedEntry private Vector3 latestLocalPositionSent; private Quaternion latestLocalRotationSent; - public WatchedEntry(Transform transform) + public WatchedEntry(NitroxId Id, Transform transform) { + this.Id = Id; this.transform = transform; vehicle = transform.GetComponent(); subControl = transform.GetComponent(); @@ -114,6 +116,13 @@ private bool HasVehicleMoved() /// public bool ShouldBroadcastMovement() { + // Watched entry validity check (e.g. for vehicle death) + if (!transform) + { + MovementBroadcaster.UnregisterWatched(Id); + return false; + } + float deltaTimeSinceBroadcast = DayNightCycle.main.timePassedAsFloat - latestBroadcastTime; if (IsDrivenCyclops() || IsDrivenVehicle() || deltaTimeSinceBroadcast < 0 || HasVehicleMoved()) diff --git a/NitroxPatcher/Patches/Dynamic/Constructable_ProgressDeconstruction_Patch.cs b/NitroxPatcher/Patches/Dynamic/Constructable_ProgressDeconstruction_Patch.cs index 1cd5fc81bf..515b0a228d 100644 --- a/NitroxPatcher/Patches/Dynamic/Constructable_ProgressDeconstruction_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/Constructable_ProgressDeconstruction_Patch.cs @@ -13,8 +13,8 @@ public sealed partial class Constructable_ProgressDeconstruction_Patch : NitroxP public static void Prefix(Constructable __instance) { - if (__instance.constructedAmount <= 0f && - __instance.transform.parent && __instance.transform.parent.TryGetComponent(out NitroxCyclops nitroxCyclops)) + if (__instance.constructedAmount <= 0f && __instance.transform.parent && + __instance.transform.parent.TryGetComponent(out NitroxCyclops nitroxCyclops) && nitroxCyclops.Virtual) { nitroxCyclops.Virtual.UnregisterConstructable(__instance.gameObject); } diff --git a/NitroxPatcher/Patches/Dynamic/PropulsionCannon_GrabObject_Patch.cs b/NitroxPatcher/Patches/Dynamic/PropulsionCannon_GrabObject_Patch.cs index 11bb9447e0..8be5bbba60 100644 --- a/NitroxPatcher/Patches/Dynamic/PropulsionCannon_GrabObject_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/PropulsionCannon_GrabObject_Patch.cs @@ -1,4 +1,4 @@ -using System.Reflection; +using System.Reflection; using NitroxClient.GameLogic; using NitroxClient.GameLogic.Simulation; using NitroxClient.MonoBehaviours; @@ -45,7 +45,11 @@ private static void ReceivedSimulationLockResponse(NitroxId id, bool lockAquired { if (lockAquired) { - EntityPositionBroadcaster.WatchEntity(id); + // In case what we grabbed wasn't a vehicle, we'll be watching it with the regular entity position broadcast system + if (!Resolve().TreatVehicleEntity(id, true, SimulationLockType.EXCLUSIVE)) + { + EntityPositionBroadcaster.WatchEntity(id); + } skipPrefixPatch = true; context.Cannon.GrabObject(context.GrabbedObject); From 7d32fe7e157f79c7edafb5c1b2c3aed04d0af572 Mon Sep 17 00:00:00 2001 From: tornac1234 <24827220+tornac1234@users.noreply.github.com> Date: Tue, 12 Nov 2024 21:11:27 +0100 Subject: [PATCH 20/20] Fix a remove while enumerating in dictionary --- NitroxClient/MonoBehaviours/MovementBroadcaster.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs index f9b46805d9..26a719d7c4 100644 --- a/NitroxClient/MonoBehaviours/MovementBroadcaster.cs +++ b/NitroxClient/MonoBehaviours/MovementBroadcaster.cs @@ -49,12 +49,17 @@ public void Update() public void BroadcastLocalData(float time) { List data = []; - foreach (KeyValuePair entry in watchedEntries) + List watchedIds = [..watchedEntries.Keys]; + + for (int i = watchedIds.Count - 1; i >= 0; i--) { - if (entry.Value.ShouldBroadcastMovement()) + NitroxId entryId = watchedIds[i]; + WatchedEntry entry = watchedEntries[entryId]; + + if (entry.ShouldBroadcastMovement()) { - data.Add(entry.Value.GetMovementData(entry.Key)); - entry.Value.OnBroadcastPosition(); + data.Add(entry.GetMovementData(entryId)); + entry.OnBroadcastPosition(); } }