Skip to content

Commit 60cff24

Browse files
fix: Coordinated Spawns duplication fixed!!! (Purple Edition) (#531)
* 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]>
1 parent 1bb383d commit 60cff24

File tree

5 files changed

+235
-89
lines changed

5 files changed

+235
-89
lines changed

Nautilus/Assets/ModPrefabCache.cs

+18-1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ public void EnterPrefabIntoCache(GameObject prefab)
110110
else
111111
{
112112
prefab.transform.parent = _prefabRoot;
113+
ResetIds(prefab);
113114
prefab.SetActive(true);
114115
}
115116
}
@@ -121,12 +122,28 @@ public void EnterPrefabIntoCache(GameObject prefab)
121122

122123
public void RemoveCachedPrefab(string classId)
123124
{
124-
if(Entries.TryGetValue(classId, out var prefab))
125+
if (Entries.TryGetValue(classId, out var prefab))
125126
{
126127
if(!prefab.IsPrefab())
127128
Destroy(prefab);
128129
InternalLogger.Debug($"ModPrefabCache: removed prefab {classId}");
129130
Entries.Remove(classId);
130131
}
131132
}
133+
134+
private void ResetIds(GameObject prefab)
135+
{
136+
var uniqueIds = prefab.GetAllComponentsInChildren<UniqueIdentifier>();
137+
138+
foreach (var uniqueId in uniqueIds)
139+
{
140+
if (string.IsNullOrEmpty(uniqueId.id))
141+
{
142+
continue;
143+
}
144+
145+
UniqueIdentifier.identifiers.Remove(uniqueId.id);
146+
uniqueId.id = null;
147+
}
148+
}
132149
}

Nautilus/Handlers/CoordinatedSpawnsHandler.cs

+11-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Linq;
44
using Nautilus.Assets;
55
using Nautilus.Patchers;
6+
using Nautilus.Utility;
67
using Newtonsoft.Json;
78
using UnityEngine;
89

@@ -19,11 +20,17 @@ public static class CoordinatedSpawnsHandler
1920
/// <param name="spawnInfo">the SpawnInfo to spawn.</param>
2021
public static void RegisterCoordinatedSpawn(SpawnInfo spawnInfo)
2122
{
22-
if (!LargeWorldStreamerPatcher.spawnInfos.Add(spawnInfo))
23+
if (!LargeWorldStreamerPatcher.SpawnInfos.Add(spawnInfo))
24+
{
25+
InternalLogger.Error($"SpawnInfo {spawnInfo} already registered.");
2326
return;
24-
25-
if (uGUI.isMainLevel)
26-
LargeWorldStreamerPatcher.CreateSpawner(spawnInfo);
27+
}
28+
29+
if (LargeWorldStreamer.main)
30+
{
31+
var batch = LargeWorldStreamer.main.GetContainingBatch(spawnInfo.SpawnPosition);
32+
LargeWorldStreamerPatcher.BatchToSpawnInfos.GetOrAddNew(batch).Add(spawnInfo);
33+
}
2734
}
2835

2936
/// <summary>

Nautilus/MonoBehaviours/EntitySpawner.cs

+50-43
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,81 @@
1+
namespace Nautilus.MonoBehaviours;
2+
13
using System.Collections;
2-
using Nautilus.Extensions;
4+
using System.Collections.Generic;
35
using Nautilus.Handlers;
46
using Nautilus.Patchers;
57
using Nautilus.Utility;
68
using UnityEngine;
79
using UWE;
810

9-
namespace Nautilus.MonoBehaviours;
10-
1111
internal class EntitySpawner : MonoBehaviour
1212
{
13-
internal SpawnInfo spawnInfo;
13+
internal Int3 batchId;
14+
internal IReadOnlyCollection<SpawnInfo> spawnInfos;
15+
internal bool global;
1416

15-
void Start()
17+
private IEnumerator Start()
1618
{
17-
StartCoroutine(SpawnAsync());
19+
yield return SpawnAsync();
20+
Destroy(gameObject);
1821
}
1922

20-
IEnumerator SpawnAsync()
23+
private IEnumerator SpawnAsync()
2124
{
22-
string stringToLog = spawnInfo.Type switch
23-
{
24-
SpawnInfo.SpawnType.ClassId => spawnInfo.ClassId,
25-
_ => spawnInfo.TechType.AsString()
26-
};
27-
28-
TaskResult<GameObject> task = new();
29-
yield return GetPrefabAsync(task);
30-
31-
GameObject prefab = task.Get();
32-
if (prefab == null)
33-
{
34-
InternalLogger.Error($"no prefab found for {stringToLog}; process for Coordinated Spawn canceled.");
35-
Destroy(gameObject);
36-
yield break;
37-
}
38-
39-
if (!prefab.IsPrefab())
40-
{
41-
prefab.SetActive(false);
42-
}
43-
44-
GameObject obj = UWE.Utils.InstantiateDeactivated(prefab, spawnInfo.SpawnPosition, spawnInfo.Rotation, spawnInfo.ActualScale);
45-
46-
LargeWorldEntity lwe = obj.GetComponent<LargeWorldEntity>();
47-
4825
LargeWorldStreamer lws = LargeWorldStreamer.main;
4926
yield return new WaitUntil(() => lws != null && lws.IsReady()); // first we make sure the world streamer is initialized
5027

51-
// non-global objects cannot be spawned in unloaded terrain so we need to wait
52-
if (lwe is {cellLevel: not (LargeWorldEntity.CellLevel.Batch or LargeWorldEntity.CellLevel.Global)})
28+
if (!global)
5329
{
54-
Int3 batch = lws.GetContainingBatch(spawnInfo.SpawnPosition);
55-
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)
30+
// then we wait until the terrain is fully loaded (must be checked on each frame for faster spawns)
31+
yield return new WaitUntil(() => lws.IsBatchReadyToCompile(batchId));
5632
}
5733

5834
LargeWorld lw = LargeWorld.main;
59-
35+
6036
yield return new WaitUntil(() => lw != null && lw.streamer.globalRoot != null); // need to make sure global root is ready too for global spawns.
37+
38+
foreach (var spawnInfo in spawnInfos)
39+
{
40+
string stringToLog = spawnInfo.Type switch
41+
{
42+
SpawnInfo.SpawnType.ClassId => spawnInfo.ClassId,
43+
_ => spawnInfo.TechType.AsString()
44+
};
45+
46+
InternalLogger.Debug($"Spawning {stringToLog}");
6147

62-
lw.streamer.cellManager.RegisterEntity(obj);
48+
TaskResult<GameObject> task = new();
49+
yield return GetPrefabAsync(spawnInfo, task);
6350

64-
obj.SetActive(true);
51+
GameObject prefab = task.Get();
52+
if (prefab == null)
53+
{
54+
InternalLogger.Error($"no prefab found for {stringToLog}; process for Coordinated Spawn canceled.");
55+
continue;
56+
}
6557

66-
LargeWorldStreamerPatcher.savedSpawnInfos.Add(spawnInfo);
58+
LargeWorldEntity lwe = prefab.GetComponent<LargeWorldEntity>();
6759

68-
Destroy(gameObject);
60+
if (!lwe)
61+
{
62+
InternalLogger.Error($"No LargeWorldEntity component found for prefab '{stringToLog}'; process for Coordinated Spawn canceled.");
63+
continue;
64+
}
65+
66+
GameObject obj = Instantiate(prefab, spawnInfo.SpawnPosition, spawnInfo.Rotation);
67+
obj.transform.localScale = spawnInfo.ActualScale;
68+
69+
obj.SetActive(true);
70+
71+
LargeWorldEntity.Register(obj);
72+
73+
LargeWorldStreamerPatcher.SavedSpawnInfos.Add(spawnInfo);
74+
InternalLogger.Debug($"spawned {stringToLog}.");
75+
}
6976
}
7077

71-
IEnumerator GetPrefabAsync(IOut<GameObject> gameObject)
78+
private IEnumerator GetPrefabAsync(SpawnInfo spawnInfo, IOut<GameObject> gameObject)
7279
{
7380
GameObject obj;
7481

0 commit comments

Comments
 (0)