Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Coordinated Spawns (Purple Edition) #531

Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion Nautilus/Assets/ModPrefabCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ public void EnterPrefabIntoCache(GameObject prefab)
else
{
prefab.transform.parent = _prefabRoot;
ResetIds(prefab);
prefab.SetActive(true);
}
}
Expand All @@ -121,12 +122,28 @@ public void EnterPrefabIntoCache(GameObject prefab)

public void RemoveCachedPrefab(string classId)
{
if(Entries.TryGetValue(classId, out var prefab))
if (Entries.TryGetValue(classId, out var prefab))
{
if(!prefab.IsPrefab())
Destroy(prefab);
InternalLogger.Debug($"ModPrefabCache: removed prefab {classId}");
Entries.Remove(classId);
}
}

private void ResetIds(GameObject prefab)
{
var uniqueIds = prefab.GetAllComponentsInChildren<UniqueIdentifier>();

foreach (var uniqueId in uniqueIds)
{
if (string.IsNullOrEmpty(uniqueId.id))
{
continue;
}

UniqueIdentifier.identifiers.Remove(uniqueId.id);
uniqueId.id = null;
}
}
}
15 changes: 11 additions & 4 deletions Nautilus/Handlers/CoordinatedSpawnsHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using Nautilus.Assets;
using Nautilus.Patchers;
using Nautilus.Utility;
using Newtonsoft.Json;
using UnityEngine;

Expand All @@ -19,11 +20,17 @@ public static class CoordinatedSpawnsHandler
/// <param name="spawnInfo">the SpawnInfo to spawn.</param>
public static void RegisterCoordinatedSpawn(SpawnInfo spawnInfo)
{
if (!LargeWorldStreamerPatcher.spawnInfos.Add(spawnInfo))
if (!LargeWorldStreamerPatcher.SpawnInfos.Add(spawnInfo))
{
InternalLogger.Error($"SpawnInfo {spawnInfo} already registered.");
return;

if (uGUI.isMainLevel)
LargeWorldStreamerPatcher.CreateSpawner(spawnInfo);
}

if (LargeWorldStreamer.main)
{
var batch = LargeWorldStreamer.main.GetContainingBatch(spawnInfo.SpawnPosition);
LargeWorldStreamerPatcher.BatchToSpawnInfos.GetOrAddNew(batch).Add(spawnInfo);
}
}

/// <summary>
Expand Down
108 changes: 64 additions & 44 deletions Nautilus/MonoBehaviours/EntitySpawner.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Nautilus.Extensions;
using Nautilus.Handlers;
using Nautilus.Patchers;
Expand All @@ -10,65 +12,83 @@ namespace Nautilus.MonoBehaviours;

internal class EntitySpawner : MonoBehaviour
{
internal SpawnInfo spawnInfo;
internal Int3 batchId;
internal IReadOnlyCollection<SpawnInfo> spawnInfos;
internal bool global;

void Start()
private IEnumerator Start()
{
StartCoroutine(SpawnAsync());
yield return SpawnAsync();
Destroy(gameObject);
}

IEnumerator SpawnAsync()
private IEnumerator SpawnAsync()
{
string stringToLog = spawnInfo.Type switch
{
SpawnInfo.SpawnType.ClassId => spawnInfo.ClassId,
_ => spawnInfo.TechType.AsString()
};

TaskResult<GameObject> task = new();
yield return GetPrefabAsync(task);

GameObject prefab = task.Get();
if (prefab == null)
{
InternalLogger.Error($"no prefab found for {stringToLog}; process for Coordinated Spawn canceled.");
Destroy(gameObject);
yield break;
}

if (!prefab.IsPrefab())
{
prefab.SetActive(false);
}

GameObject obj = UWE.Utils.InstantiateDeactivated(prefab, spawnInfo.SpawnPosition, spawnInfo.Rotation, spawnInfo.ActualScale);

LargeWorldEntity lwe = obj.GetComponent<LargeWorldEntity>();

LargeWorldStreamer lws = LargeWorldStreamer.main;
yield return new WaitUntil(() => lws != null && lws.IsReady()); // first we make sure the world streamer is initialized

// non-global objects cannot be spawned in unloaded terrain so we need to wait
if (lwe is {cellLevel: not (LargeWorldEntity.CellLevel.Batch or LargeWorldEntity.CellLevel.Global)})
if (!global)
{
Int3 batch = lws.GetContainingBatch(spawnInfo.SpawnPosition);
yield return new WaitUntil(() => lws.IsBatchReadyToCompile(batch)); // then we wait until the terrain is fully loaded (must be checked on each frame for faster spawns)
// then we wait until the terrain is fully loaded (must be checked on each frame for faster spawns)
yield return new WaitUntil(() => lws.IsBatchReadyToCompile(batchId));
}


var batchCenter = lws.GetBatchCenter(batchId);
var bounds = new Bounds(batchCenter, Vector3.zero);

LargeWorld lw = LargeWorld.main;

yield return new WaitUntil(() => lw != null && lw.streamer.globalRoot != null); // need to make sure global root is ready too for global spawns.

lw.streamer.cellManager.RegisterEntity(obj);

obj.SetActive(true);

LargeWorldStreamerPatcher.savedSpawnInfos.Add(spawnInfo);
foreach (var spawnInfo in spawnInfos)
{
string stringToLog = spawnInfo.Type switch
{
SpawnInfo.SpawnType.ClassId => spawnInfo.ClassId,
_ => spawnInfo.TechType.AsString()
};

InternalLogger.Debug($"Spawning {stringToLog}");

TaskResult<GameObject> task = new();
yield return GetPrefabAsync(spawnInfo, task);

GameObject prefab = task.Get();
if (prefab == null)
{
InternalLogger.Error($"no prefab found for {stringToLog}; process for Coordinated Spawn canceled.");
Destroy(gameObject);
yield break;
}

LargeWorldEntity lwe = prefab.GetComponent<LargeWorldEntity>();

if (!lwe)
{
InternalLogger.Error($"No LargeWorldEntity component found for prefab '{stringToLog}'; process for Coordinated Spawn canceled.");
Destroy(gameObject);
yield break;
}

if (lwe.cellLevel != LargeWorldEntity.CellLevel.Global && lwe.cellLevel != LargeWorldEntity.CellLevel.Batch && !lws.cellManager.AreCellsLoaded(bounds, lwe.cellLevel))
{
// Cells aren't ready yet. We have to wait until they are
yield return new WaitUntil(() => lws.cellManager.AreCellsLoaded(bounds, lwe.cellLevel));
}

GameObject obj = Instantiate(prefab, spawnInfo.SpawnPosition, spawnInfo.Rotation);
obj.transform.localScale = spawnInfo.ActualScale;

obj.SetActive(true);

LargeWorldEntity.Register(obj);

Destroy(gameObject);
LargeWorldStreamerPatcher.SavedSpawnInfos.Add(spawnInfo);
InternalLogger.Debug($"spawned {stringToLog}.");
}
}

IEnumerator GetPrefabAsync(IOut<GameObject> gameObject)
private IEnumerator GetPrefabAsync(SpawnInfo spawnInfo, IOut<GameObject> gameObject)
{
GameObject obj;

Expand Down
Loading