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 watchingEntityIds = new(); private static Dictionary splineUpdatesById = new(); private IPacketSender packetSender; private float time; public void Awake() { packetSender = NitroxServiceLocator.LocateService(); } 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 nonSplineEntitiesById = NitroxEntity.GetObjectsFrom(watchingEntityIds) .Where(item => !item.Value.GetComponent() && !item.Value.GetComponent()) .ToDictionary(item => item.Key, item => item.Value); List updates = BuildUpdates(nonSplineEntitiesById); if (updates.Count > 0) { packetSender.Send(new EntityTransformUpdates(updates)); } } } } private List BuildUpdates(Dictionary nonSplineEntitiesById) { List updates = new(); foreach (KeyValuePair 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().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); } }