first commit
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
using System.Collections;
|
||||
using NitroxClient.GameLogic.Spawning.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Bases;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.Bases;
|
||||
|
||||
public class BaseLeakEntitySpawner : SyncEntitySpawner<BaseLeakEntity>
|
||||
{
|
||||
private readonly LiveMixinManager liveMixinManager;
|
||||
|
||||
public BaseLeakEntitySpawner(LiveMixinManager liveMixinManager)
|
||||
{
|
||||
this.liveMixinManager = liveMixinManager;
|
||||
}
|
||||
|
||||
protected override IEnumerator SpawnAsync(BaseLeakEntity entity, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
SpawnSync(entity, result);
|
||||
yield break;
|
||||
}
|
||||
|
||||
protected override bool SpawnsOwnChildren(BaseLeakEntity entity) => false;
|
||||
|
||||
protected override bool SpawnSync(BaseLeakEntity entity, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (!NitroxEntity.TryGetComponentFrom(entity.ParentId, out BaseHullStrength baseHullStrength))
|
||||
{
|
||||
Log.Error($"[{nameof(BaseLeakEntitySpawner)}] Couldn't find a {nameof(BaseHullStrength)} from id {entity.ParentId}");
|
||||
return true;
|
||||
}
|
||||
|
||||
BaseLeakManager baseLeakManager = baseHullStrength.gameObject.EnsureComponent<BaseLeakManager>();
|
||||
baseLeakManager.EnsureLeak(entity.RelativeCell.ToUnity(), entity.Id, entity.Health);
|
||||
return true;
|
||||
}
|
||||
}
|
170
NitroxClient/GameLogic/Spawning/Bases/BuildEntitySpawner.cs
Normal file
170
NitroxClient/GameLogic/Spawning/Bases/BuildEntitySpawner.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using NitroxClient.GameLogic.Bases;
|
||||
using NitroxClient.GameLogic.Helper;
|
||||
using NitroxClient.GameLogic.Spawning.Abstract;
|
||||
using NitroxClient.GameLogic.Spawning.Metadata;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.GameLogic.Bases;
|
||||
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 BuildEntitySpawner : EntitySpawner<BuildEntity>
|
||||
{
|
||||
private readonly Entities entities;
|
||||
private readonly BaseLeakEntitySpawner baseLeakEntitySpawner;
|
||||
|
||||
public BuildEntitySpawner(Entities entities, BaseLeakEntitySpawner baseLeakEntitySpawner)
|
||||
{
|
||||
this.entities = entities;
|
||||
this.baseLeakEntitySpawner = baseLeakEntitySpawner;
|
||||
}
|
||||
|
||||
protected override IEnumerator SpawnAsync(BuildEntity entity, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (NitroxEntity.TryGetObjectFrom(entity.Id, out GameObject gameObject) && gameObject)
|
||||
{
|
||||
Log.Error("Trying to respawn an already spawned Base without a proper resync process.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
Stopwatch stopwatch = Stopwatch.StartNew();
|
||||
#endif
|
||||
GameObject newBase = UnityEngine.Object.Instantiate(BaseGhost._basePrefab, LargeWorldStreamer.main.globalRoot.transform, entity.Transform.LocalPosition.ToUnity(), entity.Transform.LocalRotation.ToUnity(), entity.Transform.LocalScale.ToUnity(), false);
|
||||
if (LargeWorld.main)
|
||||
{
|
||||
LargeWorld.main.streamer.cellManager.RegisterEntity(newBase);
|
||||
}
|
||||
Base @base = newBase.GetComponent<Base>();
|
||||
yield return SetupBase(entity, @base, entities, result);
|
||||
#if DEBUG
|
||||
Log.Verbose($"Took {stopwatch.ElapsedMilliseconds}ms to create the Base");
|
||||
#endif
|
||||
yield return entities.SpawnBatchAsync(entity.ChildEntities.OfType<PlayerWorldEntity>().ToList<Entity>());
|
||||
yield return MoonpoolManager.RestoreMoonpools(entity.ChildEntities.OfType<MoonpoolEntity>(), @base);
|
||||
|
||||
TaskResult<Optional<GameObject>> childResult = new();
|
||||
bool atLeastOneLeak = false;
|
||||
foreach (Entity childEntity in entity.ChildEntities)
|
||||
{
|
||||
switch (childEntity)
|
||||
{
|
||||
case MapRoomEntity mapRoomEntity:
|
||||
yield return InteriorPieceEntitySpawner.RestoreMapRoom(@base, mapRoomEntity);
|
||||
break;
|
||||
case BaseLeakEntity baseLeakEntity:
|
||||
atLeastOneLeak = true;
|
||||
yield return baseLeakEntitySpawner.SpawnAsync(baseLeakEntity, childResult);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (atLeastOneLeak)
|
||||
{
|
||||
BaseHullStrength baseHullStrength = @base.GetComponent<BaseHullStrength>();
|
||||
ErrorMessage.AddMessage(Language.main.GetFormat("BaseHullStrDamageDetected", baseHullStrength.totalStrength));
|
||||
}
|
||||
|
||||
result.Set(@base.gameObject);
|
||||
}
|
||||
|
||||
protected override bool SpawnsOwnChildren(BuildEntity entity) => true;
|
||||
|
||||
public static BuildEntity From(Base targetBase, EntityMetadataManager entityMetadataManager)
|
||||
{
|
||||
BuildEntity buildEntity = BuildEntity.MakeEmpty();
|
||||
if (targetBase.TryGetNitroxId(out NitroxId baseId))
|
||||
{
|
||||
buildEntity.Id = baseId;
|
||||
}
|
||||
|
||||
buildEntity.Transform = targetBase.transform.ToLocalDto();
|
||||
|
||||
buildEntity.BaseData = GetBaseData(targetBase);
|
||||
buildEntity.ChildEntities.AddRange(BuildUtils.GetChildEntities(targetBase, baseId, entityMetadataManager));
|
||||
|
||||
return buildEntity;
|
||||
}
|
||||
|
||||
public static BaseData GetBaseData(Base targetBase)
|
||||
{
|
||||
return new BaseData()
|
||||
{
|
||||
BaseShape = targetBase.baseShape.ToInt3().ToDto(),
|
||||
Faces = BaseSerializationHelper.CompressData(targetBase.faces, faceType => (byte)faceType),
|
||||
Cells = BaseSerializationHelper.CompressData(targetBase.cells, cellType => (byte)cellType),
|
||||
Links = BaseSerializationHelper.CompressBytes(targetBase.links),
|
||||
PreCompressionSize = targetBase.links.Length,
|
||||
CellOffset = targetBase.cellOffset.ToDto(),
|
||||
Masks = BaseSerializationHelper.CompressBytes(targetBase.masks),
|
||||
IsGlass = BaseSerializationHelper.CompressData(targetBase.isGlass, isGlass => isGlass ? (byte)1 : (byte)0),
|
||||
Anchor = targetBase.anchor.ToDto()
|
||||
};
|
||||
}
|
||||
|
||||
public static void ApplyBaseData(BaseData baseData, Base @base)
|
||||
{
|
||||
int size = baseData.PreCompressionSize;
|
||||
|
||||
@base.baseShape = new(); // Reset it so that the following instruction is understood as a change
|
||||
@base.SetSize(baseData.BaseShape.ToUnity());
|
||||
@base.faces = BaseSerializationHelper.DecompressData(baseData.Faces, size * 6, faceType => (Base.FaceType)faceType);
|
||||
@base.cells = BaseSerializationHelper.DecompressData(baseData.Cells, size, cellType => (Base.CellType)cellType);
|
||||
@base.links = BaseSerializationHelper.DecompressBytes(baseData.Links, size);
|
||||
@base.cellOffset = new(baseData.CellOffset.ToUnity());
|
||||
@base.masks = BaseSerializationHelper.DecompressBytes(baseData.Masks, size);
|
||||
@base.isGlass = BaseSerializationHelper.DecompressData(baseData.IsGlass, size, num => num == 1);
|
||||
@base.anchor = new(baseData.Anchor.ToUnity());
|
||||
}
|
||||
|
||||
public static IEnumerator SetupBase(BuildEntity buildEntity, Base @base, Entities entities, TaskResult<Optional<GameObject>> result = null)
|
||||
{
|
||||
GameObject baseObject = @base.gameObject;
|
||||
|
||||
NitroxEntity.SetNewId(@base.gameObject, buildEntity.Id);
|
||||
ApplyBaseData(buildEntity.BaseData, @base);
|
||||
|
||||
// Ghosts need an active base to be correctly spawned onto it
|
||||
// While the rest must be spawned earlier for the base to load correctly (mostly InteriorPieceEntity)
|
||||
// Which is why the spawn loops are separated by the SetActive instruction
|
||||
// NB: We aim at spawning very precise entity types (InteriorPieceEntity, ModuleEntity and GlobalRootEntity)
|
||||
// Thus we use GetType() == instead of "is GlobalRootEntity" so that derived types from it aren't selected
|
||||
List<GhostEntity> ghostChildrenEntities = new();
|
||||
foreach (Entity childEntity in buildEntity.ChildEntities)
|
||||
{
|
||||
if (childEntity is InteriorPieceEntity || childEntity is ModuleEntity ||
|
||||
childEntity.GetType() == typeof(GlobalRootEntity))
|
||||
{
|
||||
switch (childEntity)
|
||||
{
|
||||
case GhostEntity ghostEntity:
|
||||
ghostChildrenEntities.Add(ghostEntity);
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return entities.SpawnEntityAsync(childEntity, true);
|
||||
}
|
||||
}
|
||||
|
||||
baseObject.SetActive(true);
|
||||
|
||||
foreach (GhostEntity childGhostEntity in ghostChildrenEntities)
|
||||
{
|
||||
yield return GhostEntitySpawner.RestoreGhost(@base.transform, childGhostEntity);
|
||||
}
|
||||
|
||||
@base.OnProtoDeserialize(null);
|
||||
@base.deserializationFinished = false;
|
||||
@base.FinishDeserialization();
|
||||
result?.Set(baseObject);
|
||||
}
|
||||
}
|
105
NitroxClient/GameLogic/Spawning/Bases/BuildingPostSpawner.cs
Normal file
105
NitroxClient/GameLogic/Spawning/Bases/BuildingPostSpawner.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.MonoBehaviours.Overrides;
|
||||
using NitroxModel.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.Bases;
|
||||
|
||||
public static class BuildingPostSpawner
|
||||
{
|
||||
public static IEnumerator ApplyPostSpawner(GameObject gameObject, NitroxId objectId)
|
||||
{
|
||||
// If we end up having more than 2-3 ifs in here in the future, create a PostSpawner generic class with detection of the required components from gameObject
|
||||
if (gameObject.TryGetComponent(out Constructable constructable) && constructable.techType.Equals(TechType.Bench))
|
||||
{
|
||||
SetupBench(constructable.gameObject, objectId);
|
||||
return null;
|
||||
}
|
||||
else if (gameObject.TryGetComponent(out WaterPark waterPark))
|
||||
{
|
||||
SetupWaterPark(waterPark, objectId);
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private const int LAYER_USEABLE = 13;
|
||||
|
||||
/// <summary>
|
||||
/// For better immersion we split the Bench in three parts (left/center/right). On each can sit one player.
|
||||
/// </summary>
|
||||
public static void SetupBench(GameObject gameObject, NitroxId benchId)
|
||||
{
|
||||
if (!gameObject.TryGetComponent(out Bench bench))
|
||||
{
|
||||
Log.Error($"[{nameof(BuildingPostSpawner)}] Could not find {nameof(Bench)} on {gameObject.name}");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
GameObject benchTileLeft = new("BenchPlaceLeft") { layer = LAYER_USEABLE };
|
||||
benchTileLeft.transform.SetParent(gameObject.transform, false);
|
||||
benchTileLeft.transform.localPosition -= new Vector3(0.75f, 0, 0);
|
||||
BoxCollider benchTileLeftCollider = benchTileLeft.AddComponent<BoxCollider>();
|
||||
benchTileLeftCollider.center = new Vector3(0, 0.25f, 0);
|
||||
benchTileLeftCollider.size = new Vector3(0.85f, 0.5f, 0.65f);
|
||||
benchTileLeftCollider.isTrigger = true;
|
||||
|
||||
GameObject benchTileCenter = new("BenchPlaceCenter") { layer = LAYER_USEABLE };
|
||||
benchTileCenter.transform.SetParent(gameObject.transform, false);
|
||||
BoxCollider benchTileCenterCollider = benchTileCenter.AddComponent<BoxCollider>();
|
||||
benchTileCenterCollider.center = new Vector3(0, 0.25f, 0);
|
||||
benchTileCenterCollider.size = new Vector3(0.7f, 0.5f, 0.65f);
|
||||
benchTileCenterCollider.isTrigger = true;
|
||||
|
||||
GameObject benchTileRight = new("BenchPlaceRight") { layer = LAYER_USEABLE };
|
||||
benchTileRight.transform.SetParent(gameObject.transform, false);
|
||||
benchTileRight.transform.localPosition += new Vector3(0.75f, 0, 0);
|
||||
BoxCollider benchTileRightCollider = benchTileRight.AddComponent<BoxCollider>();
|
||||
benchTileRightCollider.center = new Vector3(0, 0.25f, 0);
|
||||
benchTileRightCollider.size = new Vector3(0.85f, 0.5f, 0.65f);
|
||||
benchTileRightCollider.isTrigger = true;
|
||||
|
||||
GameObject animationRoot = gameObject.FindChild("bench_animation");
|
||||
|
||||
MultiplayerBench.FromBench(bench, benchTileLeft, MultiplayerBench.Side.LEFT, animationRoot);
|
||||
MultiplayerBench.FromBench(bench, benchTileCenter, MultiplayerBench.Side.CENTER, animationRoot);
|
||||
MultiplayerBench.FromBench(bench, benchTileRight, MultiplayerBench.Side.RIGHT, animationRoot);
|
||||
|
||||
NitroxId benchLeftId = benchId.Increment();
|
||||
NitroxId benchCenterId = benchLeftId.Increment();
|
||||
NitroxId benchRightId = benchCenterId.Increment();
|
||||
NitroxEntity.SetNewId(benchTileLeft, benchLeftId);
|
||||
NitroxEntity.SetNewId(benchTileCenter, benchCenterId);
|
||||
NitroxEntity.SetNewId(benchTileRight, benchRightId);
|
||||
|
||||
UnityEngine.Object.Destroy(bench);
|
||||
UnityEngine.Object.Destroy(gameObject.FindChild("Builder Trigger"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetupWaterPark(WaterPark waterPark, NitroxId waterParkId)
|
||||
{
|
||||
if (waterPark is LargeRoomWaterPark largeRoomWaterPark)
|
||||
{
|
||||
NitroxId leftId = waterParkId.Increment();
|
||||
NitroxId rightId = leftId.Increment();
|
||||
|
||||
NitroxEntity.SetNewId(largeRoomWaterPark.planters.leftPlanter.gameObject, leftId);
|
||||
NitroxEntity.SetNewId(largeRoomWaterPark.planters.rightPlanter.gameObject, rightId);
|
||||
return;
|
||||
}
|
||||
|
||||
NitroxId planterId = waterParkId.Increment();
|
||||
|
||||
NitroxEntity.SetNewId(waterPark.planter.gameObject, planterId);
|
||||
}
|
||||
}
|
169
NitroxClient/GameLogic/Spawning/Bases/GhostEntitySpawner.cs
Normal file
169
NitroxClient/GameLogic/Spawning/Bases/GhostEntitySpawner.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using NitroxClient.GameLogic.Bases;
|
||||
using NitroxClient.GameLogic.Spawning.Abstract;
|
||||
using NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Bases;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.Bases;
|
||||
|
||||
public class GhostEntitySpawner : EntitySpawner<GhostEntity>
|
||||
{
|
||||
protected override IEnumerator SpawnAsync(GhostEntity entity, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (NitroxEntity.TryGetObjectFrom(entity.Id, out GameObject gameObject))
|
||||
{
|
||||
if (gameObject.TryGetComponent(out Constructable constructable))
|
||||
{
|
||||
constructable.constructedAmount = 0;
|
||||
yield return constructable.ProgressDeconstruction();
|
||||
}
|
||||
GameObject.Destroy(gameObject);
|
||||
}
|
||||
Transform parent = BuildingHandler.GetParentOrGlobalRoot(entity.ParentId);
|
||||
yield return RestoreGhost(parent, entity, result);
|
||||
}
|
||||
|
||||
protected override bool SpawnsOwnChildren(GhostEntity entity) => true;
|
||||
|
||||
public static GhostEntity From(ConstructableBase constructableBase)
|
||||
{
|
||||
GhostEntity ghost = GhostEntity.MakeEmpty();
|
||||
ModuleEntitySpawner.FillObject(ghost, constructableBase);
|
||||
|
||||
if (constructableBase.moduleFace.HasValue)
|
||||
{
|
||||
ghost.BaseFace = constructableBase.moduleFace.Value.ToDto();
|
||||
}
|
||||
|
||||
ghost.BaseData = BuildEntitySpawner.GetBaseData(constructableBase.model.GetComponent<Base>());
|
||||
if (constructableBase.name.Equals("BaseDeconstructable(Clone)"))
|
||||
{
|
||||
ghost.TechType = constructableBase.techType.ToDto();
|
||||
}
|
||||
|
||||
if (constructableBase.TryGetComponentInChildren(out BaseGhost baseGhost, true))
|
||||
{
|
||||
ghost.Metadata = GhostMetadataRetriever.GetMetadataForGhost(baseGhost);
|
||||
}
|
||||
|
||||
return ghost;
|
||||
}
|
||||
|
||||
public static IEnumerator RestoreGhost(Transform parent, GhostEntity ghostEntity, TaskResult<Optional<GameObject>> result = null)
|
||||
{
|
||||
if (!DefaultWorldEntitySpawner.TryGetCachedPrefab(out GameObject prefab, classId: ghostEntity.ClassId))
|
||||
{
|
||||
TaskResult<GameObject> prefabResult = new();
|
||||
yield return DefaultWorldEntitySpawner.RequestPrefab(ghostEntity.ClassId, prefabResult);
|
||||
if (!prefabResult.Get())
|
||||
{
|
||||
Log.Error($"Couldn't find a prefab for ghost of ClassId {ghostEntity.ClassId}");
|
||||
yield break;
|
||||
}
|
||||
prefab = prefabResult.Get();
|
||||
}
|
||||
|
||||
// The instructions for ghost spawning are written in a VERY PRECISE order which needs to be respected even if
|
||||
// it looks like it can be optimized. Swapping some lines may break the full spawning behaviour.
|
||||
bool isInBase = parent.TryGetComponent(out Base @base);
|
||||
|
||||
// Instantiating the ghost, gathering some useful references and giving it some basic data
|
||||
GameObject ghostObject = UnityEngine.Object.Instantiate(prefab);
|
||||
Transform ghostTransform = ghostObject.transform;
|
||||
ConstructableBase constructableBase = ghostObject.GetComponent<ConstructableBase>();
|
||||
GameObject ghostModel = constructableBase.model;
|
||||
BaseGhost baseGhost = ghostModel.GetComponent<BaseGhost>();
|
||||
Base ghostBase = ghostModel.GetComponent<Base>();
|
||||
bool isBaseDeconstructable = ghostObject.name.Equals("BaseDeconstructable(Clone)");
|
||||
|
||||
MoveTransformToGhostEntity(ghostTransform, ghostEntity, false);
|
||||
if (isBaseDeconstructable && ghostEntity.TechType != null)
|
||||
{
|
||||
constructableBase.techType = ghostEntity.TechType.ToUnity();
|
||||
}
|
||||
|
||||
// only useful instruction in Builder.CreateGhost()
|
||||
baseGhost.SetupGhost();
|
||||
// ghost's Base should then be assigned its data (from BaseGhost.UpdatePlacement)
|
||||
BuildEntitySpawner.ApplyBaseData(ghostEntity.BaseData, ghostBase);
|
||||
ghostBase.OnProtoDeserialize(null);
|
||||
if (@base)
|
||||
{
|
||||
@base.SetPlacementGhost(baseGhost);
|
||||
}
|
||||
baseGhost.targetBase = @base;
|
||||
|
||||
// Little fix for cell objects being already generated (wrongly)
|
||||
if (ghostBase.cellObjects != null)
|
||||
{
|
||||
Array.Clear(ghostBase.cellObjects, 0, ghostBase.cellObjects.Length);
|
||||
}
|
||||
ghostBase.FinishDeserialization();
|
||||
|
||||
// Apply the right metadata accordingly
|
||||
IEnumerator baseDeconstructableInstructions = GhostMetadataApplier.ApplyMetadataToGhost(baseGhost, ghostEntity.Metadata, @base);
|
||||
if (baseDeconstructableInstructions != null)
|
||||
{
|
||||
yield return baseDeconstructableInstructions;
|
||||
}
|
||||
|
||||
// Verify that the metadata didn't destroy the GameObject (possible in GhostMetadataApplier.ApplyBaseDeconstructableMetadataTo)
|
||||
if (!ghostObject)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
// From ConstructableBase.OnProtoDeserialize()
|
||||
// NB: Very important to fix the ghost visual glitch where the renderer is wrongly placed
|
||||
constructableBase.SetGhostVisible(false);
|
||||
|
||||
// The rest is from Builder.TryPlace
|
||||
if (!isBaseDeconstructable)
|
||||
{
|
||||
// Not executed by BaseDeconstructable
|
||||
baseGhost.Place();
|
||||
}
|
||||
|
||||
if (isInBase)
|
||||
{
|
||||
ghostTransform.parent = parent;
|
||||
MoveTransformToGhostEntity(ghostTransform, ghostEntity);
|
||||
}
|
||||
|
||||
constructableBase.SetState(false, false);
|
||||
constructableBase.constructedAmount = ghostEntity.ConstructedAmount;
|
||||
// Addition to ensure visuals appear correctly (would be called from OnGlobalEntitiesLoaded)
|
||||
yield return constructableBase.ReplaceMaterialsAsync();
|
||||
|
||||
if (isBaseDeconstructable)
|
||||
{
|
||||
baseGhost.DisableGhostModelScripts();
|
||||
}
|
||||
|
||||
NitroxEntity.SetNewId(ghostObject, ghostEntity.Id);
|
||||
result?.Set(ghostObject);
|
||||
}
|
||||
|
||||
private static void MoveTransformToGhostEntity(Transform transform, GhostEntity ghostEntity, bool localCoordinates = true)
|
||||
{
|
||||
if (localCoordinates)
|
||||
{
|
||||
transform.localPosition = ghostEntity.Transform.LocalPosition.ToUnity();
|
||||
transform.localRotation = ghostEntity.Transform.LocalRotation.ToUnity();
|
||||
transform.localScale = ghostEntity.Transform.LocalScale.ToUnity();
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Once fixed, use NitroxTransform.Position and Rotation instead of locals
|
||||
// Current issue is NitroxTransform doesn't have a reparenting behaviour when deserialized on client-side
|
||||
transform.SetPositionAndRotation(ghostEntity.Transform.LocalPosition.ToUnity(), ghostEntity.Transform.LocalRotation.ToUnity());
|
||||
transform.localScale = ghostEntity.Transform.LocalScale.ToUnity();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,183 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NitroxClient.GameLogic.Spawning.Abstract;
|
||||
using NitroxClient.GameLogic.Spawning.Metadata;
|
||||
using NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
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.DataStructures.Util;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.Bases;
|
||||
|
||||
public class InteriorPieceEntitySpawner : EntitySpawner<InteriorPieceEntity>
|
||||
{
|
||||
private readonly Entities entities;
|
||||
private readonly EntityMetadataManager entityMetadataManager;
|
||||
|
||||
public InteriorPieceEntitySpawner(Entities entities, EntityMetadataManager entityMetadataManager)
|
||||
{
|
||||
this.entities = entities;
|
||||
this.entityMetadataManager = entityMetadataManager;
|
||||
}
|
||||
|
||||
protected override IEnumerator SpawnAsync(InteriorPieceEntity entity, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (entity.ParentId == null || !NitroxEntity.TryGetComponentFrom(entity.ParentId, out Base @base))
|
||||
{
|
||||
Log.Error($"Couldn't find a Base component on the parent object of InteriorPieceEntity {entity.Id}");
|
||||
yield break;
|
||||
}
|
||||
yield return RestoreInteriorPiece(entity, @base, result);
|
||||
if (!result.Get().HasValue)
|
||||
{
|
||||
Log.Error($"Restoring interior piece failed: {entity}");
|
||||
yield break;
|
||||
}
|
||||
bool isWaterPark = entity.IsWaterPark;
|
||||
|
||||
List<Entity> batch = new();
|
||||
foreach (Entity childEntity in entity.ChildEntities)
|
||||
{
|
||||
switch(childEntity)
|
||||
{
|
||||
case InventoryItemEntity:
|
||||
case InstalledModuleEntity:
|
||||
batch.Add(childEntity);
|
||||
break;
|
||||
|
||||
case PlanterEntity:
|
||||
foreach (InventoryItemEntity childItemEntity in childEntity.ChildEntities.OfType<InventoryItemEntity>())
|
||||
{
|
||||
batch.Add(childItemEntity);
|
||||
}
|
||||
break;
|
||||
|
||||
case WorldEntity:
|
||||
if (isWaterPark)
|
||||
{
|
||||
batch.Add(childEntity);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isWaterPark)
|
||||
{
|
||||
// Must happen before child plant spawning
|
||||
foreach (Planter planter in result.Get().Value.GetComponentsInChildren<Planter>(true))
|
||||
{
|
||||
yield return planter.DeserializeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
yield return entities.SpawnBatchAsync(batch, true);
|
||||
|
||||
if (result.Get().Value.TryGetComponent(out PowerSource powerSource))
|
||||
{
|
||||
// TODO: Have synced/restored power
|
||||
powerSource.SetPower(powerSource.maxPower);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool SpawnsOwnChildren(InteriorPieceEntity entity) => true;
|
||||
|
||||
public IEnumerator RestoreInteriorPiece(InteriorPieceEntity interiorPiece, Base @base, TaskResult<Optional<GameObject>> result = null)
|
||||
{
|
||||
if (!DefaultWorldEntitySpawner.TryGetCachedPrefab(out GameObject prefab, classId: interiorPiece.ClassId))
|
||||
{
|
||||
TaskResult<GameObject> prefabResult = new();
|
||||
yield return DefaultWorldEntitySpawner.RequestPrefab(interiorPiece.ClassId, prefabResult);
|
||||
if (!prefabResult.Get())
|
||||
{
|
||||
Log.Error($"Couldn't find a prefab for interior piece of ClassId {interiorPiece.ClassId}");
|
||||
yield break;
|
||||
}
|
||||
prefab = prefabResult.Get();
|
||||
}
|
||||
|
||||
Base.Face face = interiorPiece.BaseFace.ToUnity();
|
||||
face.cell += @base.GetAnchor();
|
||||
GameObject moduleObject = @base.SpawnModule(prefab, face);
|
||||
if (moduleObject)
|
||||
{
|
||||
NitroxEntity.SetNewId(moduleObject, interiorPiece.Id);
|
||||
yield return BuildingPostSpawner.ApplyPostSpawner(moduleObject, interiorPiece.Id);
|
||||
entityMetadataManager.ApplyMetadata(moduleObject, interiorPiece.Metadata);
|
||||
result.Set(moduleObject);
|
||||
}
|
||||
}
|
||||
|
||||
public static InteriorPieceEntity From(IBaseModule module, EntityMetadataManager entityMetadataManager)
|
||||
{
|
||||
InteriorPieceEntity interiorPiece = InteriorPieceEntity.MakeEmpty();
|
||||
GameObject gameObject = (module as Component).gameObject;
|
||||
if (gameObject && gameObject.TryGetComponent(out PrefabIdentifier identifier))
|
||||
{
|
||||
interiorPiece.ClassId = identifier.ClassId;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warn($"Couldn't find an identifier for the interior piece {module.GetType()}");
|
||||
}
|
||||
|
||||
if (gameObject.TryGetIdOrWarn(out NitroxId entityId))
|
||||
{
|
||||
interiorPiece.Id = entityId;
|
||||
}
|
||||
|
||||
if (gameObject.TryGetComponentInParent(out Base parentBase, true) &&
|
||||
parentBase.TryGetNitroxId(out NitroxId parentId))
|
||||
{
|
||||
interiorPiece.ParentId = parentId;
|
||||
}
|
||||
|
||||
switch (module)
|
||||
{
|
||||
case LargeRoomWaterPark:
|
||||
PlanterEntity leftPlanter = new(interiorPiece.Id.Increment(), interiorPiece.Id);
|
||||
PlanterEntity rightPlanter = new(leftPlanter.Id.Increment(), interiorPiece.Id);
|
||||
interiorPiece.ChildEntities.Add(leftPlanter);
|
||||
interiorPiece.ChildEntities.Add(rightPlanter);
|
||||
break;
|
||||
// When you deconstruct (not entirely) then construct back those pieces, they keep their inventories
|
||||
case BaseNuclearReactor baseNuclearReactor:
|
||||
interiorPiece.ChildEntities.AddRange(Items.GetEquipmentModuleEntities(baseNuclearReactor.equipment, entityId, entityMetadataManager));
|
||||
break;
|
||||
case BaseBioReactor baseBioReactor:
|
||||
foreach (ItemsContainer.ItemGroup itemGroup in baseBioReactor.container._items.Values)
|
||||
{
|
||||
foreach (InventoryItem item in itemGroup.items)
|
||||
{
|
||||
interiorPiece.ChildEntities.Add(Items.ConvertToInventoryItemEntity(item.item.gameObject, interiorPiece.Id, entityMetadataManager));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WaterPark:
|
||||
PlanterEntity planter = new(interiorPiece.Id.Increment(), interiorPiece.Id);
|
||||
interiorPiece.ChildEntities.Add(planter);
|
||||
break;
|
||||
}
|
||||
|
||||
interiorPiece.BaseFace = module.moduleFace.ToDto();
|
||||
|
||||
return interiorPiece;
|
||||
}
|
||||
|
||||
public static IEnumerator RestoreMapRoom(Base @base, MapRoomEntity mapRoomEntity)
|
||||
{
|
||||
MapRoomFunctionality mapRoomFunctionality = @base.GetMapRoomFunctionalityForCell(mapRoomEntity.Cell.ToUnity());
|
||||
if (!mapRoomFunctionality)
|
||||
{
|
||||
Log.Error($"Couldn't find MapRoomFunctionality in base for cell {mapRoomEntity.Cell}");
|
||||
yield break;
|
||||
}
|
||||
NitroxEntity.SetNewId(mapRoomFunctionality.gameObject, mapRoomEntity.Id);
|
||||
}
|
||||
}
|
159
NitroxClient/GameLogic/Spawning/Bases/ModuleEntitySpawner.cs
Normal file
159
NitroxClient/GameLogic/Spawning/Bases/ModuleEntitySpawner.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
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<ModuleEntity>
|
||||
{
|
||||
private readonly Entities entities;
|
||||
|
||||
public ModuleEntitySpawner(Entities entities)
|
||||
{
|
||||
this.entities = entities;
|
||||
}
|
||||
|
||||
protected override IEnumerator SpawnAsync(ModuleEntity entity, TaskResult<Optional<GameObject>> 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<ItemsContainer> opContainer = InventoryContainerHelper.TryGetContainerByOwner(moduleObject);
|
||||
if (opContainer.HasValue)
|
||||
{
|
||||
yield return entities.SpawnBatchAsync(entity.ChildEntities.OfType<InventoryItemEntity>().ToList<Entity>(), true);
|
||||
}
|
||||
|
||||
Optional<Equipment> opEquipment = EquipmentHelper.FindEquipmentComponent(moduleObject);
|
||||
if (opEquipment.HasValue)
|
||||
{
|
||||
yield return entities.SpawnBatchAsync(entity.ChildEntities.OfType<InstalledModuleEntity>().ToList<Entity>(), 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<Optional<GameObject>> result = null)
|
||||
{
|
||||
if (!DefaultWorldEntitySpawner.TryGetCachedPrefab(out GameObject prefab, classId: moduleEntity.ClassId))
|
||||
{
|
||||
TaskResult<GameObject> 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<Constructable>());
|
||||
}
|
||||
|
||||
yield return BuildingPostSpawner.ApplyPostSpawner(moduleObject, moduleEntity.Id);
|
||||
}
|
||||
|
||||
public static void ApplyModuleData(ModuleEntity moduleEntity, GameObject moduleObject, TaskResult<Optional<GameObject>> result = null)
|
||||
{
|
||||
Constructable constructable = moduleObject.GetComponent<Constructable>();
|
||||
constructable.SetIsInside(moduleEntity.IsInside);
|
||||
if (moduleEntity.IsInside)
|
||||
{
|
||||
SkyEnvironmentChanged.Send(moduleObject, moduleObject.GetComponentInParent<SubRoot>(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<PrefabIdentifier>().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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user