Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of CrossbowMemory v1.0.0
CrossbowMemory.dll
Decompiled 2 days agousing 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"; } }