180 lines
8.0 KiB
C#
180 lines
8.0 KiB
C#
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<BuildingResync>
|
|
{
|
|
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<BuildEntity, int> buildEntities, Dictionary<ModuleEntity, int> moduleEntities)
|
|
{
|
|
Stopwatch stopwatch = Stopwatch.StartNew();
|
|
BuildingHandler.Main.StartResync(buildEntities);
|
|
yield return UpdateEntities<Base, BuildEntity>(buildEntities.Keys.ToList(), OverwriteBase, IsInCloseProximity).OnYieldError(exception => Log.Error(exception, $"Encountered an exception while resyncing BuildEntities"));
|
|
|
|
BuildingHandler.Main.StartResync(moduleEntities);
|
|
yield return UpdateEntities<Constructable, ModuleEntity>(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<C>(WorldEntity entity, C componentInWorld) where C : Component
|
|
{
|
|
return Vector3.Distance(entity.Transform.Position.ToUnity(), componentInWorld.transform.position) < 0.001f;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The provided list is modified by the function. Make sure it's not used somewhere else.
|
|
/// </remarks>
|
|
/// <typeparam name="C">The Unity component to be looked for</typeparam>
|
|
/// <typeparam name="E">The GlobalRootEntity type which will be updated</typeparam>
|
|
/// <param name="overwrite">A function to overwrite a given component by a given entity</param>
|
|
/// <param name="correspondingPredicate">
|
|
/// Predicate to determine if an entity can overwrite the GameObject of the provided component.
|
|
/// </param>
|
|
public IEnumerator UpdateEntities<C,E>(List<E> entitiesToUpdate, Func<C, E, IEnumerator> overwrite, Func<E, C, bool> correspondingPredicate) where C : Component where E : GlobalRootEntity
|
|
{
|
|
List<C> unmarkedComponents = new();
|
|
Dictionary<NitroxId, E> 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<MoonpoolEntity>(), @base);
|
|
yield return entities.SpawnBatchAsync(buildEntity.ChildEntities.OfType<PlayerWorldEntity>().ToList<Entity>(), 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Destroys manually ghosts, modules, interior pieces and vehicles of a base
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
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<IBaseModule>().AliveOrNull() || child.GetComponent<Constructable>())
|
|
{
|
|
UnityEngine.Object.Destroy(child.gameObject);
|
|
}
|
|
}
|
|
foreach (VehicleDockingBay vehicleDockingBay in @base.GetComponentsInChildren<VehicleDockingBay>(true))
|
|
{
|
|
if (vehicleDockingBay.dockedVehicle)
|
|
{
|
|
UnityEngine.Object.Destroy(vehicleDockingBay.dockedVehicle.gameObject);
|
|
vehicleDockingBay.SetVehicleUndocked();
|
|
}
|
|
}
|
|
}
|
|
}
|