Decompiled source of PickYourSpirit v1.0.0

PickYourSpirit.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("PickYourSpirit")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+0e75840dcb525b730e1f13ec15aadf9e3d4c5975")]
[assembly: AssemblyProduct("PickYourSpirit")]
[assembly: AssemblyTitle("PickYourSpirit")]
[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 PickYourSpirit
{
	[HarmonyPatch]
	internal static class ButtonPatches
	{
		private static FieldRef<Character, bool> s_secondaryAttack;

		private static bool s_errorLogged;

		private static bool Prepare()
		{
			if (s_secondaryAttack != null)
			{
				return true;
			}
			try
			{
				s_secondaryAttack = AccessTools.FieldRefAccess<Character, bool>("m_secondaryAttack");
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Middle mouse can't pick spirits: the game's press field wasn't found (a game update?): " + ex.Message));
				return false;
			}
		}

		[HarmonyPatch(typeof(Player), "SetControls")]
		[HarmonyPostfix]
		private static void SetControls(Player __instance)
		{
			try
			{
				if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && s_secondaryAttack.Invoke((Character)(object)__instance))
				{
					ItemData currentWeapon = ((Humanoid)__instance).GetCurrentWeapon();
					if (SpiritCaller.IsSpiritCaller(currentWeapon))
					{
						SpiritCaller.Press(currentWeapon);
					}
				}
			}
			catch (Exception arg)
			{
				if (!s_errorLogged)
				{
					s_errorLogged = true;
					Plugin.Log.LogError((object)$"Picking the next spirit failed (logged once): {arg}");
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class CastPatches
	{
		private static bool s_errorLogged;

		[HarmonyPatch(typeof(SpawnAbility), "Setup")]
		[HarmonyPrefix]
		private static void Setup(SpawnAbility __instance, Character owner, ItemData item)
		{
			try
			{
				string pick = SpiritCaller.Pick;
				if (pick != null && !((Object)(object)owner == (Object)null) && !((Object)(object)owner != (Object)(object)Player.m_localPlayer) && SpiritCaller.IsSpiritCaller(item))
				{
					GameObject[] spawnPrefab = __instance.m_spawnPrefab;
					int num = SpiritRules.Resolve(pick, SpiritCaller.Names(spawnPrefab));
					if (num >= 0)
					{
						__instance.m_spawnPrefab = (GameObject[])(object)new GameObject[1] { spawnPrefab[num] };
					}
				}
			}
			catch (Exception arg)
			{
				if (!s_errorLogged)
				{
					s_errorLogged = true;
					Plugin.Log.LogError((object)$"Applying the picked spirit failed (logged once; the cast stays random): {arg}");
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class HintPatches
	{
		private static KeyHints s_hints;

		private static bool s_relabelled;

		private static bool s_off;

		[HarmonyPatch(typeof(KeyHints), "UpdateHints")]
		[HarmonyPostfix]
		private static void UpdateHints(KeyHints __instance)
		{
			if (s_off)
			{
				return;
			}
			try
			{
				if ((Object)(object)__instance != (Object)(object)s_hints)
				{
					s_hints = __instance;
					s_relabelled = false;
				}
				Player localPlayer = Player.m_localPlayer;
				bool flag = (Object)(object)localPlayer != (Object)null && (Object)(object)__instance.m_combatHints != (Object)null && __instance.m_combatHints.activeSelf && SpiritCaller.IsSpiritCaller(((Humanoid)localPlayer).GetCurrentWeapon());
				if (flag != s_relabelled)
				{
					Relabel(__instance, flag);
				}
				if (flag && !s_off)
				{
					__instance.m_secondaryAttackKB.SetActive(true);
					__instance.m_secondaryAttackGP.SetActive(true);
				}
			}
			catch (Exception arg)
			{
				TurnOff(__instance, $"The 'Next spirit' key hint failed; the hint is left to the game from now on: {arg}");
			}
		}

		private static void Relabel(KeyHints hints, bool spirit)
		{
			if (!Labels(hints, out var keyboard, out var gamepad))
			{
				TurnOff(hints, "The key hint's label wasn't found (a game update?): the hint is left to the game");
				return;
			}
			SetText(keyboard, spirit ? "Next spirit" : "$settings_secondaryattack");
			SetText(gamepad, spirit ? "Next spirit <mspace=0.6em> $KEY_SecondaryAttack</mspace>" : "$settings_secondaryattack <mspace=0.6em> $KEY_SecondaryAttack</mspace>");
			s_relabelled = spirit;
		}

		private static bool Labels(KeyHints hints, out TMP_Text keyboard, out TMP_Text gamepad)
		{
			Transform val = (((Object)(object)hints.m_secondaryAttackKB != (Object)null) ? hints.m_secondaryAttackKB.transform.Find("Text") : null);
			keyboard = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<TMP_Text>() : null);
			gamepad = (((Object)(object)hints.m_secondaryAttackGP != (Object)null) ? hints.m_secondaryAttackGP.GetComponent<TMP_Text>() : null);
			if ((Object)(object)keyboard != (Object)null)
			{
				return (Object)(object)gamepad != (Object)null;
			}
			return false;
		}

		private static void SetText(TMP_Text text, string value)
		{
			Localization.instance.RemoveTextFromCache(text);
			text.text = value;
			Localization.instance.Localize(text.transform);
		}

		private static void TurnOff(KeyHints hints, string message)
		{
			s_off = true;
			Plugin.Log.LogWarning((object)message);
			if (!s_relabelled)
			{
				return;
			}
			try
			{
				if (Labels(hints, out var keyboard, out var gamepad))
				{
					SetText(keyboard, "$settings_secondaryattack");
					SetText(gamepad, "$settings_secondaryattack <mspace=0.6em> $KEY_SecondaryAttack</mspace>");
				}
			}
			catch (Exception)
			{
			}
			s_relabelled = false;
		}
	}
	[BepInPlugin("algo7.pickyourspirit", "PickYourSpirit", "1.0.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "algo7.pickyourspirit";

		public const string Name = "PickYourSpirit";

		public const string PluginVersion = "1.0.0";

		internal static ManualLogSource Log;

		private void Awake()
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			if (Application.isBatchMode)
			{
				Log.LogInfo((object)"PickYourSpirit loaded (v1.0.0): dedicated server, nothing to do");
				return;
			}
			Harmony harmony = new Harmony("algo7.pickyourspirit");
			if (!TryPatch(harmony, typeof(CastPatches), "cast hook"))
			{
				Log.LogError((object)"Picking spirits is off: without the cast hook a pick would do nothing");
				return;
			}
			TryPatch(harmony, typeof(ButtonPatches), "middle mouse hook");
			if (!TryPatch(harmony, typeof(HintPatches), "key hint hook"))
			{
				Log.LogWarning((object)"No 'Next spirit' key hint (picking still works)");
			}
			SceneManager.sceneLoaded += OnSceneLoaded;
			Log.LogInfo((object)"PickYourSpirit loaded (v1.0.0)");
		}

		private void OnDestroy()
		{
			SceneManager.sceneLoaded -= OnSceneLoaded;
		}

		private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			SpiritCaller.Pick = null;
		}

		private static bool TryPatch(Harmony harmony, Type patches, string what)
		{
			try
			{
				harmony.PatchAll(patches);
				return true;
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Could not install the {what}: {arg}");
				return false;
			}
		}
	}
	internal static class SpiritCaller
	{
		public const string ItemName = "$item_staff_spiritcaller";

		public static string Pick;

		private static bool s_warnedNoList;

		public static bool IsSpiritCaller(ItemData item)
		{
			if (item != null && item.m_shared != null)
			{
				return item.m_shared.m_name == "$item_staff_spiritcaller";
			}
			return false;
		}

		public static GameObject[] SpawnList(ItemData staff)
		{
			GameObject val = staff?.m_shared?.m_attack?.m_attackProjectile;
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			SpawnAbility component = val.GetComponent<SpawnAbility>();
			if (!((Object)(object)component != (Object)null))
			{
				return null;
			}
			return component.m_spawnPrefab;
		}

		public static List<string> Names(GameObject[] list)
		{
			List<string> list2 = new List<string>();
			if (list == null)
			{
				return list2;
			}
			foreach (GameObject val in list)
			{
				list2.Add(((Object)(object)val != (Object)null) ? ((Object)val).name : null);
			}
			return list2;
		}

		public static void Press(ItemData staff)
		{
			GameObject[] list = SpawnList(staff);
			List<string> list2 = SpiritRules.Choices(Names(list));
			if (list2.Count == 0)
			{
				if (!s_warnedNoList)
				{
					s_warnedNoList = true;
					Plugin.Log.LogWarning((object)"The Spirit Caller has no spirit list (a game update?): its spirits stay random");
				}
			}
			else
			{
				Pick = SpiritRules.Next(Pick, list2);
				if (!((Object)(object)MessageHud.instance == (Object)null))
				{
					MessageHud.instance.ShowMessage((MessageType)2, SpiritRules.Message(ShownName(list, Pick)), 0, (Sprite)null, false, false);
				}
			}
		}

		private static string ShownName(GameObject[] list, string pick)
		{
			if (pick == null)
			{
				return null;
			}
			string text = "";
			foreach (GameObject val in list)
			{
				if (!((Object)(object)val == (Object)null) && !(((Object)val).name != pick))
				{
					text = NameOf(val);
					break;
				}
			}
			string text2 = SpiritRules.AnimalOf(pick);
			string text3 = (((Object)(object)ZNetScene.instance != (Object)null) ? NameOf(ZNetScene.instance.GetPrefab(text2)) : "");
			return SpiritRules.FirstName(text, text3, text2, pick);
		}

		private static string NameOf(GameObject prefab)
		{
			Character val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<Character>() : null);
			if ((Object)(object)val == (Object)null || string.IsNullOrEmpty(val.m_name))
			{
				return "";
			}
			return Localization.instance.Localize(val.m_name);
		}
	}
	internal static class SpiritRules
	{
		public const string HintLabel = "Next spirit";

		public const string GamepadKey = " <mspace=0.6em> $KEY_SecondaryAttack</mspace>";

		public const string VanillaKeyboardHint = "$settings_secondaryattack";

		public const string VanillaGamepadHint = "$settings_secondaryattack <mspace=0.6em> $KEY_SecondaryAttack</mspace>";

		public const string SpiritGamepadHint = "Next spirit <mspace=0.6em> $KEY_SecondaryAttack</mspace>";

		private const string SpiritEnding = "_spiritcaller";

		public static List<string> Choices(IEnumerable<string> names)
		{
			List<string> list = new List<string>();
			if (names == null)
			{
				return list;
			}
			foreach (string name in names)
			{
				if (!string.IsNullOrEmpty(name) && !list.Contains(name))
				{
					list.Add(name);
				}
			}
			return list;
		}

		public static string Next(string pick, IReadOnlyList<string> choices)
		{
			if (choices == null || choices.Count == 0)
			{
				return null;
			}
			if (pick == null)
			{
				return choices[0];
			}
			int num = IndexOf(choices, pick);
			if (num < 0 || num + 1 >= choices.Count)
			{
				return null;
			}
			return choices[num + 1];
		}

		public static int Resolve(string pick, IReadOnlyList<string> names)
		{
			if (pick == null || names == null)
			{
				return -1;
			}
			return IndexOf(names, pick);
		}

		public static string Message(string spirit)
		{
			return "Next spirit: " + (spirit ?? "Random");
		}

		public static string AnimalOf(string spiritPrefab)
		{
			if (spiritPrefab == null || spiritPrefab.Length <= "_spiritcaller".Length)
			{
				return spiritPrefab;
			}
			if (!spiritPrefab.EndsWith("_spiritcaller", StringComparison.OrdinalIgnoreCase))
			{
				return spiritPrefab;
			}
			return spiritPrefab.Substring(0, spiritPrefab.Length - "_spiritcaller".Length);
		}

		public static string FirstName(params string[] names)
		{
			for (int i = 0; i < names.Length; i++)
			{
				string text = names[i]?.Trim();
				if (!string.IsNullOrEmpty(text) && (!text.StartsWith("[", StringComparison.Ordinal) || !text.EndsWith("]", StringComparison.Ordinal)))
				{
					return text;
				}
			}
			return null;
		}

		private static int IndexOf(IReadOnlyList<string> names, string name)
		{
			for (int i = 0; i < names.Count; i++)
			{
				if (string.Equals(names[i], name, StringComparison.Ordinal))
				{
					return i;
				}
			}
			return -1;
		}
	}
	internal static class PluginInfo
	{
		public const string Version = "1.0.0";
	}
}