first commit
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
using System.Collections;
|
||||
using NitroxClient.GameLogic.Spawning.Metadata.Processor;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
|
||||
public class CrashEntitySpawner : IWorldEntitySpawner, IWorldEntitySyncSpawner
|
||||
{
|
||||
public IEnumerator SpawnAsync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (!DefaultWorldEntitySpawner.TryGetCachedPrefab(out GameObject prefab, classId: entity.ClassId))
|
||||
{
|
||||
TaskResult<GameObject> prefabResult = new();
|
||||
yield return DefaultWorldEntitySpawner.RequestPrefab(entity.ClassId, prefabResult);
|
||||
if (!prefabResult.Get())
|
||||
{
|
||||
Log.Error($"Couldn't find a prefab for {nameof(WorldEntity)} of ClassId {entity.ClassId}");
|
||||
yield break;
|
||||
}
|
||||
prefab = prefabResult.Get();
|
||||
}
|
||||
|
||||
GameObject gameObject = GameObjectHelper.InstantiateWithId(prefab, entity.Id);
|
||||
if (!VerifyCanSpawnOrError(entity, gameObject, parent.Value, out Crash crash, out CrashHome crashHome))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
SetupObject(entity, crash, crashHome);
|
||||
|
||||
result.Set(gameObject);
|
||||
}
|
||||
|
||||
public bool SpawnsOwnChildren() => false;
|
||||
|
||||
public bool SpawnSync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (!DefaultWorldEntitySpawner.TryGetCachedPrefab(out GameObject prefab, classId: entity.ClassId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GameObject gameObject = GameObjectHelper.InstantiateWithId(prefab, entity.Id);
|
||||
if (!VerifyCanSpawnOrError(entity, gameObject, parent.Value, out Crash crash, out CrashHome crashHome))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
SetupObject(entity, crash, crashHome);
|
||||
|
||||
result.Set(gameObject);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool VerifyCanSpawnOrError(WorldEntity entity, GameObject prefabObject, GameObject parentObject, out Crash crash, out CrashHome crashHome)
|
||||
{
|
||||
if (!prefabObject.TryGetComponent(out crash))
|
||||
{
|
||||
Log.Error($"Couldn't find component {nameof(Crash)} on prefab with ClassId: {entity.ClassId}");
|
||||
crashHome = null;
|
||||
return false;
|
||||
}
|
||||
if (parentObject && parentObject.TryGetComponent(out crashHome))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
crashHome = null;
|
||||
Log.Error($"Couldn't find a valid parent for {entity}");
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void SetupObject(WorldEntity worldEntity, Crash crash, CrashHome crashHome)
|
||||
{
|
||||
crash.transform.SetPositionAndRotation(worldEntity.Transform.Position.ToUnity(), worldEntity.Transform.Rotation.ToUnity());
|
||||
crash.transform.localScale = worldEntity.Transform.LocalScale.ToUnity();
|
||||
crashHome.crash = crash;
|
||||
CrashHomeMetadataProcessor.UpdateCrashHomeOpen(crashHome);
|
||||
LargeWorldStreamer.main.MakeEntityTransient(crash.gameObject);
|
||||
}
|
||||
}
|
@@ -0,0 +1,157 @@
|
||||
using System.Collections;
|
||||
using NitroxClient.GameLogic.Simulation;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
|
||||
public class CreatureRespawnEntitySpawner : IWorldEntitySpawner, IWorldEntitySyncSpawner
|
||||
{
|
||||
private readonly SimulationOwnership simulationOwnership;
|
||||
|
||||
public CreatureRespawnEntitySpawner(SimulationOwnership simulationOwnership)
|
||||
{
|
||||
this.simulationOwnership = simulationOwnership;
|
||||
}
|
||||
|
||||
public IEnumerator SpawnAsync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (entity is not CreatureRespawnEntity creatureRespawnEntity)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (!DefaultWorldEntitySpawner.TryGetCachedPrefab(out GameObject prefab, classId: entity.ClassId))
|
||||
{
|
||||
TaskResult<GameObject> prefabResult = new();
|
||||
yield return DefaultWorldEntitySpawner.RequestPrefab(entity.ClassId, prefabResult);
|
||||
if (!prefabResult.Get())
|
||||
{
|
||||
Log.Error($"Couldn't find a prefab for {nameof(OxygenPipeEntity)} of ClassId {entity.ClassId}");
|
||||
yield break;
|
||||
}
|
||||
prefab = prefabResult.Get();
|
||||
}
|
||||
|
||||
GameObject gameObject = GameObjectHelper.InstantiateInactiveWithId(prefab, entity.Id);
|
||||
if (!VerifyCanSpawnOrError(creatureRespawnEntity, gameObject, out Respawn respawn))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
SetupObject(creatureRespawnEntity, gameObject, respawn);
|
||||
|
||||
result.Set(gameObject);
|
||||
}
|
||||
|
||||
public bool SpawnSync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (entity is not CreatureRespawnEntity creatureRespawnEntity)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!DefaultWorldEntitySpawner.TryGetCachedPrefab(out GameObject prefab, classId: entity.ClassId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GameObject gameObject = GameObjectHelper.InstantiateInactiveWithId(prefab, entity.Id);
|
||||
if (!VerifyCanSpawnOrError(creatureRespawnEntity, gameObject, out Respawn respawn))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
SetupObject(creatureRespawnEntity, gameObject, respawn);
|
||||
|
||||
result.Set(gameObject);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SpawnsOwnChildren() => false;
|
||||
|
||||
private bool VerifyCanSpawnOrError(CreatureRespawnEntity entity, GameObject gameObject, out Respawn respawn)
|
||||
{
|
||||
// Respawn's logic only work during their Start method so we'll either execute it directly when spawning this entity, or destroy it
|
||||
if (DayNightCycle.main.timePassedAsFloat < entity.SpawnTime)
|
||||
{
|
||||
GameObject.Destroy(gameObject);
|
||||
respawn = null;
|
||||
return false;
|
||||
}
|
||||
if (gameObject.TryGetComponent(out respawn))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Log.Error($"Could not find component {nameof(Respawn)} on prefab with ClassId: {entity.ClassId}");
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetupObject(CreatureRespawnEntity entity, GameObject gameObject, Respawn respawn)
|
||||
{
|
||||
RespawnContext context = new() { Entity = entity, GameObject = gameObject, Respawn = respawn };
|
||||
LockRequest<RespawnContext> lockRequest = new(entity.Id, SimulationLockType.TRANSIENT, TriggerRespawnCallback, context);
|
||||
|
||||
simulationOwnership.RequestSimulationLock(lockRequest);
|
||||
}
|
||||
|
||||
private static void TriggerRespawnCallback(NitroxId entityId, bool acquired, RespawnContext context)
|
||||
{
|
||||
if (!acquired)
|
||||
{
|
||||
GameObject.Destroy(context.GameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject gameObject = context.GameObject;
|
||||
CreatureRespawnEntity entity = context.Entity;
|
||||
Respawn respawn = context.Respawn;
|
||||
|
||||
// This will only happen if the respawn is ready to be activated
|
||||
Transform transform = gameObject.transform;
|
||||
transform.localPosition = entity.Transform.Position.ToUnity();
|
||||
transform.localRotation = entity.Transform.Rotation.ToUnity();
|
||||
transform.localScale = entity.Transform.LocalScale.ToUnity();
|
||||
|
||||
// It's possible that either the respawn was parented to something (e.g. a Reefback) or directly to a cell
|
||||
if (entity.ParentId != null)
|
||||
{
|
||||
if (LargeWorldStreamer.main)
|
||||
{
|
||||
LargeWorldStreamer.main.cellManager.UnregisterEntity(gameObject);
|
||||
}
|
||||
if (NitroxEntity.TryGetComponentFrom(entity.ParentId, out Transform parent))
|
||||
{
|
||||
transform.parent = parent;
|
||||
}
|
||||
}
|
||||
else if (gameObject.TryGetComponent(out LargeWorldEntity largeWorldEntity))
|
||||
{
|
||||
largeWorldEntity.cellLevel = (LargeWorldEntity.CellLevel)entity.Level;
|
||||
if (LargeWorldStreamer.main)
|
||||
{
|
||||
LargeWorldStreamer.main.cellManager.RegisterEntity(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
respawn.spawnTime = entity.SpawnTime;
|
||||
respawn.techType = entity.RespawnTechType.ToUnity();
|
||||
respawn.addComponents.Clear();
|
||||
respawn.addComponents.AddRange(entity.AddComponents);
|
||||
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
internal class RespawnContext : LockRequestContext
|
||||
{
|
||||
public CreatureRespawnEntity Entity;
|
||||
public GameObject GameObject;
|
||||
public Respawn Respawn;
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
using System.Collections;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
|
||||
public class CreepvineEntitySpawner(DefaultWorldEntitySpawner defaultWorldEntitySpawner) : IWorldEntitySpawner, IWorldEntitySyncSpawner
|
||||
{
|
||||
private readonly DefaultWorldEntitySpawner defaultWorldEntitySpawner = defaultWorldEntitySpawner;
|
||||
|
||||
public IEnumerator SpawnAsync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
yield return defaultWorldEntitySpawner.SpawnAsync(entity, parent, cellRoot, result);
|
||||
if (!result.value.HasValue)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
SetupObject(result.value.Value);
|
||||
|
||||
// result is already set by defaultWorldEntitySpawner.SpawnAsync
|
||||
}
|
||||
|
||||
public bool SpawnsOwnChildren() => false;
|
||||
|
||||
public bool SpawnSync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (!defaultWorldEntitySpawner.SpawnSync(entity, parent, cellRoot, result))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SetupObject(result.value.Value);
|
||||
|
||||
// result is already set
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void SetupObject(GameObject gameObject)
|
||||
{
|
||||
if (gameObject.GetComponent<FruitPlant>())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FruitPlant fruitPlant = gameObject.AddComponent<FruitPlant>();
|
||||
fruitPlant.fruitSpawnEnabled = false;
|
||||
fruitPlant.timeNextFruit = -1;
|
||||
fruitPlant.fruits = gameObject.GetComponentsInChildren<PickPrefab>(true);
|
||||
}
|
||||
}
|
@@ -0,0 +1,198 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
using UWE;
|
||||
using static NitroxClient.Unity.Helper.GameObjectHelper;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
|
||||
public class DefaultWorldEntitySpawner : IWorldEntitySpawner, IWorldEntitySyncSpawner
|
||||
{
|
||||
private static readonly Dictionary<TechType, GameObject> prefabCacheByTechType = [];
|
||||
private static readonly Dictionary<string, GameObject> prefabCacheByClassId = [];
|
||||
private static readonly HashSet<(string, TechType)> prefabNotFound = [];
|
||||
private static readonly HashSet<string> classIdsWithoutPrefab = [];
|
||||
|
||||
public IEnumerator SpawnAsync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
TechType techType = entity.TechType.ToUnity();
|
||||
|
||||
TaskResult<GameObject> gameObjectResult = new();
|
||||
yield return CreateGameObject(techType, entity.ClassId, entity.Id, gameObjectResult);
|
||||
|
||||
GameObject gameObject = gameObjectResult.Get();
|
||||
SetupObject(entity, parent, gameObject, cellRoot, techType);
|
||||
|
||||
result.Set(Optional.Of(gameObject));
|
||||
}
|
||||
|
||||
private void SetupObject(WorldEntity entity, Optional<GameObject> parent, GameObject gameObject, EntityCell cellRoot, TechType techType)
|
||||
{
|
||||
gameObject.transform.position = entity.Transform.Position.ToUnity();
|
||||
gameObject.transform.rotation = entity.Transform.Rotation.ToUnity();
|
||||
gameObject.transform.localScale = entity.Transform.LocalScale.ToUnity();
|
||||
|
||||
CrafterLogic.NotifyCraftEnd(gameObject, techType);
|
||||
|
||||
WaterPark parentWaterPark = null;
|
||||
if (parent.HasValue)
|
||||
{
|
||||
Items.TryGetParentWaterPark(parent.Value.transform.parent, out parentWaterPark);
|
||||
}
|
||||
|
||||
if (!parentWaterPark)
|
||||
{
|
||||
if (parent.HasValue && !parent.Value.GetComponent<LargeWorldEntityCell>())
|
||||
{
|
||||
LargeWorldEntity.Register(gameObject); // This calls SetActive on the GameObject
|
||||
}
|
||||
else if (gameObject.GetComponent<LargeWorldEntity>() && !gameObject.transform.parent && cellRoot.liveRoot)
|
||||
{
|
||||
gameObject.transform.SetParent(cellRoot.liveRoot.transform, true);
|
||||
LargeWorldEntity.Register(gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (parent.HasValue)
|
||||
{
|
||||
if (parentWaterPark && gameObject.TryGetComponent(out Pickupable pickupable))
|
||||
{
|
||||
pickupable.SetVisible(false);
|
||||
pickupable.Activate(false);
|
||||
parentWaterPark.AddItem(pickupable);
|
||||
}
|
||||
else
|
||||
{
|
||||
gameObject.transform.SetParent(parent.Value.transform, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetCachedPrefab(out GameObject prefab, TechType techType = TechType.None, string classId = null)
|
||||
{
|
||||
if (classId != null && prefabCacheByClassId.TryGetValue(classId, out prefab))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we've never even once issued a request prefab for the class id we need to do it because multiple prefabs
|
||||
// can have the same TechType so it's not good enough to find the right prefab
|
||||
if (!classIdsWithoutPrefab.Contains(classId) || techType == TechType.None)
|
||||
{
|
||||
prefab = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return prefabCacheByTechType.TryGetValue(techType, out prefab);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Either gets the prefab reference from the cache or requests it and fills the provided result with it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="PrefabDatabase"/> requires executing an extra yield instruction which is avoided here.
|
||||
/// Because each yield costs a non-required time (and non-neglectable considering the amount of entities) for batch spawning.
|
||||
/// Pumping a coroutine isn't possible when it contains prefab loading instructions as the one used here.
|
||||
/// </remarks>
|
||||
public static IEnumerator RequestPrefab(TechType techType, TaskResult<GameObject> result)
|
||||
{
|
||||
if (prefabCacheByTechType.TryGetValue(techType, out GameObject prefab))
|
||||
{
|
||||
result.Set(prefab);
|
||||
yield break;
|
||||
}
|
||||
CoroutineTask<GameObject> techPrefabCoroutine = CraftData.GetPrefabForTechTypeAsync(techType, false);
|
||||
yield return techPrefabCoroutine;
|
||||
prefabCacheByTechType[techType] = techPrefabCoroutine.GetResult();
|
||||
result.Set(techPrefabCoroutine.GetResult());
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="RequestPrefab(TechType, TaskResult{GameObject})"/>
|
||||
public static IEnumerator RequestPrefab(string classId, TaskResult<GameObject> result)
|
||||
{
|
||||
if (prefabCacheByClassId.TryGetValue(classId, out GameObject prefab))
|
||||
{
|
||||
result.Set(prefab);
|
||||
yield break;
|
||||
}
|
||||
IPrefabRequest prefabCoroutine = PrefabDatabase.GetPrefabAsync(classId);
|
||||
yield return prefabCoroutine;
|
||||
if (prefabCoroutine.TryGetPrefab(out prefab))
|
||||
{
|
||||
prefabCacheByClassId[classId] = prefab;
|
||||
}
|
||||
result.Set(prefab);
|
||||
}
|
||||
|
||||
public static IEnumerator CreateGameObject(TechType techType, string classId, NitroxId nitroxId, TaskResult<GameObject> result)
|
||||
{
|
||||
IPrefabRequest prefabCoroutine = PrefabDatabase.GetPrefabAsync(classId);
|
||||
yield return prefabCoroutine;
|
||||
if (prefabCoroutine.TryGetPrefab(out GameObject prefab))
|
||||
{
|
||||
prefabCacheByClassId[classId] = prefab;
|
||||
}
|
||||
else
|
||||
{
|
||||
classIdsWithoutPrefab.Add(classId);
|
||||
CoroutineTask<GameObject> techPrefabCoroutine = CraftData.GetPrefabForTechTypeAsync(techType, false);
|
||||
yield return techPrefabCoroutine;
|
||||
prefab = techPrefabCoroutine.GetResult();
|
||||
if (!prefab)
|
||||
{
|
||||
result.Set(CreateGenericLoot(techType, nitroxId));
|
||||
prefabNotFound.Add((classId, techType));
|
||||
yield break;
|
||||
}
|
||||
else
|
||||
{
|
||||
prefabCacheByTechType[techType] = prefab;
|
||||
}
|
||||
}
|
||||
|
||||
result.Set(SpawnFromPrefab(prefab, nitroxId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks in prefab cache and creates a GameObject out of it if possible, or returns false.
|
||||
/// </summary>
|
||||
public static bool TryCreateGameObjectSync(TechType techType, string classId, NitroxId nitroxId, out GameObject gameObject)
|
||||
{
|
||||
if (prefabNotFound.Contains((classId, techType)))
|
||||
{
|
||||
gameObject = CreateGenericLoot(techType, nitroxId);
|
||||
return true;
|
||||
}
|
||||
else if (TryGetCachedPrefab(out GameObject prefab, techType, classId))
|
||||
{
|
||||
gameObject = SpawnFromPrefab(prefab, nitroxId);
|
||||
return true;
|
||||
}
|
||||
gameObject = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool SpawnSync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
TechType techType = entity.TechType.ToUnity();
|
||||
|
||||
if (TryCreateGameObjectSync(techType, entity.ClassId, entity.Id, out GameObject gameObject))
|
||||
{
|
||||
SetupObject(entity, parent, gameObject, cellRoot, techType);
|
||||
result.Set(gameObject);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool SpawnsOwnChildren() => false;
|
||||
}
|
@@ -0,0 +1,116 @@
|
||||
using System.Collections;
|
||||
using NitroxClient.Communication;
|
||||
using NitroxClient.GameLogic.FMOD;
|
||||
using NitroxClient.GameLogic.Spawning.Metadata.Processor;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.MonoBehaviours.CinematicController;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.Packets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
|
||||
public class EscapePodWorldEntitySpawner : IWorldEntitySpawner
|
||||
{
|
||||
/*
|
||||
* When creating additional escape pods (multiple users with multiple pods)
|
||||
* we want to suppress the escape pod's awake method so it doesn't override
|
||||
* EscapePod.main to the new escape pod.
|
||||
*/
|
||||
public static bool SuppressEscapePodAwakeMethod;
|
||||
|
||||
private readonly LocalPlayer localPlayer;
|
||||
|
||||
public EscapePodWorldEntitySpawner(LocalPlayer localPlayer)
|
||||
{
|
||||
this.localPlayer = localPlayer;
|
||||
}
|
||||
|
||||
public IEnumerator SpawnAsync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (entity is not EscapePodWorldEntity escapePodEntity)
|
||||
{
|
||||
result.Set(Optional.Empty);
|
||||
Log.Error($"Received incorrect entity type: {entity.GetType()}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
SuppressEscapePodAwakeMethod = true;
|
||||
|
||||
GameObject escapePod = CreateNewEscapePod(escapePodEntity);
|
||||
|
||||
SuppressEscapePodAwakeMethod = false;
|
||||
|
||||
result.Set(Optional.Of(escapePod));
|
||||
}
|
||||
|
||||
private GameObject CreateNewEscapePod(EscapePodWorldEntity escapePodEntity)
|
||||
{
|
||||
// TODO: When we want to implement multiple escape pods, instantiate the prefab. Backlog task: #1945
|
||||
// This will require some work as instantiating the prefab as-is will not make it visible.
|
||||
//GameObject escapePod = Object.Instantiate(EscapePod.main.gameObject);
|
||||
GameObject escapePod = EscapePod.main.gameObject;
|
||||
EscapePod pod = escapePod.GetComponent<EscapePod>();
|
||||
|
||||
Object.DestroyImmediate(escapePod.GetComponent<NitroxEntity>()); // if template has a pre-existing NitroxEntity, remove.
|
||||
NitroxEntity.SetNewId(escapePod, escapePodEntity.Id);
|
||||
|
||||
if (escapePod.TryGetComponent(out Rigidbody rigidbody))
|
||||
{
|
||||
rigidbody.constraints = RigidbodyConstraints.FreezeAll;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("Escape pod did not have a rigid body!");
|
||||
}
|
||||
|
||||
pod.anchorPosition = escapePod.transform.position = escapePodEntity.Transform.Position.ToUnity();
|
||||
|
||||
pod.ForceSkyApplier();
|
||||
pod.escapePodCinematicControl.StopAll();
|
||||
|
||||
// Player is not new and has completed the intro cinematic. If not EscapePod repair status is handled by the intro cinematic.
|
||||
if (escapePodEntity.Metadata is EscapePodMetadata metadata && localPlayer.IntroCinematicMode == IntroCinematicMode.COMPLETED)
|
||||
{
|
||||
using FMODSoundSuppressor soundSuppressor = FMODSystem.SuppressSubnauticaSounds();
|
||||
using PacketSuppressor<EntityMetadataUpdate> packetSuppressor = PacketSuppressor<EntityMetadataUpdate>.Suppress();
|
||||
|
||||
Radio radio = pod.radioSpawner.spawnedObj.GetComponent<Radio>();
|
||||
EscapePodMetadataProcessor.ProcessInitialSyncMetadata(pod, radio, metadata);
|
||||
// NB: Entities.SpawnBatchAsync (which is the function calling the current spawner)
|
||||
// will still apply the metadata another time but we don't care as it's not destructive
|
||||
}
|
||||
|
||||
FixStartMethods(escapePod);
|
||||
|
||||
return escapePod;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start() isn't executed for the EscapePod and children (Why? Idk, maybe because it's a scene...) so we call the components here where we have patches in Start.
|
||||
/// </summary>
|
||||
private static void FixStartMethods(GameObject escapePod)
|
||||
{
|
||||
foreach (FMOD_CustomEmitter customEmitter in escapePod.GetComponentsInChildren<FMOD_CustomEmitter>(true))
|
||||
{
|
||||
customEmitter.Start();
|
||||
}
|
||||
|
||||
foreach (FMOD_StudioEventEmitter studioEventEmitter in escapePod.GetComponentsInChildren<FMOD_StudioEventEmitter>(true))
|
||||
{
|
||||
studioEventEmitter.Start();
|
||||
}
|
||||
|
||||
MultiplayerCinematicReference reference = escapePod.EnsureComponent<MultiplayerCinematicReference>();
|
||||
foreach (PlayerCinematicController controller in escapePod.GetComponentsInChildren<PlayerCinematicController>(true))
|
||||
{
|
||||
reference.AddController(controller);
|
||||
}
|
||||
}
|
||||
|
||||
public bool SpawnsOwnChildren() => false;
|
||||
}
|
@@ -0,0 +1,107 @@
|
||||
using System.Collections;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
using UWE;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
|
||||
public class GeyserWorldEntitySpawner : IWorldEntitySpawner, IWorldEntitySyncSpawner
|
||||
{
|
||||
private readonly Entities entities;
|
||||
|
||||
public GeyserWorldEntitySpawner(Entities entities)
|
||||
{
|
||||
this.entities = entities;
|
||||
}
|
||||
|
||||
public IEnumerator SpawnAsync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (entity is not GeyserWorldEntity geyserWorldEntity)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (!DefaultWorldEntitySpawner.TryGetCachedPrefab(out GameObject prefab, classId: entity.ClassId))
|
||||
{
|
||||
TaskResult<GameObject> prefabResult = new();
|
||||
yield return DefaultWorldEntitySpawner.RequestPrefab(entity.ClassId, prefabResult);
|
||||
if (!prefabResult.Get())
|
||||
{
|
||||
Log.Error($"Couldn't find a prefab for {nameof(GeyserWorldEntity)} of ClassId {entity.ClassId}");
|
||||
yield break;
|
||||
}
|
||||
prefab = prefabResult.Get();
|
||||
}
|
||||
|
||||
GameObject gameObject = GameObjectHelper.InstantiateInactiveWithId(prefab, entity.Id);
|
||||
if (!VerifyCanSpawnOrError(geyserWorldEntity, gameObject, out Geyser geyser))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
SetupObject(geyserWorldEntity, cellRoot, geyser);
|
||||
gameObject.SetActive(true);
|
||||
|
||||
result.Set(Optional.Of(gameObject));
|
||||
}
|
||||
|
||||
public bool SpawnSync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (entity is not GeyserWorldEntity geyserWorldEntity)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!DefaultWorldEntitySpawner.TryGetCachedPrefab(out GameObject prefab, classId: entity.ClassId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GameObject gameObject = GameObjectHelper.InstantiateInactiveWithId(prefab, entity.Id);
|
||||
if (!VerifyCanSpawnOrError(geyserWorldEntity, gameObject, out Geyser geyser))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
SetupObject(geyserWorldEntity, cellRoot, geyser);
|
||||
gameObject.SetActive(true);
|
||||
|
||||
result.Set(gameObject);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SpawnsOwnChildren() => false;
|
||||
|
||||
private static bool VerifyCanSpawnOrError(GeyserWorldEntity geyserEntity, GameObject prefabObject, out Geyser geyser)
|
||||
{
|
||||
if (prefabObject.TryGetComponent(out geyser))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Log.Error($"Could not find component {nameof(Geyser)} on prefab with ClassId: {geyserEntity.ClassId}");
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void SetupObject(GeyserWorldEntity geyserEntity, EntityCell cellRoot, Geyser geyser)
|
||||
{
|
||||
Transform transform = geyser.transform;
|
||||
transform.localPosition = geyserEntity.Transform.LocalPosition.ToUnity();
|
||||
transform.localRotation = geyserEntity.Transform.LocalRotation.ToUnity();
|
||||
transform.localScale = geyserEntity.Transform.LocalScale.ToUnity();
|
||||
transform.SetParent(cellRoot.liveRoot.transform);
|
||||
|
||||
// To ensure Geyser.Start() happens first, we delay our code by a frame
|
||||
CoroutineHost.StartCoroutine(DelayedGeyserSetup(geyserEntity, geyser));
|
||||
}
|
||||
|
||||
private static IEnumerator DelayedGeyserSetup(GeyserWorldEntity geyserEntity, Geyser geyser)
|
||||
{
|
||||
// Delay by an entire frame
|
||||
yield return null;
|
||||
geyser.gameObject.EnsureComponent<NitroxGeyser>().Initialize(geyserEntity, geyser);
|
||||
}
|
||||
}
|
@@ -0,0 +1,121 @@
|
||||
using System.Collections;
|
||||
using NitroxClient.Communication;
|
||||
using NitroxClient.GameLogic.Spawning.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Packets;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
using UWE;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
|
||||
public class GlobalRootEntitySpawner : SyncEntitySpawner<GlobalRootEntity>
|
||||
{
|
||||
protected override IEnumerator SpawnAsync(GlobalRootEntity entity, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
TaskResult<GameObject> gameObjectResult = new();
|
||||
yield return DefaultWorldEntitySpawner.CreateGameObject(entity.TechType.ToUnity(), entity.ClassId, entity.Id, gameObjectResult);
|
||||
GameObject gameObject = gameObjectResult.Get();
|
||||
|
||||
SetupObject(entity, gameObject);
|
||||
|
||||
result.Set(gameObject);
|
||||
}
|
||||
|
||||
protected override bool SpawnSync(GlobalRootEntity entity, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (!DefaultWorldEntitySpawner.TryGetCachedPrefab(out GameObject prefab, entity.TechType.ToUnity(), entity.ClassId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
GameObject gameObject = GameObjectHelper.InstantiateWithId(prefab, entity.Id);
|
||||
SetupObject(entity, gameObject);
|
||||
|
||||
result.Set(gameObject);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SetupObject(GlobalRootEntity entity, GameObject gameObject)
|
||||
{
|
||||
LargeWorldEntity largeWorldEntity = gameObject.EnsureComponent<LargeWorldEntity>();
|
||||
largeWorldEntity.cellLevel = LargeWorldEntity.CellLevel.Global;
|
||||
|
||||
LargeWorld.main.streamer.cellManager.RegisterEntity(largeWorldEntity);
|
||||
largeWorldEntity.Start();
|
||||
|
||||
gameObject.transform.localPosition = entity.Transform.LocalPosition.ToUnity();
|
||||
gameObject.transform.localRotation = entity.Transform.LocalRotation.ToUnity();
|
||||
gameObject.transform.localScale = entity.Transform.LocalScale.ToUnity();
|
||||
|
||||
if (entity.ParentId != null && NitroxEntity.TryGetComponentFrom(entity.ParentId, out Transform parentTransform))
|
||||
{
|
||||
// WaterParks have a child named "items_root" where the fish are put
|
||||
if (parentTransform.TryGetComponent(out WaterPark waterPark))
|
||||
{
|
||||
SetupObjectInWaterPark(gameObject, waterPark);
|
||||
|
||||
// TODO: When metadata is reworked (it'll be possible to give different metadatas to the same entity)
|
||||
// this will no longer be needed because the entity metadata will set this to false accordingly
|
||||
|
||||
// If fishes are in a WaterPark, it means that they were once picked up
|
||||
if (gameObject.TryGetComponent(out CreatureDeath creatureDeath))
|
||||
{
|
||||
// This is set to false when picking up a fish or when a fish is born in the WaterPark
|
||||
creatureDeath.respawn = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gameObject.transform.SetParent(parentTransform, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (gameObject.GetComponent<PlaceTool>())
|
||||
{
|
||||
PlacedWorldEntitySpawner.AdditionalSpawningSteps(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetupObjectInWaterPark(GameObject gameObject, WaterPark waterPark)
|
||||
{
|
||||
gameObject.transform.SetParent(waterPark.itemsRoot, false);
|
||||
using (PacketSuppressor<EntityMetadataUpdate>.Suppress())
|
||||
{
|
||||
waterPark.AddItem(gameObject.EnsureComponent<Pickupable>());
|
||||
|
||||
// While being fully loaded, the base is inactive so GameObject.SendMessage doesn't work and we need to execute their callbacks manually
|
||||
if (!Multiplayer.Main || Multiplayer.Main.InitialSyncCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Below are distinct incompatible cases
|
||||
if (gameObject.TryGetComponent(out CreatureEgg creatureEgg) && !creatureEgg.insideWaterPark)
|
||||
{
|
||||
creatureEgg.OnAddToWaterPark();
|
||||
}
|
||||
else if (gameObject.TryGetComponent(out CuteFish cuteFish))
|
||||
{
|
||||
cuteFish.OnAddToWaterPark(null);
|
||||
}
|
||||
else if (gameObject.TryGetComponent(out CrabSnake crabSnake))
|
||||
{
|
||||
// This callback interacts with an animator, but this behaviour needs to be initialized (probably during Start) before it can be modified
|
||||
IEnumerator PostponedCallback()
|
||||
{
|
||||
yield return new WaitUntil(() => !crabSnake || crabSnake.animationController.animator.isInitialized);
|
||||
if (crabSnake)
|
||||
{
|
||||
crabSnake.OnAddToWaterPark();
|
||||
}
|
||||
}
|
||||
CoroutineHost.StartCoroutine(PostponedCallback());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool SpawnsOwnChildren(GlobalRootEntity entity) => false;
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
using System.Collections;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.WorldEntities
|
||||
{
|
||||
/**
|
||||
* Allows us to create custom entity spawners for different world entity types.
|
||||
*/
|
||||
public interface IWorldEntitySpawner
|
||||
{
|
||||
IEnumerator SpawnAsync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result);
|
||||
|
||||
bool SpawnsOwnChildren();
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
|
||||
public interface IWorldEntitySyncSpawner
|
||||
{
|
||||
bool SpawnSync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result);
|
||||
}
|
@@ -0,0 +1,127 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using NitroxClient.GameLogic.Spawning.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
|
||||
public class OxygenPipeEntitySpawner : SyncEntitySpawner<OxygenPipeEntity>
|
||||
{
|
||||
private readonly Entities entities;
|
||||
private readonly WorldEntitySpawner worldEntitySpawner;
|
||||
|
||||
private readonly Dictionary<NitroxId, List<OxygenPipe>> childrenPipeEntitiesByParentId = new();
|
||||
|
||||
public OxygenPipeEntitySpawner(Entities entities, WorldEntitySpawner worldEntitySpawner)
|
||||
{
|
||||
this.entities = entities;
|
||||
this.worldEntitySpawner = worldEntitySpawner;
|
||||
}
|
||||
|
||||
protected override IEnumerator SpawnAsync(OxygenPipeEntity entity, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (!DefaultWorldEntitySpawner.TryGetCachedPrefab(out GameObject prefab, classId: entity.ClassId))
|
||||
{
|
||||
TaskResult<GameObject> prefabResult = new();
|
||||
yield return DefaultWorldEntitySpawner.RequestPrefab(entity.ClassId, prefabResult);
|
||||
if (!prefabResult.Get())
|
||||
{
|
||||
Log.Error($"Couldn't find a prefab for {nameof(OxygenPipeEntity)} of ClassId {entity.ClassId}");
|
||||
yield break;
|
||||
}
|
||||
prefab = prefabResult.Get();
|
||||
}
|
||||
|
||||
GameObject gameObject = GameObjectHelper.InstantiateInactiveWithId(prefab, entity.Id);
|
||||
if (!VerifyCanSpawnOrError(entity, gameObject, out OxygenPipe oxygenPipe))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
SetupObject(entity, gameObject, oxygenPipe);
|
||||
gameObject.SetActive(true);
|
||||
|
||||
result.Set(Optional.Of(gameObject));
|
||||
}
|
||||
|
||||
protected override bool SpawnSync(OxygenPipeEntity entity, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (!DefaultWorldEntitySpawner.TryGetCachedPrefab(out GameObject prefab, classId: entity.ClassId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GameObject gameObject = GameObjectHelper.InstantiateInactiveWithId(prefab, entity.Id);
|
||||
if (!VerifyCanSpawnOrError(entity, gameObject, out OxygenPipe oxygenPipe))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
SetupObject(entity, gameObject, oxygenPipe);
|
||||
gameObject.SetActive(true);
|
||||
|
||||
result.Set(gameObject);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool SpawnsOwnChildren(OxygenPipeEntity entity) => false;
|
||||
|
||||
private bool VerifyCanSpawnOrError(OxygenPipeEntity entity, GameObject prefabObject, out OxygenPipe oxygenPipe)
|
||||
{
|
||||
if (prefabObject.TryGetComponent(out oxygenPipe))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
Log.Error($"Couldn't find component {nameof(OxygenPipe)} on prefab with ClassId: {entity.ClassId}");
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetupObject(OxygenPipeEntity entity, GameObject gameObject, OxygenPipe oxygenPipe)
|
||||
{
|
||||
EntityCell cellRoot = worldEntitySpawner.EnsureCell(entity);
|
||||
|
||||
gameObject.transform.SetParent(cellRoot.liveRoot.transform, false);
|
||||
gameObject.transform.position = entity.Transform.Position.ToUnity();
|
||||
gameObject.transform.rotation = entity.Transform.Rotation.ToUnity();
|
||||
gameObject.transform.localScale = entity.Transform.LocalScale.ToUnity();
|
||||
|
||||
// The reference IDs must be set even if the target is not spawned yet
|
||||
oxygenPipe.parentPipeUID = entity.ParentPipeId.ToString();
|
||||
oxygenPipe.rootPipeUID = entity.RootPipeId.ToString();
|
||||
oxygenPipe.parentPosition = entity.ParentPosition.ToUnity();
|
||||
|
||||
// It can happen that the parent connection hasn't loaded yet (normal behaviour)
|
||||
if (NitroxEntity.TryGetComponentFrom(entity.ParentPipeId, out IPipeConnection parentConnection))
|
||||
{
|
||||
oxygenPipe.parentPosition = parentConnection.GetAttachPoint();
|
||||
parentConnection.AddChild(oxygenPipe);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We add this pipe to a pending list so that its parent pipe will know which children are already spawned when being spanwed
|
||||
if (!childrenPipeEntitiesByParentId.TryGetValue(entity.ParentPipeId, out List<OxygenPipe> pendingChildren))
|
||||
{
|
||||
childrenPipeEntitiesByParentId[entity.ParentPipeId] = pendingChildren = new();
|
||||
}
|
||||
pendingChildren.Add(oxygenPipe);
|
||||
}
|
||||
|
||||
if (childrenPipeEntitiesByParentId.TryGetValue(entity.Id, out List<OxygenPipe> children))
|
||||
{
|
||||
foreach (OxygenPipe childPipe in children)
|
||||
{
|
||||
oxygenPipe.AddChild(childPipe);
|
||||
}
|
||||
childrenPipeEntitiesByParentId.Remove(entity.Id);
|
||||
}
|
||||
|
||||
UWE.Utils.SetIsKinematicAndUpdateInterpolation(oxygenPipe.rigidBody, true, false);
|
||||
oxygenPipe.UpdatePipe();
|
||||
}
|
||||
}
|
@@ -0,0 +1,100 @@
|
||||
using System.Collections;
|
||||
using NitroxClient.GameLogic.Spawning.Abstract;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
|
||||
public class PlacedWorldEntitySpawner : SyncEntitySpawner<PlacedWorldEntity>
|
||||
{
|
||||
private readonly WorldEntitySpawner worldEntitySpawner;
|
||||
|
||||
public PlacedWorldEntitySpawner(WorldEntitySpawner worldEntitySpawner)
|
||||
{
|
||||
this.worldEntitySpawner = worldEntitySpawner;
|
||||
}
|
||||
|
||||
protected override IEnumerator SpawnAsync(PlacedWorldEntity entity, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (!DefaultWorldEntitySpawner.TryGetCachedPrefab(out GameObject prefab, classId: entity.ClassId))
|
||||
{
|
||||
TaskResult<GameObject> prefabResult = new();
|
||||
yield return DefaultWorldEntitySpawner.RequestPrefab(entity.ClassId, prefabResult);
|
||||
if (!prefabResult.Get())
|
||||
{
|
||||
Log.Error($"Couldn't find a prefab for {nameof(OxygenPipeEntity)} of ClassId {entity.ClassId}");
|
||||
yield break;
|
||||
}
|
||||
prefab = prefabResult.Get();
|
||||
}
|
||||
|
||||
GameObject gameObject = GameObjectHelper.InstantiateWithId(prefab, entity.Id);
|
||||
if (!VerifyCanSpawnOrError(entity, gameObject))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
SetupObject(entity, gameObject);
|
||||
|
||||
result.Set(Optional.Of(gameObject));
|
||||
}
|
||||
|
||||
protected override bool SpawnSync(PlacedWorldEntity entity, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (!DefaultWorldEntitySpawner.TryGetCachedPrefab(out GameObject prefab, classId: entity.ClassId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GameObject gameObject = GameObjectHelper.InstantiateWithId(prefab, entity.Id);
|
||||
if (!VerifyCanSpawnOrError(entity, gameObject))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
SetupObject(entity, gameObject);
|
||||
|
||||
result.Set(gameObject);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool SpawnsOwnChildren(PlacedWorldEntity entity) => false;
|
||||
|
||||
public static void AdditionalSpawningSteps(GameObject gameObject)
|
||||
{
|
||||
if (gameObject.TryGetComponent(out PlaceTool placeTool))
|
||||
{
|
||||
if (gameObject.TryGetComponentInParent(out SubRoot subRoot))
|
||||
{
|
||||
SkyEnvironmentChanged.Send(gameObject, subRoot);
|
||||
}
|
||||
if (gameObject.TryGetComponent(out Rigidbody rigidbody))
|
||||
{
|
||||
UWE.Utils.SetIsKinematicAndUpdateInterpolation(rigidbody, true, false);
|
||||
}
|
||||
placeTool.OnPlace();
|
||||
}
|
||||
}
|
||||
|
||||
private bool VerifyCanSpawnOrError(PlacedWorldEntity entity, GameObject prefabObject)
|
||||
{
|
||||
if (prefabObject.GetComponent<PlaceTool>())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
Log.Error($"Couldn't find component {nameof(PlaceTool)} on prefab with ClassId: {entity.ClassId}");
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetupObject(PlacedWorldEntity entity, GameObject gameObject)
|
||||
{
|
||||
EntityCell cellRoot = worldEntitySpawner.EnsureCell(entity);
|
||||
|
||||
gameObject.transform.SetParent(cellRoot.liveRoot.transform, false);
|
||||
gameObject.transform.position = entity.Transform.Position.ToUnity();
|
||||
gameObject.transform.rotation = entity.Transform.Rotation.ToUnity();
|
||||
gameObject.transform.localScale = entity.Transform.LocalScale.ToUnity();
|
||||
AdditionalSpawningSteps(gameObject);
|
||||
}
|
||||
}
|
@@ -0,0 +1,163 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using NitroxClient.GameLogic.Spawning.Metadata;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
|
||||
/// <remarks>
|
||||
/// This spawner can't hold a SpawnSync function because it is also responsible for spawning its children
|
||||
/// so the <see cref="SpawnAsync"/> function will still use sync spawning when possible and fall back to async when required.
|
||||
/// </remarks>
|
||||
public class PlaceholderGroupWorldEntitySpawner : IWorldEntitySpawner
|
||||
{
|
||||
private readonly Entities entities;
|
||||
private readonly WorldEntitySpawnerResolver spawnerResolver;
|
||||
private readonly DefaultWorldEntitySpawner defaultSpawner;
|
||||
private readonly EntityMetadataManager entityMetadataManager;
|
||||
private readonly PrefabPlaceholderEntitySpawner prefabPlaceholderEntitySpawner;
|
||||
|
||||
public PlaceholderGroupWorldEntitySpawner(Entities entities, WorldEntitySpawnerResolver spawnerResolver, DefaultWorldEntitySpawner defaultSpawner, EntityMetadataManager entityMetadataManager, PrefabPlaceholderEntitySpawner prefabPlaceholderEntitySpawner)
|
||||
{
|
||||
this.entities = entities;
|
||||
this.spawnerResolver = spawnerResolver;
|
||||
this.defaultSpawner = defaultSpawner;
|
||||
this.entityMetadataManager = entityMetadataManager;
|
||||
this.prefabPlaceholderEntitySpawner = prefabPlaceholderEntitySpawner;
|
||||
}
|
||||
|
||||
public IEnumerator SpawnAsync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (entity is not PlaceholderGroupWorldEntity placeholderGroupEntity)
|
||||
{
|
||||
Log.Error($"[{nameof(PlaceholderGroupWorldEntitySpawner)}] Can't spawn {entity.Id} of type {entity.GetType()} because it is not a {nameof(PlaceholderGroupWorldEntity)}");
|
||||
yield break;
|
||||
}
|
||||
|
||||
TaskResult<Optional<GameObject>> prefabPlaceholderGroupTaskResult = new();
|
||||
if (!defaultSpawner.SpawnSync(entity, parent, cellRoot, prefabPlaceholderGroupTaskResult))
|
||||
{
|
||||
yield return defaultSpawner.SpawnAsync(entity, parent, cellRoot, prefabPlaceholderGroupTaskResult);
|
||||
}
|
||||
|
||||
Optional<GameObject> prefabPlaceholderGroupGameObject = prefabPlaceholderGroupTaskResult.Get();
|
||||
|
||||
if (!prefabPlaceholderGroupGameObject.HasValue)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
GameObject groupObject = prefabPlaceholderGroupGameObject.Value;
|
||||
// Spawning PrefabPlaceholders as siblings to the group
|
||||
PrefabPlaceholdersGroup prefabPlaceholderGroup = groupObject.GetComponent<PrefabPlaceholdersGroup>();
|
||||
|
||||
// Spawning all children iteratively
|
||||
Stack<Entity> stack = new(placeholderGroupEntity.ChildEntities);
|
||||
|
||||
TaskResult<Optional<GameObject>> childResult = new();
|
||||
Dictionary<NitroxId, GameObject> parentById = new()
|
||||
{
|
||||
{ entity.Id, groupObject }
|
||||
};
|
||||
while (stack.Count > 0)
|
||||
{
|
||||
// It may happen that the chunk is unloaded, and the group along so we just cancel this spawn behaviour
|
||||
if (!groupObject)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
childResult.Set(Optional.Empty);
|
||||
Entity current = stack.Pop();
|
||||
switch (current)
|
||||
{
|
||||
case PrefabPlaceholderEntity prefabEntity:
|
||||
if (!prefabPlaceholderEntitySpawner.SpawnSync(prefabEntity, groupObject, cellRoot, childResult))
|
||||
{
|
||||
yield return prefabPlaceholderEntitySpawner.SpawnAsync(prefabEntity, groupObject, cellRoot, childResult);
|
||||
}
|
||||
break;
|
||||
|
||||
case PlaceholderGroupWorldEntity groupEntity:
|
||||
PrefabPlaceholder placeholder = prefabPlaceholderGroup.prefabPlaceholders[groupEntity.ComponentIndex];
|
||||
yield return SpawnAsync(groupEntity, placeholder.transform.parent.gameObject, cellRoot, childResult);
|
||||
break;
|
||||
|
||||
case WorldEntity worldEntity:
|
||||
if (!SpawnWorldEntityChildSync(worldEntity, cellRoot, parentById.GetOrDefault(current.ParentId, null), childResult, out IEnumerator asyncInstructions))
|
||||
{
|
||||
yield return asyncInstructions;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.Error($"[{nameof(PlaceholderGroupWorldEntitySpawner)}] Can't spawn a child entity which is not a WorldEntity: {current}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!childResult.value.HasValue)
|
||||
{
|
||||
Log.Error($"[{nameof(PlaceholderGroupWorldEntitySpawner)}] Spawning of child failed {current}");
|
||||
continue;
|
||||
}
|
||||
|
||||
GameObject childObject = childResult.value.Value;
|
||||
entities.MarkAsSpawned(current);
|
||||
parentById[current.Id] = childObject;
|
||||
entityMetadataManager.ApplyMetadata(childObject, current.Metadata);
|
||||
|
||||
// PlaceholderGroupWorldEntity's children spawning is already handled by this function which is called recursively
|
||||
if (current is not PlaceholderGroupWorldEntity)
|
||||
{
|
||||
// Adding children to be spawned by this loop
|
||||
foreach (Entity slotEntityChild in current.ChildEntities)
|
||||
{
|
||||
stack.Push(slotEntityChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.Set(prefabPlaceholderGroupGameObject);
|
||||
}
|
||||
|
||||
public bool SpawnsOwnChildren() => true;
|
||||
|
||||
private IEnumerator SpawnWorldEntityChildAsync(WorldEntity worldEntity, EntityCell cellRoot, GameObject parent, TaskResult<Optional<GameObject>> worldEntityResult)
|
||||
{
|
||||
IWorldEntitySpawner spawner = spawnerResolver.ResolveEntitySpawner(worldEntity);
|
||||
yield return spawner.SpawnAsync(worldEntity, parent, cellRoot, worldEntityResult);
|
||||
if (!worldEntityResult.value.HasValue)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
GameObject spawnedObject = worldEntityResult.value.Value;
|
||||
|
||||
spawnedObject.transform.localPosition = worldEntity.Transform.LocalPosition.ToUnity();
|
||||
spawnedObject.transform.localRotation = worldEntity.Transform.LocalRotation.ToUnity();
|
||||
spawnedObject.transform.localScale = worldEntity.Transform.LocalScale.ToUnity();
|
||||
}
|
||||
|
||||
private bool SpawnWorldEntityChildSync(WorldEntity worldEntity, EntityCell cellRoot, GameObject parent, TaskResult<Optional<GameObject>> worldEntityResult, out IEnumerator asyncInstructions)
|
||||
{
|
||||
IWorldEntitySpawner spawner = spawnerResolver.ResolveEntitySpawner(worldEntity);
|
||||
|
||||
if (spawner is not IWorldEntitySyncSpawner syncSpawner ||
|
||||
!syncSpawner.SpawnSync(worldEntity, parent, cellRoot, worldEntityResult) ||
|
||||
!worldEntityResult.value.HasValue)
|
||||
{
|
||||
asyncInstructions = SpawnWorldEntityChildAsync(worldEntity, cellRoot, parent, worldEntityResult);
|
||||
return false;
|
||||
}
|
||||
GameObject spawnedObject = worldEntityResult.value.Value;
|
||||
|
||||
spawnedObject.transform.localPosition = worldEntity.Transform.LocalPosition.ToUnity();
|
||||
spawnedObject.transform.localRotation = worldEntity.Transform.LocalRotation.ToUnity();
|
||||
spawnedObject.transform.localScale = worldEntity.Transform.LocalScale.ToUnity();
|
||||
asyncInstructions = null;
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,137 @@
|
||||
using System.Collections;
|
||||
using NitroxClient.GameLogic.PlayerLogic.PlayerModel.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
|
||||
public class PlayerWorldEntitySpawner : IWorldEntitySpawner
|
||||
{
|
||||
private readonly PlayerManager playerManager;
|
||||
private readonly ILocalNitroxPlayer localPlayer;
|
||||
|
||||
public PlayerWorldEntitySpawner(PlayerManager playerManager, ILocalNitroxPlayer localPlayer)
|
||||
{
|
||||
this.playerManager = playerManager;
|
||||
this.localPlayer = localPlayer;
|
||||
}
|
||||
|
||||
public IEnumerator SpawnAsync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (Player.main.TryGetNitroxId(out NitroxId localPlayerId) && localPlayerId == entity.Id)
|
||||
{
|
||||
// No special setup for the local player. Simply return saying it is spawned.
|
||||
result.Set(Player.main.gameObject);
|
||||
yield break;
|
||||
}
|
||||
|
||||
Optional<RemotePlayer> remotePlayer = playerManager.Find(entity.Id);
|
||||
|
||||
// The server may send us a player entity but they are not guarenteed to be actively connected at the moment - don't spawn them. In the
|
||||
// future, we could make this configurable to be able to spawn disconnected players in the world.
|
||||
if (remotePlayer.HasValue && !remotePlayer.Value.Body)
|
||||
{
|
||||
GameObject remotePlayerBody = CloneLocalPlayerBodyPrototype();
|
||||
|
||||
remotePlayer.Value.InitializeGameObject(remotePlayerBody);
|
||||
|
||||
if (!IsSwimming(entity.Transform.Position.ToUnity(), parent))
|
||||
{
|
||||
remotePlayer.Value.UpdateAnimationAndCollider(AnimChangeType.UNDERWATER, AnimChangeState.OFF);
|
||||
}
|
||||
|
||||
if (parent.HasValue)
|
||||
{
|
||||
AttachToParent(remotePlayer.Value, parent.Value);
|
||||
}
|
||||
|
||||
result.Set(Optional.Of(remotePlayerBody));
|
||||
yield break;
|
||||
}
|
||||
|
||||
result.Set(Optional.Empty);
|
||||
}
|
||||
|
||||
public bool SpawnsOwnChildren()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private GameObject CloneLocalPlayerBodyPrototype()
|
||||
{
|
||||
GameObject clone = Object.Instantiate(localPlayer.BodyPrototype, null, false);
|
||||
clone.SetActive(true);
|
||||
return clone;
|
||||
}
|
||||
|
||||
private void AttachToParent(RemotePlayer remotePlayer, GameObject parent)
|
||||
{
|
||||
if (parent.TryGetComponent(out SubRoot subRoot))
|
||||
{
|
||||
Log.Debug($"Found sub root for {remotePlayer.PlayerName}. Will add him and update animation.");
|
||||
remotePlayer.SetSubRoot(subRoot);
|
||||
}
|
||||
else if (parent.TryGetComponent(out EscapePod escapePod))
|
||||
{
|
||||
Log.Debug($"Found EscapePod for {remotePlayer.PlayerName}.");
|
||||
remotePlayer.SetEscapePod(escapePod);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"Found neither SubRoot component nor EscapePod on {parent.name} for {remotePlayer.PlayerName}.");
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSwimming(Vector3 playerPosition, Optional<GameObject> parent)
|
||||
{
|
||||
if (parent.HasValue)
|
||||
{
|
||||
parent.Value.TryGetComponent<SubRoot>(out SubRoot subroot);
|
||||
|
||||
// Set the animation for the remote player to standing instead of swimming if player is not in a flooded subroot
|
||||
// or in a waterpark
|
||||
if (subroot)
|
||||
{
|
||||
if (subroot.IsUnderwater(playerPosition))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (subroot.isCyclops)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// We know that we are in a subroot. But we can also be in a waterpark in a subroot, where we would swim
|
||||
BaseRoot baseRoot = subroot.GetComponentInParent<BaseRoot>();
|
||||
if (baseRoot)
|
||||
{
|
||||
WaterPark[] waterParks = baseRoot.GetComponentsInChildren<WaterPark>();
|
||||
foreach (WaterPark waterPark in waterParks)
|
||||
{
|
||||
if (waterPark.IsPointInside(playerPosition))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Log.Debug($"Trying to find escape pod for {parent}.");
|
||||
parent.Value.TryGetComponent<EscapePod>(out EscapePod escapePod);
|
||||
if (escapePod)
|
||||
{
|
||||
Log.Debug("Found escape pod for player. Will add him and update animation.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Player can be above ocean level.
|
||||
float oceanLevel = Ocean.GetOceanLevel();
|
||||
return playerPosition.y < oceanLevel;
|
||||
}
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
using System.Collections;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
|
||||
public class PrefabPlaceholderEntitySpawner : IWorldEntitySpawner, IWorldEntitySyncSpawner
|
||||
{
|
||||
private readonly DefaultWorldEntitySpawner defaultEntitySpawner;
|
||||
|
||||
public PrefabPlaceholderEntitySpawner(DefaultWorldEntitySpawner defaultEntitySpawner)
|
||||
{
|
||||
this.defaultEntitySpawner = defaultEntitySpawner;
|
||||
}
|
||||
|
||||
public IEnumerator SpawnAsync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (!VerifyCanSpawnOrError(entity, parent, out PrefabPlaceholder placeholder))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
yield return defaultEntitySpawner.SpawnAsync(entity, placeholder.transform.parent.gameObject, cellRoot, result);
|
||||
if (!result.value.HasValue)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
SetupObject(entity, result.value.Value);
|
||||
}
|
||||
|
||||
public bool SpawnsOwnChildren() => false;
|
||||
|
||||
public bool SpawnSync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (!VerifyCanSpawnOrError(entity, parent, out PrefabPlaceholder placeholder))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!defaultEntitySpawner.SpawnSync(entity, placeholder.transform.parent.gameObject, cellRoot, result))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SetupObject(entity, result.value.Value);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool VerifyCanSpawnOrError(WorldEntity entity, Optional<GameObject> parent, out PrefabPlaceholder placeholder)
|
||||
{
|
||||
if (entity is PrefabPlaceholderEntity prefabEntity &&
|
||||
parent.Value && parent.Value.TryGetComponent(out PrefabPlaceholdersGroup group))
|
||||
{
|
||||
placeholder = group.prefabPlaceholders[prefabEntity.ComponentIndex];
|
||||
return true;
|
||||
}
|
||||
|
||||
Log.Error($"[{nameof(PrefabPlaceholderEntitySpawner)}] Can't find a {nameof(PrefabPlaceholdersGroup)} on parent for {entity.Id}");
|
||||
placeholder = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetupObject(WorldEntity entity, GameObject gameObject)
|
||||
{
|
||||
gameObject.transform.localPosition = entity.Transform.LocalPosition.ToUnity();
|
||||
gameObject.transform.localRotation = entity.Transform.LocalRotation.ToUnity();
|
||||
gameObject.transform.localScale = entity.Transform.LocalScale.ToUnity();
|
||||
}
|
||||
}
|
@@ -0,0 +1,102 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NitroxClient.GameLogic.Spawning.Abstract;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
|
||||
public class RadiationLeakEntitySpawner : SyncEntitySpawner<RadiationLeakEntity>
|
||||
{
|
||||
// This constant is defined by Subnautica and should never be modified (same as for SubnauticaWorldModifier)
|
||||
private const int TOTAL_LEAKS = 11;
|
||||
private readonly TimeManager timeManager;
|
||||
private readonly List<float> registeredLeaksFixTime = new();
|
||||
|
||||
public RadiationLeakEntitySpawner(TimeManager timeManager)
|
||||
{
|
||||
this.timeManager = timeManager;
|
||||
}
|
||||
|
||||
protected override IEnumerator SpawnAsync(RadiationLeakEntity entity, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
SpawnSync(entity, result);
|
||||
yield break;
|
||||
}
|
||||
|
||||
protected override bool SpawnSync(RadiationLeakEntity entity, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
// This script is located under (Aurora Scene) //Aurora-Main/Aurora so it's a good starting point to search through the GameObjects
|
||||
CrashedShipExploder crashedShipExploder = CrashedShipExploder.main;
|
||||
LeakingRadiation leakingRadiation = LeakingRadiation.main;
|
||||
if (!crashedShipExploder || !leakingRadiation || entity.Metadata is not RadiationMetadata metadata)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
Transform radiationLeaksHolder = crashedShipExploder.transform.Find("radiationleaks").GetChild(0);
|
||||
RadiationLeak radiationLeak = radiationLeaksHolder.GetChild(entity.ObjectIndex).GetComponent<RadiationLeak>();
|
||||
NitroxEntity.SetNewId(radiationLeak.gameObject, entity.Id);
|
||||
radiationLeak.liveMixin.health = metadata.Health;
|
||||
registeredLeaksFixTime.Add(metadata.FixRealTime);
|
||||
|
||||
// We can only calculate the radiation increment and dissipation once we got all radiation leaks info
|
||||
if (crashedShipExploder.IsExploded() && registeredLeaksFixTime.Count == TOTAL_LEAKS)
|
||||
{
|
||||
RecalculateRadiationRadius(leakingRadiation);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RecalculateRadiationRadius(LeakingRadiation leakingRadiation)
|
||||
{
|
||||
float realElapsedTime = (float)timeManager.RealTimeElapsed;
|
||||
// We substract the explosion time from the real time because before that, the radius doesn't increment
|
||||
float realExplosionTime = timeManager.AuroraRealExplosionTime;
|
||||
float maxRegisteredLeakFixTime = registeredLeaksFixTime.Max();
|
||||
|
||||
// Note: Only increment radius if leaks were fixed AFTER explosion (before, game code doesn't increase radius)
|
||||
|
||||
// If leaks aren't all fixed yet we calculate from current real elapsed time
|
||||
float deltaTimeAfterExplosion = realElapsedTime - realExplosionTime;
|
||||
if (maxRegisteredLeakFixTime == -1)
|
||||
{
|
||||
if (deltaTimeAfterExplosion > 0)
|
||||
{
|
||||
float radiusIncrement = deltaTimeAfterExplosion * leakingRadiation.kGrowRate;
|
||||
// Calculation lines from LeakingRadiation.Update
|
||||
leakingRadiation.currentRadius = Mathf.Clamp(leakingRadiation.kStartRadius + radiusIncrement, 0f, leakingRadiation.kMaxRadius);
|
||||
leakingRadiation.damagePlayerInRadius.damageRadius = leakingRadiation.currentRadius;
|
||||
leakingRadiation.radiatePlayerInRange.radiateRadius = leakingRadiation.currentRadius;
|
||||
}
|
||||
// If leaks aren't fixed, we won't need to calculate a radius decrement
|
||||
return;
|
||||
}
|
||||
leakingRadiation.radiationFixed = true;
|
||||
|
||||
// If all leaks are fixed we calculate from the time they were fixed
|
||||
float deltaAliveTime = maxRegisteredLeakFixTime - realExplosionTime;
|
||||
if (deltaAliveTime > 0)
|
||||
{
|
||||
float radiusIncrement = deltaAliveTime * leakingRadiation.kGrowRate;
|
||||
leakingRadiation.currentRadius = Mathf.Clamp(leakingRadiation.kStartRadius + radiusIncrement, 0f, leakingRadiation.kMaxRadius);
|
||||
}
|
||||
|
||||
// Now calculate the natural dissipation decrement from the time leaks are fixed
|
||||
// If they were fixed before real explosion time, we calculate from real explosion time
|
||||
float deltaFixedTimeAfterExplosion = realElapsedTime - Mathf.Max(maxRegisteredLeakFixTime, realExplosionTime);
|
||||
if (deltaFixedTimeAfterExplosion > 0)
|
||||
{
|
||||
float radiusDecrement = deltaFixedTimeAfterExplosion * leakingRadiation.kNaturalDissipation;
|
||||
leakingRadiation.currentRadius = Mathf.Clamp(leakingRadiation.currentRadius + radiusDecrement, 0f, leakingRadiation.kMaxRadius);
|
||||
}
|
||||
leakingRadiation.damagePlayerInRadius.damageRadius = leakingRadiation.currentRadius;
|
||||
leakingRadiation.radiatePlayerInRange.radiateRadius = leakingRadiation.currentRadius;
|
||||
}
|
||||
|
||||
protected override bool SpawnsOwnChildren(RadiationLeakEntity entity) => false;
|
||||
}
|
@@ -0,0 +1,101 @@
|
||||
using System.Collections;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
|
||||
public class ReefbackChildEntitySpawner : IWorldEntitySpawner, IWorldEntitySyncSpawner
|
||||
{
|
||||
public IEnumerator SpawnAsync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (entity is not ReefbackChildEntity reefbackChildEntity)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (!DefaultWorldEntitySpawner.TryGetCachedPrefab(out GameObject prefab, classId: entity.ClassId))
|
||||
{
|
||||
TaskResult<GameObject> prefabResult = new();
|
||||
yield return DefaultWorldEntitySpawner.RequestPrefab(entity.ClassId, prefabResult);
|
||||
if (!prefabResult.Get())
|
||||
{
|
||||
Log.Error($"Couldn't find a prefab for {nameof(OxygenPipeEntity)} of ClassId {entity.ClassId}");
|
||||
yield break;
|
||||
}
|
||||
prefab = prefabResult.Get();
|
||||
}
|
||||
|
||||
GameObject gameObject = GameObjectHelper.InstantiateWithId(prefab, entity.Id);
|
||||
if (!VerifyCanSpawnOrError(reefbackChildEntity, out ReefbackLife parentReefbackLife))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
SetupObject(reefbackChildEntity, gameObject, parentReefbackLife);
|
||||
|
||||
result.Set(Optional.Of(gameObject));
|
||||
}
|
||||
|
||||
public bool SpawnSync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (entity is not ReefbackChildEntity reefbackChildEntity)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!DefaultWorldEntitySpawner.TryGetCachedPrefab(out GameObject prefab, classId: entity.ClassId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!VerifyCanSpawnOrError(reefbackChildEntity, out ReefbackLife parentReefbackLife))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
GameObject gameObject = GameObjectHelper.InstantiateWithId(prefab, entity.Id);
|
||||
SetupObject(reefbackChildEntity, gameObject, parentReefbackLife);
|
||||
|
||||
result.Set(gameObject);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SpawnsOwnChildren() => false;
|
||||
|
||||
private static bool VerifyCanSpawnOrError(ReefbackChildEntity entity, out ReefbackLife parentReefbackLife)
|
||||
{
|
||||
if (NitroxEntity.TryGetComponentFrom(entity.ParentId, out parentReefbackLife))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
Log.Error($"Could not find a valid parent with {nameof(ReefbackLife)} from Id: {entity.ParentId}");
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void SetupObject(ReefbackChildEntity entity, GameObject gameObject, ReefbackLife parentReefbackLife)
|
||||
{
|
||||
Transform transform = gameObject.transform;
|
||||
|
||||
transform.localPosition = entity.Transform.LocalPosition.ToUnity();
|
||||
transform.localRotation = entity.Transform.LocalRotation.ToUnity();
|
||||
transform.localScale = entity.Transform.LocalScale.ToUnity();
|
||||
|
||||
// Positioning from ReefbackLife.SpawnPlants and ReefbackLife.SpawnCreatures
|
||||
switch (entity.Type)
|
||||
{
|
||||
case ReefbackChildEntity.ReefbackChildType.PLANT:
|
||||
transform.SetParent(parentReefbackLife.plantSlots[0].parent, false);
|
||||
gameObject.AddComponent<ReefbackPlant>();
|
||||
break;
|
||||
|
||||
case ReefbackChildEntity.ReefbackChildType.CREATURE:
|
||||
transform.SetParent(parentReefbackLife.creatureSlots[0].parent, false);
|
||||
gameObject.AddComponent<ReefbackCreature>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
|
||||
public class ReefbackEntitySpawner : IWorldEntitySpawner, IWorldEntitySyncSpawner
|
||||
{
|
||||
private readonly ReefbackChildEntitySpawner reefbackChildEntitySpawner;
|
||||
|
||||
public ReefbackEntitySpawner(ReefbackChildEntitySpawner reefbackChildEntitySpawner)
|
||||
{
|
||||
this.reefbackChildEntitySpawner = reefbackChildEntitySpawner;
|
||||
}
|
||||
|
||||
public IEnumerator SpawnAsync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (entity is not ReefbackEntity reefbackEntity)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (!DefaultWorldEntitySpawner.TryGetCachedPrefab(out GameObject prefab, classId: entity.ClassId))
|
||||
{
|
||||
TaskResult<GameObject> prefabResult = new();
|
||||
yield return DefaultWorldEntitySpawner.RequestPrefab(entity.ClassId, prefabResult);
|
||||
if (!prefabResult.Get())
|
||||
{
|
||||
Log.Error($"Couldn't find a prefab for {nameof(OxygenPipeEntity)} of ClassId {entity.ClassId}");
|
||||
yield break;
|
||||
}
|
||||
prefab = prefabResult.Get();
|
||||
}
|
||||
|
||||
GameObject gameObject = GameObjectHelper.InstantiateWithId(prefab, entity.Id);
|
||||
if (!VerifyCanSpawnOrError(reefbackEntity, gameObject, out ReefbackLife reefbackLife))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
SetupObject(reefbackEntity, gameObject, cellRoot, reefbackLife);
|
||||
|
||||
result.Set(gameObject);
|
||||
}
|
||||
|
||||
public bool SpawnSync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (entity is not ReefbackEntity reefbackEntity)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!DefaultWorldEntitySpawner.TryGetCachedPrefab(out GameObject prefab, classId: entity.ClassId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GameObject gameObject = GameObjectHelper.InstantiateWithId(prefab, entity.Id);
|
||||
if (!VerifyCanSpawnOrError(reefbackEntity, gameObject, out ReefbackLife reefbackLife))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
SetupObject(reefbackEntity, gameObject, cellRoot, reefbackLife);
|
||||
|
||||
result.Set(gameObject);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SpawnsOwnChildren() => false;
|
||||
|
||||
private static bool VerifyCanSpawnOrError(ReefbackEntity entity, GameObject prefabObject, out ReefbackLife reefbackLife)
|
||||
{
|
||||
if (prefabObject.TryGetComponent(out reefbackLife))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
Log.Error($"Could not find component {nameof(ReefbackLife)} on prefab with ClassId: {entity.ClassId}");
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void SetupObject(ReefbackEntity entity, GameObject gameObject, EntityCell entityCell, ReefbackLife reefbackLife)
|
||||
{
|
||||
Transform transform = gameObject.transform;
|
||||
transform.localPosition = entity.Transform.Position.ToUnity();
|
||||
transform.localRotation = entity.Transform.Rotation.ToUnity();
|
||||
transform.localScale = entity.Transform.LocalScale.ToUnity();
|
||||
entityCell.EnsureRoot();
|
||||
transform.SetParent(entityCell.liveRoot.transform);
|
||||
|
||||
// Replicate only the useful parts of ReefbackLife.Initialize
|
||||
reefbackLife.initialized = true;
|
||||
reefbackLife.needToRemovePlantPhysics = false;
|
||||
reefbackLife.hasCorals = gameObject.transform.localScale.x > 0.8f;
|
||||
|
||||
if (reefbackLife.hasCorals && LargeWorld.main)
|
||||
{
|
||||
string biome = LargeWorld.main.GetBiome(entity.OriginalPosition.ToUnity());
|
||||
if (!string.IsNullOrEmpty(biome) && biome.StartsWith("grassyplateaus", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
reefbackLife.grassIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
reefbackLife.grassIndex = entity.GrassIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Only useful stuff from ReefbackLife.CoSpawn
|
||||
reefbackLife.corals.SetActive(reefbackLife.hasCorals);
|
||||
reefbackLife.islands.SetActive(reefbackLife.hasCorals);
|
||||
if (reefbackLife.grassIndex >= 0 && reefbackLife.grassIndex < reefbackLife.grassVariants.Length)
|
||||
{
|
||||
reefbackLife.grassVariants[reefbackLife.grassIndex].SetActive(true);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.Unity;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
using UWE;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
|
||||
public class SerializedWorldEntitySpawner : IWorldEntitySpawner, IWorldEntitySyncSpawner
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the only types we allow the server to instantiate on clients (for security concerns)
|
||||
/// </summary>
|
||||
private readonly HashSet<Type> typesWhitelist = new()
|
||||
{
|
||||
typeof(Light), typeof(DisableBeforeExplosion), typeof(BoxCollider), typeof(SphereCollider)
|
||||
};
|
||||
|
||||
public SerializedWorldEntitySpawner()
|
||||
{
|
||||
// Preloading a useful asset
|
||||
if (!NitroxEnvironment.IsTesting && !ProtobufSerializer.emptyGameObjectPrefab)
|
||||
{
|
||||
ProtobufSerializer.emptyGameObjectPrefab = Resources.Load<GameObject>("SerializerEmptyGameObject");
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator SpawnAsync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
SpawnSync(entity, parent, cellRoot, result);
|
||||
yield break;
|
||||
}
|
||||
|
||||
public bool SpawnsOwnChildren() => false;
|
||||
|
||||
public bool SpawnSync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (entity is not SerializedWorldEntity serializedWorldEntity)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
using PooledObject<ProtobufSerializer> proxy = ProtobufSerializerPool.GetProxy();
|
||||
ProtobufSerializer serializer = proxy.Value;
|
||||
|
||||
UniqueIdentifier uniqueIdentifier = serializer.CreateEmptyGameObject("SerializerEmptyGameObject");
|
||||
GameObject gameObject = uniqueIdentifier.gameObject;
|
||||
gameObject.SetActive(false);
|
||||
gameObject.layer = serializedWorldEntity.Layer;
|
||||
gameObject.tag = "Untagged"; // Same tag for all empty game objects
|
||||
|
||||
LargeWorldEntity largeWorldEntity = gameObject.AddComponent<LargeWorldEntity>();
|
||||
largeWorldEntity.cellLevel = (LargeWorldEntity.CellLevel)serializedWorldEntity.Level;
|
||||
|
||||
Transform transform = gameObject.transform;
|
||||
transform.SetParent(cellRoot.liveRoot.transform);
|
||||
NitroxVector3 localPosition = serializedWorldEntity.Transform.LocalPosition - serializedWorldEntity.AbsoluteEntityCell.Position;
|
||||
transform.localPosition = localPosition.ToUnity();
|
||||
transform.localRotation = serializedWorldEntity.Transform.LocalRotation.ToUnity();
|
||||
transform.localScale = serializedWorldEntity.Transform.LocalScale.ToUnity();
|
||||
|
||||
// Code inspired from ProtobufSerializer.DeserializeIntoGameObject
|
||||
Dictionary<Type, int> dictionary = ProtobufSerializer.componentCountersPool.Get();
|
||||
dictionary.Clear();
|
||||
foreach (SerializedComponent serializedComponent in serializedWorldEntity.Components)
|
||||
{
|
||||
string typeName = serializedComponent.TypeName;
|
||||
Type cachedType = ProtobufSerializer.GetCachedType(typeName);
|
||||
if (!typesWhitelist.Contains(cachedType))
|
||||
{
|
||||
Log.ErrorOnce($"Server asked to instantiate a non-whitelisted type {typeName}.");
|
||||
return true;
|
||||
}
|
||||
|
||||
using MemoryStream stream = new(serializedComponent.Data);
|
||||
int id = ProtobufSerializer.IncrementComponentCounter(dictionary, cachedType);
|
||||
Component orAddComponent = ProtobufSerializer.GetOrAddComponent(gameObject, cachedType, typeName, id, true);
|
||||
if (orAddComponent)
|
||||
{
|
||||
serializer.Deserialize(stream, orAddComponent, cachedType, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ErrorOnce($"Deserializing component {typeName} into {gameObject} failed");
|
||||
}
|
||||
ProtobufSerializer.SetIsEnabled(orAddComponent, serializedComponent.IsEnabled);
|
||||
}
|
||||
foreach (IProtoEventListener listener in gameObject.GetComponents<IProtoEventListener>())
|
||||
{
|
||||
listener.OnProtoDeserialize(serializer);
|
||||
}
|
||||
dictionary.Clear();
|
||||
ProtobufSerializer.componentCountersPool.Return(dictionary);
|
||||
|
||||
gameObject.SetActive(true);
|
||||
|
||||
result.Set(gameObject);
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,225 @@
|
||||
using System.Collections;
|
||||
using NitroxClient.MonoBehaviours;
|
||||
using NitroxClient.MonoBehaviours.CinematicController;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities.Metadata;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
using NitroxModel.Helper;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
|
||||
public class VehicleWorldEntitySpawner : IWorldEntitySpawner
|
||||
{
|
||||
private readonly Entities entities;
|
||||
|
||||
public VehicleWorldEntitySpawner(Entities entities)
|
||||
{
|
||||
this.entities = entities;
|
||||
}
|
||||
|
||||
// The constructor has mixed results when the remote player is a long distance away. UWE even has a built in distance tracker to ensure
|
||||
// that they are within allowed range. However, this range is a bit restrictive. We will allow constructor spawning up to a specified
|
||||
// distance - anything more will simply use world spawning (no need to play the animation anyways).
|
||||
private const float ALLOWED_CONSTRUCTOR_DISTANCE = 100.0f;
|
||||
|
||||
public IEnumerator SpawnAsync(WorldEntity entity, Optional<GameObject> parent, EntityCell cellRoot, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
VehicleWorldEntity vehicleEntity = (VehicleWorldEntity)entity;
|
||||
|
||||
bool withinConstructorSpawnWindow = (DayNightCycle.main.timePassedAsFloat - vehicleEntity.ConstructionTime) < GetCraftDuration(vehicleEntity.TechType.ToUnity());
|
||||
Optional<GameObject> spawnerObj = NitroxEntity.GetObjectFrom(vehicleEntity.SpawnerId);
|
||||
|
||||
if (withinConstructorSpawnWindow && spawnerObj.HasValue)
|
||||
{
|
||||
Constructor constructor = spawnerObj.Value.GetComponent<Constructor>();
|
||||
float distance = (constructor.transform.position - Player.main.transform.position).magnitude;
|
||||
bool withinDistance = distance <= ALLOWED_CONSTRUCTOR_DISTANCE;
|
||||
|
||||
if (constructor && withinDistance)
|
||||
{
|
||||
MobileVehicleBay.TransmitLocalSpawns = false;
|
||||
yield return SpawnViaConstructor(vehicleEntity, constructor, result);
|
||||
MobileVehicleBay.TransmitLocalSpawns = true;
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
yield return SpawnInWorld(vehicleEntity, result, parent);
|
||||
}
|
||||
|
||||
private IEnumerator SpawnInWorld(VehicleWorldEntity vehicleEntity, TaskResult<Optional<GameObject>> result, Optional<GameObject> parent)
|
||||
{
|
||||
TechType techType = vehicleEntity.TechType.ToUnity();
|
||||
GameObject gameObject = null;
|
||||
|
||||
bool isCyclops = techType == TechType.Cyclops;
|
||||
if (isCyclops)
|
||||
{
|
||||
GameObject prefab = null;
|
||||
LightmappedPrefabs.main.RequestScenePrefab("cyclops", (go) => prefab = go);
|
||||
yield return new WaitUntil(() => prefab != null);
|
||||
SubConsoleCommand.main.OnSubPrefabLoaded(prefab);
|
||||
gameObject = SubConsoleCommand.main.GetLastCreatedSub();
|
||||
}
|
||||
else
|
||||
{
|
||||
CoroutineTask<GameObject> techPrefabCoroutine = CraftData.GetPrefabForTechTypeAsync(techType, false);
|
||||
yield return techPrefabCoroutine;
|
||||
GameObject techPrefab = techPrefabCoroutine.GetResult();
|
||||
gameObject = Utils.SpawnPrefabAt(techPrefab, null, vehicleEntity.Transform.Position.ToUnity());
|
||||
Validate.NotNull(gameObject, $"{nameof(VehicleWorldEntitySpawner)}: No prefab for tech type: {techType}");
|
||||
Vehicle vehicle = gameObject.GetComponent<Vehicle>();
|
||||
|
||||
if (vehicle)
|
||||
{
|
||||
vehicle.LazyInitialize();
|
||||
}
|
||||
}
|
||||
|
||||
AddCinematicControllers(gameObject);
|
||||
|
||||
gameObject.transform.position = vehicleEntity.Transform.Position.ToUnity();
|
||||
gameObject.transform.rotation = vehicleEntity.Transform.Rotation.ToUnity();
|
||||
gameObject.SetActive(true);
|
||||
gameObject.SendMessage("StartConstruction", SendMessageOptions.DontRequireReceiver);
|
||||
|
||||
CrafterLogic.NotifyCraftEnd(gameObject, CraftData.GetTechType(gameObject));
|
||||
Rigidbody rigidBody = gameObject.RequireComponent<Rigidbody>();
|
||||
rigidBody.isKinematic = false;
|
||||
|
||||
yield return Yielders.WaitForEndOfFrame;
|
||||
|
||||
RemoveConstructionAnimations(gameObject);
|
||||
|
||||
yield return Yielders.WaitForEndOfFrame;
|
||||
|
||||
Vehicles.RemoveNitroxEntitiesTagging(gameObject);
|
||||
|
||||
NitroxEntity.SetNewId(gameObject, vehicleEntity.Id);
|
||||
|
||||
if (vehicleEntity.Metadata is CyclopsMetadata cyclopsMetadata && cyclopsMetadata.IsDestroyed)
|
||||
{
|
||||
// Swap to destroyed look without triggering animations / effects
|
||||
gameObject.BroadcastMessage("SwapToDamagedModels");
|
||||
gameObject.BroadcastMessage("OnKill");
|
||||
gameObject.BroadcastMessage("CyclopsDeathEvent", SendMessageOptions.DontRequireReceiver);
|
||||
}
|
||||
|
||||
if (parent.HasValue)
|
||||
{
|
||||
DockVehicle(gameObject, parent.Value);
|
||||
}
|
||||
|
||||
result.Set(gameObject);
|
||||
}
|
||||
|
||||
private IEnumerator SpawnViaConstructor(VehicleWorldEntity vehicleEntity, Constructor constructor, TaskResult<Optional<GameObject>> result)
|
||||
{
|
||||
if (!constructor.deployed)
|
||||
{
|
||||
constructor.Deploy(true);
|
||||
}
|
||||
|
||||
float craftDuration = GetCraftDuration(vehicleEntity.TechType.ToUnity()) - (DayNightCycle.main.timePassedAsFloat - vehicleEntity.ConstructionTime);
|
||||
|
||||
ConstructorInput crafter = constructor.gameObject.RequireComponentInChildren<ConstructorInput>(true);
|
||||
|
||||
yield return crafter.OnCraftingBeginAsync(vehicleEntity.TechType.ToUnity(), craftDuration);
|
||||
|
||||
GameObject constructedObject = MobileVehicleBay.MostRecentlyCrafted;
|
||||
Validate.IsTrue(constructedObject, $"Could not find constructed object from MobileVehicleBay {constructor.gameObject.name}");
|
||||
|
||||
NitroxEntity.SetNewId(constructedObject, vehicleEntity.Id);
|
||||
|
||||
AddCinematicControllers(constructedObject);
|
||||
|
||||
result.Set(constructedObject);
|
||||
yield break;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For scene objects like cyclops, PlayerCinematicController Start() will not be called to add Cinematic reference.
|
||||
/// </summary>
|
||||
private void AddCinematicControllers(GameObject gameObject)
|
||||
{
|
||||
if (gameObject.GetComponent<MultiplayerCinematicReference>())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerCinematicController[] controllers = gameObject.GetComponentsInChildren<PlayerCinematicController>(true);
|
||||
|
||||
if (controllers.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MultiplayerCinematicReference reference = gameObject.AddComponent<MultiplayerCinematicReference>();
|
||||
|
||||
foreach (PlayerCinematicController controller in controllers)
|
||||
{
|
||||
reference.AddController(controller);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When loading in vehicles, they still briefly have their blue crafting animation playing. Force them to stop.
|
||||
/// </summary>
|
||||
private void RemoveConstructionAnimations(GameObject gameObject)
|
||||
{
|
||||
VFXConstructing[] vfxConstructions = gameObject.GetComponentsInChildren<VFXConstructing>();
|
||||
|
||||
foreach (VFXConstructing vfxConstructing in vfxConstructions)
|
||||
{
|
||||
vfxConstructing.EndGracefully();
|
||||
}
|
||||
}
|
||||
|
||||
private void DockVehicle(GameObject gameObject, GameObject parent)
|
||||
{
|
||||
Vehicle vehicle = gameObject.GetComponent<Vehicle>();
|
||||
|
||||
if (!vehicle)
|
||||
{
|
||||
Log.Info($"Could not find vehicle component on docked vehicle {gameObject.name}");
|
||||
return;
|
||||
}
|
||||
|
||||
VehicleDockingBay dockingBay = parent.GetComponentInChildren<VehicleDockingBay>(true);
|
||||
|
||||
if (!dockingBay)
|
||||
{
|
||||
Log.Info($"Could not find VehicleDockingBay component on dock object {parent.name}");
|
||||
return;
|
||||
}
|
||||
|
||||
dockingBay.DockVehicle(vehicle);
|
||||
}
|
||||
|
||||
public bool SpawnsOwnChildren()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private float GetCraftDuration(TechType techType)
|
||||
{
|
||||
// UWE hard codes the build times into if/else logic inside ConstructorInput.Craft().
|
||||
|
||||
switch(techType)
|
||||
{
|
||||
case TechType.Seamoth:
|
||||
return 10f;
|
||||
case TechType.Exosuit:
|
||||
return 10f;
|
||||
case TechType.Cyclops:
|
||||
return 20f;
|
||||
case TechType.RocketBase:
|
||||
return 25f;
|
||||
}
|
||||
|
||||
return 10f;
|
||||
}
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
using System.Collections.Generic;
|
||||
using NitroxClient.GameLogic.Spawning.Metadata;
|
||||
using NitroxModel.DataStructures.GameLogic.Entities;
|
||||
using NitroxModel_Subnautica.DataStructures;
|
||||
|
||||
namespace NitroxClient.GameLogic.Spawning.WorldEntities;
|
||||
|
||||
public class WorldEntitySpawnerResolver
|
||||
{
|
||||
private readonly DefaultWorldEntitySpawner defaultEntitySpawner = new();
|
||||
private readonly VehicleWorldEntitySpawner vehicleWorldEntitySpawner;
|
||||
|
||||
private readonly PrefabPlaceholderEntitySpawner prefabPlaceholderEntitySpawner;
|
||||
private readonly PlaceholderGroupWorldEntitySpawner placeholderGroupWorldEntitySpawner;
|
||||
private readonly PlayerWorldEntitySpawner playerWorldEntitySpawner;
|
||||
private readonly SerializedWorldEntitySpawner serializedWorldEntitySpawner;
|
||||
private readonly GeyserWorldEntitySpawner geyserWorldEntitySpawner;
|
||||
private readonly ReefbackEntitySpawner reefbackEntitySpawner;
|
||||
private readonly ReefbackChildEntitySpawner reefbackChildEntitySpawner;
|
||||
private readonly CreatureRespawnEntitySpawner creatureRespawnEntitySpawner;
|
||||
|
||||
private readonly Dictionary<TechType, IWorldEntitySpawner> customSpawnersByTechType = new();
|
||||
|
||||
public WorldEntitySpawnerResolver(EntityMetadataManager entityMetadataManager, PlayerManager playerManager, LocalPlayer localPlayer, Entities entities, SimulationOwnership simulationOwnership)
|
||||
{
|
||||
customSpawnersByTechType[TechType.Crash] = new CrashEntitySpawner();
|
||||
customSpawnersByTechType[TechType.EscapePod] = new EscapePodWorldEntitySpawner(localPlayer);
|
||||
customSpawnersByTechType[TechType.Creepvine] = new CreepvineEntitySpawner(defaultEntitySpawner);
|
||||
|
||||
vehicleWorldEntitySpawner = new VehicleWorldEntitySpawner(entities);
|
||||
prefabPlaceholderEntitySpawner = new PrefabPlaceholderEntitySpawner(defaultEntitySpawner);
|
||||
placeholderGroupWorldEntitySpawner = new PlaceholderGroupWorldEntitySpawner(entities, this, defaultEntitySpawner, entityMetadataManager, prefabPlaceholderEntitySpawner);
|
||||
playerWorldEntitySpawner = new PlayerWorldEntitySpawner(playerManager, localPlayer);
|
||||
serializedWorldEntitySpawner = new SerializedWorldEntitySpawner();
|
||||
geyserWorldEntitySpawner = new GeyserWorldEntitySpawner(entities);
|
||||
reefbackChildEntitySpawner = new ReefbackChildEntitySpawner();
|
||||
reefbackEntitySpawner = new ReefbackEntitySpawner(reefbackChildEntitySpawner);
|
||||
creatureRespawnEntitySpawner = new CreatureRespawnEntitySpawner(simulationOwnership);
|
||||
}
|
||||
|
||||
public IWorldEntitySpawner ResolveEntitySpawner(WorldEntity entity)
|
||||
{
|
||||
switch (entity)
|
||||
{
|
||||
case PrefabPlaceholderEntity:
|
||||
return prefabPlaceholderEntitySpawner;
|
||||
case PlaceholderGroupWorldEntity:
|
||||
return placeholderGroupWorldEntitySpawner;
|
||||
case PlayerWorldEntity:
|
||||
return playerWorldEntitySpawner;
|
||||
case VehicleWorldEntity:
|
||||
return vehicleWorldEntitySpawner;
|
||||
case SerializedWorldEntity:
|
||||
return serializedWorldEntitySpawner;
|
||||
case GeyserWorldEntity:
|
||||
return geyserWorldEntitySpawner;
|
||||
case ReefbackEntity:
|
||||
return reefbackEntitySpawner;
|
||||
case ReefbackChildEntity:
|
||||
return reefbackChildEntitySpawner;
|
||||
case CreatureRespawnEntity:
|
||||
return creatureRespawnEntitySpawner;
|
||||
}
|
||||
|
||||
TechType techType = entity.TechType.ToUnity();
|
||||
|
||||
if (customSpawnersByTechType.TryGetValue(techType, out IWorldEntitySpawner value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return defaultEntitySpawner;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user