Decompiled source of CrossbowMemory v1.0.0

CrossbowMemory.dll

Decompiled 2 days ago
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("CrossbowMemory")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("CrossbowMemory")]
[assembly: AssemblyTitle("CrossbowMemory")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace CrossbowMemory
{
	internal static class LoadedMemory
	{
		internal const string KeyLoaded = "CrossbowMemory.loaded";

		internal const string KeyAmmo = "CrossbowMemory.ammo";

		private static FieldRef<Player, ItemData> _weaponLoaded;

		private static Action<Player, ItemData> _setWeaponLoaded;

		private static ManualLogSource _log;

		internal static int UnequipDepth;

		private static ItemData _pendingWeapon;

		private static string _pendingAmmo;

		private static float _pendingDeadline;

		internal static bool Init(ManualLogSource log)
		{
			_log = log;
			try
			{
				_weaponLoaded = AccessTools.FieldRefAccess<Player, ItemData>("m_weaponLoaded");
				MethodInfo methodInfo = AccessTools.Method(typeof(Player), "SetWeaponLoaded", new Type[1] { typeof(ItemData) }, (Type[])null);
				if (methodInfo == null)
				{
					return false;
				}
				_setWeaponLoaded = AccessTools.MethodDelegate<Action<Player, ItemData>>(methodInfo, (object)null, true);
				return _weaponLoaded != null && _setWeaponLoaded != null;
			}
			catch (Exception ex)
			{
				log.LogError((object)ex);
				return false;
			}
		}

		internal static bool IsLocal(Humanoid h)
		{
			Player val = (Player)(object)((h is Player) ? h : null);
			if (val != null)
			{
				return val == Player.m_localPlayer;
			}
			return false;
		}

		internal static bool NeedsReload(ItemData item)
		{
			if (item?.m_shared?.m_attack != null && item.m_shared.m_attack.m_requiresReload)
			{
				return item.IsWeapon();
			}
			return false;
		}

		internal static bool HasMemory(ItemData item)
		{
			if (item.m_customData.TryGetValue("CrossbowMemory.loaded", out var value))
			{
				return value == "1";
			}
			return false;
		}

		internal static bool IsLoaded(Player player, ItemData weapon)
		{
			return _weaponLoaded.Invoke(player) == weapon;
		}

		internal static void MarkLoaded(Player player, ItemData weapon)
		{
			weapon.m_customData["CrossbowMemory.loaded"] = "1";
			ItemData ammoItem = ((Humanoid)player).GetAmmoItem();
			if (ammoItem != null && ammoItem.m_shared.m_ammoType == weapon.m_shared.m_ammoType && PrefabName(ammoItem) != null)
			{
				weapon.m_customData["CrossbowMemory.ammo"] = PrefabName(ammoItem);
			}
		}

		internal static bool Forget(ItemData weapon)
		{
			bool num = weapon.m_customData.Remove("CrossbowMemory.loaded");
			bool flag = weapon.m_customData.Remove("CrossbowMemory.ammo");
			return num || flag;
		}

		internal static void Remember(Player player, ItemData weapon, string why)
		{
			if (!IsLoaded(player, weapon))
			{
				if (Forget(weapon))
				{
					Info(PrefabName(weapon) + " is not loaded (" + why + "); forgot it");
				}
				return;
			}
			MarkLoaded(player, weapon);
			weapon.m_customData.TryGetValue("CrossbowMemory.ammo", out var value);
			Info(PrefabName(weapon) + " is loaded (" + (value ?? "no bolt") + "); remembered (" + why + ")");
		}

		internal static void Fired(ItemData weapon)
		{
			if (Forget(weapon))
			{
				Info(PrefabName(weapon) + " fired; memory cleared");
			}
		}

		internal static void Restore(Player player, ItemData weapon, string why)
		{
			if (HasMemory(weapon))
			{
				weapon.m_customData.TryGetValue("CrossbowMemory.ammo", out var value);
				try
				{
					_setWeaponLoaded(player, weapon);
				}
				catch (Exception arg)
				{
					_log.LogWarning((object)$"could not mark {PrefabName(weapon)} as loaded ({why}): {arg}");
					return;
				}
				if (Plugin.RestoreBoltType.Value && !string.IsNullOrEmpty(value))
				{
					_pendingWeapon = weapon;
					_pendingAmmo = value;
					_pendingDeadline = Time.unscaledTime + 2f;
				}
				Info(PrefabName(weapon) + " equipped (" + why + "): still loaded (" + (value ?? "no bolt remembered") + ")");
			}
		}

		internal static void AfterLoad(Player player)
		{
			int num = 0;
			int num2 = 0;
			foreach (ItemData allItem in ((Humanoid)player).GetInventory().GetAllItems())
			{
				if (NeedsReload(allItem))
				{
					num++;
					bool flag = HasMemory(allItem);
					if (flag)
					{
						num2++;
					}
					Info("login: " + PrefabName(allItem) + " memory=" + (flag ? "yes" : "no") + " equipped=" + (((Humanoid)player).IsItemEquiped(allItem) ? "yes" : "no") + " loaded=" + (IsLoaded(player, allItem) ? "yes" : "no"));
				}
			}
			Info($"login: {num} crossbow(s) in the inventory, {num2} remembered as loaded");
			ItemData currentWeapon = ((Humanoid)player).GetCurrentWeapon();
			if (NeedsReload(currentWeapon) && HasMemory(currentWeapon) && !IsLoaded(player, currentWeapon))
			{
				Restore(player, currentWeapon, "login");
			}
		}

		internal static void Tick()
		{
			if (_pendingWeapon == null)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || Time.unscaledTime > _pendingDeadline || !((Humanoid)localPlayer).IsItemEquiped(_pendingWeapon))
			{
				ClearPending();
				return;
			}
			ItemData ammoItem = ((Humanoid)localPlayer).GetInventory().GetAmmoItem(_pendingWeapon.m_shared.m_ammoType, _pendingAmmo);
			if (ammoItem == null)
			{
				Info("no " + _pendingAmmo + " in the inventory; keeping the current bolt");
				ClearPending();
			}
			else if (((Humanoid)localPlayer).GetAmmoItem() == ammoItem)
			{
				ClearPending();
			}
			else if (((Humanoid)localPlayer).EquipItem(ammoItem, true))
			{
				Info("re-equipped " + _pendingAmmo);
				ClearPending();
			}
		}

		private static void ClearPending()
		{
			_pendingWeapon = null;
			_pendingAmmo = null;
		}

		private static string PrefabName(ItemData item)
		{
			if (!((Object)(object)item.m_dropPrefab != (Object)null))
			{
				return item.m_shared?.m_name;
			}
			return ((Object)item.m_dropPrefab).name;
		}

		private static void Info(string message)
		{
			if (Plugin.LogEvents.Value)
			{
				_log.LogInfo((object)message);
			}
		}
	}
	[HarmonyPatch]
	internal static class Patches
	{
		private static bool Active => Plugin.Enabled.Value;

		[HarmonyPrefix]
		[HarmonyPatch(typeof(Humanoid), "UnequipItem", new Type[]
		{
			typeof(ItemData),
			typeof(bool)
		})]
		private static void UnequipItem_Prefix(Humanoid __instance, ItemData item)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			LoadedMemory.UnequipDepth++;
			if (Active && LoadedMemory.IsLocal(__instance) && LoadedMemory.NeedsReload(item) && __instance.IsItemEquiped(item))
			{
				LoadedMemory.Remember((Player)__instance, item, "unequipped");
			}
		}

		[HarmonyFinalizer]
		[HarmonyPatch(typeof(Humanoid), "UnequipItem", new Type[]
		{
			typeof(ItemData),
			typeof(bool)
		})]
		private static void UnequipItem_Finalizer()
		{
			if (LoadedMemory.UnequipDepth > 0)
			{
				LoadedMemory.UnequipDepth--;
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Humanoid), "EquipItem", new Type[]
		{
			typeof(ItemData),
			typeof(bool)
		})]
		private static void EquipItem_Postfix(Humanoid __instance, ItemData item, bool __result)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			if (__result && LoadedMemory.IsLocal(__instance) && LoadedMemory.NeedsReload(item))
			{
				if (!Active)
				{
					LoadedMemory.Forget(item);
				}
				else
				{
					LoadedMemory.Restore((Player)__instance, item, "equipped");
				}
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Player), "SetWeaponLoaded", new Type[] { typeof(ItemData) })]
		private static void SetWeaponLoaded_Postfix(Player __instance, ItemData weapon)
		{
			if (weapon != null && Active && LoadedMemory.IsLocal((Humanoid)(object)__instance) && LoadedMemory.NeedsReload(weapon))
			{
				LoadedMemory.MarkLoaded(__instance, weapon);
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(Player), "ResetLoadedWeapon")]
		private static void ResetLoadedWeapon_Prefix(ItemData ___m_weaponLoaded, ref ItemData __state)
		{
			__state = ___m_weaponLoaded;
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Player), "ResetLoadedWeapon")]
		private static void ResetLoadedWeapon_Postfix(Player __instance, ItemData __state)
		{
			if (__state != null && LoadedMemory.UnequipDepth <= 0 && Active && LoadedMemory.IsLocal((Humanoid)(object)__instance))
			{
				LoadedMemory.Fired(__state);
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(Player), "Save", new Type[] { typeof(ZPackage) })]
		private static void Save_Prefix(Player __instance)
		{
			if (Active && LoadedMemory.IsLocal((Humanoid)(object)__instance))
			{
				ItemData currentWeapon = ((Humanoid)__instance).GetCurrentWeapon();
				if (LoadedMemory.NeedsReload(currentWeapon))
				{
					LoadedMemory.Remember(__instance, currentWeapon, "saving");
				}
			}
		}

		[HarmonyPostfix]
		[HarmonyPriority(100)]
		[HarmonyPatch(typeof(Player), "Load", new Type[] { typeof(ZPackage) })]
		private static void Load_Postfix(Player __instance)
		{
			if (Active && LoadedMemory.IsLocal((Humanoid)(object)__instance))
			{
				LoadedMemory.AfterLoad(__instance);
			}
		}
	}
	[BepInPlugin("com.crossbowmemory.valheim", "CrossbowMemory", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<bool> RestoreBoltType;

		internal static ConfigEntry<bool> LogEvents;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Keep crossbows loaded when you switch weapons, stash them or log out. Off = vanilla behaviour (reload every time). Changes apply immediately.");
			RestoreBoltType = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Restore bolt type", true, "When you take out a loaded crossbow, re-equip the kind of bolt that was loaded in it, if you still carry some. Off = keep whatever bolt is equipped now. Changes apply immediately.");
			LogEvents = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Log events", false, "Write a line to the BepInEx console/log whenever a crossbow's loaded state is remembered, restored or spent, and list your crossbows at login. Turn on when something seems off.");
			if (!LoadedMemory.Init(Log))
			{
				Log.LogError((object)"CrossbowMemory could not find the game's weapon-loading code (game update?). The mod stays off.");
				return;
			}
			try
			{
				_harmony = new Harmony("com.crossbowmemory.valheim");
				_harmony.PatchAll(typeof(Plugin).Assembly);
			}
			catch (Exception arg)
			{
				Log.LogError((object)string.Format("{0} could not patch the game (game update?). The mod stays off.\n{1}", "CrossbowMemory", arg));
				return;
			}
			Log.LogInfo((object)"CrossbowMemory 1.0.0 loaded");
		}

		private void Update()
		{
			LoadedMemory.Tick();
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class PluginInfo
	{
		public const string Guid = "com.crossbowmemory.valheim";

		public const string Name = "CrossbowMemory";

		public const string Version = "1.0.0";
	}
}