Files
Nitrox/NitroxClient/GameLogic/Spawning/WorldEntities/VehicleWorldEntitySpawner.cs
2025-07-06 00:23:46 +02:00

226 lines
8.3 KiB
C#

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;
}
}