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 1 commit
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
Prev Previous commit
Next Next commit
Combined old with new to correctly allow global and batch spawns.
and hopefully fix the NRE that i still cannot replicate.
  • Loading branch information
MrPurple6411 committed Jan 15, 2024
commit 41368459f64d212dfd373e252121f740b1744dbf
38 changes: 27 additions & 11 deletions Nautilus/MonoBehaviours/EntitySpawner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ IEnumerator SpawnAsync()
};

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

GameObject prefab = task.Get();
if (prefab == null)
Expand All @@ -36,25 +36,41 @@ IEnumerator SpawnAsync()
yield break;
}

LargeWorldEntity lwe = prefab.GetComponent<LargeWorldEntity>();
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

if (lwe == null)
// 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)})
{
InternalLogger.Error($"no LargeWorldEntity found for {stringToLog}; process for Coordinated Spawn canceled.");
Destroy(gameObject);
yield break;
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)
}

GameObject obj = UWE.Utils.InstantiateDeactivated(prefab, spawnInfo.SpawnPosition, spawnInfo.Rotation, spawnInfo.ActualScale);
lwe = obj.GetComponent<LargeWorldEntity>();
LargeWorld lw = LargeWorld.main;

yield return new WaitUntil(()=> LargeWorld.main?.streamer?.cellManager?.RegisterEntity(lwe)?? false); // then we register the entity to the cell manager
yield return new WaitUntil(() => lw != null && lw.streamer.globalRoot != null); // need to make sure global root is ready too for global spawns.

obj.SetActive(true);
yield return new WaitUntil(() => obj == null || (LargeWorld.main?.streamer?.cellManager?.RegisterEntity(lwe) ?? false)); // then we register the entity to the cell manager

if (obj == null)
{
LargeWorldStreamerPatcher.SpawnInfos.Add(spawnInfo);
InternalLogger.Warn($"failed to spawn {stringToLog} will try again later.");
}
else
{
obj.SetActive(true);
LargeWorldStreamerPatcher.SavedSpawnInfos.Add(spawnInfo);
InternalLogger.Debug($"spawned {stringToLog}.");
}
Destroy(gameObject);
}

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

Expand Down
113 changes: 86 additions & 27 deletions Nautilus/Patchers/LargeWorldStreamerPatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ namespace Nautilus.Patchers;
using System.Linq;
using System.Reflection;
using HarmonyLib;
using Nautilus.Assets;
using Nautilus.Handlers;
using Nautilus.Json.Converters;
using Nautilus.MonoBehaviours;
using Nautilus.Utility;
using Newtonsoft.Json;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using UWE;

internal static class LargeWorldStreamerPatcher
Expand Down Expand Up @@ -66,13 +69,89 @@ private static void InitializePostfix()
InternalLogger.Debug("Coordinated Spawns have been initialized in the current save.");

// Preload all the prefabs for faster spawning.
SpawnInfos.Do((info) =>
new List<SpawnInfo>(SpawnInfos).Do((info) =>
{
// Ensures the prefab is loaded in the cache for faster spawning.
CoroutineHost.StartCoroutine(EntitySpawner.GetPrefabAsync(new TaskResult<GameObject>(), info));
string keyToCheck = info.Type switch
{
SpawnInfo.SpawnType.TechType => CraftData.GetClassIdForTechType(info.TechType),
_ => info.ClassId
};
if (!PrefabDatabase.TryGetPrefabFilename(keyToCheck, out string prefabName))
{
InternalLogger.Error($"Failed to get prefab name for {keyToCheck}; process for Coordinated Spawn canceled.");
SpawnInfos.Remove(info);
return;
}

if (PrefabHandler.Prefabs.TryGetInfoForFileName(prefabName, out var prefabInfo))
{
InternalLogger.Debug($"Preloading {keyToCheck}");
CoroutineHost.StartCoroutine(PreloadModdedPrefab(info ,prefabInfo));
}
else
{
var task = new AssetReferenceGameObject(prefabName).LoadAssetAsync();
task.Completed += (t) =>
{
if (t.Status != AsyncOperationStatus.Succeeded)
{
InternalLogger.Error($"Failed to preload {keyToCheck} with error: {t.OperationException}");
return;
}

var prefab = t.Result;
if (prefab == null)
{
InternalLogger.Error($"no prefab found for {keyToCheck}; process for Coordinated Spawn canceled.");
SpawnInfos.Remove(info);
return;
}

LargeWorldEntity lwe = prefab.GetComponent<LargeWorldEntity>();
if (lwe is null)
{
InternalLogger.Error($"No LargeWorldEntity found on {keyToCheck}; Please ensure the prefab has a LargeWorldEntity component when using Coordinated Spawns.");
lwe = prefab.AddComponent<LargeWorldEntity>();
}

if (lwe is { cellLevel: LargeWorldEntity.CellLevel.Global })
{
CreateSpawner(info);
InternalLogger.Debug($"Created spawner for {keyToCheck} at the Global level.");
SpawnInfos.Remove(info);
}

InternalLogger.Debug($"Preloaded {keyToCheck}");
};
}
});
}

private static IEnumerator<object> PreloadModdedPrefab(SpawnInfo info, PrefabInfo prefabInfo)
{
var request = new ModPrefabRequest(prefabInfo);
yield return request;

if (!request.TryGetPrefab(out var prefab))
{
InternalLogger.Error($"no prefab found for {prefabInfo.ClassID}; process for Coordinated Spawn canceled.");
SpawnInfos.Remove(info);
yield break;
}
LargeWorldEntity lwe = prefab.GetComponent<LargeWorldEntity>();
if (lwe is null)
{
InternalLogger.Error($"No LargeWorldEntity found on {prefabInfo.ClassID}; Please ensure the prefab has a LargeWorldEntity component when using Coordinated Spawns.");
lwe = prefab.AddComponent<LargeWorldEntity>();
}
if (lwe is { cellLevel: LargeWorldEntity.CellLevel.Global })
{
CreateSpawner(info);
InternalLogger.Debug($"Created spawner for {info.ClassId} at the Global level.");
SpawnInfos.Remove(info);
}
}

private static void SaveData()
{
string file = Path.Combine(SaveLoadManager.GetTemporarySavePath(), "CoordinatedSpawnsInitialized.nautilus");
Expand Down Expand Up @@ -117,21 +196,15 @@ private static void OnBatchFullyLoadedPostfix(LargeWorldStreamer __instance, Int
{
if (__instance.GetContainingBatch(spawnInfo.SpawnPosition) == batchId)
{
if (CreateSpawner(spawnInfo, __instance))
{
spawned.Add(spawnInfo);
}
CreateSpawner(spawnInfo);
spawned.Add(spawnInfo);
}
}

spawned.Do((info) =>
{
SavedSpawnInfos.Add(info);
SpawnInfos.Remove(info);
});
SpawnInfos.RemoveWhere(spawned.Contains);
}

private static bool CreateSpawner(SpawnInfo spawnInfo, LargeWorldStreamer streamer)
private static void CreateSpawner(SpawnInfo spawnInfo)
{
string keyToCheck = spawnInfo.Type switch
{
Expand All @@ -141,21 +214,7 @@ private static bool CreateSpawner(SpawnInfo spawnInfo, LargeWorldStreamer stream

InternalLogger.Debug($"Creating Spawner for {keyToCheck}");
GameObject obj = new($"{keyToCheck}Spawner");

obj.SetActive(false);

obj.transform.SetPositionAndRotation(spawnInfo.SpawnPosition, spawnInfo.Rotation);
LargeWorldEntity lwe = obj.EnsureComponent<LargeWorldEntity>();
lwe.cellLevel = LargeWorldEntity.CellLevel.Batch;
obj.EnsureComponent<EntitySpawner>().spawnInfo = spawnInfo;

if (!streamer.cellManager.RegisterEntity(lwe))
{
GameObject.Destroy(obj);
return false;
}

obj.SetActive(true);
return true;
}
}