diff --git a/JotunnLib/Entities/CustomLocation.cs b/JotunnLib/Entities/CustomLocation.cs
index 6cbf58088..7f21a9fce 100644
--- a/JotunnLib/Entities/CustomLocation.cs
+++ b/JotunnLib/Entities/CustomLocation.cs
@@ -4,6 +4,7 @@
using Jotunn.Managers;
using SoftReferenceableAssets;
using UnityEngine;
+using Object = UnityEngine.Object;
namespace Jotunn.Entities
{
@@ -38,6 +39,12 @@ public class CustomLocation : CustomEntity
/// Indicator if references from s will be replaced at runtime.
///
public bool FixReference { get; set; }
+
+ ///
+ /// Indicator if location is added from SoftReferenceableAssets.
+ /// Used to delay mocking prefabs until ZoneSystem.SpawnLocation()
+ ///
+ public bool SoftReference { get; set; }
///
/// Custom location from a prefab with a attached.
@@ -103,6 +110,38 @@ public CustomLocation(GameObject exteriorPrefab, GameObject interiorPrefab, bool
FixReference = fixReference;
}
+ ///
+ /// Custom location from a prefab with a attached. Using SoftReference system.
+ ///
+ /// The exterior prefab for this custom location.
+ /// If true references for objects get resolved at runtime by Jötunn.
+ /// The for this custom location.
+ public CustomLocation(SoftReference softReferencePrefab, bool fixReference, LocationConfig locationConfig) : base(Assembly.GetCallingAssembly())
+ {
+ if (!softReferencePrefab.IsValid)
+ {
+ Logger.LogError($"SoftReference invalid for prefab: {softReferencePrefab.Name}");
+ return;
+ }
+
+ var parent = ZoneManager.Instance.LocationContainer.transform;
+ AssetManager.Instance.ResolveMocksOnLoad(softReferencePrefab, parent, OnLocationResolve);
+ Name = softReferencePrefab.Name;
+ ZoneLocation = locationConfig.GetZoneLocation();
+ ZoneLocation.m_prefab = softReferencePrefab;
+ ZoneLocation.m_prefabName = softReferencePrefab.Name;
+ FixReference = fixReference;
+ SoftReference = true;
+ }
+
+ private void OnLocationResolve(GameObject gameObject)
+ {
+ if (gameObject.TryGetComponent(out var zoneLocation))
+ {
+ ZoneManager.Instance.PrepareLocation(zoneLocation, SourceMod);
+ }
+ }
+
///
/// Helper method to determine if a location prefab with a given name is a custom location created with Jötunn.
///
diff --git a/JotunnLib/Managers/AssetManager.cs b/JotunnLib/Managers/AssetManager.cs
index 0685c8726..2229ae23c 100644
--- a/JotunnLib/Managers/AssetManager.cs
+++ b/JotunnLib/Managers/AssetManager.cs
@@ -32,6 +32,9 @@ public class AssetManager : IManager
private Dictionary> mapNameToAssetID;
internal Dictionary> MapNameToAssetID => mapNameToAssetID ??= CreateNameToAssetID();
+ private GameObject ResolvedAssetsContainer;
+ private Dictionary assetsToResolve = new Dictionary();
+
///
/// Hide .ctor
///
@@ -45,6 +48,11 @@ static AssetManager()
void IManager.Init()
{
Main.LogInit(nameof(AssetManager));
+
+ ResolvedAssetsContainer = new GameObject("Resolved Assets");
+ ResolvedAssetsContainer.transform.parent = Main.RootObject.transform;
+ ResolvedAssetsContainer.SetActive(false);
+
Main.Harmony.PatchAll(typeof(Patches));
}
@@ -84,6 +92,31 @@ private static IEnumerable AssetBundleLoader_GetAllAssetPathsMa
.SetInstruction(new CodeInstruction(OpCodes.Call, addSafeMethod))
.InstructionEnumeration();
}
+
+ [HarmonyPatch(typeof(AssetLoader), nameof(AssetLoader.InvokeCallbacks)), HarmonyPrefix]
+ private static void SwapResolvedAsset(ref AssetLoader __instance, LoadResult result)
+ {
+ if (result == LoadResult.Succeeded)
+ {
+ if (Instance.assetsToResolve.TryGetValue(__instance.m_assetID, out var mockedAsset))
+ {
+ mockedAsset.InstantiateAndResolveAsset(__instance.m_asset);
+ __instance.m_asset = mockedAsset.Asset;
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(AssetLoader), nameof(AssetLoader.Release)), HarmonyPostfix]
+ private static void AssetLoader_Release(ref AssetLoader __instance)
+ {
+ if (__instance.ReferenceCount == 0)
+ {
+ if (Instance.assetsToResolve.TryGetValue(__instance.m_assetID, out var mockedAsset))
+ {
+ mockedAsset.DestroyAsset();
+ }
+ }
+ }
}
///
@@ -135,6 +168,59 @@ public AssetID AddAsset(Object asset)
return AddAsset(asset, null);
}
+ ///
+ /// Registers an asset to be instantiated under the given parent and have its mock references resolved on load.
+ /// Must be called before the asset is loaded the first time.
+ ///
+ /// The of the asset to instantiate and resolve mocks for on load
+ public void ResolveMocksOnLoad(AssetID assetID)
+ {
+ ResolveMocksOnLoad(assetID, null, null);
+ }
+
+ ///
+ /// Registers an asset to be instantiated under the given parent and have its mock references resolved on load.
+ /// Must be called before the asset is loaded the first time.
+ ///
+ /// The of the asset to instantiate and resolve mocks for on load
+ /// Optional transform under which the asset will be instantiated, otherwise a default container is used
+ public void ResolveMocksOnLoad(AssetID assetID, Transform parent)
+ {
+ ResolveMocksOnLoad(assetID, parent, null);
+ }
+
+ ///
+ /// Registers an asset to be instantiated under the given parent and have its mock references resolved on load.
+ /// Must be called before the asset is loaded the first time.
+ ///
+ /// The to instantiate and resolve mocks for on load
+ /// Optional transform under which the asset will be instantiated, otherwise a default container is used
+ /// Adds a callback when the asset was resolved and instantiated
+ public void ResolveMocksOnLoad(SoftReference softReference, Transform parent, Action resolveCallback) where T : Object
+ {
+ ResolveMocksOnLoad(softReference.m_assetID, parent, (asset) => resolveCallback?.Invoke(asset as T));
+ }
+
+ ///
+ /// Registers an asset to be instantiated under the given parent and have its mock references resolved on load.
+ /// Must be called before the asset is loaded the first time.
+ ///
+ /// The of the asset to instantiate and resolve mocks for on load
+ /// Optional transform under which the asset will be instantiated, otherwise a default container is used
+ /// Adds a callback when the asset was resolved and instantiated
+ public void ResolveMocksOnLoad(AssetID assetID, Transform parent, Action
/// to add to the
- public void RegisterLocationInZoneSystem(ZoneLocation zoneLocation) =>
- RegisterLocationInZoneSystem(ZoneSystem.instance, zoneLocation, BepInExUtils.GetSourceModMetadata());
+ public void RegisterLocationInZoneSystem(ZoneLocation zoneLocation)
+ {
+ PrepareLocation(zoneLocation, BepInExUtils.GetSourceModMetadata());
+ RegisterLocationInZoneSystem(ZoneSystem.instance, zoneLocation);
+ }
- ///
- /// Internal method for adding a ZoneLocation to a specific ZoneSystem.
- ///
- /// the location should be added to
- /// to add
- /// which created the location
- private void RegisterLocationInZoneSystem(ZoneSystem zoneSystem, ZoneLocation zoneLocation, BepInPlugin sourceMod)
+ internal void PrepareLocation(ZoneLocation zoneLocation, BepInPlugin sourceMod)
{
zoneLocation.m_prefab.Load();
foreach (var znet in global::Utils.GetEnabledComponentsInChildren(zoneLocation.m_prefab.Asset))
{
- string prefabName = znet.GetPrefabName();
- if (!ZNetScene.instance.m_namedPrefabs.ContainsKey(prefabName.GetStableHashCode()))
- {
- var prefab = Object.Instantiate(znet.gameObject, PrefabManager.Instance.PrefabContainer.transform);
- prefab.name = prefabName;
- CustomPrefab customPrefab = new CustomPrefab(prefab, sourceMod);
- PrefabManager.Instance.AddPrefab(customPrefab);
- PrefabManager.Instance.RegisterToZNetScene(customPrefab.Prefab);
- }
+ RegisterUnknownPrefab(sourceMod, znet);
}
RandomSpawn[] randomSpawns = global::Utils.GetEnabledComponentsInChildren(zoneLocation.m_prefab.Asset);
@@ -615,17 +608,30 @@ private void RegisterLocationInZoneSystem(ZoneSystem zoneSystem, ZoneLocation zo
foreach (var znet in randomSpawns.SelectMany(x => x.m_childNetViews))
{
- string prefabName = znet.GetPrefabName();
- if (!ZNetScene.instance.m_namedPrefabs.ContainsKey(prefabName.GetStableHashCode()))
- {
- var prefab = Object.Instantiate(znet.gameObject, PrefabManager.Instance.PrefabContainer.transform);
- prefab.name = prefabName;
- CustomPrefab customPrefab = new CustomPrefab(prefab, sourceMod);
- PrefabManager.Instance.AddPrefab(customPrefab);
- PrefabManager.Instance.RegisterToZNetScene(customPrefab.Prefab);
- }
+ RegisterUnknownPrefab(sourceMod, znet);
}
+ }
+ private static void RegisterUnknownPrefab(BepInPlugin sourceMod, ZNetView znet) {
+ string prefabName = znet.GetPrefabName();
+
+ if (prefabName.StartsWith(MockManager.JVLMockPrefix))
+ {
+ return;
+ }
+
+ if (!ZNetScene.instance.m_namedPrefabs.ContainsKey(prefabName.GetStableHashCode()))
+ {
+ var prefab = Object.Instantiate(znet.gameObject, PrefabManager.Instance.PrefabContainer.transform);
+ prefab.name = prefabName;
+ CustomPrefab customPrefab = new CustomPrefab(prefab, sourceMod);
+ PrefabManager.Instance.AddPrefab(customPrefab);
+ PrefabManager.Instance.RegisterToZNetScene(customPrefab.Prefab);
+ }
+ }
+
+ private void RegisterLocationInZoneSystem(ZoneSystem zoneSystem, ZoneLocation zoneLocation)
+ {
if (!zoneSystem.m_locationsByHash.ContainsKey(zoneLocation.Hash))
{
zoneSystem.m_locationsByHash.Add(zoneLocation.Hash, zoneLocation);