Skip to content

Commit

Permalink
Merge branch 'master' into docs
Browse files Browse the repository at this point in the history
  • Loading branch information
LeeTwentyThree committed Jul 31, 2023
2 parents 47e054a + 77c5bd9 commit eac2fb9
Show file tree
Hide file tree
Showing 12 changed files with 255 additions and 42 deletions.
8 changes: 8 additions & 0 deletions CLA/Signatures.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@
"created_at": "2023-07-01T19:37:09Z",
"repoId": 123711758,
"pullRequestNo": 426
},
{
"name": "Metious",
"id": 71298690,
"comment_id": 1656791439,
"created_at": "2023-07-29T17:09:01Z",
"repoId": 123711758,
"pullRequestNo": 439
}
]
}
12 changes: 10 additions & 2 deletions Nautilus/Assets/CustomPrefab.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ public class CustomPrefab : ICustomPrefab
private readonly List<Action> _onRegister = new();
private readonly List<Action> _onUnregister = new();

// Very simple way of preventing Unity from unloading asset bundle prefabs via Resources.UnloadUnusedAssets.
private GameObject _prefab;

private bool _registered;

/// <inheritdoc/>
Expand Down Expand Up @@ -257,12 +260,17 @@ public void AddOnUnregister(Action onUnregisterCallback)
/// </summary>
/// <param name="prefabTemplate">The prefab template object to set.</param>
public void SetGameObject(PrefabTemplate prefabTemplate) => Prefab = prefabTemplate.GetPrefabAsync;

/// <summary>
/// Sets a game object as the prefab of this custom prefab.
/// </summary>
/// <remarks>Only use this overload on GameObjects that are loaded from asset bundles <b>without</b> instantiating them. For objects that could be destroyed on scene load, use <see cref="SetGameObject(System.Func{UnityEngine.GameObject})"/> instead.</remarks>
/// <param name="prefab">The game object to set.</param>
public void SetGameObject(GameObject prefab) => Prefab = obj => SyncPrefab(obj, prefab);
public void SetGameObject(GameObject prefab)
{
_prefab = prefab;
Prefab = obj => SyncPrefab(obj, prefab);
}

/// <summary>
/// Sets a function as the game object constructor of this custom prefab. This is a synchronous version.
Expand Down
13 changes: 11 additions & 2 deletions Nautilus/Assets/ModPrefabCache.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Nautilus.Utility;
using System.Collections.Generic;
using Nautilus.Extensions;
using UnityEngine;

namespace Nautilus.Assets;
Expand Down Expand Up @@ -88,8 +89,16 @@ private void Awake()

public void EnterPrefabIntoCache(GameObject prefab)
{
prefab.transform.parent = _prefabRoot;
prefab.SetActive(true);
// Proper prefabs can never exist in the scene, so parenting them is dangerous and pointless.
if (prefab.IsPrefab())
{
InternalLogger.Debug($"Game Object: {prefab} is a proper prefab. Skipping parenting for cache.");
}
else
{
prefab.transform.parent = _prefabRoot;
prefab.SetActive(true);
}

var prefabIdentifier = prefab.GetComponent<PrefabIdentifier>();

Expand Down
14 changes: 7 additions & 7 deletions Nautilus/Assets/PrefabTemplates/AssetBundleTemplate.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

namespace Nautilus.Assets.PrefabTemplates;
Expand All @@ -14,19 +10,25 @@ namespace Nautilus.Assets.PrefabTemplates;
/// </summary>
public class AssetBundleTemplate : PrefabTemplate
{
private GameObject _prefab;
private static Dictionary<Assembly, AssetBundle> _loadedBundles = new Dictionary<Assembly, AssetBundle>();

/// <summary>
/// Instantiates a new AssetBundleTemplate
/// </summary>
/// <param name="bundle">The AssetBundle to load the asset from</param>
/// <param name="prefabName">The name of the prefab gameobject to load from the bundle</param>
/// <param name="prefabName">The name of the prefab game object to load from the bundle</param>
/// <param name="info">The prefab info to base this template off of.</param>
public AssetBundleTemplate(AssetBundle bundle, string prefabName, PrefabInfo info) : base(info)
{
_prefab = bundle.LoadAsset<GameObject>(prefabName);
}

/// <summary>
/// A reference to the loaded prefab for modification purposes. This points directly to the prefab contained in the Asset Bundle.
/// </summary>
public GameObject Prefab => _prefab;

/// <summary>
/// Instantiates a new AssetBundleTemplate. Automatically loads the bundle by calling <see cref = "Utility.AssetBundleLoadingUtils.LoadFromAssetsFolder(Assembly, string)"/>,
/// which expects the bundle to be in your mod's Assets folder.
Expand Down Expand Up @@ -54,8 +56,6 @@ public AssetBundleTemplate(string assetBundleFileName, string prefabName, Prefab
_prefab = bundle.LoadAsset<GameObject>(prefabName);
}

private GameObject _prefab;

/// <inheritdoc/>
public override IEnumerator GetPrefabAsync(TaskResult<GameObject> gameObject)
{
Expand Down
10 changes: 10 additions & 0 deletions Nautilus/Extensions/GameObjectExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,14 @@ public static Transform SearchChild(this Transform transform, string name)
/// <param name="name">The name of the object that is being searched for.</param>
/// <returns></returns>
public static GameObject SearchChild(this GameObject gameObject, string name) => SearchChild(gameObject.transform, name).gameObject;

/// <summary>
/// Checks if this game object is a proper prefab. Proper prefabs are those that are made via the Unity Editor and are .prefab formatted.
/// </summary>
/// <param name="gameObject">The game object to check.</param>
/// <returns>True if this game object is a proper prefab, otherwise false.</returns>
public static bool IsPrefab(this GameObject gameObject)
{
return gameObject.transform.parent == null && !gameObject.activeInHierarchy && gameObject.activeSelf;
}
}
1 change: 1 addition & 0 deletions Nautilus/Initializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,6 @@ public class Initializer : BaseUnityPlugin
StoryGoalPatcher.Patch(_harmony);
PDAEncyclopediaTabPatcher.Patch(_harmony);
NewtonsoftJsonPatcher.Patch(_harmony);
InventoryPatcher.Patch(_harmony);
}
}
41 changes: 41 additions & 0 deletions Nautilus/Patchers/InventoryPatcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.Reflection.Emit;
using HarmonyLib;

namespace Nautilus.Patchers;

/* This class fixes a bug related to picked up items not having "verbose" analysis tech
* In other words... there are no notifications for unlocked tech when you pick up creatures, resources, etc...
* We're technically fixing a vanilla bug, which does go beyond the normal scope of Nautilus. However, this bug does affect Nautilus!
*/
internal static class InventoryPatcher
{
private const string CHANGESET_WHERE_BUG_EXISTS = "71288";

internal static void Patch(Harmony harmony)
{
// The bug does NOT exist in Below Zero!
#if SN_STABLE
// Only enable the fix for the latest version of Subnautica:
if (SNUtils.GetPlasticChangeSetOfBuild() != CHANGESET_WHERE_BUG_EXISTS)
return;

var transpiler = new HarmonyMethod(AccessTools.Method(typeof(InventoryPatcher), nameof(VerbosePickupFixTranspiler)));

harmony.Patch(AccessTools.Method(typeof(Inventory), nameof(Inventory.Pickup)),
transpiler: transpiler);
harmony.Patch(AccessTools.Method(typeof(Inventory), nameof(Inventory.OnAddItem)),
transpiler: transpiler);
#endif
}

// Modifies both the Inventory.Pickup AND Inventory.OnAddItem method to always analyze picked up technology verbosely
private static IEnumerable<CodeInstruction> VerbosePickupFixTranspiler(IEnumerable<CodeInstruction> instructions)
{
return new CodeMatcher(instructions)
.MatchForward(false, new CodeMatch(OpCodes.Call, AccessTools.Method(typeof(KnownTech), nameof(KnownTech.Analyze))))
.Advance(-1)
.Set(OpCodes.Ldc_I4_1, null)
.Instructions();
}
}
52 changes: 47 additions & 5 deletions Nautilus/Patchers/SMLHelperCompatibilityPatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,45 @@ namespace Nautilus.Patchers;
// This class can be SAFELY removed if we ever decide to make Nautilus incompatible with SMLHelper (which it already kinda is...)
internal class SMLHelperCompatibilityPatcher
{
private const string SMLHarmonyInstance = "com.ahk1221.smlhelper"; // This string is both the harmony instance & plugin GUID.
public const string SMLHarmonyInstance = "com.ahk1221.smlhelper"; // This string is both the harmony instance & plugin GUID.
public const string QModManagerGUID = "QModManager.QMMLoader";
private const string SMLAssemblyName = "SMLHelper";
private const string SMLHelperModJsonID = "SMLHelper";

private static Assembly _smlHelperAssembly;

internal static void Patch(Harmony harmony)
private static bool? _smlHelperInstalled;

public static bool SMLHelperInstalled
{
if (BepInEx.Bootstrap.Chainloader.PluginInfos.ContainsKey(SMLHarmonyInstance))
get
{
CoroutineHost.StartCoroutine(WaitOnSMLHelperForPatches(harmony));
if (!_smlHelperInstalled.HasValue)
{
_smlHelperInstalled = GetSMLHelperExists();
}
return _smlHelperInstalled.Value;
}
}

private static bool GetSMLHelperExists()
{
#if SUBNAUTICA
return BepInEx.Bootstrap.Chainloader.PluginInfos.ContainsKey(SMLHarmonyInstance);
#elif BELOWZERO
if (!BepInEx.Bootstrap.Chainloader.PluginInfos.TryGetValue(QModManagerGUID, out var qmodManager))
return false;
var qmodServices = Assembly.GetAssembly(qmodManager.Instance.GetType()).GetType("QModManager.API.QModServices");
var qmodServicesInstance = AccessTools.PropertyGetter(qmodServices, "Main").Invoke(null, new object[0]);
return (bool)AccessTools.Method(qmodServices, "ModPresent").Invoke(qmodServicesInstance, new object[] { SMLHelperModJsonID });
#endif
}

internal static void Patch(Harmony harmony)
{
CoroutineHost.StartCoroutine(WaitOnSMLHelperForPatches(harmony));
}

private static IEnumerator WaitOnSMLHelperForPatches(Harmony harmony)
{
// This code is arbitrary but was taken from an older version of SMLHelper so that this patch applies only AFTER has been patched.
Expand All @@ -44,10 +70,18 @@ private static IEnumerator WaitOnSMLHelperForPatches(Harmony harmony)

yield return null;

if (!SMLHelperInstalled)
{
yield break;
}

InternalLogger.Log("Patching SMLHelper compatibility fixes", BepInEx.Logging.LogLevel.Info);

// Finally apply the patches:

UnpatchSMLOptionsMethods(harmony);
FixSMLOptionsException(harmony);
UnpatchSMLTooltipPatches(harmony);
}

private static void UnpatchSMLOptionsMethods(Harmony harmony)
Expand Down Expand Up @@ -114,6 +148,14 @@ private static bool ChangeAdjusterComponentPrefix(object __instance, ref Type __
return false;
}

// We don't want duplicate tooltips!!
private static void UnpatchSMLTooltipPatches(Harmony harmony)
{
harmony.Unpatch(AccessTools.Method(typeof(TooltipFactory), nameof(TooltipFactory.BuildTech)), HarmonyPatchType.Postfix, SMLHarmonyInstance);
harmony.Unpatch(AccessTools.Method(typeof(TooltipFactory), nameof(TooltipFactory.ItemCommons)), HarmonyPatchType.Postfix, SMLHarmonyInstance);
harmony.Unpatch(AccessTools.Method(typeof(TooltipFactory), nameof(TooltipFactory.CraftRecipe)), HarmonyPatchType.Postfix, SMLHarmonyInstance);
}

private static Assembly GetSMLAssembly()
{
if (_smlHelperAssembly != null)
Expand All @@ -130,7 +172,7 @@ private static Assembly GetSMLAssembly()
return _smlHelperAssembly;
}

private static Type GetSMLType(string typeName)
internal static Type GetSMLType(string typeName)
{
var assembly = GetSMLAssembly();
return assembly.GetType(typeName);
Expand Down
Loading

0 comments on commit eac2fb9

Please sign in to comment.