using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using NitroxClient.Communication.Packets.Processors.Abstract; using NitroxClient.GameLogic; using NitroxClient.GameLogic.Bases; using NitroxClient.GameLogic.Spawning.Bases; using NitroxClient.GameLogic.Spawning.Metadata; using NitroxClient.MonoBehaviours; using NitroxClient.Unity.Helper; using NitroxModel.DataStructures; using NitroxModel.DataStructures.GameLogic; using NitroxModel.DataStructures.GameLogic.Entities; using NitroxModel.DataStructures.GameLogic.Entities.Bases; using NitroxModel.Packets; using NitroxModel_Subnautica.DataStructures; using UnityEngine; namespace NitroxClient.Communication.Packets.Processors; public class BuildingResyncProcessor : ClientPacketProcessor { private readonly Entities entities; private readonly EntityMetadataManager entityMetadataManager; public BuildingResyncProcessor(Entities entities, EntityMetadataManager entityMetadataManager) { this.entities = entities; this.entityMetadataManager = entityMetadataManager; } public override void Process(BuildingResync packet) { if (!BuildingHandler.Main) { return; } BuildingHandler.Main.StartCoroutine(ResyncBuildingEntities(packet.BuildEntities, packet.ModuleEntities)); } public IEnumerator ResyncBuildingEntities(Dictionary buildEntities, Dictionary moduleEntities) { Stopwatch stopwatch = Stopwatch.StartNew(); BuildingHandler.Main.StartResync(buildEntities); yield return UpdateEntities(buildEntities.Keys.ToList(), OverwriteBase, IsInCloseProximity).OnYieldError(exception => Log.Error(exception, $"Encountered an exception while resyncing BuildEntities")); BuildingHandler.Main.StartResync(moduleEntities); yield return UpdateEntities(moduleEntities.Keys.ToList(), OverwriteModule, IsInCloseProximity).OnYieldError(exception => Log.Error(exception, $"Encountered an exception while resyncing ModuleEntities")); BuildingHandler.Main.StopResync(); stopwatch.Stop(); int totalEntities = buildEntities.Count + moduleEntities.Count; Log.InGame(Language.main.Get("Nitrox_FinishedResyncRequest").Replace("{TIME}", stopwatch.ElapsedMilliseconds.ToString()).Replace("{COUNT}", totalEntities.ToString())); } private bool IsInCloseProximity(WorldEntity entity, C componentInWorld) where C : Component { return Vector3.Distance(entity.Transform.Position.ToUnity(), componentInWorld.transform.position) < 0.001f; } /// /// Tries to overwrite components of the provided type found in GlobalRoot's hierarchy by the provided list of entities to update. /// If no component is found to be corresponding to a provided entity, the entity will be spawned independently. /// Other components of the provided type which weren't updated shall be destroyed. /// /// /// The provided list is modified by the function. Make sure it's not used somewhere else. /// /// The Unity component to be looked for /// The GlobalRootEntity type which will be updated /// A function to overwrite a given component by a given entity /// /// Predicate to determine if an entity can overwrite the GameObject of the provided component. /// public IEnumerator UpdateEntities(List entitiesToUpdate, Func overwrite, Func correspondingPredicate) where C : Component where E : GlobalRootEntity { List unmarkedComponents = new(); Dictionary entitiesToUpdateById = entitiesToUpdate.ToDictionary(e => e.Id); foreach (Transform childTransform in LargeWorldStreamer.main.globalRoot.transform) { if (childTransform.TryGetComponent(out C component)) { if (component.TryGetNitroxId(out NitroxId id) && entitiesToUpdateById.TryGetValue(id, out E correspondingEntity)) { yield return overwrite(component, correspondingEntity).OnYieldError(Log.Error); entitiesToUpdate.Remove(correspondingEntity); continue; } unmarkedComponents.Add(component); } } for (int i = entitiesToUpdate.Count - 1; i >= 0; i--) { E entity = entitiesToUpdate[i]; C associatedComponent = unmarkedComponents.Find(c => correspondingPredicate(entity, c)); yield return overwrite(associatedComponent, entity).OnYieldError(Log.Error); unmarkedComponents.Remove(associatedComponent); entitiesToUpdate.RemoveAt(i); } for (int i = unmarkedComponents.Count - 1; i >= 0; i--) { Log.Info($"[{typeof(E)} RESYNC] Destroyed GameObject {unmarkedComponents[i].gameObject}"); GameObject.Destroy(unmarkedComponents[i].gameObject); } foreach (E entity in entitiesToUpdate) { Log.Info($"[{typeof(E)} RESYNC] spawning entity {entity.Id}"); yield return entities.SpawnEntityAsync(entity).OnYieldError(Log.Error); } } public IEnumerator OverwriteBase(Base @base, BuildEntity buildEntity) { Log.Info($"[Base RESYNC] Overwriting base with id {buildEntity.Id}"); ClearBaseChildren(@base); // Frame to let all children be deleted properly yield return Yielders.WaitForEndOfFrame; yield return BuildEntitySpawner.SetupBase(buildEntity, @base, entities); yield return MoonpoolManager.RestoreMoonpools(buildEntity.ChildEntities.OfType(), @base); yield return entities.SpawnBatchAsync(buildEntity.ChildEntities.OfType().ToList(), false, false); foreach (Entity childEntity in buildEntity.ChildEntities) { switch (childEntity) { case MapRoomEntity mapRoomEntity: yield return InteriorPieceEntitySpawner.RestoreMapRoom(@base, mapRoomEntity); break; case BaseLeakEntity baseLeakEntity: yield return entities.SpawnEntityAsync(baseLeakEntity, true); break; } } } public IEnumerator OverwriteModule(Constructable constructable, ModuleEntity moduleEntity) { Log.Info($"[Module RESYNC] Overwriting module with id {moduleEntity.Id}"); ModuleEntitySpawner.ApplyModuleData(moduleEntity, constructable.gameObject); entityMetadataManager.ApplyMetadata(constructable.gameObject, moduleEntity.Metadata); yield break; } /// /// Destroys manually ghosts, modules, interior pieces and vehicles of a base /// /// /// This is the destructive way of clearing the base, if the base isn't modified consequently, IBaseModuleGeometry under the base cells may start spamming errors. /// public static void ClearBaseChildren(Base @base) { for (int i = @base.transform.childCount - 1; i >= 0; i--) { Transform child = @base.transform.GetChild(i); if (child.GetComponent().AliveOrNull() || child.GetComponent()) { UnityEngine.Object.Destroy(child.gameObject); } } foreach (VehicleDockingBay vehicleDockingBay in @base.GetComponentsInChildren(true)) { if (vehicleDockingBay.dockedVehicle) { UnityEngine.Object.Destroy(vehicleDockingBay.dockedVehicle.gameObject); vehicleDockingBay.SetVehicleUndocked(); } } } }