Files
Nitrox/NitroxClient/MonoBehaviours/EntityPositionBroadcaster.cs
2025-07-06 00:23:46 +02:00

115 lines
4.1 KiB
C#

using System.Collections.Generic;
using System.Linq;
using NitroxClient.Communication.Abstract;
using NitroxClient.GameLogic;
using NitroxModel.Core;
using NitroxModel.DataStructures;
using NitroxModel.Packets;
using NitroxModel_Subnautica.DataStructures;
using UnityEngine;
using static NitroxModel.Packets.EntityTransformUpdates;
namespace NitroxClient.MonoBehaviours;
public class EntityPositionBroadcaster : MonoBehaviour
{
public static readonly float BROADCAST_INTERVAL = 0.25f;
private static HashSet<NitroxId> watchingEntityIds = new();
private static Dictionary<NitroxId, SplineTransformUpdate> splineUpdatesById = new();
private IPacketSender packetSender;
private float time;
public void Awake()
{
packetSender = NitroxServiceLocator.LocateService<IPacketSender>();
}
public void Update()
{
time += Time.deltaTime;
// Only do on a specific cadence to avoid hammering server
if (time >= BROADCAST_INTERVAL)
{
time = 0;
if (watchingEntityIds.Count > 0)
{
Dictionary<NitroxId, GameObject> nonSplineEntitiesById = NitroxEntity.GetObjectsFrom(watchingEntityIds)
.Where(item => !item.Value.GetComponent<SwimBehaviour>() &&
!item.Value.GetComponent<WalkBehaviour>())
.ToDictionary(item => item.Key, item => item.Value);
List<EntityTransformUpdate> updates = BuildUpdates(nonSplineEntitiesById);
if (updates.Count > 0)
{
packetSender.Send(new EntityTransformUpdates(updates));
}
}
}
}
private List<EntityTransformUpdate> BuildUpdates(Dictionary<NitroxId, GameObject> nonSplineEntitiesById)
{
List<EntityTransformUpdate> updates = new();
foreach (KeyValuePair<NitroxId, GameObject> gameObjectWithId in nonSplineEntitiesById)
{
if (gameObjectWithId.Value)
{
updates.Add(new RawTransformUpdate(gameObjectWithId.Key, gameObjectWithId.Value.transform.position.ToDto(), gameObjectWithId.Value.transform.rotation.ToDto()));
}
}
// Only send data for entities still simulated by the local player
updates.AddRange(splineUpdatesById.Values.Where(
splineUpdate => this.Resolve<SimulationOwnership>().HasAnyLockType(splineUpdate.Id)
));
splineUpdatesById.Clear();
return updates;
}
public static void WatchEntity(NitroxId id)
{
watchingEntityIds.Add(id);
// The game object may not exist at this very moment (due to being spawned in async). This is OK as we will
// automatically start sending updates when we finally get it in the world. This behavior will also allow us
// to resync or respawn entities while still have broadcasting enabled without doing anything extra.
if (NitroxEntity.TryGetComponentFrom(id, out RemotelyControlled remotelyControlled))
{
Object.Destroy(remotelyControlled);
}
}
public static void StopWatchingEntity(NitroxId id)
{
watchingEntityIds.Remove(id);
}
public static void RegisterSplineMovementChange(NitroxId id, GameObject gameObject, Vector3 targetPos, Vector3 targetDir, float velocity)
{
if (watchingEntityIds.Contains(id))
{
splineUpdatesById[id] = new(id, gameObject.transform.position.ToDto(), gameObject.transform.rotation.ToDto(), targetPos.ToDto(), targetDir.ToDto(), velocity);
}
}
public static void RemoveEntityMovementControl(GameObject gameObject, NitroxId entityId)
{
if (gameObject.TryGetComponent(out RemotelyControlled remotelyControlled))
{
Destroy(remotelyControlled);
}
StopWatchingEntity(entityId);
}
}