Decompiled source of SpellEditor v1.0.1

BepInEx/plugins/SpellEditor/SpellEditor.dll

Decompiled an hour ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mirror;
using UnityEngine;
using UnityEngine.SceneManagement;
using YAPYAP;

[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("SpellEditor")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("SpellEditor")]
[assembly: AssemblyTitle("SpellEditor")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 SpellEditor
{
	[StructLayout(LayoutKind.Sequential, Size = 1)]
	public struct FlightRequestMessage : NetworkMessage
	{
	}
	public static class FlightCommand
	{
		[HarmonyPatch(typeof(NetworkManager), "OnStartServer")]
		public static class OnStartServerPatch
		{
			public static void Postfix()
			{
				RegisterHandlers();
			}
		}

		[HarmonyPatch(typeof(NetworkManager), "OnStartClient")]
		public static class OnStartClientPatch
		{
			public static void Postfix()
			{
				RegisterHandlers();
			}
		}

		[HarmonyPatch(typeof(PawnVoiceCommands), "Start")]
		public static class PawnVoiceCommandsStartPatch
		{
			public static void Postfix(PawnVoiceCommands __instance)
			{
				Pawn component = ((Component)__instance).GetComponent<Pawn>();
				if (!((Object)(object)component == (Object)null) && ((NetworkBehaviour)component).isLocalPlayer && !((Object)(object)component.Voice == (Object)null))
				{
					PawnVoice voice = component.Voice;
					voice.OnVoiceResult = (Action<string>)Delegate.Combine(voice.OnVoiceResult, new Action<string>(OnVoiceResult));
					if ((Object)(object)UIManager.Instance?.uiChat != (Object)null)
					{
						UIManager.Instance.uiChat.OnMessageSendVoice += OnVoiceResult;
					}
					Plugin.Log.LogInfo((object)"[FlightCommand] Voice listener registered on local player.");
				}
			}
		}

		[HarmonyPatch(typeof(StatusFlying), "OnFixedUpdate")]
		public static class StatusFlyingLandingPatch
		{
			public static void Postfix(StatusFlying __instance)
			{
				//IL_004f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0067: Unknown result type (might be due to invalid IL or missing references)
				//IL_006c: Unknown result type (might be due to invalid IL or missing references)
				object? obj = _pawnField?.GetValue(__instance);
				Pawn val = (Pawn)((obj is Pawn) ? obj : null);
				if ((Object)(object)val == (Object)null || !((NetworkBehaviour)val).isServer)
				{
					return;
				}
				Rigidbody rigidbody = val.Rigidbody;
				if (!((Object)(object)rigidbody == (Object)null) && !(Time.time - _activatedTime < 3f) && !(rigidbody.linearVelocity.y > 0.5f) && Physics.Raycast(((Component)val).transform.position, Vector3.down, 2f))
				{
					StatusFlying lastStatus = val.PawnStatus.GetLastStatus<StatusFlying>();
					if (lastStatus != null)
					{
						val.PawnStatus.RemoveStatus((Status)(object)lastStatus);
					}
				}
			}
		}

		private const string LocKey = "PLAYER_COMMAND_BECOME_WIND";

		private const float Duration = 60f;

		private static StatusFlyingSO? _statusSO;

		private static float _activatedTime = -999f;

		private const float GracePeriod = 3f;

		private static readonly FieldInfo _pawnField = typeof(Status).GetField("_pawn", BindingFlags.Instance | BindingFlags.NonPublic);

		private const float LandingRayDistance = 2f;

		public static void RegisterHandlers()
		{
			try
			{
				NetworkServer.RegisterHandler<FlightRequestMessage>((Action<NetworkConnectionToClient, FlightRequestMessage>)OnServerReceive, false);
			}
			catch
			{
			}
			try
			{
				NetworkClient.RegisterHandler<FlightRequestMessage>((Action<FlightRequestMessage>)OnClientReceive, false);
			}
			catch
			{
			}
		}

		private static void OnVoiceResult(string input)
		{
			if (string.IsNullOrEmpty(input) || !MatchesTrigger(input))
			{
				return;
			}
			if (NetworkServer.active)
			{
				Pawn val = ((IEnumerable<Pawn>)Object.FindObjectsByType<Pawn>((FindObjectsSortMode)0)).FirstOrDefault((Func<Pawn, bool>)((Pawn p) => ((NetworkBehaviour)p).isLocalPlayer));
				if ((Object)(object)val != (Object)null)
				{
					Apply(val);
				}
			}
			else if (NetworkClient.isConnected)
			{
				NetworkClient.Send<FlightRequestMessage>(default(FlightRequestMessage), 0);
			}
		}

		private static bool MatchesTrigger(string input)
		{
			string[] currentWords = VoiceInjector.GetCurrentWords("PLAYER_COMMAND_BECOME_WIND", (SystemLanguage)10);
			if (currentWords == null || currentWords.Length == 0)
			{
				return false;
			}
			int num = -1;
			string[] array = currentWords;
			foreach (string text in array)
			{
				int num2 = input.IndexOf(text, num + 1, StringComparison.OrdinalIgnoreCase);
				if (num2 < 0)
				{
					return false;
				}
				num = num2 + text.Length - 1;
			}
			return true;
		}

		private static void OnServerReceive(NetworkConnectionToClient conn, FlightRequestMessage msg)
		{
			NetworkIdentity identity = ((NetworkConnection)conn).identity;
			Pawn val = ((identity != null) ? ((Component)identity).GetComponent<Pawn>() : null);
			if ((Object)(object)val != (Object)null)
			{
				Apply(val);
			}
		}

		private static void OnClientReceive(FlightRequestMessage msg)
		{
		}

		private static void Apply(Pawn pawn)
		{
			if (NetworkServer.active)
			{
				StatusFlyingSO statusFlyingSO = GetStatusFlyingSO();
				if ((Object)(object)statusFlyingSO == (Object)null)
				{
					Plugin.Log.LogWarning((object)"[FlightCommand] StatusFlyingSO not found in resources.");
					return;
				}
				_activatedTime = Time.time;
				pawn.PawnStatus.AddStatus((StatusSO)(object)statusFlyingSO, 60f, 0f);
				Plugin.Log.LogInfo((object)$"[FlightCommand] StatusFlying applied to {((Object)pawn).name} for {60f}s.");
			}
		}

		private static StatusFlyingSO? GetStatusFlyingSO()
		{
			if ((Object)(object)_statusSO != (Object)null)
			{
				return _statusSO;
			}
			_statusSO = Resources.FindObjectsOfTypeAll<StatusFlyingSO>().FirstOrDefault();
			return _statusSO;
		}
	}
	public class NetworkSync : MonoBehaviour
	{
		[HarmonyPatch(typeof(NetworkManager), "OnStartServer")]
		public static class ServerStartPatch
		{
			public static void Postfix()
			{
				TryRegisterHandlers();
			}
		}

		[HarmonyPatch(typeof(NetworkManager), "OnStartClient")]
		public static class ClientStartPatch
		{
			public static void Postfix()
			{
				TryRegisterHandlers();
			}
		}

		public static NetworkSync? Instance { get; private set; }

		private void Awake()
		{
			Instance = this;
		}

		private void Start()
		{
			TryRegisterHandlers();
		}

		public static void TryRegisterHandlers()
		{
			try
			{
				NetworkServer.RegisterHandler<WandSpellFieldMessage>((Action<NetworkConnectionToClient, WandSpellFieldMessage>)OnServerReceive, false);
			}
			catch
			{
			}
			try
			{
				NetworkClient.RegisterHandler<WandSpellFieldMessage>((Action<WandSpellFieldMessage>)OnClientReceive, false);
			}
			catch
			{
			}
		}

		public void BroadcastFieldChange(NetworkPuppetWandProp wand, string fieldName, string value)
		{
			WandSpellFieldMessage wandSpellFieldMessage = default(WandSpellFieldMessage);
			wandSpellFieldMessage.WandNetId = WandRegistry.GetWandId(wand);
			wandSpellFieldMessage.FieldName = fieldName;
			wandSpellFieldMessage.FieldValue = value;
			WandSpellFieldMessage wandSpellFieldMessage2 = wandSpellFieldMessage;
			if (NetworkServer.active)
			{
				NetworkServer.SendToAll<WandSpellFieldMessage>(wandSpellFieldMessage2, 0, false);
			}
			else if (NetworkClient.isConnected)
			{
				NetworkClient.Send<WandSpellFieldMessage>(wandSpellFieldMessage2, 0);
			}
			else
			{
				ApplyFieldChange(wandSpellFieldMessage2);
			}
		}

		private static void ApplyFieldChange(WandSpellFieldMessage msg)
		{
			NetworkPuppetWandProp val = ((IEnumerable<NetworkPuppetWandProp>)WandRegistry.All).FirstOrDefault((Func<NetworkPuppetWandProp, bool>)((NetworkPuppetWandProp w) => WandRegistry.GetWandId(w) == msg.WandNetId));
			if ((Object)(object)val == (Object)null)
			{
				WandRegistry.Refresh();
				val = ((IEnumerable<NetworkPuppetWandProp>)WandRegistry.All).FirstOrDefault((Func<NetworkPuppetWandProp, bool>)((NetworkPuppetWandProp w) => WandRegistry.GetWandId(w) == msg.WandNetId));
			}
			if ((Object)(object)val == (Object)null)
			{
				Plugin.Log.LogWarning((object)("NetworkSync: wand '" + msg.WandNetId + "' not found."));
				return;
			}
			Spell spell = val.Spell;
			SpellData val2 = ((spell != null) ? spell.SpellData : null);
			if ((Object)(object)val2 == (Object)null)
			{
				Plugin.Log.LogWarning((object)("NetworkSync: wand '" + msg.WandNetId + "' has no SpellData."));
				return;
			}
			FieldInfo field = typeof(SpellData).GetField(msg.FieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field == null)
			{
				Plugin.Log.LogWarning((object)("NetworkSync: field '" + msg.FieldName + "' not found on SpellData."));
				return;
			}
			try
			{
				object obj = SpellEditorWindow.ParseValue(field.FieldType, msg.FieldValue);
				if (obj != null)
				{
					field.SetValue(val2, obj);
				}
				Plugin.Log.LogInfo((object)("NetworkSync: applied " + msg.FieldName + "=" + msg.FieldValue + " to wand " + msg.WandNetId + "."));
			}
			catch
			{
				Plugin.Log.LogWarning((object)("NetworkSync: could not parse '" + msg.FieldValue + "' for field '" + msg.FieldName + "'."));
			}
		}

		private static void OnServerReceive(NetworkConnectionToClient conn, WandSpellFieldMessage msg)
		{
			ApplyFieldChange(msg);
			NetworkServer.SendToAll<WandSpellFieldMessage>(msg, 0, false);
		}

		private static void OnClientReceive(WandSpellFieldMessage msg)
		{
			if (!NetworkServer.active)
			{
				ApplyFieldChange(msg);
			}
		}
	}
	public struct WandSpellFieldMessage : NetworkMessage
	{
		public string WandNetId;

		public string FieldName;

		public string FieldValue;
	}
	public static class OculusTempestVariant
	{
		[HarmonyPatch(typeof(ProjectileTornado), "Fire")]
		public static class OculusTornadoSpawnPatch
		{
			public static void Postfix(ProjectileTornado __instance)
			{
				object? obj = _ownerSpellField?.GetValue(__instance);
				object? obj2 = ((obj is Spell) ? obj : null);
				if (((obj2 == null) ? null : ((Spell)obj2).SpellData?.voiceCommandKey) == "SPELL_BAS_OCULUS_TEMPEST")
				{
					_oculusTornadoes.Add(__instance);
				}
			}
		}

		[HarmonyPatch(typeof(ProjectileTornado), "FixedUpdate")]
		public static class OculusTempestMovementPatch
		{
			public static void Postfix(ProjectileTornado __instance)
			{
				//IL_0050: Unknown result type (might be due to invalid IL or missing references)
				//IL_0055: Unknown result type (might be due to invalid IL or missing references)
				//IL_0084: Unknown result type (might be due to invalid IL or missing references)
				//IL_009b: Unknown result type (might be due to invalid IL or missing references)
				if (!((NetworkBehaviour)__instance).isServer || !_oculusTornadoes.Contains(__instance))
				{
					return;
				}
				object? obj = _casterField?.GetValue(__instance);
				NetworkIdentity val = (NetworkIdentity)((obj is NetworkIdentity) ? obj : null);
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				Pawn component = ((Component)val).GetComponent<Pawn>();
				if (!((Object)(object)component == (Object)null))
				{
					Vector3 aimDirection = component.Blackboard.AimDirection;
					aimDirection.y = 0f;
					if (!(((Vector3)(ref aimDirection)).sqrMagnitude < 0.01f))
					{
						((Vector3)(ref aimDirection)).Normalize();
						_baseDirectionField?.SetValue(__instance, aimDirection);
						_targetDirectionField?.SetValue(__instance, aimDirection);
						_isChangingDirectionField?.SetValue(__instance, false);
						_nextDirectionChangeTimeField?.SetValue(__instance, Time.time + 9999f);
					}
				}
			}
		}

		[HarmonyPatch(typeof(ProjectileTornado), "OnDestroy")]
		public static class OculusTornadoDestroyPatch
		{
			public static void Postfix(ProjectileTornado __instance)
			{
				_oculusTornadoes.Remove(__instance);
			}
		}

		private static readonly BindingFlags BF = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		private static readonly FieldInfo _baseDirectionField = typeof(ProjectileTornado).GetField("baseDirection", BF);

		private static readonly FieldInfo _targetDirectionField = typeof(ProjectileTornado).GetField("targetDirection", BF);

		private static readonly FieldInfo _nextDirectionChangeTimeField = typeof(ProjectileTornado).GetField("nextDirectionChangeTime", BF);

		private static readonly FieldInfo _isChangingDirectionField = typeof(ProjectileTornado).GetField("isChangingDirection", BF);

		private static readonly FieldInfo _casterField = typeof(BaseProjectile).GetField("Caster", BF);

		private const string OculusKey = "SPELL_BAS_OCULUS_TEMPEST";

		private static readonly FieldInfo _ownerSpellField = typeof(BaseProjectile).GetField("OwnerSpell", BF);

		private static readonly HashSet<ProjectileTornado> _oculusTornadoes = new HashSet<ProjectileTornado>();

		public static void RegisterProjectilePrefab(GameObject prefab)
		{
		}
	}
	[BepInPlugin("com.local.yapyap.SpellEditor", "SpellEditor", "1.0.1")]
	public class Plugin : BaseUnityPlugin
	{
		public const string GUID = "com.local.yapyap.SpellEditor";

		public const string Name = "SpellEditor";

		public const string Version = "1.0.1";

		private static readonly Dictionary<string, string> RequiredVoiceEntries = new Dictionary<string, string>
		{
			{ "SPELL_BAS_FEATHER_FALL", "FEATHER-FALL" },
			{ "SPELL_BAS_AERO_MAXIMA", "FUS" },
			{ "SPELL_BAS_OCULUS_TEMPEST", "OCULUS-TEMPEST" },
			{ "PLAYER_COMMAND_BECOME_WIND", "LET-GO-YOUR-EARTHLY-TETHER-ENTER-THE-VOID-EMPTY-AND-BECOME-WIND" }
		};

		internal static ManualLogSource Log { get; private set; } = null;


		private static void PatchEnglishTxt()
		{
			string text = Path.Combine(Application.dataPath, "StreamingAssets", "Vosk", "Localisation", "english.txt");
			if (!File.Exists(text))
			{
				Log.LogWarning((object)("[SpellEditor] english.txt not found at " + text));
				return;
			}
			List<string> list = new List<string>(File.ReadAllLines(text));
			bool flag = false;
			foreach (KeyValuePair<string, string> requiredVoiceEntry in RequiredVoiceEntries)
			{
				string key = requiredVoiceEntry.Key;
				string value = requiredVoiceEntry.Value;
				string text2 = key + " :: " + value;
				bool flag2 = false;
				foreach (string item in list)
				{
					if (item.StartsWith(key + " ::"))
					{
						flag2 = true;
						break;
					}
				}
				if (!flag2)
				{
					list.Add(text2);
					flag = true;
					Log.LogInfo((object)("[SpellEditor] Added voice entry: " + text2));
				}
			}
			if (flag)
			{
				File.WriteAllLines(text, list);
			}
			else
			{
				Log.LogInfo((object)"[SpellEditor] english.txt already up to date.");
			}
		}

		private void Awake()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Expected O, but got Unknown
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			PatchEnglishTxt();
			new Harmony("com.local.yapyap.SpellEditor").PatchAll(Assembly.GetExecutingAssembly());
			FlightCommand.RegisterHandlers();
			SpellVariants.RegisterTriggers();
			VoiceInjector.SetOverride("PLAYER_COMMAND_BECOME_WIND", (SystemLanguage)10, VoiceInjector.ParseInput("LET-GO-YOUR-EARTHLY-TETHER-ENTER-THE-VOID-EMPTY-AND-BECOME-WIND"));
			VoiceInjector.SetOverride("SPELL_BAS_OCULUS_TEMPEST", (SystemLanguage)10, VoiceInjector.ParseInput("OCULUS-TEMPEST"));
			VoiceInjector.SetOverride("SPELL_BAS_FEATHER_FALL", (SystemLanguage)10, VoiceInjector.ParseInput("FEATHER-FALL"));
			VoiceInjector.SetOverride("SPELL_BAS_AERO_MAXIMA", (SystemLanguage)10, VoiceInjector.ParseInput("FUS"));
			GameObject val = new GameObject("SpellEditorRoot");
			Object.DontDestroyOnLoad((Object)val);
			val.AddComponent<SpellEditorWindow>();
			val.AddComponent<NetworkSync>();
			Log.LogInfo((object)"SpellEditor 1.0.1 loaded. Press F8 to open.");
		}
	}
	public static class PrefabInjector
	{
		[HarmonyPatch(typeof(UpdraftSpell), "Update")]
		public static class FeatherFallLandingPatch
		{
			public static void Postfix(UpdraftSpell __instance)
			{
				//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
				//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
				if (((Spell)__instance).SpellData?.voiceCommandKey != "SPELL_BAS_FEATHER_FALL")
				{
					return;
				}
				object obj = _isUpdraftActiveField?.GetValue(__instance);
				if (!(obj is bool) || !(bool)obj)
				{
					return;
				}
				object? obj2 = _casterPawnField?.GetValue(__instance);
				Pawn val = (Pawn)((obj2 is Pawn) ? obj2 : null);
				if ((Object)(object)val == (Object)null || !((NetworkBehaviour)val).isLocalPlayer)
				{
					return;
				}
				float num2 = ((_updraftStartTimeField?.GetValue(__instance) is float num) ? num : 0f);
				if (!(Time.time - num2 < 3f))
				{
					object? obj3 = _getActiveRigidbodyMethod?.Invoke(__instance, null);
					Rigidbody val2 = (Rigidbody)((obj3 is Rigidbody) ? obj3 : null);
					if (!((Object)(object)val2 == (Object)null) && !(val2.linearVelocity.y > 0.5f) && Physics.Raycast(((Component)__instance).transform.position, Vector3.down, 2f))
					{
						_stopUpdraftMethod?.Invoke(__instance, null);
					}
				}
			}
		}

		[HarmonyPatch(typeof(VoiceSpell), "CollectHeldSpells")]
		public static class CollectHeldSpellsPatch
		{
			public static void Postfix(VoiceSpell __instance)
			{
				if (_allSpellsField?.GetValue(__instance) is List<Spell> allSpells)
				{
					TryAdd(__instance, allSpells, (Spell?)(object)((IEnumerable<UpdraftSpell>)((Component)__instance).GetComponents<UpdraftSpell>()).FirstOrDefault((Func<UpdraftSpell, bool>)((UpdraftSpell u) => ((Spell)u).SpellData?.voiceCommandKey == "SPELL_BAS_FEATHER_FALL")));
					TryAdd(__instance, allSpells, ((IEnumerable<Spell>)((Component)__instance).GetComponents<Spell>()).FirstOrDefault((Func<Spell, bool>)((Spell s) => ((s == null) ? null : s.SpellData?.voiceCommandKey) == "SPELL_BAS_AERO_MAXIMA")));
					TryAdd(__instance, allSpells, ((IEnumerable<Spell>)((Component)__instance).GetComponents<Spell>()).FirstOrDefault((Func<Spell, bool>)((Spell s) => ((s == null) ? null : s.SpellData?.voiceCommandKey) == "SPELL_BAS_OCULUS_TEMPEST")));
				}
			}

			private static void TryAdd(VoiceSpell vs, List<Spell> allSpells, Spell? spell)
			{
				if ((Object)(object)spell == (Object)null || allSpells.Contains(spell))
				{
					return;
				}
				if (!spell.IsInitialized)
				{
					spell.Initialize(((Spell)vs).CasterIdentity);
					if (NetworkServer.active)
					{
						spell.SetWandHeld(true);
					}
				}
				allSpells.Add(spell);
				_sortSpells?.Invoke(vs, null);
			}
		}

		[HarmonyPatch(typeof(Spell), "CanCast")]
		public static class FeatherFallCanCastPatch
		{
			public static bool Prefix(Spell __instance, ref bool __result)
			{
				if (__instance.SpellData?.voiceCommandKey != "SPELL_BAS_FEATHER_FALL")
				{
					return true;
				}
				UpdraftSpell val = FindParentUpDog((Component)(object)__instance);
				if ((Object)(object)val == (Object)null)
				{
					return true;
				}
				__result = ((Spell)val).CanCast();
				return false;
			}
		}

		[HarmonyPatch(typeof(UpdraftSpell), "OnSpellActivate")]
		public static class FeatherFallActivatePatch
		{
			public static void Postfix(UpdraftSpell __instance)
			{
				if (!(((Spell)__instance).SpellData?.voiceCommandKey != "SPELL_BAS_FEATHER_FALL") && NetworkServer.active)
				{
					UpdraftSpell? obj = FindParentUpDog((Component)(object)__instance);
					if (obj != null)
					{
						((Spell)obj).SetCastData();
					}
				}
			}
		}

		[HarmonyPatch(typeof(Spell), "OnSpellDeactivate")]
		public static class FeatherFallDeactivatePatch
		{
			public static void Postfix(Spell __instance)
			{
				if (!(__instance.SpellData?.voiceCommandKey != "SPELL_BAS_FEATHER_FALL") && NetworkServer.active)
				{
					UpdraftSpell? obj = FindParentUpDog((Component)(object)__instance);
					if (obj != null)
					{
						((Spell)obj).OnSpellDeactivate();
					}
				}
			}
		}

		[HarmonyPatch(typeof(Spell), "CanCast")]
		public static class AeroMaximaCanCastPatch
		{
			public static bool Prefix(Spell __instance, ref bool __result)
			{
				if (__instance.SpellData?.voiceCommandKey != "SPELL_BAS_AERO_MAXIMA")
				{
					return true;
				}
				Spell val = FindParentAero((Component)(object)__instance);
				if ((Object)(object)val == (Object)null)
				{
					return true;
				}
				__result = val.CanCast() && val.CurrentCharges >= 2;
				return false;
			}
		}

		[HarmonyPatch(typeof(Spell), "OnSpellActivate")]
		public static class AeroMaximaActivatePatch
		{
			public static void Postfix(Spell __instance)
			{
				if (__instance.SpellData?.voiceCommandKey != "SPELL_BAS_AERO_MAXIMA" || !NetworkServer.active)
				{
					return;
				}
				Spell val = FindParentAero((Component)(object)__instance);
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				val.SetCastData();
				val.SetCastData();
				if (Time.time - _lastYellTime < 1f)
				{
					return;
				}
				_lastYellTime = Time.time;
				NetworkIdentity casterIdentity = __instance.CasterIdentity;
				Pawn val2 = ((casterIdentity != null) ? ((Component)casterIdentity).GetComponent<Pawn>() : null);
				if ((Object)(object)((val2 != null) ? val2.Emotes : null) != (Object)null)
				{
					object? obj = ((object)val2.Emotes).GetType().GetField("emoteState", BF)?.GetValue(val2.Emotes);
					object? obj2 = ((obj is ControllerStateEmote) ? obj : null);
					if (obj2 != null)
					{
						((ControllerStateEmote)obj2).PlayEmote(_yellEmoteHash, 1.5f);
					}
				}
			}
		}

		[HarmonyPatch(typeof(Spell), "OnSpellDeactivate")]
		public static class AeroMaximaDeactivatePatch
		{
			public static void Postfix(Spell __instance)
			{
				if (!(__instance.SpellData?.voiceCommandKey != "SPELL_BAS_AERO_MAXIMA") && NetworkServer.active)
				{
					Spell? obj = FindParentAero((Component)(object)__instance);
					if (obj != null)
					{
						obj.OnSpellDeactivate();
					}
				}
			}
		}

		[HarmonyPatch(typeof(Spell), "CanCast")]
		public static class OculusTempestCanCastPatch
		{
			public static bool Prefix(Spell __instance, ref bool __result)
			{
				if (__instance.SpellData?.voiceCommandKey != "SPELL_BAS_OCULUS_TEMPEST")
				{
					return true;
				}
				Spell val = FindParentTempest((Component)(object)__instance);
				if ((Object)(object)val == (Object)null)
				{
					return true;
				}
				__result = val.CanCast();
				return false;
			}
		}

		[HarmonyPatch(typeof(Spell), "OnSpellActivate")]
		public static class OculusTempestActivatePatch
		{
			public static void Postfix(Spell __instance)
			{
				if (!(__instance.SpellData?.voiceCommandKey != "SPELL_BAS_OCULUS_TEMPEST") && NetworkServer.active)
				{
					Spell? obj = FindParentTempest((Component)(object)__instance);
					if (obj != null)
					{
						obj.SetCastData();
					}
				}
			}
		}

		[HarmonyPatch(typeof(Spell), "OnSpellDeactivate")]
		public static class OculusTempestDeactivatePatch
		{
			public static void Postfix(Spell __instance)
			{
				if (!(__instance.SpellData?.voiceCommandKey != "SPELL_BAS_OCULUS_TEMPEST") && NetworkServer.active)
				{
					Spell? obj = FindParentTempest((Component)(object)__instance);
					if (obj != null)
					{
						obj.OnSpellDeactivate();
					}
				}
			}
		}

		[HarmonyPatch(typeof(NetworkManager), "OnStartServer")]
		public static class OnStartServerPatch
		{
			public static void Postfix()
			{
				Inject();
			}
		}

		[HarmonyPatch(typeof(NetworkManager), "OnStartClient")]
		public static class OnStartClientPatch
		{
			public static void Postfix()
			{
				Inject();
			}
		}

		private static bool _done;

		private static readonly BindingFlags BF = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		private static readonly FieldInfo _spellDataField = typeof(Spell).GetField("spellData", BF);

		private static readonly FieldInfo _isSecretField = typeof(Spell).GetField("isSecret", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly FieldInfo _allSpellsField = typeof(VoiceSpell).GetField("allSpells", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly MethodInfo _sortSpells = typeof(VoiceSpell).GetMethod("SortSpells", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly FieldInfo _isUpdraftActiveField = typeof(UpdraftSpell).GetField("isUpdraftActive", BF);

		private static readonly FieldInfo _updraftStartTimeField = typeof(UpdraftSpell).GetField("updraftStartTime", BF);

		private static readonly FieldInfo _casterPawnField = typeof(Spell).GetField("casterPawn", BF);

		private static readonly MethodInfo _stopUpdraftMethod = typeof(UpdraftSpell).GetMethod("StopUpdraft", BF);

		private static readonly MethodInfo _getActiveRigidbodyMethod = typeof(UpdraftSpell).GetMethod("GetActiveRigidbody", BF);

		private const float FeatherFallGracePeriod = 3f;

		private const float FeatherFallLandingRayDist = 2f;

		private static readonly int _yellEmoteHash = Animator.StringToHash("Emote_Yell");

		private static float _lastYellTime = -999f;

		private static UpdraftSpell? FindParentUpDog(Component feather)
		{
			return Array.Find(feather.GetComponents<UpdraftSpell>(), (UpdraftSpell u) => ((Spell)u).SpellData?.voiceCommandKey == "SPELL_BAS_UPDOG");
		}

		private static Spell? FindParentAero(Component variant)
		{
			return Array.Find(variant.GetComponents<Spell>(), (Spell s) => ((s == null) ? null : s.SpellData?.voiceCommandKey) == "SPELL_BAS_VENTO");
		}

		private static Spell? FindParentTempest(Component variant)
		{
			return Array.Find(variant.GetComponents<Spell>(), (Spell s) => ((s == null) ? null : s.SpellData?.voiceCommandKey) == "SPELL_BAS_TEMPEST");
		}

		public static void Inject()
		{
			if (!_done)
			{
				NetworkPuppetWandProp val = FindWandPrefab();
				if ((Object)(object)val == (Object)null)
				{
					Plugin.Log.LogWarning((object)"[PrefabInjector] PropWandOfBasics prefab not found.");
					return;
				}
				InjectFeatherFall(val);
				InjectAeroMaxima(val);
				InjectOculusTempest(val);
				_done = true;
			}
		}

		private static void InjectFeatherFall(NetworkPuppetWandProp wand)
		{
			UpdraftSpell[] components = ((Component)wand).GetComponents<UpdraftSpell>();
			if (components.Length < 2)
			{
				UpdraftSpell val = components.FirstOrDefault();
				if ((Object)(object)((val != null) ? ((Spell)val).SpellData : null) == (Object)null)
				{
					Plugin.Log.LogWarning((object)"[PrefabInjector] Feather-Fall: source UpdraftSpell missing.");
					return;
				}
				SpellData val2 = SpellRegistry.Clone(((Spell)val).SpellData, "FEATHER-FALL");
				SetSdField(val2, "voiceCommandKey", "SPELL_BAS_FEATHER_FALL");
				UpdraftSpell val3 = ((Component)wand).gameObject.AddComponent<UpdraftSpell>();
				CopySerializedFields(typeof(UpdraftSpell), val, val3);
				_spellDataField?.SetValue(val3, val2);
				_isSecretField?.SetValue(val3, true);
				SetCompField((Component)(object)val3, "initialUpwardForce", 0f);
				SetCompField((Component)(object)val3, "continuousUpwardForce", 0f);
				SetCompField((Component)(object)val3, "fallSlowMultiplier", 0.88f);
				SetCompField((Component)(object)val3, "duration", 10f);
				((Behaviour)val3).enabled = true;
				Plugin.Log.LogInfo((object)"[PrefabInjector] Feather-Fall added.");
			}
		}

		private static void InjectOculusTempest(NetworkPuppetWandProp wand)
		{
			Spell[] components = ((Component)wand).GetComponents<Spell>();
			if (Array.Exists(components, (Spell s) => ((s == null) ? null : s.SpellData?.voiceCommandKey) == "SPELL_BAS_OCULUS_TEMPEST"))
			{
				return;
			}
			Spell val = Array.Find(components, (Spell s) => ((s == null) ? null : s.SpellData?.voiceCommandKey) == "SPELL_BAS_TEMPEST");
			if ((Object)(object)((val != null) ? val.SpellData : null) == (Object)null)
			{
				Plugin.Log.LogWarning((object)"[PrefabInjector] Oculus-Tempest: source Tempest spell missing.");
				return;
			}
			SpellData val2 = SpellRegistry.Clone(val.SpellData, "OCULUS-TEMPEST");
			SetSdField(val2, "voiceCommandKey", "SPELL_BAS_OCULUS_TEMPEST");
			GameObject val3 = CloneOculusProjectile(val.SpellData.objectToSpawnPrefab);
			if ((Object)(object)val3 != (Object)null)
			{
				SetSdField(val2, "objectToSpawnPrefab", val3);
			}
			Component obj = ((Component)wand).gameObject.AddComponent(((object)val).GetType());
			Spell val4 = (Spell)(object)((obj is Spell) ? obj : null);
			if ((Object)(object)val4 == (Object)null)
			{
				Plugin.Log.LogWarning((object)"[PrefabInjector] Oculus-Tempest: AddComponent failed.");
				return;
			}
			CopySerializedFields(((object)val).GetType(), val, val4);
			_spellDataField?.SetValue(val4, val2);
			_isSecretField?.SetValue(val4, true);
			((Behaviour)val4).enabled = true;
			Plugin.Log.LogInfo((object)"[PrefabInjector] Oculus-Tempest added.");
		}

		private static GameObject? CloneOculusProjectile(GameObject? original)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			if ((Object)(object)original == (Object)null)
			{
				return null;
			}
			GameObject val = new GameObject("OculusTempestProjectileContainer");
			val.SetActive(false);
			Object.DontDestroyOnLoad((Object)(object)val);
			GameObject obj = Object.Instantiate<GameObject>(original, val.transform);
			OculusTempestVariant.RegisterProjectilePrefab(obj);
			return obj;
		}

		private static void InjectAeroMaxima(NetworkPuppetWandProp wand)
		{
			Spell[] components = ((Component)wand).GetComponents<Spell>();
			if (Array.Exists(components, (Spell s) => ((s == null) ? null : s.SpellData?.voiceCommandKey) == "SPELL_BAS_AERO_MAXIMA"))
			{
				return;
			}
			Spell val = Array.Find(components, (Spell s) => ((s == null) ? null : s.SpellData?.voiceCommandKey) == "SPELL_BAS_VENTO");
			if ((Object)(object)((val != null) ? val.SpellData : null) == (Object)null)
			{
				Plugin.Log.LogWarning((object)"[PrefabInjector] Aero-Maxima: source Aero spell missing.");
				return;
			}
			SpellData val2 = SpellRegistry.Clone(val.SpellData, "AERO-MAXIMA");
			SetSdField(val2, "voiceCommandKey", "SPELL_BAS_AERO_MAXIMA");
			GameObject val3 = CloneProjectilePrefab(val.SpellData.objectToSpawnPrefab);
			if ((Object)(object)val3 != (Object)null)
			{
				SetSdField(val2, "objectToSpawnPrefab", val3);
			}
			Component obj = ((Component)wand).gameObject.AddComponent(((object)val).GetType());
			Spell val4 = (Spell)(object)((obj is Spell) ? obj : null);
			if ((Object)(object)val4 == (Object)null)
			{
				Plugin.Log.LogWarning((object)"[PrefabInjector] Aero-Maxima: AddComponent failed.");
				return;
			}
			CopySerializedFields(((object)val).GetType(), val, val4);
			_spellDataField?.SetValue(val4, val2);
			_isSecretField?.SetValue(val4, true);
			((Behaviour)val4).enabled = true;
			Plugin.Log.LogInfo((object)"[PrefabInjector] Aero-Maxima added.");
		}

		private static GameObject? CloneProjectilePrefab(GameObject? original)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)original == (Object)null)
			{
				return null;
			}
			GameObject val = new GameObject("AeroMaximaProjectileContainer");
			val.SetActive(false);
			Object.DontDestroyOnLoad((Object)(object)val);
			GameObject obj = Object.Instantiate<GameObject>(original, val.transform);
			obj.transform.localScale = original.transform.localScale * 1.5f;
			ProjectilePush component = obj.GetComponent<ProjectilePush>();
			if ((Object)(object)component != (Object)null)
			{
				SetCompField((Component)(object)component, "pushForce", 340f);
				SetCompField((Component)(object)component, "playerPushForceMultiplier", 2.4f);
				SetCompField((Component)(object)component, "playerPushLiftMultiplier", 2f);
				SetCompField((Component)(object)component, "npcPushForceMultiplier", 4f);
				SetCompField((Component)(object)component, "maxTravelDistanceOverride", 15f);
				SetCompField((Component)(object)component, "decreaseForceByDistance", false);
				return obj;
			}
			Plugin.Log.LogWarning((object)"[PrefabInjector] Aero-Maxima: ProjectilePush not found on clone.");
			return obj;
		}

		private static void CopySerializedFields(Type type, object source, object dest)
		{
			FieldInfo[] fields = type.GetFields(BF);
			foreach (FieldInfo fieldInfo in fields)
			{
				if (!fieldInfo.IsStatic && (fieldInfo.IsPublic || fieldInfo.GetCustomAttributes(typeof(SerializeField), inherit: true).Length != 0))
				{
					fieldInfo.SetValue(dest, fieldInfo.GetValue(source));
				}
			}
		}

		private static NetworkPuppetWandProp? FindWandPrefab()
		{
			return Array.Find(Resources.FindObjectsOfTypeAll<NetworkPuppetWandProp>(), delegate(NetworkPuppetWandProp w)
			{
				//IL_0018: Unknown result type (might be due to invalid IL or missing references)
				//IL_001d: Unknown result type (might be due to invalid IL or missing references)
				if (((Object)w).name == "PropWandOfBasics")
				{
					Scene scene = ((Component)w).gameObject.scene;
					return !((Scene)(ref scene)).IsValid();
				}
				return false;
			});
		}

		private static void SetSdField(SpellData sd, string name, object value)
		{
			typeof(SpellData).GetField(name, BF)?.SetValue(sd, value);
		}

		private static void SetCompField(Component comp, string name, object value)
		{
			((object)comp).GetType().GetField(name, BF)?.SetValue(comp, value);
		}
	}
	public class SpellEditorWindow : MonoBehaviour
	{
		private bool _visible;

		private Rect _windowRect = new Rect(20f, 20f, 1100f, 680f);

		private int _mainTab;

		private int _detailTab;

		private string _search = "";

		private string _schoolFilter = "ALL";

		private Vector2 _listScroll;

		private int _selectedIndex = -1;

		private List<SpellData> _allSpells = new List<SpellData>();

		private List<SpellData> _filtered = new List<SpellData>();

		private Dictionary<string, string> _keyTriggers = new Dictionary<string, string>();

		private List<(FieldInfo field, string draft, string live)> _fields = new List<(FieldInfo, string, string)>();

		private Vector2 _detailScroll;

		private string _editKey = "";

		private string _editTrigger = "";

		private static readonly Dictionary<string, Color32> Schools = new Dictionary<string, Color32>
		{
			["ARC"] = new Color32((byte)153, (byte)102, (byte)230, byte.MaxValue),
			["BAS"] = new Color32((byte)102, (byte)204, (byte)102, byte.MaxValue),
			["BLS"] = new Color32((byte)230, (byte)204, (byte)76, byte.MaxValue),
			["FIR"] = new Color32((byte)230, (byte)102, (byte)51, byte.MaxValue),
			["GRS"] = new Color32((byte)179, (byte)230, (byte)51, byte.MaxValue),
			["ICE"] = new Color32((byte)102, (byte)204, (byte)230, byte.MaxValue),
			["ILU"] = new Color32((byte)230, (byte)102, (byte)179, byte.MaxValue),
			["TEL"] = new Color32((byte)230, (byte)153, (byte)51, byte.MaxValue),
			["WIP"] = new Color32((byte)153, (byte)153, (byte)153, byte.MaxValue)
		};

		private static readonly string[] SchoolKeys = new string[10] { "ALL", "ARC", "BAS", "BLS", "FIR", "GRS", "ICE", "ILU", "TEL", "WIP" };

		private bool _stylesReady;

		private GUIStyle _winStyle;

		private GUIStyle _header;

		private GUIStyle _subLabel;

		private GUIStyle _fieldVal;

		private GUIStyle _tab;

		private GUIStyle _tabActive;

		private GUIStyle _row;

		private GUIStyle _rowSel;

		private Texture2D _txDark;

		private Texture2D _txPanel;

		private Texture2D _txSel;

		private Texture2D _txBtn;

		private Texture2D _txBtnHov;

		private void Update()
		{
			if (Input.GetKeyDown((KeyCode)289))
			{
				_visible = !_visible;
				if (_visible)
				{
					Refresh();
				}
			}
		}

		private void OnGUI()
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected O, but got Unknown
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			if (_visible)
			{
				EnsureStyles();
				GUI.skin.label.richText = true;
				_windowRect = GUILayout.Window(47001, _windowRect, new WindowFunction(DrawWindow), "", _winStyle, Array.Empty<GUILayoutOption>());
			}
		}

		private void Refresh()
		{
			SpellRegistry.Refresh();
			WandRegistry.Refresh();
			_keyTriggers = VoiceInjector.GetAllKeysWithTriggers((SystemLanguage)10).ToDictionary<(string, string), string, string>(((string key, string trigger) e) => e.key, ((string key, string trigger) e) => e.trigger);
			_allSpells = SpellRegistry.All.Where((SpellData s) => !string.IsNullOrEmpty(s.voiceCommandKey)).ToList();
			ApplyFilter();
			if (_selectedIndex >= _filtered.Count)
			{
				SelectSpell(-1);
			}
		}

		private void ApplyFilter()
		{
			_filtered = _allSpells.Where(delegate(SpellData s)
			{
				bool num = _schoolFilter == "ALL" || (s.voiceCommandKey?.Contains("_" + _schoolFilter + "_") ?? false);
				int num2;
				if (!string.IsNullOrEmpty(_search))
				{
					string spellName = s.spellName;
					if (spellName == null || spellName.IndexOf(_search, StringComparison.OrdinalIgnoreCase) < 0)
					{
						string voiceCommandKey = s.voiceCommandKey;
						num2 = ((voiceCommandKey != null && voiceCommandKey.IndexOf(_search, StringComparison.OrdinalIgnoreCase) >= 0) ? 1 : 0);
						goto IL_008c;
					}
				}
				num2 = 1;
				goto IL_008c;
				IL_008c:
				bool flag = (byte)num2 != 0;
				return num && flag;
			}).ToList();
			if (_selectedIndex >= _filtered.Count)
			{
				SelectSpell(-1);
			}
		}

		private void SelectSpell(int index)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			_selectedIndex = index;
			_detailScroll = Vector2.zero;
			if (index < 0 || index >= _filtered.Count)
			{
				_fields = new List<(FieldInfo, string, string)>();
				_editKey = (_editTrigger = "");
				return;
			}
			SpellData val = _filtered[index];
			_editKey = val.voiceCommandKey ?? "";
			_editTrigger = (_keyTriggers.TryGetValue(_editKey, out string value) ? value : "");
			LoadFields(val);
		}

		private void LoadFields(SpellData sd)
		{
			SpellData sd2 = sd;
			_fields = (from f in typeof(SpellData).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
				where IsEditable(f.FieldType) && f.Name != "spellName" && f.Name != "voiceCommandKey" && f.Name != "remainingCooldownAlpha"
				select f).Select(delegate(FieldInfo f)
			{
				string text = f.GetValue(sd2)?.ToString() ?? "";
				return (f, text, text);
			}).ToList();
		}

		private void DrawWindow(int id)
		{
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label("<b>SpellEditor</b>  <size=10><color=#777>v2.0  ·  F8 to toggle</color></size>", _header, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
			GUILayout.Label($"<size=10><color=#555>{SpellRegistry.All.Count} spells  ·  {WandRegistry.All.Count} wands</color></size>", _subLabel, Array.Empty<GUILayoutOption>());
			GUILayout.Space(8f);
			if (GUILayout.Button("Refresh", _tab, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) }))
			{
				Refresh();
			}
			GUILayout.EndHorizontal();
			GUILayout.Space(6f);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			DrawMainTab(0, "  Spells  ");
			DrawMainTab(1, "  Variants  ");
			GUILayout.FlexibleSpace();
			GUILayout.EndHorizontal();
			DrawSeparator();
			GUILayout.Space(4f);
			if (_mainTab == 0)
			{
				DrawSpellsTab();
			}
			else
			{
				DrawVariantsTab();
			}
			GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref _windowRect)).width, 30f));
		}

		private void DrawMainTab(int index, string label)
		{
			if (GUILayout.Button(label, (_mainTab == index) ? _tabActive : _tab, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(26f) }) && _mainTab != index)
			{
				_mainTab = index;
			}
		}

		private void DrawSpellsTab()
		{
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(290f) });
			DrawBrowser();
			GUILayout.EndVertical();
			GUILayout.Space(10f);
			GUILayout.BeginVertical(Array.Empty<GUILayoutOption>());
			DrawDetail();
			GUILayout.EndVertical();
			GUILayout.EndHorizontal();
		}

		private void DrawBrowser()
		{
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0205: Unknown result type (might be due to invalid IL or missing references)
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			_ = GUI.skin.textField.normal.background;
			string text = GUILayout.TextField(_search, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
			if (text != _search)
			{
				_search = text;
				ApplyFilter();
			}
			GUILayout.EndHorizontal();
			GUILayout.Space(4f);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.FlexibleSpace();
			string[] schoolKeys = SchoolKeys;
			Color32 val = default(Color32);
			foreach (string text2 in schoolKeys)
			{
				if (_schoolFilter == text2)
				{
					val = (Color32)(Schools.TryGetValue(text2, out var value) ? value : new Color32((byte)80, (byte)120, (byte)220, byte.MaxValue));
				}
				else
				{
					((Color32)(ref val))..ctor((byte)50, (byte)50, (byte)58, byte.MaxValue);
				}
				Color backgroundColor = GUI.backgroundColor;
				GUI.backgroundColor = Color32.op_Implicit(val);
				if (GUILayout.Button(text2, _tab, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width((float)((text2 == "ALL") ? 34 : 30)) }))
				{
					_schoolFilter = text2;
					ApplyFilter();
				}
				GUI.backgroundColor = backgroundColor;
			}
			GUILayout.FlexibleSpace();
			GUILayout.EndHorizontal();
			GUILayout.Space(4f);
			GUILayout.Label($"<size=10><color=#555>{_filtered.Count} spells</color></size>", _subLabel, Array.Empty<GUILayoutOption>());
			GUILayout.Space(2f);
			_listScroll = GUILayout.BeginScrollView(_listScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) });
			for (int j = 0; j < _filtered.Count; j++)
			{
				SpellData val2 = _filtered[j];
				string school = GetSchool(val2.voiceCommandKey);
				string value2;
				string text3 = (_keyTriggers.TryGetValue(val2.voiceCommandKey ?? "", out value2) ? value2 : "?");
				Color32 value3;
				string text4 = (Schools.TryGetValue(school, out value3) ? $"#{value3.r:X2}{value3.g:X2}{value3.b:X2}" : "#888");
				if (GUILayout.Button("<color=" + text4 + ">◆</color>  <b>" + val2.spellName + "</b>\n<size=10><color=#888>" + text3 + "</color></size>", (j == _selectedIndex) ? _rowSel : _row, Array.Empty<GUILayoutOption>()))
				{
					SelectSpell(j);
				}
			}
			GUILayout.EndScrollView();
		}

		private void DrawDetail()
		{
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			if (_selectedIndex < 0 || _selectedIndex >= _filtered.Count)
			{
				GUILayout.FlexibleSpace();
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				GUILayout.FlexibleSpace();
				GUILayout.Label("<color=#444>← select a spell</color>", _subLabel, Array.Empty<GUILayoutOption>());
				GUILayout.FlexibleSpace();
				GUILayout.EndHorizontal();
				GUILayout.FlexibleSpace();
				return;
			}
			SpellData val = _filtered[_selectedIndex];
			string school = GetSchool(val.voiceCommandKey);
			Color32 value;
			string text = (Schools.TryGetValue(school, out value) ? $"#{value.r:X2}{value.g:X2}{value.b:X2}" : "#888");
			GUILayout.Label("<b><size=15>" + val.spellName + "</size></b>  <color=" + text + "><size=11>" + school + "</size></color>  <size=10><color=#555>" + val.voiceCommandKey + "</color></size>", _header, Array.Empty<GUILayoutOption>());
			GUILayout.Space(4f);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			DrawDetailTab(0, " Fields ");
			DrawDetailTab(1, " Voice ");
			GUILayout.FlexibleSpace();
			GUILayout.EndHorizontal();
			DrawSeparator();
			GUILayout.Space(4f);
			_detailScroll = GUILayout.BeginScrollView(_detailScroll, Array.Empty<GUILayoutOption>());
			if (_detailTab == 0)
			{
				DrawFieldsDetail(val);
			}
			else
			{
				DrawVoiceDetail();
			}
			GUILayout.EndScrollView();
		}

		private void DrawDetailTab(int index, string label)
		{
			if (GUILayout.Button(label, (_detailTab == index) ? _tabActive : _tab, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) }) && _detailTab != index)
			{
				_detailTab = index;
			}
		}

		private void DrawFieldsDetail(SpellData sd)
		{
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_020e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0250: Unknown result type (might be due to invalid IL or missing references)
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			GUILayout.Label("<color=#555>Field</color>", _subLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(180f) });
			GUILayout.Label("<color=#555>Current</color>", _subLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) });
			GUILayout.Label("<color=#555>New value</color>", _subLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
			GUILayout.EndHorizontal();
			DrawSeparator();
			GUILayout.Space(2f);
			for (int i = 0; i < _fields.Count; i++)
			{
				(FieldInfo field, string draft, string live) tuple = _fields[i];
				FieldInfo item = tuple.field;
				string item2 = tuple.draft;
				string item3 = tuple.live;
				bool flag = item2 != item3;
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				GUILayout.Label(item.Name, _subLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(180f) });
				Color color = GUI.color;
				GUI.color = new Color(1f, 1f, 1f, 0.35f);
				GUILayout.Label(item3, _fieldVal, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) });
				GUI.color = color;
				Color backgroundColor = GUI.backgroundColor;
				if (flag)
				{
					GUI.backgroundColor = new Color(0.25f, 0.45f, 0.25f);
				}
				string text = GUILayout.TextField(item2, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
				GUI.backgroundColor = backgroundColor;
				if (text != item2)
				{
					_fields[i] = (item, text, item3);
				}
				GUILayout.EndHorizontal();
			}
			GUILayout.Space(10f);
			bool num = _fields.Any<(FieldInfo, string, string)>(((FieldInfo field, string draft, string live) f) => f.draft != f.live);
			Color backgroundColor2 = GUI.backgroundColor;
			GUI.backgroundColor = (num ? new Color(0.25f, 0.6f, 0.25f) : new Color(0.3f, 0.3f, 0.3f));
			GUI.enabled = num;
			if (GUILayout.Button("Apply Changes", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(30f) }))
			{
				ApplyFields(sd);
				LoadFields(sd);
			}
			GUI.enabled = true;
			GUI.backgroundColor = backgroundColor2;
		}

		private void DrawVoiceDetail()
		{
			GUILayout.Label("Voice Key  <size=10><color=#555>internal command identifier</color></size>", _subLabel, Array.Empty<GUILayoutOption>());
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			_editKey = GUILayout.TextField(_editKey, Array.Empty<GUILayoutOption>());
			if (GUILayout.Button("Assign", _tab, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) }))
			{
				SpellData sd = _filtered[_selectedIndex];
				AssignKey(sd, _editKey);
			}
			GUILayout.EndHorizontal();
			GUILayout.Space(12f);
			GUILayout.Label("Trigger  <size=10><color=#555>what you say — hyphens or spaces</color></size>", _subLabel, Array.Empty<GUILayoutOption>());
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			_editTrigger = GUILayout.TextField(_editTrigger, Array.Empty<GUILayoutOption>());
			if (GUILayout.Button("Apply", _tab, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) }))
			{
				ApplyTrigger(_editKey, _editTrigger);
			}
			GUILayout.EndHorizontal();
		}

		private void DrawVariantsTab()
		{
			GUILayout.Label("Runtime Spell Variants  <size=10><color=#555>injected when wands spawn · removed when mod is unloaded</color></size>", _header, Array.Empty<GUILayoutOption>());
			DrawSeparator();
			GUILayout.Space(6f);
			GUILayout.Label("<color=#444>No variants defined.</color>", _subLabel, Array.Empty<GUILayoutOption>());
		}

		private void AssignKey(SpellData sd, string newKey)
		{
			typeof(SpellData).GetField("voiceCommandKey", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(sd, newKey);
			_editTrigger = (_keyTriggers.TryGetValue(newKey, out string value) ? value : "");
			Plugin.Log.LogInfo((object)("[SpellEditor] Assigned key '" + newKey + "' to '" + sd.spellName + "'."));
		}

		private void ApplyTrigger(string key, string input)
		{
			if (!string.IsNullOrWhiteSpace(key))
			{
				string[] array = VoiceInjector.ParseInput(input);
				VoiceInjector.SetOverride(key, (SystemLanguage)10, array);
				_keyTriggers[key] = VoiceInjector.SyllablesToDisplay(array);
				Plugin.Log.LogInfo((object)("[SpellEditor] Trigger '" + key + "' → '" + string.Join("-", array) + "'."));
			}
		}

		private void ApplyFields(SpellData sd)
		{
			foreach (var (fieldInfo, text, _) in _fields)
			{
				try
				{
					object obj = ParseValue(fieldInfo.FieldType, text);
					if (obj != null)
					{
						fieldInfo.SetValue(sd, obj);
					}
				}
				catch
				{
					Plugin.Log.LogWarning((object)("[SpellEditor] Could not parse '" + text + "' for '" + fieldInfo.Name + "'."));
				}
			}
			Plugin.Log.LogInfo((object)("[SpellEditor] Fields applied to '" + sd.spellName + "'."));
		}

		private static string GetSchool(string? key)
		{
			if (key == null)
			{
				return "???";
			}
			string[] array = key.Split(new char[1] { '_' });
			if (array.Length < 2)
			{
				return "???";
			}
			return array[1];
		}

		private void DrawSeparator()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			Color color = GUI.color;
			GUI.color = new Color(1f, 1f, 1f, 0.1f);
			GUILayout.Box("", (GUILayoutOption[])(object)new GUILayoutOption[2]
			{
				GUILayout.ExpandWidth(true),
				GUILayout.Height(1f)
			});
			GUI.color = color;
		}

		private static bool IsEditable(Type t)
		{
			if (!(t == typeof(string)) && !(t == typeof(int)) && !(t == typeof(float)) && !(t == typeof(bool)))
			{
				return t == typeof(double);
			}
			return true;
		}

		public static object? ParseValue(Type t, string s)
		{
			if (t == typeof(string))
			{
				return s;
			}
			if (t == typeof(int) && int.TryParse(s, out var result))
			{
				return result;
			}
			if (t == typeof(float) && float.TryParse(s, out var result2))
			{
				return result2;
			}
			if (t == typeof(double) && double.TryParse(s, out var result3))
			{
				return result3;
			}
			if (t == typeof(bool) && bool.TryParse(s, out var result4))
			{
				return result4;
			}
			return null;
		}

		private void EnsureStyles()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Expected O, but got Unknown
			//IL_00c2: Expected O, but got Unknown
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Expected O, but got Unknown
			//IL_0136: Expected O, but got Unknown
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Expected O, but got Unknown
			//IL_017e: Expected O, but got Unknown
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d0: Expected O, but got Unknown
			//IL_01d5: Expected O, but got Unknown
			//IL_01ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_0204: Unknown result type (might be due to invalid IL or missing references)
			//IL_0209: Unknown result type (might be due to invalid IL or missing references)
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_0218: Unknown result type (might be due to invalid IL or missing references)
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0227: Expected O, but got Unknown
			//IL_022c: Expected O, but got Unknown
			//IL_025c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0287: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d4: Expected O, but got Unknown
			//IL_02f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0320: Unknown result type (might be due to invalid IL or missing references)
			//IL_0335: Unknown result type (might be due to invalid IL or missing references)
			//IL_033a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0341: Unknown result type (might be due to invalid IL or missing references)
			//IL_0348: Unknown result type (might be due to invalid IL or missing references)
			//IL_0350: Unknown result type (might be due to invalid IL or missing references)
			//IL_0356: Unknown result type (might be due to invalid IL or missing references)
			//IL_0360: Expected O, but got Unknown
			//IL_0365: Expected O, but got Unknown
			//IL_0386: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03cc: Expected O, but got Unknown
			//IL_03ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_0418: Unknown result type (might be due to invalid IL or missing references)
			if (!_stylesReady)
			{
				_stylesReady = true;
				_txDark = MakeTex(new Color32((byte)28, (byte)28, (byte)32, byte.MaxValue));
				_txPanel = MakeTex(new Color32((byte)38, (byte)38, (byte)44, byte.MaxValue));
				_txSel = MakeTex(new Color32((byte)48, (byte)96, (byte)200, (byte)220));
				_txBtn = MakeTex(new Color32((byte)52, (byte)52, (byte)60, byte.MaxValue));
				_txBtnHov = MakeTex(new Color32((byte)68, (byte)68, (byte)80, byte.MaxValue));
				_winStyle = new GUIStyle(GUI.skin.window)
				{
					padding = new RectOffset(12, 12, 12, 12)
				};
				_winStyle.normal.background = _txDark;
				_winStyle.normal.textColor = Color.white;
				_winStyle.onNormal.background = _txDark;
				_header = new GUIStyle(GUI.skin.label)
				{
					fontSize = 13,
					richText = true,
					padding = new RectOffset(4, 4, 2, 2)
				};
				_header.normal.textColor = Color.white;
				_subLabel = new GUIStyle(GUI.skin.label)
				{
					fontSize = 11,
					richText = true,
					padding = new RectOffset(4, 4, 2, 2)
				};
				_subLabel.normal.textColor = new Color(0.8f, 0.8f, 0.8f);
				_fieldVal = new GUIStyle(GUI.skin.label)
				{
					fontSize = 11,
					richText = false,
					padding = new RectOffset(4, 4, 2, 2)
				};
				_fieldVal.normal.textColor = new Color(0.55f, 0.55f, 0.55f);
				_tab = new GUIStyle(GUI.skin.button)
				{
					fontSize = 11,
					richText = false,
					padding = new RectOffset(8, 8, 4, 4)
				};
				_tab.normal.background = _txBtn;
				_tab.normal.textColor = new Color(0.72f, 0.72f, 0.72f);
				_tab.hover.background = _txBtnHov;
				_tab.hover.textColor = Color.white;
				_tab.active.background = _txSel;
				_tab.active.textColor = Color.white;
				_tabActive = new GUIStyle(_tab)
				{
					fontStyle = (FontStyle)1
				};
				_tabActive.normal.background = _txSel;
				_tabActive.normal.textColor = Color.white;
				_tabActive.hover.background = _txSel;
				_tabActive.hover.textColor = Color.white;
				_row = new GUIStyle(GUI.skin.button)
				{
					alignment = (TextAnchor)3,
					richText = true,
					fontSize = 12,
					padding = new RectOffset(10, 6, 6, 6)
				};
				_row.normal.background = _txPanel;
				_row.normal.textColor = Color.white;
				_row.hover.background = _txBtn;
				_row.hover.textColor = Color.white;
				_rowSel = new GUIStyle(_row);
				_rowSel.normal.background = _txSel;
				_rowSel.normal.textColor = Color.white;
				_rowSel.hover.background = _txSel;
				_rowSel.hover.textColor = Color.white;
			}
		}

		private static Texture2D MakeTex(Color32 c)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected O, but got Unknown
			Texture2D val = new Texture2D(1, 1);
			val.SetPixel(0, 0, Color32.op_Implicit(c));
			val.Apply();
			return val;
		}
	}
	public static class SpellRegistry
	{
		public static List<SpellData> All { get; private set; } = new List<SpellData>();


		public static void Refresh()
		{
			SpellData[] array = Resources.LoadAll<SpellData>("");
			All = (from s in array.Concat(Resources.FindObjectsOfTypeAll<SpellData>()).Distinct()
				orderby s.voiceCommandKey
				select s).ToList();
			Plugin.Log.LogInfo((object)$"SpellRegistry: found {All.Count} spells ({array.Length} from Resources).");
		}

		public static SpellData Clone(SpellData source, string newName)
		{
			string newName2 = newName;
			SpellData val = ScriptableObject.CreateInstance<SpellData>();
			FieldInfo[] fields = typeof(SpellData).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (FieldInfo fieldInfo in fields)
			{
				fieldInfo.SetValue(val, fieldInfo.GetValue(source));
			}
			val.spellName = newName2;
			if (!All.Any((SpellData s) => s.spellName == newName2))
			{
				All.Add(val);
			}
			Plugin.Log.LogInfo((object)("Cloned '" + source.spellName + "' → '" + newName2 + "'"));
			return val;
		}

		public static SpellData? Find(string spellName)
		{
			string spellName2 = spellName;
			return ((IEnumerable<SpellData>)All).FirstOrDefault((Func<SpellData, bool>)((SpellData s) => s.spellName == spellName2));
		}
	}
	public static class SpellVariants
	{
		public static void RegisterTriggers()
		{
		}
	}
	public static class VoiceInjector
	{
		[HarmonyPatch(typeof(VoiceManager), "LoadLocalisationData")]
		public static class InjectOnLoad
		{
			public static void Postfix(VoiceManager __instance)
			{
				//IL_002f: Unknown result type (might be due to invalid IL or missing references)
				foreach (KeyValuePair<string, Dictionary<SystemLanguage, string[]>> @override in Overrides)
				{
					foreach (KeyValuePair<SystemLanguage, string[]> item in @override.Value)
					{
						if (TryGetTranslatorCommands(__instance, item.Key, out Dictionary<string, string[]> commands))
						{
							commands[@override.Key] = item.Value;
						}
					}
				}
				Plugin.Log.LogInfo((object)$"VoiceInjector: injected {Overrides.Count} overrides.");
			}
		}

		private static readonly Dictionary<string, Dictionary<SystemLanguage, string[]>> Overrides = new Dictionary<string, Dictionary<SystemLanguage, string[]>>();

		private static VoiceManager? _vm;

		private static bool _rebuildLogged;

		public static IReadOnlyDictionary<string, Dictionary<SystemLanguage, string[]>> All => Overrides;

		private static VoiceManager? GetVM()
		{
			if ((Object)(object)_vm == (Object)null)
			{
				_vm = Object.FindAnyObjectByType<VoiceManager>();
			}
			return _vm;
		}

		public static void SetOverride(string locKey, SystemLanguage lang, string[] syllables)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			if (!Overrides.TryGetValue(locKey, out Dictionary<SystemLanguage, string[]> value))
			{
				value = new Dictionary<SystemLanguage, string[]>();
				Overrides[locKey] = value;
			}
			value[lang] = syllables.Select((string w) => w.ToUpperInvariant()).ToArray();
			PushLiveUpdate(locKey, lang, value[lang]);
		}

		public static void RemoveOverride(string locKey)
		{
			Overrides.Remove(locKey);
		}

		public static bool TryGetOverride(string locKey, SystemLanguage lang, out string[] syllables)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			syllables = Array.Empty<string>();
			if (Overrides.TryGetValue(locKey, out Dictionary<SystemLanguage, string[]> value))
			{
				return value.TryGetValue(lang, out syllables);
			}
			return false;
		}

		public static List<(string key, string trigger)> GetAllKeysWithTriggers(SystemLanguage lang = 10)
		{
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			VoiceManager vM = GetVM();
			if ((Object)(object)vM != (Object)null && TryGetTranslatorCommands(vM, lang, out Dictionary<string, string[]> commands))
			{
				return (from k in commands
					orderby k.Key
					select (k.Key, string.Join("-", k.Value))).ToList();
			}
			return LoadKeysFromFile(lang);
		}

		private static List<(string key, string trigger)> LoadKeysFromFile(SystemLanguage lang)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				string path = Path.Combine(Application.streamingAssetsPath, "Vosk", "Localisation");
				string path2 = Path.Combine(path, LanguageFileName(lang));
				if (!File.Exists(path2))
				{
					path2 = Path.Combine(path, "english.txt");
				}
				List<(string, string)> list = new List<(string, string)>();
				string[] array = File.ReadAllLines(path2);
				foreach (string text in array)
				{
					int num = text.IndexOf(" :: ", StringComparison.Ordinal);
					if (num >= 0)
					{
						string text2 = text.Substring(0, num).Trim();
						string item = text.Substring(num + 4).Trim();
						if (!string.IsNullOrEmpty(text2))
						{
							list.Add((text2, item));
						}
					}
				}
				return list.OrderBy(((string, string) x) => x.Item1).ToList();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("VoiceInjector: could not read localization file — " + ex.Message));
				return new List<(string, string)>();
			}
		}

		private static string LanguageFileName(SystemLanguage lang)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Invalid comparison between Unknown and I4
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Invalid comparison between Unknown and I4
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Invalid comparison between Unknown and I4
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Invalid comparison between Unknown and I4
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Invalid comparison between Unknown and I4
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Invalid comparison between Unknown and I4
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Invalid comparison between Unknown and I4
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Invalid comparison between Unknown and I4
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Invalid comparison between Unknown and I4
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Invalid comparison between Unknown and I4
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Invalid comparison between Unknown and I4
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Invalid comparison between Unknown and I4
			if ((int)lang == 14)
			{
				return "french.txt";
			}
			if ((int)lang == 15)
			{
				return "german.txt";
			}
			if ((int)lang == 34)
			{
				return "spanish.txt";
			}
			if ((int)lang == 21)
			{
				return "italian.txt";
			}
			if ((int)lang == 28)
			{
				return "portuguese.txt";
			}
			if ((int)lang == 30)
			{
				return "russian.txt";
			}
			if ((int)lang == 22)
			{
				return "japanese.txt";
			}
			if ((int)lang == 23)
			{
				return "korean.txt";
			}
			if ((int)lang == 40)
			{
				return "schinese.txt";
			}
			if ((int)lang == 27)
			{
				return "polish.txt";
			}
			if ((int)lang == 37)
			{
				return "turkish.txt";
			}
			if ((int)lang == 38)
			{
				return "ukrainian.txt";
			}
			return "english.txt";
		}

		public static string[]? GetCurrentWords(string locKey, SystemLanguage lang)
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			string locKey2 = locKey;
			VoiceManager vM = GetVM();
			if ((Object)(object)vM != (Object)null && TryGetTranslatorCommands(vM, lang, out Dictionary<string, string[]> commands))
			{
				if (!commands.TryGetValue(locKey2, out var value))
				{
					return null;
				}
				return value;
			}
			(string, string) tuple = LoadKeysFromFile(lang).FirstOrDefault<(string, string)>(((string key, string trigger) e) => e.key == locKey2);
			if (tuple.Item1 == null)
			{
				return null;
			}
			return ParseInput(tuple.Item2);
		}

		public static string[] ParseInput(string input)
		{
			return input.ToUpperInvariant().Split(new char[2] { ' ', '-' }, StringSplitOptions.RemoveEmptyEntries);
		}

		public static string SyllablesToDisplay(string[]? syllables)
		{
			if (syllables == null || syllables.Length == 0)
			{
				return "";
			}
			return string.Join("-", syllables);
		}

		private static void PushLiveUpdate(string locKey, SystemLanguage lang, string[] syllables)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			VoiceManager vM = GetVM();
			if (!((Object)(object)vM == (Object)null) && TryGetTranslatorCommands(vM, lang, out Dictionary<string, string[]> commands))
			{
				commands[locKey] = syllables;
				TryRebuildGrammar(vM);
			}
		}

		private static bool TryGetTranslatorCommands(VoiceManager vm, SystemLanguage lang, out Dictionary<string, string[]> commands)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			commands = null;
			if (!(typeof(VoiceManager).GetField("_voskTranslatorLookup", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(vm) is IDictionary dictionary))
			{
				return false;
			}
			object obj = dictionary[lang];
			if (obj == null)
			{
				return false;
			}
			commands = (obj.GetType().GetField("_commands", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj) as Dictionary<string, string[]>) ?? null;
			return commands != null;
		}

		private static void TryRebuildGrammar(VoiceManager vm)
		{
			string[] array = new string[8] { "RebuildGrammar", "RecreateGrammar", "BuildGrammar", "ReloadGrammar", "Reinitialize", "InitGrammar", "SetupGrammar", "LoadGrammar" };
			foreach (string name in array)
			{
				MethodInfo method = typeof(VoiceManager).GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (method != null)
				{
					method.Invoke(vm, null);
					_rebuildLogged = false;
					return;
				}
			}
			if (!_rebuildLogged)
			{
				_rebuildLogged = true;
				Plugin.Log.LogWarning((object)"VoiceInjector: grammar rebuild method not found — override applies on next scene load.");
				MethodInfo[] methods = typeof(VoiceManager).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				Plugin.Log.LogInfo((object)("VoiceManager methods: " + string.Join(", ", Array.ConvertAll(methods, (MethodInfo m) => m.Name))));
			}
		}
	}
	public static class WandRegistry
	{
		private static readonly FieldInfo SpellsField = typeof(VoiceSpell).GetField("spells", BindingFlags.Instance | BindingFlags.NonPublic);

		public static List<NetworkPuppetWandProp> All { get; private set; } = new List<NetworkPuppetWandProp>();


		public static void Refresh()
		{
			All = (from w in Object.FindObjectsByType<NetworkPuppetWandProp>((FindObjectsSortMode)0)
				where ((Component)w).gameObject.activeInHierarchy
				group w by ((NetworkBehaviour)w).netId into g
				select g.First() into w
				where (Object)(object)GetVoiceSpell(w) != (Object)null
				orderby ((Object)((Component)w).gameObject).name
				select w).ToList();
			Plugin.Log.LogInfo((object)$"WandRegistry: found {All.Count} VoiceSpell wands.");
		}

		public static VoiceSpell? GetVoiceSpell(NetworkPuppetWandProp wand)
		{
			Spell spell = wand.Spell;
			return (VoiceSpell?)(((object)((spell is VoiceSpell) ? spell : null)) ?? ((object)((Component)wand).GetComponent<VoiceSpell>()));
		}

		public static List<Spell> GetSpellComponents(NetworkPuppetWandProp wand)
		{
			VoiceSpell voiceSpell = GetVoiceSpell(wand);
			if ((Object)(object)voiceSpell == (Object)null)
			{
				return new List<Spell>();
			}
			if (!(SpellsField?.GetValue(voiceSpell) is Spell[] source))
			{
				return new List<Spell>();
			}
			return source.Where((Spell s) => (Object)(object)s != (Object)null).ToList();
		}

		public static List<SpellData> GetSpells(NetworkPuppetWandProp wand)
		{
			return (from s in GetSpellComponents(wand)
				where (Object)(object)s.SpellData != (Object)null
				select s.SpellData).ToList();
		}

		public static string GetWandId(NetworkPuppetWandProp wand)
		{
			return ((NetworkBehaviour)wand).netId.ToString();
		}
	}
}