using System.Collections; using System.Linq; using NitroxClient.GameLogic.Bases; using NitroxClient.GameLogic.Helper; using NitroxClient.GameLogic.Spawning.Abstract; using NitroxClient.GameLogic.Spawning.WorldEntities; using NitroxClient.MonoBehaviours; using NitroxClient.MonoBehaviours.Cyclops; using NitroxClient.Unity.Helper; using NitroxModel.DataStructures; using NitroxModel.DataStructures.GameLogic; using NitroxModel.DataStructures.GameLogic.Entities; using NitroxModel.DataStructures.GameLogic.Entities.Bases; using NitroxModel.DataStructures.Util; using NitroxModel_Subnautica.DataStructures; using UnityEngine; namespace NitroxClient.GameLogic.Spawning.Bases; public class ModuleEntitySpawner : EntitySpawner { private readonly Entities entities; public ModuleEntitySpawner(Entities entities) { this.entities = entities; } protected override IEnumerator SpawnAsync(ModuleEntity entity, TaskResult> result) { if (NitroxEntity.TryGetObjectFrom(entity.Id, out GameObject gameObject) && gameObject) { Log.Error("Trying to respawn an already spawned module without a proper resync process."); yield break; } Transform parent = BuildingHandler.GetParentOrGlobalRoot(entity.ParentId); yield return RestoreModule(parent, entity, result); if (!result.Get().HasValue) { Log.Error($"Module couldn't be spawned {entity}"); yield break; } GameObject moduleObject = result.Get().Value; Optional opContainer = InventoryContainerHelper.TryGetContainerByOwner(moduleObject); if (opContainer.HasValue) { yield return entities.SpawnBatchAsync(entity.ChildEntities.OfType().ToList(), true); } Optional opEquipment = EquipmentHelper.FindEquipmentComponent(moduleObject); if (opEquipment.HasValue) { yield return entities.SpawnBatchAsync(entity.ChildEntities.OfType().ToList(), true); } if (moduleObject.TryGetComponent(out PowerSource powerSource)) { // TODO: Have synced/restored power powerSource.SetPower(powerSource.maxPower); } } protected override bool SpawnsOwnChildren(ModuleEntity entity) => true; public static IEnumerator RestoreModule(Transform parent, ModuleEntity moduleEntity, TaskResult> result = null) { if (!DefaultWorldEntitySpawner.TryGetCachedPrefab(out GameObject prefab, classId: moduleEntity.ClassId)) { TaskResult prefabResult = new(); yield return DefaultWorldEntitySpawner.RequestPrefab(moduleEntity.ClassId, prefabResult); if (!prefabResult.Get()) { Log.Error($"Couldn't find a prefab for module of ClassId {moduleEntity.ClassId}"); yield break; } prefab = prefabResult.Get(); } GameObject moduleObject = UnityEngine.Object.Instantiate(prefab); Transform moduleTransform = moduleObject.transform; moduleTransform.parent = parent; moduleTransform.localPosition = moduleEntity.Transform.LocalPosition.ToUnity(); moduleTransform.localRotation = moduleEntity.Transform.LocalRotation.ToUnity(); moduleTransform.localScale = moduleEntity.Transform.LocalScale.ToUnity(); ApplyModuleData(moduleEntity, moduleObject, result); MoveToGlobalRoot(moduleObject); if (parent && parent.TryGetComponent(out NitroxCyclops nitroxCyclops) && nitroxCyclops.Virtual) { nitroxCyclops.Virtual.ReplicateConstructable(moduleObject.GetComponent()); } yield return BuildingPostSpawner.ApplyPostSpawner(moduleObject, moduleEntity.Id); } public static void ApplyModuleData(ModuleEntity moduleEntity, GameObject moduleObject, TaskResult> result = null) { Constructable constructable = moduleObject.GetComponent(); constructable.SetIsInside(moduleEntity.IsInside); if (moduleEntity.IsInside) { SkyEnvironmentChanged.Send(moduleObject, moduleObject.GetComponentInParent(true)); } else { SkyEnvironmentChanged.Send(moduleObject, (Component)null); } constructable.constructedAmount = moduleEntity.ConstructedAmount; constructable.SetState(moduleEntity.ConstructedAmount >= 1f, false); constructable.UpdateMaterial(); NitroxEntity.SetNewId(moduleObject, moduleEntity.Id); result?.Set(moduleObject); } public static void FillObject(ModuleEntity moduleEntity, Constructable constructable) { moduleEntity.ClassId = constructable.GetComponent().ClassId; if (constructable.TryGetNitroxId(out NitroxId entityId)) { moduleEntity.Id = entityId; } if (constructable.TryGetComponentInParent(out Base parentBase, true) && parentBase.TryGetNitroxId(out NitroxId parentId)) { moduleEntity.ParentId = parentId; } moduleEntity.Transform = constructable.transform.ToLocalDto(); moduleEntity.TechType = constructable.techType.ToDto(); moduleEntity.ConstructedAmount = constructable.constructedAmount; moduleEntity.IsInside = constructable.isInside; } /// /// We don't want constructables to be put in CellRoots but in GlobalRoot, because when a player has simulation ownership over a base, /// they also need to keep loaded everything which could be related to the said base (e.g. power relays) /// public static void MoveToGlobalRoot(GameObject gameObject) { if (!gameObject.TryGetComponent(out LargeWorldEntity largeWorldEntity)) { return; } largeWorldEntity.cellLevel = LargeWorldEntity.CellLevel.Global; largeWorldEntity.Start(); } public static ModuleEntity From(Constructable constructable) { ModuleEntity module = ModuleEntity.MakeEmpty(); FillObject(module, constructable); return module; } }