diff --git a/Nitrox.Test/Client/Communication/MultiplayerSession/ConnectionState/DisconnectedStateTests.cs b/Nitrox.Test/Client/Communication/MultiplayerSession/ConnectionState/DisconnectedStateTests.cs index b9a46c131f..66a055832b 100644 --- a/Nitrox.Test/Client/Communication/MultiplayerSession/ConnectionState/DisconnectedStateTests.cs +++ b/Nitrox.Test/Client/Communication/MultiplayerSession/ConnectionState/DisconnectedStateTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading.Tasks; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -29,7 +29,7 @@ public void NegotiateShouldStartTheClientOnTheContext() Disconnected connectionState = new Disconnected(); // Act - connectionState.NegotiateReservationAsync(connectionContext); + connectionState.NegotiateReservationAsync(connectionContext).RunSynchronously(); // Assert serverClient.IsConnected.Should().BeTrue(); @@ -52,7 +52,7 @@ public void NegotiateShouldSendMultiplayerSessionPolicyRequestPacketToClient() Disconnected connectionState = new Disconnected(); // Act - connectionState.NegotiateReservationAsync(connectionContext); + connectionState.NegotiateReservationAsync(connectionContext).RunSynchronously(); // Assert serverClient.Received().Send(Arg.Any()); @@ -75,7 +75,7 @@ public void NegotiateShouldTransitionToEstablishingSessionPolicyState() Disconnected connectionState = new Disconnected(); // Act - connectionState.NegotiateReservationAsync(connectionContext); + connectionState.NegotiateReservationAsync(connectionContext).RunSynchronously(); // Assert connectionContext.Received().UpdateConnectionState(Arg.Any()); diff --git a/NitroxClient/Communication/Packets/Processors/BasicMovementProcessor.cs b/NitroxClient/Communication/Packets/Processors/BasicMovementProcessor.cs new file mode 100644 index 0000000000..84f465361a --- /dev/null +++ b/NitroxClient/Communication/Packets/Processors/BasicMovementProcessor.cs @@ -0,0 +1,28 @@ +using System.Collections; +using NitroxClient.Communication.Packets.Processors.Abstract; +using NitroxClient.GameLogic; +using NitroxClient.MonoBehaviours; +using NitroxClient.Unity.Helper; +using NitroxModel_Subnautica.DataStructures; +using NitroxModel.DataStructures.Util; +using NitroxModel.Packets; +using UnityEngine; + +namespace NitroxClient.Communication.Packets.Processors; + +public class BasicMovementProcessor : ClientPacketProcessor +{ + public BasicMovementProcessor() + { + } + + public override void Process(BasicMovement movement) + { + if (NitroxEntity.TryGetObjectFrom(movement.Id, out GameObject gameObject)) + { + MovementController mc = gameObject.EnsureComponent(); + mc.TargetPosition = movement.Position.ToUnity(); + mc.TargetRotation = movement.Rotation.ToUnity(); + } + } +} diff --git a/NitroxClient/Communication/Packets/Processors/PlayerMovementProcessor.cs b/NitroxClient/Communication/Packets/Processors/PlayerMovementProcessor.cs index 6dc6082d8c..75ee300f11 100644 --- a/NitroxClient/Communication/Packets/Processors/PlayerMovementProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/PlayerMovementProcessor.cs @@ -20,14 +20,14 @@ public PlayerMovementProcessor(PlayerManager remotePlayerManager) public override void Process(PlayerMovement movement) { - Optional remotePlayer = remotePlayerManager.Find(movement.PlayerId); + Optional remotePlayer = remotePlayerManager.Find(movement.Id); if (!remotePlayer.HasValue) { return; } remotePlayer.Value.UpdatePosition(movement.Position.ToUnity(), - movement.BodyRotation.ToUnity(), + movement.Rotation.ToUnity(), movement.AimingRotation.ToUnity()); } } diff --git a/NitroxClient/Communication/Packets/Processors/SimulationOwnershipResponseProcessor.cs b/NitroxClient/Communication/Packets/Processors/SimulationOwnershipResponseProcessor.cs index 45578434b5..f7faca9962 100644 --- a/NitroxClient/Communication/Packets/Processors/SimulationOwnershipResponseProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/SimulationOwnershipResponseProcessor.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; @@ -37,6 +37,8 @@ public override void Process(SimulationOwnershipResponse response) { RemoveRemoteController(response.Id); } + + SwapMovementController(response.Id, response.LockAcquired); } private void RemoveRemoteController(NitroxId id) @@ -49,5 +51,17 @@ private void RemoveRemoteController(NitroxId id) Object.Destroy(remotelyControlled); } } + + private void SwapMovementController(NitroxId id, bool lockAcquired) + { + Optional gameObject = NitroxEntity.GetObjectFrom(id); + + if (gameObject.HasValue) + { + MovementController movementController = gameObject.Value.GetComponent(); + movementController.SetBroadcasting(lockAcquired); + movementController.SetReceiving(!lockAcquired); + } + } } } diff --git a/NitroxClient/Communication/Packets/Processors/VehicleMovementProcessor.cs b/NitroxClient/Communication/Packets/Processors/VehicleMovementProcessor.cs index 2963d91a2c..347cf5e06c 100644 --- a/NitroxClient/Communication/Packets/Processors/VehicleMovementProcessor.cs +++ b/NitroxClient/Communication/Packets/Processors/VehicleMovementProcessor.cs @@ -1,4 +1,4 @@ -using NitroxClient.Communication.Packets.Processors.Abstract; +using NitroxClient.Communication.Packets.Processors.Abstract; using NitroxClient.GameLogic; using NitroxModel.DataStructures.GameLogic; using NitroxModel.DataStructures.Util; @@ -20,7 +20,7 @@ public VehicleMovementProcessor(PlayerManager remotePlayerManager, Vehicles vehi public override void Process(VehicleMovement vehicleMovement) { VehicleMovementData vehicleModel = vehicleMovement.VehicleMovementData; - Optional player = remotePlayerManager.Find(vehicleMovement.PlayerId); + Optional player = remotePlayerManager.Find(vehicleMovement.Id); vehicles.UpdateVehiclePosition(vehicleModel, player); } } diff --git a/NitroxClient/Debuggers/NetworkDebugger.cs b/NitroxClient/Debuggers/NetworkDebugger.cs index a062d005b8..4e39b9269e 100644 --- a/NitroxClient/Debuggers/NetworkDebugger.cs +++ b/NitroxClient/Debuggers/NetworkDebugger.cs @@ -18,7 +18,8 @@ public class NetworkDebugger : BaseDebugger, INetworkDebugger private readonly List filter = new() { nameof(PlayerMovement), nameof(EntityTransformUpdates), nameof(PlayerStats), nameof(SpawnEntities), nameof(VehicleMovement), nameof(PlayerCinematicControllerCall), - nameof(PlayFMODAsset), nameof(PlayFMODCustomEmitter), nameof(PlayFMODStudioEmitter), nameof(PlayFMODCustomLoopingEmitter), nameof(SimulationOwnershipChange) + nameof(PlayFMODAsset), nameof(PlayFMODCustomEmitter), nameof(PlayFMODStudioEmitter), nameof(PlayFMODCustomLoopingEmitter), nameof(SimulationOwnershipChange), + nameof(BasicMovement) }; private readonly List packets = new List(PACKET_STORED_COUNT); diff --git a/NitroxClient/GameLogic/Entities.cs b/NitroxClient/GameLogic/Entities.cs index a11e2fe00b..d1c07f252b 100644 --- a/NitroxClient/GameLogic/Entities.cs +++ b/NitroxClient/GameLogic/Entities.cs @@ -29,7 +29,7 @@ public class Entities private readonly Dictionary entitySpawnersByType = new Dictionary(); - public Entities(IPacketSender packetSender, ThrottledPacketSender throttledPacketSender, PlayerManager playerManager, ILocalNitroxPlayer localPlayer) + public Entities(IPacketSender packetSender, ThrottledPacketSender throttledPacketSender, PlayerManager playerManager, ILocalNitroxPlayer localPlayer, SimulationOwnership simulationOwnership) { this.packetSender = packetSender; this.throttledPacketSender = throttledPacketSender; @@ -40,7 +40,7 @@ public Entities(IPacketSender packetSender, ThrottledPacketSender throttledPacke entitySpawnersByType[typeof(InstalledBatteryEntity)] = new InstalledBatteryEntitySpawner(); entitySpawnersByType[typeof(InventoryEntity)] = new InventoryEntitySpawner(); entitySpawnersByType[typeof(InventoryItemEntity)] = new InventoryItemEntitySpawner(); - entitySpawnersByType[typeof(WorldEntity)] = new WorldEntitySpawner(playerManager, localPlayer); + entitySpawnersByType[typeof(WorldEntity)] = new WorldEntitySpawner(playerManager, localPlayer, simulationOwnership); entitySpawnersByType[typeof(PlaceholderGroupWorldEntity)] = entitySpawnersByType[typeof(WorldEntity)]; entitySpawnersByType[typeof(EscapePodWorldEntity)] = entitySpawnersByType[typeof(WorldEntity)]; entitySpawnersByType[typeof(PlayerWorldEntity)] = entitySpawnersByType[typeof(WorldEntity)]; diff --git a/NitroxClient/GameLogic/LocalPlayer.cs b/NitroxClient/GameLogic/LocalPlayer.cs index f3f6d4c0c4..0b3e5b4309 100644 --- a/NitroxClient/GameLogic/LocalPlayer.cs +++ b/NitroxClient/GameLogic/LocalPlayer.cs @@ -25,6 +25,7 @@ public class LocalPlayer : ILocalNitroxPlayer private readonly Lazy body; private readonly Lazy playerModel; private readonly Lazy bodyPrototype; + private readonly NitroxEntity entity; public GameObject Body => body.Value; @@ -34,6 +35,7 @@ public class LocalPlayer : ILocalNitroxPlayer public string PlayerName => multiplayerSession.AuthenticationContext.Username; public ushort PlayerId => multiplayerSession.Reservation.PlayerId; + public PlayerSettings PlayerSettings => multiplayerSession.PlayerSettings; public Perms Permissions; @@ -47,18 +49,24 @@ public LocalPlayer(IMultiplayerSession multiplayerSession, IPacketSender packetS playerModel = new Lazy(() => Body.RequireGameObject("player_view")); bodyPrototype = new Lazy(CreateBodyPrototype); Permissions = Perms.PLAYER; + + if (!Player.mainObject.TryGetComponent(out NitroxEntity nitroxEntity)) + { + nitroxEntity = Player.mainObject.AddComponent(); + } + entity = nitroxEntity; } - public void BroadcastLocation(Vector3 location, Vector3 velocity, Quaternion bodyRotation, Quaternion aimingRotation, Optional vehicle) + public void BroadcastLocation(Vector3 location, Quaternion bodyRotation, Quaternion aimingRotation, Optional vehicle) { Movement movement; if (vehicle.HasValue) { - movement = new VehicleMovement(multiplayerSession.Reservation.PlayerId, vehicle.Value); + movement = new VehicleMovement(entity.Id, vehicle.Value); } else { - movement = new PlayerMovement(multiplayerSession.Reservation.PlayerId, location.ToDto(), bodyRotation.ToDto(), aimingRotation.ToDto()); + movement = new PlayerMovement(entity.Id, location.ToDto(), bodyRotation.ToDto(), aimingRotation.ToDto()); } packetSender.Send(movement); diff --git a/NitroxClient/GameLogic/MobileVehicleBay.cs b/NitroxClient/GameLogic/MobileVehicleBay.cs index 16c6456ce7..1d19c0fbdd 100644 --- a/NitroxClient/GameLogic/MobileVehicleBay.cs +++ b/NitroxClient/GameLogic/MobileVehicleBay.cs @@ -15,12 +15,12 @@ public class MobileVehicleBay public static GameObject MostRecentlyCrafted { get; set; } private readonly IPacketSender packetSender; - private readonly Vehicles vehicles; + private readonly SimulationOwnership simulationOwnership; - public MobileVehicleBay(IPacketSender packetSender, Vehicles vehicles) + public MobileVehicleBay(IPacketSender packetSender, Vehicles vehicles, SimulationOwnership simulationOwnership) { this.packetSender = packetSender; - this.vehicles = vehicles; + this.simulationOwnership = simulationOwnership; } public void BeginCrafting(ConstructorInput constructor, GameObject constructedObject, TechType techType, float duration) @@ -42,11 +42,12 @@ public void BeginCrafting(ConstructorInput constructor, GameObject constructedOb NitroxId constructedObjectId = NitroxEntity.GenerateNewId(constructedObject); + MovementController mc = constructedObject.AddComponent(); + VehicleWorldEntity vehicleEntity = new(constructorId, DayNightCycle.main.timePassedAsFloat, constructedObject.transform.ToLocalDto(), string.Empty, false, constructedObjectId, techType.ToDto(), null); VehicleChildEntityHelper.PopulateChildren(constructedObjectId, constructedObject.GetFullHierarchyPath(), vehicleEntity.ChildEntities, constructedObject); packetSender.Send(new EntitySpawnedByClient(vehicleEntity)); - - constructor.StartCoroutine(vehicles.UpdateVehiclePositionAfterSpawn(constructedObjectId, techType, constructedObject, duration + 10.0f)); + simulationOwnership.RequestSimulationLock(constructedObjectId, SimulationLockType.TRANSIENT); } } diff --git a/NitroxClient/GameLogic/RemotePlayer.cs b/NitroxClient/GameLogic/RemotePlayer.cs index f0d266a59e..4957a2b126 100644 --- a/NitroxClient/GameLogic/RemotePlayer.cs +++ b/NitroxClient/GameLogic/RemotePlayer.cs @@ -111,7 +111,7 @@ public void UpdatePosition(Vector3 position, Quaternion bodyRotation, Quaternion SetVehicle(null); SetPilotingChair(null); - MovementController.IsMoving = true; + MovementController.SetReceiving(true); MovementController.TargetPosition = position; MovementController.TargetRotation = bodyRotation; @@ -169,7 +169,10 @@ public void SetPilotingChair(PilotingChair newPilotingChair) RigidBody.isKinematic = AnimationController["cyclops_steering"] = isInPilotingChair; RigidBody.interpolation = isInPilotingChair ? RigidbodyInterpolation.None : RigidbodyInterpolation.Interpolate; - MovementController.IsMoving = !isInPilotingChair; + if (isInPilotingChair) + { + MovementController.SetReceiving(false); + } } } @@ -246,7 +249,10 @@ public void SetVehicle(Vehicle newVehicle) RigidBody.interpolation = Vehicle ? RigidbodyInterpolation.None : RigidbodyInterpolation.Interpolate; RigidBody.isKinematic = Vehicle; - MovementController.IsMoving = !Vehicle; + if (Vehicle) + { + MovementController.SetReceiving(false); + } AnimationController["in_seamoth"] = newVehicle is SeaMoth; AnimationController["in_exosuit"] = AnimationController["using_mechsuit"] = newVehicle is Exosuit; diff --git a/NitroxClient/GameLogic/Simulation/LockRequest.cs b/NitroxClient/GameLogic/Simulation/LockRequest.cs index 9ccda1eb19..cefebed1f7 100644 --- a/NitroxClient/GameLogic/Simulation/LockRequest.cs +++ b/NitroxClient/GameLogic/Simulation/LockRequest.cs @@ -1,4 +1,4 @@ -using NitroxModel.DataStructures; +using NitroxModel.DataStructures; namespace NitroxClient.GameLogic.Simulation { @@ -19,7 +19,7 @@ public override void LockRequestComplete(NitroxId id, bool lockAquired) { if (onComplete != null) { - onComplete(id, lockAquired, (T)context); + onComplete(id, lockAquired, context); } } diff --git a/NitroxClient/GameLogic/SimulationOwnership.cs b/NitroxClient/GameLogic/SimulationOwnership.cs index 00c1535c5b..f8024266df 100644 --- a/NitroxClient/GameLogic/SimulationOwnership.cs +++ b/NitroxClient/GameLogic/SimulationOwnership.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using NitroxClient.Communication.Abstract; using NitroxClient.GameLogic.Simulation; using NitroxModel.DataStructures; @@ -13,6 +14,9 @@ public class SimulationOwnership private readonly Dictionary simulatedIdsByLockType = new Dictionary(); private readonly Dictionary lockRequestsById = new Dictionary(); + public event Action StoppedSimulatingEntity; + public event Action StartedSimulatingEntity; + public SimulationOwnership(IMultiplayerSession muliplayerSession, IPacketSender packetSender) { this.muliplayerSession = muliplayerSession; @@ -68,11 +72,20 @@ public void ReceivedSimulationLockResponse(NitroxId id, bool lockAquired, Simula public void SimulateEntity(NitroxId id, SimulationLockType lockType) { simulatedIdsByLockType[id] = lockType; + if (StartedSimulatingEntity != null) + { + StartedSimulatingEntity(id); + } } public void StopSimulatingEntity(NitroxId id) { simulatedIdsByLockType.Remove(id); + + if (StoppedSimulatingEntity != null) + { + StoppedSimulatingEntity(id); + } } } } diff --git a/NitroxClient/GameLogic/Spawning/WorldEntities/ReefbackWorldEntitySpawner.cs b/NitroxClient/GameLogic/Spawning/WorldEntities/ReefbackWorldEntitySpawner.cs index d6f56aa4e5..06777200ce 100644 --- a/NitroxClient/GameLogic/Spawning/WorldEntities/ReefbackWorldEntitySpawner.cs +++ b/NitroxClient/GameLogic/Spawning/WorldEntities/ReefbackWorldEntitySpawner.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections; using NitroxModel.DataStructures.GameLogic; using NitroxModel.DataStructures.GameLogic.Entities; using NitroxModel.DataStructures.Util; @@ -33,7 +33,7 @@ public IEnumerator SpawnAsync(WorldEntity entity, Optional parent, E } life.initialized = true; - life.SpawnPlants(); + yield return life.SpawnPlants(); foreach (Entity childEntity in entity.ChildEntities) { if (childEntity is WorldEntity worldChild) diff --git a/NitroxClient/GameLogic/Spawning/WorldEntities/VehicleWorldEntitySpawner.cs b/NitroxClient/GameLogic/Spawning/WorldEntities/VehicleWorldEntitySpawner.cs index 2674533031..8fc1e6071c 100644 --- a/NitroxClient/GameLogic/Spawning/WorldEntities/VehicleWorldEntitySpawner.cs +++ b/NitroxClient/GameLogic/Spawning/WorldEntities/VehicleWorldEntitySpawner.cs @@ -3,6 +3,7 @@ using NitroxClient.MonoBehaviours; using NitroxClient.MonoBehaviours.Overrides; using NitroxClient.Unity.Helper; +using NitroxModel.DataStructures; using NitroxModel.DataStructures.GameLogic.Entities; using NitroxModel.DataStructures.Util; using NitroxModel.Helper; @@ -17,6 +18,12 @@ public class VehicleWorldEntitySpawner : IWorldEntitySpawner // that they are within allowed range. However, this range is a bit restrictive. We will allow constructor spawning up to a specified // distance - anything more will simply use world spawning (no need to play the animation anyways). private const float ALLOWED_CONSTRUCTOR_DISTANCE = 100.0f; + private readonly SimulationOwnership simulationOwnership; + + internal VehicleWorldEntitySpawner(SimulationOwnership simulationOwnership) + { + this.simulationOwnership = simulationOwnership; + } public IEnumerator SpawnAsync(WorldEntity entity, Optional parent, EntityCell cellRoot, TaskResult> result) { @@ -35,12 +42,17 @@ public IEnumerator SpawnAsync(WorldEntity entity, Optional parent, E { MobileVehicleBay.TransmitLocalSpawns = false; yield return SpawnViaConstructor(vehicleEntity, constructor, result); + result.value.Value.EnsureComponent(); + + simulationOwnership.RequestSimulationLock(vehicleEntity.Id, SimulationLockType.TRANSIENT); MobileVehicleBay.TransmitLocalSpawns = true; yield break; } } - yield return SpawnInWorld(vehicleEntity, result, parent); + yield return SpawnInWorld(vehicleEntity, result, parent); + result.value.Value.EnsureComponent(); + simulationOwnership.RequestSimulationLock(vehicleEntity.Id, SimulationLockType.TRANSIENT); } private IEnumerator SpawnInWorld(VehicleWorldEntity vehicleEntity, TaskResult> result, Optional parent) diff --git a/NitroxClient/GameLogic/Spawning/WorldEntities/WorldEntitySpawnerResolver.cs b/NitroxClient/GameLogic/Spawning/WorldEntities/WorldEntitySpawnerResolver.cs index 1d8ef1ae7c..d549e8fc3c 100644 --- a/NitroxClient/GameLogic/Spawning/WorldEntities/WorldEntitySpawnerResolver.cs +++ b/NitroxClient/GameLogic/Spawning/WorldEntities/WorldEntitySpawnerResolver.cs @@ -8,20 +8,21 @@ namespace NitroxClient.GameLogic.Spawning.WorldEntities; public class WorldEntitySpawnerResolver { private readonly DefaultWorldEntitySpawner defaultEntitySpawner = new(); - private readonly VehicleWorldEntitySpawner vehicleWorldEntitySpawner = new(); + private readonly VehicleWorldEntitySpawner vehicleWorldEntitySpawner; private readonly PlaceholderGroupWorldEntitySpawner prefabWorldEntitySpawner; private readonly PlayerWorldEntitySpawner playerWorldEntitySpawner; private readonly Dictionary customSpawnersByTechType = new(); - public WorldEntitySpawnerResolver(PlayerManager playerManager, ILocalNitroxPlayer localPlayer) + public WorldEntitySpawnerResolver(PlayerManager playerManager, ILocalNitroxPlayer localPlayer, SimulationOwnership simulationOwnership) { customSpawnersByTechType[TechType.Crash] = new CrashEntitySpawner(); customSpawnersByTechType[TechType.Reefback] = new ReefbackWorldEntitySpawner(defaultEntitySpawner); customSpawnersByTechType[TechType.EscapePod] = new EscapePodWorldEntitySpawner(); prefabWorldEntitySpawner = new PlaceholderGroupWorldEntitySpawner(this, defaultEntitySpawner); playerWorldEntitySpawner = new PlayerWorldEntitySpawner(playerManager, localPlayer); + vehicleWorldEntitySpawner = new VehicleWorldEntitySpawner(simulationOwnership); } public IWorldEntitySpawner ResolveEntitySpawner(WorldEntity entity) diff --git a/NitroxClient/GameLogic/Spawning/WorldEntitySpawner.cs b/NitroxClient/GameLogic/Spawning/WorldEntitySpawner.cs index 722fc04d01..239d6676e3 100644 --- a/NitroxClient/GameLogic/Spawning/WorldEntitySpawner.cs +++ b/NitroxClient/GameLogic/Spawning/WorldEntitySpawner.cs @@ -16,9 +16,9 @@ public class WorldEntitySpawner : EntitySpawner private readonly WorldEntitySpawnerResolver worldEntitySpawnResolver; private readonly Dictionary batchCellsById; - public WorldEntitySpawner(PlayerManager playerManager, ILocalNitroxPlayer localPlayer) + public WorldEntitySpawner(PlayerManager playerManager, ILocalNitroxPlayer localPlayer, SimulationOwnership simulationOwnership) { - worldEntitySpawnResolver = new WorldEntitySpawnerResolver(playerManager, localPlayer); + worldEntitySpawnResolver = new WorldEntitySpawnerResolver(playerManager, localPlayer, simulationOwnership); if (NitroxEnvironment.IsNormal) { diff --git a/NitroxClient/GameLogic/Vehicles.cs b/NitroxClient/GameLogic/Vehicles.cs index d45d21002e..9a7f033c6c 100644 --- a/NitroxClient/GameLogic/Vehicles.cs +++ b/NitroxClient/GameLogic/Vehicles.cs @@ -173,16 +173,6 @@ public IEnumerator AllowMovementPacketsAfterDockingAnimation(PacketSuppressor {}; public event Action BeforeFixedUpdate = () => {}; public event Action AfterUpdate = () => {}; public event Action AfterFixedUpdate = () => {}; + private Vector3 velocity; + private float curTime; private Rigidbody rigidbody; - public Vector3 Velocity; + private IPacketSender packetSender; + private SimulationOwnership simulationOwnership; + private NitroxEntity entity; + private static bool runOnce; + + public void SetBroadcasting(bool broadcasting) + { + Broadcasting = broadcasting; + if (Receiving && broadcasting) + { + Receiving = !broadcasting; + } + } + + public void SetReceiving(bool receiving) + { + Receiving = receiving; + if (Broadcasting && receiving) + { + Broadcasting = !receiving; + } + } + + private static void StartedSimulatingEntity(NitroxId id) + { + if (NitroxEntity.TryGetObjectFrom(id, out GameObject gameObject)) + { + if (gameObject.TryGetComponent(out MovementController mc)) + { + mc.SetBroadcasting(true); + } + } + } + + private static void StoppedSimulatingEntity(NitroxId id) + { + if (NitroxEntity.TryGetObjectFrom(id, out GameObject gameObject)) + { + if (gameObject.TryGetComponent(out MovementController mc)) + { + mc.SetReceiving(true); + } + } + } + + private void Awake() + { + packetSender = NitroxServiceLocator.LocateService(); + simulationOwnership = NitroxServiceLocator.LocateService(); + if (!runOnce) + { + runOnce = true; + simulationOwnership.StartedSimulatingEntity += StartedSimulatingEntity; + simulationOwnership.StoppedSimulatingEntity += StoppedSimulatingEntity; + } + } private void Start() { rigidbody = GetComponent(); + if (!TryGetComponent(out NitroxEntity nitroxEntity)) + { + NitroxEntity.GenerateNewId(gameObject); + + nitroxEntity = GetComponent(); + } + entity = nitroxEntity; } private void Update() { BeforeUpdate(); - if (!rigidbody && IsMoving) + if (!rigidbody && Receiving) { - transform.position = Vector3.SmoothDamp(transform.position, TargetPosition, ref Velocity, Scalar * Time.deltaTime); + transform.position = Vector3.SmoothDamp(transform.position, TargetPosition, ref velocity, Scalar * Time.deltaTime); transform.rotation = Quaternion.Lerp(transform.rotation, TargetRotation, Scalar * Time.deltaTime); } AfterUpdate(); @@ -44,10 +121,22 @@ private void Update() private void FixedUpdate() { BeforeFixedUpdate(); - if (rigidbody && IsMoving) + + if (Broadcasting && simulationOwnership.HasAnyLockType(entity.Id)) + { + curTime += Time.fixedDeltaTime; + if (curTime >= LOCATION_BROADCAST_TIME) + { + curTime = 0f; + + packetSender.Send(new BasicMovement(entity.Id, transform.position.ToDto(), transform.rotation.ToDto())); + } + } + + if (rigidbody && Receiving) { float timing = Scalar * Time.fixedDeltaTime; - Vector3 newPos = Vector3.SmoothDamp(transform.position, TargetPosition, ref Velocity, timing); + Vector3 newPos = Vector3.SmoothDamp(transform.position, TargetPosition, ref velocity, timing); Quaternion newRot = Quaternion.Lerp(transform.rotation, TargetRotation, timing); if (rigidbody.isKinematic) @@ -57,7 +146,7 @@ private void FixedUpdate() } else { - rigidbody.velocity = Velocity; + rigidbody.velocity = velocity; Quaternion delta = TargetRotation * transform.rotation.GetInverse(); delta.ToAngleAxis(out float angle, out Vector3 axis); diff --git a/NitroxClient/MonoBehaviours/MultiplayerVehicleControl.cs b/NitroxClient/MonoBehaviours/MultiplayerVehicleControl.cs index 4517d1eac3..574d23e21c 100644 --- a/NitroxClient/MonoBehaviours/MultiplayerVehicleControl.cs +++ b/NitroxClient/MonoBehaviours/MultiplayerVehicleControl.cs @@ -56,13 +56,12 @@ internal virtual void SetArmPositions(Vector3 leftArmPosition, Vector3 rightArmP internal virtual void Enter() { - movementController.IsMoving = true; + movementController.SetReceiving(true); enabled = true; } public virtual void Exit() { - movementController.IsMoving = false; enabled = false; } diff --git a/NitroxClient/MonoBehaviours/NitroxEntity.cs b/NitroxClient/MonoBehaviours/NitroxEntity.cs index e2862e16d1..b9986f8a82 100644 --- a/NitroxClient/MonoBehaviours/NitroxEntity.cs +++ b/NitroxClient/MonoBehaviours/NitroxEntity.cs @@ -80,7 +80,10 @@ public static void SetNewId(GameObject gameObject, NitroxId id) if (gameObject.TryGetComponent(out NitroxEntity entity)) { - gameObjectsById.Remove(entity.Id); + if (entity.Id != null) + { + gameObjectsById.Remove(entity.Id); + } } else { diff --git a/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs b/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs index e5145de510..675689726b 100644 --- a/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs +++ b/NitroxClient/MonoBehaviours/PlayerMovementBroadcaster.cs @@ -41,7 +41,6 @@ public void FixedUpdate() } Vector3 currentPosition = Player.main.transform.position; - Vector3 playerVelocity = Player.main.playerController.velocity; // IDEA: possibly only CameraRotation is of interest, because bodyrotation is extracted from that. Quaternion bodyRotation = MainCameraControl.main.viewModel.transform.rotation; @@ -55,7 +54,7 @@ public void FixedUpdate() vehicle.Value.DriverRotation = Player.main.transform.rotation.ToDto(); } - localPlayer.BroadcastLocation(currentPosition, playerVelocity, bodyRotation, aimingRotation, vehicle); + localPlayer.BroadcastLocation(currentPosition, bodyRotation, aimingRotation, vehicle); } private Optional GetVehicleMovement() @@ -82,6 +81,11 @@ private Optional GetVehicleMovement() return Optional.Empty; } + if (vehicle.TryGetComponent(out MovementController mc)) + { + mc.SetReceiving(false); + } + Transform vehicleTransform = vehicle.transform; position = vehicleTransform.position; rotation = vehicleTransform.rotation; diff --git a/NitroxModel/Packets/BasicMovementPacket.cs b/NitroxModel/Packets/BasicMovementPacket.cs new file mode 100644 index 0000000000..2c5611eb94 --- /dev/null +++ b/NitroxModel/Packets/BasicMovementPacket.cs @@ -0,0 +1,29 @@ +using NitroxModel.DataStructures.Unity; +using NitroxModel.DataStructures; +using NitroxModel.Networking; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static NitroxModel.Packets.Packet; + +namespace NitroxModel.Packets +{ + [Serializable] + public class BasicMovement : Movement + { + public override NitroxId Id { get; } + public override NitroxVector3 Position { get; } + public override NitroxQuaternion Rotation { get; } + + public BasicMovement(NitroxId id, NitroxVector3 position, NitroxQuaternion rotation) + { + Id = id; + Position = position; + Rotation = rotation; + DeliveryMethod = NitroxDeliveryMethod.DeliveryMethod.UNRELIABLE_SEQUENCED; + UdpChannel = UdpChannelId.MISC_MOVEMENT; + } + } +} diff --git a/NitroxModel/Packets/Movement.cs b/NitroxModel/Packets/Movement.cs index 03c1a46b49..7fef304003 100644 --- a/NitroxModel/Packets/Movement.cs +++ b/NitroxModel/Packets/Movement.cs @@ -1,13 +1,14 @@ using System; +using NitroxModel.DataStructures; using NitroxModel.DataStructures.Unity; +using NitroxModel.Networking; namespace NitroxModel.Packets; [Serializable] public abstract class Movement : Packet { - public abstract ushort PlayerId { get; } + public abstract NitroxId Id { get; } public abstract NitroxVector3 Position { get; } - public abstract NitroxQuaternion BodyRotation { get; } - public abstract NitroxQuaternion AimingRotation { get; } + public abstract NitroxQuaternion Rotation { get; } } diff --git a/NitroxModel/Packets/Packet.cs b/NitroxModel/Packets/Packet.cs index b49142aa3f..654eb66147 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; @@ -79,7 +79,8 @@ public enum UdpChannelId DEFAULT = 0, PLAYER_MOVEMENT = 1, VEHICLE_MOVEMENT = 2, - PLAYER_STATS = 3 + PLAYER_STATS = 3, + MISC_MOVEMENT = 4 } public byte[] Serialize() diff --git a/NitroxModel/Packets/PlayerMovement.cs b/NitroxModel/Packets/PlayerMovement.cs index 965d042eb7..78a5015db0 100644 --- a/NitroxModel/Packets/PlayerMovement.cs +++ b/NitroxModel/Packets/PlayerMovement.cs @@ -1,4 +1,5 @@ using System; +using NitroxModel.DataStructures; using NitroxModel.DataStructures.Unity; using NitroxModel.Networking; @@ -7,16 +8,16 @@ namespace NitroxModel.Packets [Serializable] public class PlayerMovement : Movement { - public override ushort PlayerId { get; } + public override NitroxId Id { get; } public override NitroxVector3 Position { get; } - public override NitroxQuaternion BodyRotation { get; } - public override NitroxQuaternion AimingRotation { get; } + public override NitroxQuaternion Rotation { get; } + public NitroxQuaternion AimingRotation { get; } - public PlayerMovement(ushort playerId, NitroxVector3 position, NitroxQuaternion bodyRotation, NitroxQuaternion aimingRotation) + public PlayerMovement(NitroxId id, NitroxVector3 position, NitroxQuaternion rotation, NitroxQuaternion aimingRotation) { - PlayerId = playerId; + Id = id; Position = position; - BodyRotation = bodyRotation; + Rotation = rotation; AimingRotation = aimingRotation; DeliveryMethod = NitroxDeliveryMethod.DeliveryMethod.UNRELIABLE_SEQUENCED; UdpChannel = UdpChannelId.PLAYER_MOVEMENT; diff --git a/NitroxModel/Packets/VehicleMovement.cs b/NitroxModel/Packets/VehicleMovement.cs index 3ea1d356aa..aa784af085 100644 --- a/NitroxModel/Packets/VehicleMovement.cs +++ b/NitroxModel/Packets/VehicleMovement.cs @@ -1,5 +1,6 @@ using System; using BinaryPack.Attributes; +using NitroxModel.DataStructures; using NitroxModel.DataStructures.GameLogic; using NitroxModel.DataStructures.Unity; using NitroxModel.Networking; @@ -9,19 +10,18 @@ namespace NitroxModel.Packets [Serializable] public class VehicleMovement : Movement { - public override ushort PlayerId { get; } + public override NitroxId Id { get; } public VehicleMovementData VehicleMovementData { get; } [IgnoredMember] public override NitroxVector3 Position => VehicleMovementData.Position; [IgnoredMember] - public override NitroxQuaternion BodyRotation => VehicleMovementData.Rotation; - [IgnoredMember] - public override NitroxQuaternion AimingRotation => VehicleMovementData.Rotation; + public override NitroxQuaternion Rotation => VehicleMovementData.Rotation; - public VehicleMovement(ushort playerId, VehicleMovementData vehicleMovementData) + /// Player Id + public VehicleMovement(NitroxId id, VehicleMovementData vehicleMovementData) { - PlayerId = playerId; + Id = id; VehicleMovementData = vehicleMovementData; DeliveryMethod = NitroxDeliveryMethod.DeliveryMethod.UNRELIABLE_SEQUENCED; UdpChannel = UdpChannelId.VEHICLE_MOVEMENT; diff --git a/NitroxPatcher/Patches/Dynamic/FreezeRigidbodyWhenFar_FixedUpdate_Patch.cs b/NitroxPatcher/Patches/Dynamic/FreezeRigidbodyWhenFar_FixedUpdate_Patch.cs index 78b05afb75..6c58e6d3fe 100644 --- a/NitroxPatcher/Patches/Dynamic/FreezeRigidbodyWhenFar_FixedUpdate_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/FreezeRigidbodyWhenFar_FixedUpdate_Patch.cs @@ -50,7 +50,7 @@ public static IEnumerable Transpiler(MethodBase original, IEnum public static bool IsMoving(GameObject go) { - return go.TryGetComponent(out MovementController mc) && mc.IsMoving; + return go.TryGetComponent(out MovementController mc) && mc.Receiving; } } } diff --git a/NitroxPatcher/Patches/Dynamic/PilotingChair_OnHandClick_Patch.cs b/NitroxPatcher/Patches/Dynamic/PilotingChair_OnHandClick_Patch.cs index e96108c1d6..2974a75f8c 100644 --- a/NitroxPatcher/Patches/Dynamic/PilotingChair_OnHandClick_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/PilotingChair_OnHandClick_Patch.cs @@ -1,7 +1,8 @@ -using System.Reflection; +using System.Reflection; using NitroxClient.GameLogic; using NitroxClient.GameLogic.HUD.Components; using NitroxClient.GameLogic.Simulation; +using NitroxClient.MonoBehaviours; using NitroxModel.DataStructures; using NitroxModel.Helper; @@ -50,6 +51,10 @@ private static void ReceivedSimulationLockResponse(NitroxId id, bool lockAquired { skipPrefix = true; pilotingChair.OnHandClick(context.GuiHand); + if (pilotingChair.subRoot.TryGetComponent(out MovementController mc)) + { + mc.SetBroadcasting(false); + } skipPrefix = false; } else diff --git a/NitroxPatcher/Patches/Dynamic/PilotingChair_OnPlayerDeath_Patch.cs b/NitroxPatcher/Patches/Dynamic/PilotingChair_OnPlayerDeath_Patch.cs index 95ff53903a..3e246488f5 100644 --- a/NitroxPatcher/Patches/Dynamic/PilotingChair_OnPlayerDeath_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/PilotingChair_OnPlayerDeath_Patch.cs @@ -1,5 +1,6 @@ -using System.Reflection; +using System.Reflection; using NitroxClient.GameLogic; +using NitroxClient.MonoBehaviours; using NitroxModel.DataStructures; using NitroxModel.Helper; @@ -18,6 +19,10 @@ 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); + if (__instance.subRoot.TryGetComponent(out MovementController mc)) + { + mc.SetBroadcasting(true); + } } } } diff --git a/NitroxPatcher/Patches/Dynamic/PilotingChair_ReleaseBy_Patch.cs b/NitroxPatcher/Patches/Dynamic/PilotingChair_ReleaseBy_Patch.cs index 6f30e5684a..8d7838f946 100644 --- a/NitroxPatcher/Patches/Dynamic/PilotingChair_ReleaseBy_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/PilotingChair_ReleaseBy_Patch.cs @@ -1,5 +1,6 @@ -using System.Reflection; +using System.Reflection; using NitroxClient.GameLogic; +using NitroxClient.MonoBehaviours; using NitroxModel.DataStructures; using NitroxModel.Helper; @@ -18,6 +19,10 @@ 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); + if (__instance.subRoot.TryGetComponent(out MovementController mc)) + { + mc.SetBroadcasting(true); + } } } } diff --git a/NitroxPatcher/Patches/Dynamic/Vehicle_OnPilotModeBegin_Patch.cs b/NitroxPatcher/Patches/Dynamic/Vehicle_OnPilotModeBegin_Patch.cs index 73711d9c10..ef9a38448d 100644 --- a/NitroxPatcher/Patches/Dynamic/Vehicle_OnPilotModeBegin_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/Vehicle_OnPilotModeBegin_Patch.cs @@ -1,5 +1,6 @@ -using System.Reflection; +using System.Reflection; using NitroxClient.GameLogic; +using NitroxClient.MonoBehaviours; using NitroxModel.Helper; namespace NitroxPatcher.Patches.Dynamic; @@ -11,5 +12,9 @@ public sealed partial class Vehicle_OnPilotModeBegin_Patch : NitroxPatch, IDynam public static void Prefix(Vehicle __instance) { Resolve().BroadcastOnPilotModeChanged(__instance, true); + if (__instance.TryGetComponent(out MovementController mc)) + { + mc.SetBroadcasting(false); + } } } diff --git a/NitroxPatcher/Patches/Dynamic/Vehicle_OnPilotModeEnd_Patch.cs b/NitroxPatcher/Patches/Dynamic/Vehicle_OnPilotModeEnd_Patch.cs index a86d788319..eb06a9e0f3 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; @@ -22,6 +22,10 @@ public static void Prefix(Vehicle __instance) if (__instance.TryGetIdOrWarn(out NitroxId id)) { Resolve().RequestSimulationLock(id, SimulationLockType.TRANSIENT); + if (__instance.TryGetComponent(out MovementController mc)) + { + mc.SetBroadcasting(true); + } } } } diff --git a/NitroxPatcher/Patches/Dynamic/Vehicle_ShouldSetKinematic_Patch.cs b/NitroxPatcher/Patches/Dynamic/Vehicle_ShouldSetKinematic_Patch.cs index 821d0801bb..7b2e48e954 100644 --- a/NitroxPatcher/Patches/Dynamic/Vehicle_ShouldSetKinematic_Patch.cs +++ b/NitroxPatcher/Patches/Dynamic/Vehicle_ShouldSetKinematic_Patch.cs @@ -10,9 +10,9 @@ public sealed partial class Vehicle_ShouldSetKinematic_Patch : NitroxPatch, IDyn public static bool Prefix(Vehicle __instance, ref bool __result) { - if (__instance.TryGetComponent(out MovementController movementController)) + if (__instance.TryGetComponent(out MovementController movementController) && movementController.Receiving) { - __result = !movementController.IsMoving; + __result = true; return false; } return true; diff --git a/NitroxServer/Communication/Packets/Processors/BasicMovementPacketProcessor.cs b/NitroxServer/Communication/Packets/Processors/BasicMovementPacketProcessor.cs new file mode 100644 index 0000000000..5c758d9256 --- /dev/null +++ b/NitroxServer/Communication/Packets/Processors/BasicMovementPacketProcessor.cs @@ -0,0 +1,35 @@ +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 BasicMovementPacketProcessor : AuthenticatedPacketProcessor + { + private readonly PlayerManager playerManager; + private readonly EntityRegistry entityRegistry; + + public BasicMovementPacketProcessor(PlayerManager playerManager, EntityRegistry entityRegistry) + { + this.playerManager = playerManager; + this.entityRegistry = entityRegistry; + } + + public override void Process(BasicMovement packet, Player player) + { + Optional entity = entityRegistry.GetEntityById(packet.Id); + + if (entity.HasValue) + { + entity.Value.Transform.Position = packet.Position; + entity.Value.Transform.Rotation = packet.Rotation; + } + + playerManager.SendPacketToOtherPlayers(packet, player); + } + } +} diff --git a/NitroxServer/Communication/Packets/Processors/PlayerMovementProcessor.cs b/NitroxServer/Communication/Packets/Processors/PlayerMovementProcessor.cs index f84bbfeb36..b11642dabd 100644 --- a/NitroxServer/Communication/Packets/Processors/PlayerMovementProcessor.cs +++ b/NitroxServer/Communication/Packets/Processors/PlayerMovementProcessor.cs @@ -26,11 +26,11 @@ public override void Process(PlayerMovement packet, Player player) if (playerEntity.HasValue) { playerEntity.Value.Transform.Position = packet.Position; - playerEntity.Value.Transform.Rotation = packet.BodyRotation; + playerEntity.Value.Transform.Rotation = packet.Rotation; } player.Position = packet.Position; - player.Rotation = packet.BodyRotation; + player.Rotation = packet.Rotation; playerManager.SendPacketToOtherPlayers(packet, player); } } diff --git a/NitroxServer/Communication/Packets/Processors/VehicleMovementPacketProcessor.cs b/NitroxServer/Communication/Packets/Processors/VehicleMovementPacketProcessor.cs index 6a22c3c9de..c222de6866 100644 --- a/NitroxServer/Communication/Packets/Processors/VehicleMovementPacketProcessor.cs +++ b/NitroxServer/Communication/Packets/Processors/VehicleMovementPacketProcessor.cs @@ -29,7 +29,7 @@ public override void Process(VehicleMovement packet, Player player) worldVehicle.Transform.Rotation = packet.VehicleMovementData.Rotation; } - if (player.Id == packet.PlayerId) + if (player.GameObjectId == packet.Id) { player.Position = packet.VehicleMovementData.DriverPosition; player.Rotation = packet.VehicleMovementData.DriverRotation; ;