Skip to content

Commit

Permalink
fix: Coordinated Spawns duplication fixed!!! (Purple Edition) (#531)
Browse files Browse the repository at this point in the history
* Coordinated Spawns (Purple Edition)

* Don't force LWE requirement.

* Revert "Don't force LWE requirement."

This reverts commit 7cd473a.

* Combined old with new to correctly allow global and batch spawns.

and hopefully fix the NRE that i still cannot replicate.

* Prefab cache no longer adds objects with IDs set

* Fixed infinite spawning for EntitySpawner

The issue was because the spawned objects was registered while being inactive in the scene.

* Coordinated spawns per-batch instead of per-entity

* Make sure it also works when a spawn is registered in-game

* Fix 1 fail = all following stopped.

* Delayed Spawns don't stop all progress.

* Update EntitySpawner.cs

* Corrected positioning mistake.

* Spawn dammit.

* remove _delayedSpawns

* Should Not exist in main branch!

---------

Co-authored-by: Metious <[email protected]>
  • Loading branch information
MrPurple6411 and Metious committed Jan 22, 2024
1 parent 1bb383d commit 60cff24
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 89 deletions.
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
93 changes: 50 additions & 43 deletions Nautilus/MonoBehaviours/EntitySpawner.cs
Original file line number Diff line number Diff line change
@@ -1,74 +1,81 @@
namespace Nautilus.MonoBehaviours;

using System.Collections;
using Nautilus.Extensions;
using System.Collections.Generic;
using Nautilus.Handlers;
using Nautilus.Patchers;
using Nautilus.Utility;
using UnityEngine;
using UWE;

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

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.

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

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

lw.streamer.cellManager.RegisterEntity(obj);
TaskResult<GameObject> task = new();
yield return GetPrefabAsync(spawnInfo, task);

obj.SetActive(true);
GameObject prefab = task.Get();
if (prefab == null)
{
InternalLogger.Error($"no prefab found for {stringToLog}; process for Coordinated Spawn canceled.");
continue;
}

LargeWorldStreamerPatcher.savedSpawnInfos.Add(spawnInfo);
LargeWorldEntity lwe = prefab.GetComponent<LargeWorldEntity>();

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

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

obj.SetActive(true);

LargeWorldEntity.Register(obj);

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

0 comments on commit 60cff24

Please sign in to comment.