Decompiled source of HavensBirthright v2.2.2

HavensBirthright.dll

Decompiled 2 weeks 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.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using HavensBirthright.Abilities;
using HavensBirthright.Patches;
using HavensBirthright.Session;
using Microsoft.CodeAnalysis;
using SunhavenMods.Shared;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using Wish;

[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("HavensBirthright")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+5c08b5aa5d0be9c4b93df77f697dc55d5ac97088")]
[assembly: AssemblyProduct("HavensBirthright")]
[assembly: AssemblyTitle("HavensBirthright")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[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 SunhavenMods.Shared
{
	public static class ConfigFileHelper
	{
		public static ConfigFile CreateNamedConfig(string pluginGuid, string configFileName, Action<string> logWarning = null)
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Expected O, but got Unknown
			string text = Path.Combine(Paths.ConfigPath, configFileName);
			string text2 = Path.Combine(Paths.ConfigPath, pluginGuid + ".cfg");
			try
			{
				if (!File.Exists(text) && File.Exists(text2))
				{
					File.Copy(text2, text);
				}
			}
			catch (Exception ex)
			{
				logWarning?.Invoke("[Config] Migration to " + configFileName + " failed: " + ex.Message);
			}
			return new ConfigFile(text, true);
		}

		public static bool ReplacePluginConfig(BaseUnityPlugin plugin, ConfigFile newConfig, Action<string> logWarning = null)
		{
			if ((Object)(object)plugin == (Object)null || newConfig == null)
			{
				return false;
			}
			try
			{
				Type typeFromHandle = typeof(BaseUnityPlugin);
				PropertyInfo property = typeFromHandle.GetProperty("Config", BindingFlags.Instance | BindingFlags.Public);
				if (property != null && property.CanWrite)
				{
					property.SetValue(plugin, newConfig, null);
					return true;
				}
				FieldInfo field = typeFromHandle.GetField("<Config>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);
				if (field != null)
				{
					field.SetValue(plugin, newConfig);
					return true;
				}
				FieldInfo[] fields = typeFromHandle.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
				foreach (FieldInfo fieldInfo in fields)
				{
					if (fieldInfo.FieldType == typeof(ConfigFile))
					{
						fieldInfo.SetValue(plugin, newConfig);
						return true;
					}
				}
			}
			catch (Exception ex)
			{
				logWarning?.Invoke("[Config] ReplacePluginConfig failed: " + ex.Message);
			}
			return false;
		}
	}
	public static class VersionChecker
	{
		public class VersionCheckResult
		{
			public bool Success { get; set; }

			public bool UpdateAvailable { get; set; }

			public string CurrentVersion { get; set; }

			public string LatestVersion { get; set; }

			public string ModName { get; set; }

			public string NexusUrl { get; set; }

			public string Changelog { get; set; }

			public string ErrorMessage { get; set; }
		}

		public class ModHealthSnapshot
		{
			public string PluginGuid { get; set; }

			public DateTime LastCheckUtc { get; set; }

			public int ExceptionCount { get; set; }

			public string LastError { get; set; }
		}

		private class VersionCheckRunner : MonoBehaviour
		{
			private ManualLogSource _pluginLog;

			public void StartCheck(string pluginGuid, string currentVersion, ManualLogSource pluginLog, Action<VersionCheckResult> onComplete)
			{
				_pluginLog = pluginLog;
				((MonoBehaviour)this).StartCoroutine(CheckVersionCoroutine(pluginGuid, currentVersion, onComplete));
			}

			private void LogInfo(string message)
			{
				ManualLogSource pluginLog = _pluginLog;
				if (pluginLog != null)
				{
					pluginLog.LogInfo((object)("[VersionChecker] " + message));
				}
			}

			private void LogWarningMsg(string message)
			{
				ManualLogSource pluginLog = _pluginLog;
				if (pluginLog != null)
				{
					pluginLog.LogWarning((object)("[VersionChecker] " + message));
				}
			}

			private void LogErrorMsg(string message)
			{
				ManualLogSource pluginLog = _pluginLog;
				if (pluginLog != null)
				{
					pluginLog.LogError((object)("[VersionChecker] " + message));
				}
			}

			private IEnumerator CheckVersionCoroutine(string pluginGuid, string currentVersion, Action<VersionCheckResult> onComplete)
			{
				VersionCheckResult result = new VersionCheckResult
				{
					CurrentVersion = currentVersion
				};
				UnityWebRequest www = UnityWebRequest.Get("https://azraelgodking.github.io/SunhavenMod/versions.json");
				try
				{
					www.timeout = 10;
					yield return www.SendWebRequest();
					if ((int)www.result == 2 || (int)www.result == 3)
					{
						result.Success = false;
						result.ErrorMessage = "Network error: " + www.error;
						RecordHealthError(pluginGuid, result.ErrorMessage);
						LogWarningMsg(result.ErrorMessage);
						onComplete?.Invoke(result);
						Object.Destroy((Object)(object)((Component)this).gameObject);
						yield break;
					}
					try
					{
						string text = www.downloadHandler.text;
						Match match = GetModPattern(pluginGuid).Match(text);
						if (!match.Success)
						{
							result.Success = false;
							result.ErrorMessage = "Mod '" + pluginGuid + "' not found in versions.json";
							RecordHealthError(pluginGuid, result.ErrorMessage);
							LogWarningMsg(result.ErrorMessage);
							onComplete?.Invoke(result);
							Object.Destroy((Object)(object)((Component)this).gameObject);
							yield break;
						}
						string value = match.Groups[1].Value;
						result.LatestVersion = ExtractJsonString(value, "version");
						result.ModName = ExtractJsonString(value, "name");
						result.NexusUrl = ExtractJsonString(value, "nexus");
						result.Changelog = ExtractJsonString(value, "changelog");
						if (string.IsNullOrEmpty(result.LatestVersion))
						{
							result.Success = false;
							result.ErrorMessage = "Could not parse version from response";
							RecordHealthError(pluginGuid, result.ErrorMessage);
							LogWarningMsg(result.ErrorMessage);
							onComplete?.Invoke(result);
							Object.Destroy((Object)(object)((Component)this).gameObject);
							yield break;
						}
						result.Success = true;
						result.UpdateAvailable = CompareVersions(currentVersion, result.LatestVersion) < 0;
						if (result.UpdateAvailable)
						{
							LogInfo("Update available for " + result.ModName + ": " + currentVersion + " -> " + result.LatestVersion);
						}
						else
						{
							LogInfo(result.ModName + " is up to date (v" + currentVersion + ")");
						}
					}
					catch (Exception ex)
					{
						result.Success = false;
						result.ErrorMessage = "Parse error: " + ex.Message;
						RecordHealthError(pluginGuid, result.ErrorMessage);
						LogErrorMsg(result.ErrorMessage);
					}
				}
				finally
				{
					((IDisposable)www)?.Dispose();
				}
				onComplete?.Invoke(result);
				Object.Destroy((Object)(object)((Component)this).gameObject);
			}

			private string ExtractJsonString(string json, string key)
			{
				Match match = ExtractFieldRegex.Match(json);
				while (match.Success)
				{
					if (string.Equals(match.Groups["key"].Value, key, StringComparison.Ordinal))
					{
						return match.Groups["value"].Value;
					}
					match = match.NextMatch();
				}
				return null;
			}
		}

		private const string VersionsUrl = "https://azraelgodking.github.io/SunhavenMod/versions.json";

		private static readonly Dictionary<string, ModHealthSnapshot> HealthByPluginGuid = new Dictionary<string, ModHealthSnapshot>(StringComparer.OrdinalIgnoreCase);

		private static readonly object HealthLock = new object();

		private static readonly Dictionary<string, Regex> ModPatternCache = new Dictionary<string, Regex>(StringComparer.Ordinal);

		private static readonly object ModPatternCacheLock = new object();

		private static readonly Regex ExtractFieldRegex = new Regex("\"(?<key>[^\"]+)\"\\s*:\\s*(?:\"(?<value>[^\"]*)\"|null)", RegexOptions.Compiled);

		public static void CheckForUpdate(string pluginGuid, string currentVersion, ManualLogSource logger = null, Action<VersionCheckResult> onComplete = null)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			TouchHealth(pluginGuid);
			VersionCheckRunner versionCheckRunner = new GameObject("VersionChecker").AddComponent<VersionCheckRunner>();
			Object.DontDestroyOnLoad((Object)(object)((Component)versionCheckRunner).gameObject);
			SceneRootSurvivor.TryRegisterPersistentRunnerGameObject(((Component)versionCheckRunner).gameObject);
			versionCheckRunner.StartCheck(pluginGuid, currentVersion, logger, onComplete);
		}

		public static ModHealthSnapshot GetHealthSnapshot(string pluginGuid)
		{
			if (string.IsNullOrWhiteSpace(pluginGuid))
			{
				return null;
			}
			lock (HealthLock)
			{
				if (!HealthByPluginGuid.TryGetValue(pluginGuid, out ModHealthSnapshot value))
				{
					return null;
				}
				return new ModHealthSnapshot
				{
					PluginGuid = value.PluginGuid,
					LastCheckUtc = value.LastCheckUtc,
					ExceptionCount = value.ExceptionCount,
					LastError = value.LastError
				};
			}
		}

		public static int CompareVersions(string v1, string v2)
		{
			if (string.IsNullOrEmpty(v1) || string.IsNullOrEmpty(v2))
			{
				return 0;
			}
			v1 = v1.TrimStart('v', 'V');
			v2 = v2.TrimStart('v', 'V');
			int num = v1.IndexOfAny(new char[2] { '-', '+' });
			if (num >= 0)
			{
				v1 = v1.Substring(0, num);
			}
			int num2 = v2.IndexOfAny(new char[2] { '-', '+' });
			if (num2 >= 0)
			{
				v2 = v2.Substring(0, num2);
			}
			string[] array = v1.Split(new char[1] { '.' });
			string[] array2 = v2.Split(new char[1] { '.' });
			int num3 = Math.Max(array.Length, array2.Length);
			for (int i = 0; i < num3; i++)
			{
				int result;
				int num4 = ((i < array.Length && int.TryParse(array[i], out result)) ? result : 0);
				int result2;
				int num5 = ((i < array2.Length && int.TryParse(array2[i], out result2)) ? result2 : 0);
				if (num4 < num5)
				{
					return -1;
				}
				if (num4 > num5)
				{
					return 1;
				}
			}
			return 0;
		}

		private static void TouchHealth(string pluginGuid)
		{
			if (string.IsNullOrWhiteSpace(pluginGuid))
			{
				return;
			}
			lock (HealthLock)
			{
				if (!HealthByPluginGuid.TryGetValue(pluginGuid, out ModHealthSnapshot value))
				{
					value = new ModHealthSnapshot
					{
						PluginGuid = pluginGuid
					};
					HealthByPluginGuid[pluginGuid] = value;
				}
				value.LastCheckUtc = DateTime.UtcNow;
			}
		}

		private static void RecordHealthError(string pluginGuid, string errorMessage)
		{
			if (string.IsNullOrWhiteSpace(pluginGuid))
			{
				return;
			}
			lock (HealthLock)
			{
				if (!HealthByPluginGuid.TryGetValue(pluginGuid, out ModHealthSnapshot value))
				{
					value = new ModHealthSnapshot
					{
						PluginGuid = pluginGuid
					};
					HealthByPluginGuid[pluginGuid] = value;
				}
				value.LastCheckUtc = DateTime.UtcNow;
				value.ExceptionCount++;
				value.LastError = errorMessage;
			}
		}

		private static Regex GetModPattern(string pluginGuid)
		{
			lock (ModPatternCacheLock)
			{
				if (!ModPatternCache.TryGetValue(pluginGuid, out Regex value))
				{
					value = new Regex("\"" + Regex.Escape(pluginGuid) + "\"\\s*:\\s*\\{([^}]+)\\}", RegexOptions.Compiled | RegexOptions.Singleline);
					ModPatternCache[pluginGuid] = value;
				}
				return value;
			}
		}
	}
	public static class VersionCheckerExtensions
	{
		public static void NotifyUpdateAvailable(this VersionChecker.VersionCheckResult result, ManualLogSource logger = null)
		{
			if (!result.UpdateAvailable)
			{
				return;
			}
			string text = result.ModName + " update available: v" + result.LatestVersion;
			try
			{
				Type type = ReflectionHelper.FindWishType("NotificationStack");
				if (type != null)
				{
					Type type2 = ReflectionHelper.FindType("SingletonBehaviour`1", "Wish");
					if (type2 != null)
					{
						object obj = type2.MakeGenericType(type).GetProperty("Instance")?.GetValue(null);
						if (obj != null)
						{
							MethodInfo method = type.GetMethod("SendNotification", new Type[5]
							{
								typeof(string),
								typeof(int),
								typeof(int),
								typeof(bool),
								typeof(bool)
							});
							if (method != null)
							{
								method.Invoke(obj, new object[5] { text, 0, 1, false, true });
								return;
							}
						}
					}
				}
			}
			catch (Exception ex)
			{
				if (logger != null)
				{
					logger.LogWarning((object)("Failed to send native notification: " + ex.Message));
				}
			}
			if (logger != null)
			{
				logger.LogWarning((object)("[UPDATE AVAILABLE] " + text));
			}
			if (!string.IsNullOrEmpty(result.NexusUrl) && logger != null)
			{
				logger.LogWarning((object)("Download at: " + result.NexusUrl));
			}
		}
	}
	public static class SceneRootSurvivor
	{
		private static readonly object Lock = new object();

		private static readonly List<string> NoKillSubstrings = new List<string>();

		private static Harmony _harmony;

		public static void TryRegisterPersistentRunnerGameObject(GameObject go)
		{
			if (!((Object)(object)go == (Object)null))
			{
				TryAddNoKillListSubstring(((Object)go).name);
			}
		}

		public static void TryAddNoKillListSubstring(string nameSubstring)
		{
			if (string.IsNullOrEmpty(nameSubstring))
			{
				return;
			}
			lock (Lock)
			{
				bool flag = false;
				for (int i = 0; i < NoKillSubstrings.Count; i++)
				{
					if (string.Equals(NoKillSubstrings[i], nameSubstring, StringComparison.OrdinalIgnoreCase))
					{
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					NoKillSubstrings.Add(nameSubstring);
				}
			}
			EnsurePatched();
		}

		private static void EnsurePatched()
		{
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: 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_009d: Expected O, but got Unknown
			//IL_00a3: Expected O, but got Unknown
			if (_harmony != null)
			{
				return;
			}
			lock (Lock)
			{
				if (_harmony == null)
				{
					MethodInfo methodInfo = AccessTools.Method(typeof(Scene), "GetRootGameObjects", Type.EmptyTypes, (Type[])null);
					if (!(methodInfo == null))
					{
						string text = typeof(SceneRootSurvivor).Assembly.GetName().Name ?? "Unknown";
						Harmony val = new Harmony("SunhavenMods.SceneRootSurvivor." + text);
						val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(SceneRootSurvivor), "OnGetRootGameObjectsPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
						_harmony = val;
					}
				}
			}
		}

		private static void OnGetRootGameObjectsPostfix(ref GameObject[] __result)
		{
			if (__result == null || __result.Length == 0)
			{
				return;
			}
			List<string> list;
			lock (Lock)
			{
				if (NoKillSubstrings.Count == 0)
				{
					return;
				}
				list = new List<string>(NoKillSubstrings);
			}
			List<GameObject> list2 = new List<GameObject>(__result);
			for (int i = 0; i < list.Count; i++)
			{
				string noKill = list[i];
				list2.RemoveAll((GameObject a) => (Object)(object)a != (Object)null && ((Object)a).name.IndexOf(noKill, StringComparison.OrdinalIgnoreCase) >= 0);
			}
			__result = list2.ToArray();
		}
	}
	public static class ReflectionHelper
	{
		public static readonly BindingFlags AllBindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy;

		public static Type FindType(string typeName, params string[] namespaces)
		{
			string typeName2 = typeName;
			Type type = AccessTools.TypeByName(typeName2);
			if (type != null)
			{
				return type;
			}
			for (int i = 0; i < namespaces.Length; i++)
			{
				type = AccessTools.TypeByName(namespaces[i] + "." + typeName2);
				if (type != null)
				{
					return type;
				}
			}
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				try
				{
					type = assembly.GetTypes().FirstOrDefault((Type t) => t.Name == typeName2 || t.FullName == typeName2);
					if (type != null)
					{
						return type;
					}
				}
				catch (ReflectionTypeLoadException)
				{
				}
			}
			return null;
		}

		public static Type FindWishType(string typeName)
		{
			return FindType(typeName, "Wish");
		}

		public static object GetStaticValue(Type type, string memberName)
		{
			if (type == null)
			{
				return null;
			}
			try
			{
				PropertyInfo property = type.GetProperty(memberName, AllBindingFlags);
				if (property != null && property.GetMethod != null && property.GetIndexParameters().Length == 0)
				{
					return property.GetValue(null);
				}
			}
			catch (AmbiguousMatchException)
			{
				return null;
			}
			FieldInfo field = type.GetField(memberName, AllBindingFlags);
			if (field != null)
			{
				return field.GetValue(null);
			}
			return null;
		}

		public static object GetSingletonInstance(Type type)
		{
			if (type == null)
			{
				return null;
			}
			string[] array = new string[5] { "Instance", "instance", "_instance", "Singleton", "singleton" };
			foreach (string memberName in array)
			{
				object staticValue = GetStaticValue(type, memberName);
				if (staticValue != null)
				{
					return staticValue;
				}
			}
			return null;
		}

		public static object GetInstanceValue(object instance, string memberName)
		{
			if (instance == null)
			{
				return null;
			}
			Type type = instance.GetType();
			while (type != null)
			{
				PropertyInfo property = type.GetProperty(memberName, AllBindingFlags);
				if (property != null && property.GetMethod != null)
				{
					return property.GetValue(instance);
				}
				FieldInfo field = type.GetField(memberName, AllBindingFlags);
				if (field != null)
				{
					return field.GetValue(instance);
				}
				type = type.BaseType;
			}
			return null;
		}

		public static bool SetInstanceValue(object instance, string memberName, object value)
		{
			if (instance == null)
			{
				return false;
			}
			Type type = instance.GetType();
			while (type != null)
			{
				PropertyInfo property = type.GetProperty(memberName, AllBindingFlags);
				if (property != null && property.SetMethod != null)
				{
					property.SetValue(instance, value);
					return true;
				}
				FieldInfo field = type.GetField(memberName, AllBindingFlags);
				if (field != null)
				{
					field.SetValue(instance, value);
					return true;
				}
				type = type.BaseType;
			}
			return false;
		}

		public static object InvokeMethod(object instance, string methodName, params object[] args)
		{
			if (instance == null)
			{
				return null;
			}
			Type type = instance.GetType();
			Type[] array = args?.Select((object a) => a?.GetType() ?? typeof(object)).ToArray() ?? Type.EmptyTypes;
			MethodInfo methodInfo = AccessTools.Method(type, methodName, array, (Type[])null);
			if (methodInfo == null)
			{
				methodInfo = type.GetMethod(methodName, AllBindingFlags);
			}
			if (methodInfo == null)
			{
				return null;
			}
			return methodInfo.Invoke(instance, args);
		}

		public static object InvokeStaticMethod(Type type, string methodName, params object[] args)
		{
			if (type == null)
			{
				return null;
			}
			Type[] array = args?.Select((object a) => a?.GetType() ?? typeof(object)).ToArray() ?? Type.EmptyTypes;
			MethodInfo methodInfo = AccessTools.Method(type, methodName, array, (Type[])null);
			if (methodInfo == null)
			{
				methodInfo = type.GetMethod(methodName, AllBindingFlags);
			}
			if (methodInfo == null)
			{
				return null;
			}
			return methodInfo.Invoke(null, args);
		}

		public static FieldInfo[] GetAllFields(Type type)
		{
			if (type == null)
			{
				return Array.Empty<FieldInfo>();
			}
			FieldInfo[] fields = type.GetFields(AllBindingFlags);
			IEnumerable<FieldInfo> second;
			if (!(type.BaseType != null) || !(type.BaseType != typeof(object)))
			{
				second = Enumerable.Empty<FieldInfo>();
			}
			else
			{
				IEnumerable<FieldInfo> allFields = GetAllFields(type.BaseType);
				second = allFields;
			}
			return fields.Concat(second).Distinct().ToArray();
		}

		public static PropertyInfo[] GetAllProperties(Type type)
		{
			if (type == null)
			{
				return Array.Empty<PropertyInfo>();
			}
			PropertyInfo[] properties = type.GetProperties(AllBindingFlags);
			IEnumerable<PropertyInfo> second;
			if (!(type.BaseType != null) || !(type.BaseType != typeof(object)))
			{
				second = Enumerable.Empty<PropertyInfo>();
			}
			else
			{
				IEnumerable<PropertyInfo> allProperties = GetAllProperties(type.BaseType);
				second = allProperties;
			}
			return (from p in properties.Concat(second)
				group p by p.Name into g
				select g.First()).ToArray();
		}

		public static T TryGetValue<T>(object instance, string memberName, T defaultValue = default(T))
		{
			try
			{
				object instanceValue = GetInstanceValue(instance, memberName);
				if (instanceValue is T result)
				{
					return result;
				}
				if (instanceValue != null && typeof(T).IsAssignableFrom(instanceValue.GetType()))
				{
					return (T)instanceValue;
				}
				return defaultValue;
			}
			catch
			{
				return defaultValue;
			}
		}
	}
	public abstract class PersistentRunnerBase : MonoBehaviour
	{
		private bool _wasInGame;

		private float _lastHeartbeat;

		private string _lastSceneName = "";

		private float _lastSceneCheckTime;

		private const float SceneCheckInterval = 0.5f;

		protected virtual float HeartbeatInterval => 0f;

		protected virtual string RunnerName => ((object)this).GetType().Name;

		protected virtual void OnUpdate()
		{
		}

		protected virtual void OnMenuTransition()
		{
		}

		protected virtual void OnGameTransition()
		{
		}

		protected virtual void Log(string message)
		{
		}

		protected virtual void LogWarning(string message)
		{
		}

		public static T CreateRunner<T>() where T : PersistentRunnerBase
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Expected O, but got Unknown
			GameObject val = new GameObject("[" + typeof(T).Name + "]")
			{
				hideFlags = (HideFlags)61
			};
			Object.DontDestroyOnLoad((Object)val);
			SceneRootSurvivor.TryRegisterPersistentRunnerGameObject(val);
			return val.AddComponent<T>();
		}

		protected virtual void Awake()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			Scene activeScene = SceneManager.GetActiveScene();
			_lastSceneName = ((Scene)(ref activeScene)).name;
			_wasInGame = !SceneHelpers.IsMenuScene(_lastSceneName);
			Log("[" + RunnerName + "] Initialized in scene: " + _lastSceneName);
		}

		protected virtual void Update()
		{
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			if (HeartbeatInterval > 0f)
			{
				_lastHeartbeat += Time.deltaTime;
				if (_lastHeartbeat >= HeartbeatInterval)
				{
					_lastHeartbeat = 0f;
					Log("[" + RunnerName + "] Heartbeat - still alive");
				}
			}
			float unscaledTime = Time.unscaledTime;
			if (unscaledTime - _lastSceneCheckTime >= 0.5f)
			{
				_lastSceneCheckTime = unscaledTime;
				Scene activeScene = SceneManager.GetActiveScene();
				string name = ((Scene)(ref activeScene)).name;
				if (name != _lastSceneName)
				{
					_lastSceneName = name;
					HandleSceneChange(name);
				}
			}
			try
			{
				OnUpdate();
			}
			catch (Exception ex)
			{
				LogWarning("[" + RunnerName + "] Error in OnUpdate: " + ex.Message);
			}
		}

		private void HandleSceneChange(string sceneName)
		{
			bool flag = SceneHelpers.IsMenuScene(sceneName);
			if (_wasInGame && flag)
			{
				Log("[" + RunnerName + "] Menu transition detected");
				try
				{
					OnMenuTransition();
				}
				catch (Exception ex)
				{
					LogWarning("[" + RunnerName + "] Error in OnMenuTransition: " + ex.Message);
				}
			}
			else if (!_wasInGame && !flag)
			{
				Log("[" + RunnerName + "] Game transition detected");
				try
				{
					OnGameTransition();
				}
				catch (Exception ex2)
				{
					LogWarning("[" + RunnerName + "] Error in OnGameTransition: " + ex2.Message);
				}
			}
			_wasInGame = !flag;
		}

		protected virtual void OnDestroy()
		{
			//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)
			Scene activeScene = SceneManager.GetActiveScene();
			string text = (((Scene)(ref activeScene)).name ?? string.Empty).ToLowerInvariant();
			if (!Application.isPlaying || text.Contains("menu") || text.Contains("title"))
			{
				Log("[" + RunnerName + "] OnDestroy during app quit/menu unload (expected).");
			}
			else
			{
				LogWarning("[" + RunnerName + "] OnDestroy outside quit/menu (unexpected).");
			}
		}
	}
	public static class SceneHelpers
	{
		private static readonly string[] MenuScenePatterns = new string[3] { "menu", "title", "bootstrap" };

		private static readonly string[] ExactMenuScenes = new string[2] { "MainMenu", "Bootstrap" };

		public static bool IsMenuScene(string sceneName)
		{
			if (string.IsNullOrEmpty(sceneName))
			{
				return true;
			}
			string[] exactMenuScenes = ExactMenuScenes;
			foreach (string text in exactMenuScenes)
			{
				if (sceneName == text)
				{
					return true;
				}
			}
			string text2 = sceneName.ToLowerInvariant();
			exactMenuScenes = MenuScenePatterns;
			foreach (string value in exactMenuScenes)
			{
				if (text2.Contains(value))
				{
					return true;
				}
			}
			return false;
		}

		public static bool IsCurrentSceneMenu()
		{
			//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)
			Scene activeScene = SceneManager.GetActiveScene();
			return IsMenuScene(((Scene)(ref activeScene)).name);
		}

		public static bool IsInGame()
		{
			return !IsCurrentSceneMenu();
		}

		public static string GetCurrentSceneName()
		{
			//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)
			Scene activeScene = SceneManager.GetActiveScene();
			return ((Scene)(ref activeScene)).name;
		}

		public static bool IsMainMenu()
		{
			//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)
			Scene activeScene = SceneManager.GetActiveScene();
			return ((Scene)(ref activeScene)).name == "MainMenu";
		}
	}
	public static class TextInputFocusGuard
	{
		private const float DefaultPollIntervalSeconds = 0.25f;

		private static float _nextPollTime = -1f;

		private static bool _cachedDefer;

		private static bool _tmpTypeLookupDone;

		private static Type _tmpInputFieldType;

		private static bool _qcLookupDone;

		private static Type _qcType;

		private static PropertyInfo _qcInstanceProp;

		private static PropertyInfo _qcIsActiveProp;

		private static FieldInfo _qcIsActiveField;

		public static bool ShouldDeferModHotkeys(ManualLogSource debugLog = null, float pollIntervalSeconds = 0.25f)
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (realtimeSinceStartup < _nextPollTime)
			{
				return _cachedDefer;
			}
			_nextPollTime = realtimeSinceStartup + Mathf.Max(0.05f, pollIntervalSeconds);
			bool flag = false;
			try
			{
				if (GUIUtility.keyboardControl != 0)
				{
					flag = true;
				}
				if (!flag)
				{
					EventSystem current = EventSystem.current;
					GameObject val = ((current != null) ? current.currentSelectedGameObject : null);
					if ((Object)(object)val != (Object)null)
					{
						if ((Object)(object)val.GetComponent<InputField>() != (Object)null)
						{
							flag = true;
						}
						else if (TryGetTmpInputField(val))
						{
							flag = true;
						}
					}
				}
				if (!flag && IsQuantumConsoleActive(debugLog))
				{
					flag = true;
				}
			}
			catch (Exception ex)
			{
				if (debugLog != null)
				{
					debugLog.LogDebug((object)("[TextInputFocusGuard] " + ex.Message));
				}
			}
			_cachedDefer = flag;
			return flag;
		}

		private static bool TryGetTmpInputField(GameObject go)
		{
			if (!_tmpTypeLookupDone)
			{
				_tmpTypeLookupDone = true;
				_tmpInputFieldType = AccessTools.TypeByName("TMPro.TMP_InputField");
			}
			if (_tmpInputFieldType == null)
			{
				return false;
			}
			return (Object)(object)go.GetComponent(_tmpInputFieldType) != (Object)null;
		}

		private static bool IsQuantumConsoleActive(ManualLogSource debugLog)
		{
			try
			{
				if (!_qcLookupDone)
				{
					_qcLookupDone = true;
					_qcType = AccessTools.TypeByName("QFSW.QC.QuantumConsole");
					if (_qcType != null)
					{
						_qcInstanceProp = AccessTools.Property(_qcType, "Instance");
						_qcIsActiveProp = AccessTools.Property(_qcType, "IsActive");
						_qcIsActiveField = AccessTools.Field(_qcType, "isActive") ?? AccessTools.Field(_qcType, "_isActive");
					}
				}
				if (_qcType == null)
				{
					return false;
				}
				object obj = _qcInstanceProp?.GetValue(null);
				if (obj == null)
				{
					return false;
				}
				if (_qcIsActiveProp != null && _qcIsActiveProp.PropertyType == typeof(bool))
				{
					return (bool)_qcIsActiveProp.GetValue(obj);
				}
				if (_qcIsActiveField != null && _qcIsActiveField.FieldType == typeof(bool))
				{
					return (bool)_qcIsActiveField.GetValue(obj);
				}
			}
			catch (Exception ex)
			{
				if (debugLog != null)
				{
					debugLog.LogDebug((object)("[TextInputFocusGuard] Quantum Console focus check failed: " + ex.Message));
				}
			}
			return false;
		}
	}
}
namespace HavensBirthright
{
	public class BirthrightRunner : PersistentRunnerBase
	{
		private float _outdoorTime;

		private float _lastTidalBlessingCheck;

		private float _lastInfernalForgeCheck;

		private float _lastFontOfLightCheck;

		private bool _tidalBlessingDiagLogged;

		protected override string RunnerName => "BirthrightRunner";

		protected override void OnUpdate()
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			if (!SceneHelpers.IsInGame())
			{
				return;
			}
			CharacterSessionController.OnUpdateIdentityChecks();
			CheckAbilityToggleHotkey();
			if (!TextInputFocusGuard.ShouldDeferModHotkeys(Plugin.Log) && (int)Plugin.StaticReloadConfigKey != 0 && Input.GetKeyDown(Plugin.StaticReloadConfigKey))
			{
				Plugin.Instance?.ReloadConfig();
			}
			if (!RacialConfig.EnableRacialBonuses.Value)
			{
				return;
			}
			RacialBonusManager racialBonusManager = Plugin.GetRacialBonusManager();
			if (racialBonusManager == null)
			{
				return;
			}
			Race? playerRace = racialBonusManager.GetPlayerRace();
			if (!playerRace.HasValue)
			{
				RaceDetectionService.RetryRaceDetection();
				playerRace = racialBonusManager.GetPlayerRace();
				if (!playerRace.HasValue)
				{
					return;
				}
			}
			GameApis.EnsureWishCachesInitialized();
			if (!StatFrameCache.IsCacheValid || (Time.frameCount & 3) == 0)
			{
				StatFrameCache.Update(playerRace.Value, GameApis.DayCycleType);
			}
			if (AbilityConfig.EnableActiveAbilities.Value)
			{
				Race race = ElementalVariantResolver.ResolveElementalAbilityRace(playerRace.Value);
				if (playerRace.Value == Race.AmariBird && AbilityConfig.EnableTailwind.Value)
				{
					UpdateTailwind();
				}
				if (race == Race.WaterElemental && AbilityConfig.EnableTidalBlessing.Value)
				{
					UpdateTidalBlessing();
				}
				if ((race == Race.FireElemental || race == Race.Elemental) && AbilityConfig.EnableInfernalForge.Value)
				{
					UpdateInfernalForge();
				}
				if (playerRace.Value == Race.Angel && AbilityConfig.EnableFontOfLight.Value)
				{
					UpdateFontOfLight();
				}
			}
		}

		protected override void OnMenuTransition()
		{
			BirthrightGameSaveContext.Reset();
			ResetAllStateForNewSave();
		}

		protected override void OnGameTransition()
		{
			ResetInstanceState();
		}

		public static void ResetAllStateForNewSave()
		{
			CharacterSessionController.ClearTrackedCharacterId();
			StatFrameCache.Reset();
			ActiveAbilityManager.ResetAll();
			Plugin.GetRacialBonusManager()?.ClearPlayerRace();
			PlayerPatches.ResetRaceDetection();
			CombatPatches.ResetNineLives();
			GameApis.ResetAllApiCaches();
			AbilityPatches.ResetReflectionCache();
			Plugin.GetRunner()?.ResetRunnerTimersOnly();
		}

		private void ResetRunnerTimersOnly()
		{
			_outdoorTime = 0f;
			_lastTidalBlessingCheck = 0f;
			_lastInfernalForgeCheck = 0f;
			_lastFontOfLightCheck = 0f;
			_tidalBlessingDiagLogged = false;
		}

		public void ResetInstanceState()
		{
			ResetRunnerTimersOnly();
			StatFrameCache.Reset();
			GameApis.ResetFarmingTileAndInventoryScanState();
		}

		protected override void Log(string message)
		{
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogInfo((object)message);
			}
		}

		protected override void LogWarning(string message)
		{
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogWarning((object)message);
			}
		}

		private void UpdateTailwind()
		{
			string text = SceneHelpers.GetCurrentSceneName().ToLowerInvariant();
			if (text.Contains("house") || text.Contains("inn") || text.Contains("shop") || text.Contains("cave") || text.Contains("mine") || text.Contains("dungeon") || text.Contains("interior"))
			{
				_outdoorTime = 0f;
				ActiveAbilityManager.SetBonusValue("TailwindOutdoor", 0f);
			}
			else
			{
				_outdoorTime += Time.deltaTime;
				float value = Mathf.Min(_outdoorTime / 60f * AbilityConfig.TailwindBonusPerMinute.Value, AbilityConfig.TailwindMaxBonus.Value);
				ActiveAbilityManager.SetBonusValue("TailwindOutdoor", value);
			}
		}

		private void UpdateTidalBlessing()
		{
			//IL_0333: 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)
			if (!ActiveAbilityManager.IsRuntimeEnabled("TidalBlessing"))
			{
				_tidalBlessingDiagLogged = false;
			}
			else
			{
				if (Time.time - _lastTidalBlessingCheck < AbilityConfig.TidalBlessingCooldown.Value)
				{
					return;
				}
				_lastTidalBlessingCheck = Time.time;
				if (ActiveAbilityManager.IsOnCooldown("TidalBlessing"))
				{
					return;
				}
				try
				{
					Player instance = Player.Instance;
					if ((Object)(object)instance == (Object)null)
					{
						return;
					}
					float maxHealth = instance.MaxHealth;
					float num = ReflectionHelper.TryGetValue(instance, "health", -1f);
					if (num < 0f)
					{
						num = ReflectionHelper.TryGetValue(instance, "Health", maxHealth);
					}
					float num2 = num / maxHealth * 100f;
					if (!_tidalBlessingDiagLogged)
					{
						_tidalBlessingDiagLogged = true;
						ManualLogSource log = Plugin.Log;
						if (log != null)
						{
							log.LogInfo((object)"[TidalBlessing] === DIAGNOSTIC ===");
						}
						ManualLogSource log2 = Plugin.Log;
						if (log2 != null)
						{
							log2.LogInfo((object)$"[TidalBlessing]   HP: {num:F0}/{maxHealth:F0} ({num2:F0}%), threshold: {AbilityConfig.TidalBlessingHPThreshold.Value}%");
						}
						ManualLogSource log3 = Plugin.Log;
						if (log3 != null)
						{
							log3.LogInfo((object)("[TidalBlessing]   TileManager: type=" + ((GameApis.TileManagerType != null) ? "ok" : "NULL") + ", instance=" + ((GameApis.TileManagerInstance != null) ? "ok" : "NULL")));
						}
						ManualLogSource log4 = Plugin.Log;
						if (log4 != null)
						{
							log4.LogInfo((object)("[TidalBlessing]   Methods: Water=" + ((GameApis.WaterTileMethod != null) ? "ok" : "NULL") + ", farmingData field=" + ((GameApis.FarmingDataField != null) ? "ok" : "NULL")));
						}
						ManualLogSource log5 = Plugin.Log;
						if (log5 != null)
						{
							log5.LogInfo((object)$"[TidalBlessing]   Cooldown: {AbilityConfig.TidalBlessingCooldown.Value}s, HP cost: {AbilityConfig.TidalBlessingHPCostPercent.Value}%");
						}
						if (GameApis.FarmingDataField != null && GameApis.TileManagerInstance != null)
						{
							try
							{
								if (GameApis.FarmingDataField.GetValue(GameApis.TileManagerInstance) is IDictionary dictionary)
								{
									int num3 = 0;
									int num4 = 0;
									{
										IDictionaryEnumerator dictionaryEnumerator = dictionary.GetEnumerator();
										try
										{
											while (dictionaryEnumerator.MoveNext())
											{
												switch (((DictionaryEntry)dictionaryEnumerator.Current).Value.ToString())
												{
												case "Hoed":
												case "2":
													num3++;
													break;
												case "Watered":
												case "3":
													num4++;
													break;
												}
											}
										}
										finally
										{
											IDisposable disposable = dictionaryEnumerator as IDisposable;
											if (disposable != null)
											{
												disposable.Dispose();
											}
										}
									}
									ManualLogSource log6 = Plugin.Log;
									if (log6 != null)
									{
										log6.LogInfo((object)$"[TidalBlessing]   farmingData: {dictionary.Count} total, {num3} hoed, {num4} watered");
									}
								}
							}
							catch (Exception ex)
							{
								ManualLogSource log7 = Plugin.Log;
								if (log7 != null)
								{
									log7.LogDebug((object)("[BirthrightRunner] TidalBlessing diagnostic: " + ex.Message));
								}
							}
						}
						ManualLogSource log8 = Plugin.Log;
						if (log8 != null)
						{
							log8.LogInfo((object)("[TidalBlessing]   Scene: " + SceneHelpers.GetCurrentSceneName()));
						}
						ManualLogSource log9 = Plugin.Log;
						if (log9 != null)
						{
							log9.LogInfo((object)$"[TidalBlessing]   Player world pos: ({((Component)instance).transform.position.x:F1}, {((Component)instance).transform.position.y:F1})");
						}
						ManualLogSource log10 = Plugin.Log;
						if (log10 != null)
						{
							log10.LogInfo((object)("[TidalBlessing]   Grid distance filter: " + ((GameApis.GridCellToWorldMethod != null && GameApis.GridInstance != null) ? "enabled (radius=1)" : "disabled (no Grid)")));
						}
						ManualLogSource log11 = Plugin.Log;
						if (log11 != null)
						{
							log11.LogInfo((object)"[TidalBlessing] === END DIAGNOSTIC ===");
						}
					}
					if (num2 <= AbilityConfig.TidalBlessingHPThreshold.Value)
					{
						return;
					}
					if (GameApis.TileManagerType != null && GameApis.IsWateredMethod != null && GameApis.WaterTileMethod != null)
					{
						UpdateTidalBlessingViaTileManager(instance, maxHealth, num);
						return;
					}
					ManualLogSource log12 = Plugin.Log;
					if (log12 != null)
					{
						log12.LogInfo((object)"[TidalBlessing] Using fallback Crop approach (TileManager methods not available)");
					}
					UpdateTidalBlessingViaCropObjects(instance, maxHealth, num);
				}
				catch (Exception ex2)
				{
					Plugin.Log.LogWarning((object)("[TidalBlessing] Error: " + ex2.Message));
					ManualLogSource log13 = Plugin.Log;
					if (log13 != null)
					{
						log13.LogWarning((object)("[TidalBlessing] Stack: " + ex2.StackTrace));
					}
				}
			}
		}

		private void UpdateTidalBlessingViaTileManager(Player player, float maxHP, float currentHP)
		{
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0293: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_0188: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0191: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_020a: Unknown result type (might be due to invalid IL or missing references)
			//IL_020f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0246: Unknown result type (might be due to invalid IL or missing references)
			object tileManagerInstance = GameApis.TileManagerInstance;
			Object val = (Object)((tileManagerInstance is Object) ? tileManagerInstance : null);
			if (val != null && val == (Object)null)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)"[TidalBlessing] TileManager instance was stale — re-acquiring");
				}
				GameApis.TileManagerInstance = null;
			}
			if (GameApis.TileManagerInstance == null)
			{
				GameApis.TryGetTileManagerInstance();
			}
			if (GameApis.TileManagerInstance == null)
			{
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogInfo((object)"[TidalBlessing] TileManager instance not available — skipping this cycle");
				}
			}
			else
			{
				if (GameApis.FarmingDataField == null || !(GameApis.FarmingDataField.GetValue(GameApis.TileManagerInstance) is IDictionary dictionary) || dictionary.Count == 0)
				{
					return;
				}
				float num = maxHP * (AbilityConfig.TidalBlessingHPCostPercent.Value / 100f);
				Scene activeScene = SceneManager.GetActiveScene();
				short num2 = (short)((Scene)(ref activeScene)).buildIndex;
				int num3 = 0;
				int num4 = 0;
				List<Vector2Int> list = new List<Vector2Int>();
				Vector2 val2 = default(Vector2);
				((Vector2)(ref val2))..ctor(((Component)player).transform.position.x, ((Component)player).transform.position.y);
				int num5 = 1;
				bool flag = GameApis.GridCellToWorldMethod != null && GameApis.GridInstance != null;
				foreach (DictionaryEntry item2 in dictionary)
				{
					string text = item2.Value.ToString();
					if (!(text == "Hoed") && !(text == "2"))
					{
						continue;
					}
					Vector2Int item = (Vector2Int)item2.Key;
					if (flag)
					{
						try
						{
							Vector3 val3 = (Vector3)GameApis.GridCellToWorldMethod.Invoke(GameApis.GridInstance, new object[1] { (object)new Vector3Int(((Vector2Int)(ref item)).x, ((Vector2Int)(ref item)).y, 0) });
							if (Vector2.Distance(val2, new Vector2(val3.x, val3.y)) <= (float)num5)
							{
								list.Add(item);
							}
						}
						catch
						{
							list.Add(item);
						}
					}
					else
					{
						list.Add(item);
					}
				}
				num4 = list.Count;
				bool flag2 = default(bool);
				foreach (Vector2Int item3 in list)
				{
					if ((currentHP - num * (float)(num3 + 1)) / maxHP * 100f <= AbilityConfig.TidalBlessingHPThreshold.Value)
					{
						break;
					}
					try
					{
						object obj2 = GameApis.WaterTileMethod.Invoke(GameApis.TileManagerInstance, new object[2] { item3, num2 });
						int num6;
						if (obj2 is bool)
						{
							flag2 = (bool)obj2;
							num6 = 1;
						}
						else
						{
							num6 = 0;
						}
						if (((uint)num6 & (flag2 ? 1u : 0u)) != 0)
						{
							num3++;
						}
					}
					catch (Exception ex)
					{
						ManualLogSource log3 = Plugin.Log;
						if (log3 != null)
						{
							log3.LogWarning((object)$"[TidalBlessing] Water({item3}) error: {ex.Message}");
						}
					}
				}
				if (num3 > 0)
				{
					float num7 = num * (float)num3;
					float num8 = currentHP - num7;
					if (num8 < 1f)
					{
						num8 = 1f;
					}
					ReflectionHelper.SetInstanceValue(player, "Health", num8);
					ManualLogSource log4 = Plugin.Log;
					if (log4 != null)
					{
						log4.LogInfo((object)$"[TidalBlessing] Watered {num3}/{num4} hoed tiles via farmingData scan");
					}
				}
				else if (num4 > 0)
				{
					ManualLogSource log5 = Plugin.Log;
					if (log5 != null)
					{
						log5.LogInfo((object)$"[TidalBlessing] Found {num4} hoed tiles but Water() returned false for all");
					}
				}
				else
				{
					ManualLogSource log6 = Plugin.Log;
					if (log6 != null)
					{
						log6.LogInfo((object)"[TidalBlessing] No hoed tiles in farmingData");
					}
				}
			}
		}

		private void UpdateTidalBlessingViaCropObjects(Player player, float maxHP, float currentHP)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: 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_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			if (GameApis.CropType == null)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)"[TidalBlessing] No Crop type and no TileManager — cannot water");
				}
				return;
			}
			Vector3 position = ((Component)player).transform.position;
			int num = 1;
			int num2 = 0;
			Object[] array = Object.FindObjectsOfType(GameApis.CropType);
			ManualLogSource log2 = Plugin.Log;
			if (log2 != null)
			{
				log2.LogInfo((object)$"[TidalBlessing] Fallback mode: FindObjectsOfType found {((array != null) ? array.Length : 0)} Crop objects");
			}
			if (array == null || array.Length == 0)
			{
				return;
			}
			float num3 = maxHP * (AbilityConfig.TidalBlessingHPCostPercent.Value / 100f);
			Object[] array2 = array;
			foreach (Object val in array2)
			{
				Component val2 = (Component)(object)((val is Component) ? val : null);
				if ((Object)(object)val2 == (Object)null || Vector2.Distance(new Vector2(position.x, position.y), new Vector2(val2.transform.position.x, val2.transform.position.y)) > (float)num)
				{
					continue;
				}
				object instanceValue = ReflectionHelper.GetInstanceValue(val, "data");
				if (instanceValue == null || !ReflectionHelper.TryGetValue(instanceValue, "watered", defaultValue: false))
				{
					if ((currentHP - num3 * (float)(num2 + 1)) / maxHP * 100f <= AbilityConfig.TidalBlessingHPThreshold.Value)
					{
						break;
					}
					MethodInfo method = ((object)val).GetType().GetMethod("Water", BindingFlags.Instance | BindingFlags.Public);
					if (method != null)
					{
						method.Invoke(val, null);
						num2++;
					}
					else if (instanceValue != null)
					{
						ReflectionHelper.SetInstanceValue(instanceValue, "watered", true);
						num2++;
					}
				}
			}
			if (num2 > 0)
			{
				float num4 = num3 * (float)num2;
				float num5 = currentHP - num4;
				if (num5 < 1f)
				{
					num5 = 1f;
				}
				ReflectionHelper.SetInstanceValue(player, "Health", num5);
				ManualLogSource log3 = Plugin.Log;
				if (log3 != null)
				{
					log3.LogInfo((object)$"[TidalBlessing] Watered {num2} crops via fallback Crop.Water()");
				}
			}
		}

		private void UpdateInfernalForge()
		{
			if (!ActiveAbilityManager.IsRuntimeEnabled("InfernalForge"))
			{
				return;
			}
			float num = AbilityConfig.InfernalForgeScanInterval?.Value ?? 2f;
			if (Time.time - _lastInfernalForgeCheck < num)
			{
				return;
			}
			_lastInfernalForgeCheck = Time.time;
			try
			{
				Player instance = Player.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					return;
				}
				object instanceValue = ReflectionHelper.GetInstanceValue(instance, "Inventory");
				if (instanceValue == null)
				{
					instanceValue = ReflectionHelper.GetInstanceValue(instance, "inventory");
				}
				if (instanceValue == null)
				{
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogInfo((object)"[InfernalForge] Could not find player inventory");
					}
					return;
				}
				GameApis.EnsureInventoryMethodsCached(instanceValue);
				float maxMana = instance.MaxMana;
				float num2 = ReflectionHelper.TryGetValue(instance, "Mana", 0f);
				if (num2 / maxMana * 100f <= AbilityConfig.InfernalForgeManaThreshold.Value)
				{
					return;
				}
				int num3 = AbilityConfig.InfernalForgeOrePerBar.Value;
				if (num3 < 1)
				{
					num3 = 3;
				}
				MethodInfo addItemIntMethod = GameApis.GetAddItemIntMethod(instanceValue);
				int num4 = 0;
				float num5 = 0f;
				foreach (KeyValuePair<int, int> item in AbilityPatches.OreToBarMap)
				{
					int key = item.Key;
					int value = item.Value;
					int inventoryAmount = GameApis.GetInventoryAmount(instanceValue, key);
					if (inventoryAmount < num3)
					{
						continue;
					}
					float value2;
					float num6 = (AbilityPatches.OreManaCostMap.TryGetValue(key, out value2) ? value2 : 3f);
					float num7 = maxMana * (num6 / 100f);
					int num8 = inventoryAmount / num3;
					int num9 = num8 * num3;
					float num10 = num7 * (float)num8;
					if ((num2 - num5 - num10) / maxMana * 100f < AbilityConfig.InfernalForgeManaThreshold.Value)
					{
						num8 = 0;
						for (int num11 = inventoryAmount / num3; num11 >= 1; num11--)
						{
							float num12 = num7 * (float)num11;
							if ((num2 - num5 - num12) / maxMana * 100f >= AbilityConfig.InfernalForgeManaThreshold.Value)
							{
								num8 = num11;
								break;
							}
						}
						if (num8 <= 0)
						{
							continue;
						}
						num9 = num8 * num3;
						num10 = num7 * (float)num8;
					}
					if (!GameApis.RemoveInventoryItem(instanceValue, key, num9))
					{
						ManualLogSource log2 = Plugin.Log;
						if (log2 != null)
						{
							log2.LogWarning((object)$"[InfernalForge] Failed to remove {num9}x ore {key}");
						}
						continue;
					}
					bool flag = false;
					if (addItemIntMethod != null)
					{
						flag = GameApis.InvokeAddItem(addItemIntMethod, instanceValue, value, num8, notify: true);
					}
					if (!flag)
					{
						ManualLogSource log3 = Plugin.Log;
						if (log3 != null)
						{
							log3.LogError((object)$"[InfernalForge] Bar addition failed for {num8}x bar {value} — returning ore");
						}
						GameApis.AddInventoryItem(instanceValue, key, num9);
						continue;
					}
					num4 += num8;
					num5 += num10;
					ManualLogSource log4 = Plugin.Log;
					if (log4 != null)
					{
						log4.LogInfo((object)$"[InfernalForge] Smelted {num9}x ore {key} → {num8}x bar {value} (cost: {num6}% per bar)");
					}
				}
				if (num4 > 0)
				{
					float num13 = num2 - num5;
					if (num13 < 0f)
					{
						num13 = 0f;
					}
					ReflectionHelper.SetInstanceValue(instance, "Mana", num13);
					GameApis.SendGameNotification($"Infernal Forge: Smelted {num4} bar(s) (-{num5:F1} Mana)");
					ManualLogSource log5 = Plugin.Log;
					if (log5 != null)
					{
						log5.LogInfo((object)$"[InfernalForge] Total: {num4} bars, {num5:F1} mana used");
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log6 = Plugin.Log;
				if (log6 != null)
				{
					log6.LogWarning((object)("[InfernalForge] Error: " + ex.Message));
				}
				ManualLogSource log7 = Plugin.Log;
				if (log7 != null)
				{
					log7.LogWarning((object)("[InfernalForge] Stack: " + ex.StackTrace));
				}
			}
		}

		private void UpdateFontOfLight()
		{
			if (!ActiveAbilityManager.IsRuntimeEnabled("FontOfLight"))
			{
				return;
			}
			float num = AbilityConfig.FontOfLightInterval?.Value ?? 45f;
			if (Time.time - _lastFontOfLightCheck < num)
			{
				return;
			}
			_lastFontOfLightCheck = Time.time;
			try
			{
				Player instance = Player.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					return;
				}
				float maxMana = instance.MaxMana;
				float num2 = ReflectionHelper.TryGetValue(instance, "Mana", 0f);
				if (((maxMana > 0f) ? (num2 / maxMana * 100f) : 0f) >= (AbilityConfig.FontOfLightManaThreshold?.Value ?? 80f))
				{
					return;
				}
				int num3 = AbilityConfig.FontOfLightGoldCost?.Value ?? 10;
				if (num3 > 0)
				{
					Type type = AccessTools.TypeByName("Wish.GameSave");
					if (type != null && ((ReflectionHelper.GetStaticValue(type, "Coins") is int num4) ? num4 : 0) < num3)
					{
						return;
					}
				}
				float num5 = AbilityConfig.FontOfLightManaPercent?.Value ?? 5f;
				float num6 = maxMana * (num5 / 100f);
				if (num6 <= 0f)
				{
					return;
				}
				ReflectionHelper.InvokeMethod(instance, "AddMana", num6, 1f);
				if (num3 > 0)
				{
					MethodInfo methodInfo = AccessTools.Method(((object)instance).GetType(), "AddMoney", new Type[4]
					{
						typeof(int),
						typeof(bool),
						typeof(bool),
						typeof(bool)
					}, (Type[])null);
					if (methodInfo != null)
					{
						methodInfo.Invoke(instance, new object[4]
						{
							-num3,
							false,
							false,
							false
						});
					}
				}
				GameApis.SendGameNotification($"Font of Light: +{num5:F0}% mana" + ((num3 > 0) ? $" (-{num3} gold)" : ""));
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[FontOfLight] Error: " + ex.Message));
				}
			}
		}

		public static float GetCurrentHour()
		{
			try
			{
				Type type = GameApis.DayCycleType ?? ReflectionHelper.FindWishType("DayCycle");
				if (type == null)
				{
					return -1f;
				}
				object singletonInstance = ReflectionHelper.GetSingletonInstance(type);
				if (singletonInstance != null)
				{
					float num = ReflectionHelper.TryGetValue(singletonInstance, "Hour", -1f);
					if (num >= 0f)
					{
						return num;
					}
					num = ReflectionHelper.TryGetValue(singletonInstance, "CurrentHour", -1f);
					if (num >= 0f)
					{
						return num;
					}
					num = ReflectionHelper.TryGetValue(singletonInstance, "currentHour", -1f);
					if (num >= 0f)
					{
						return num;
					}
					int num2 = ReflectionHelper.TryGetValue(singletonInstance, "Hour", -1);
					if (num2 >= 0)
					{
						return num2;
					}
				}
				return ReflectionHelper.TryGetValue(null, "Hour", -1f);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogDebug((object)("[BirthrightRunner] Failed to read current hour: " + ex.Message));
				}
				return -1f;
			}
		}

		public static string GetCurrentSeason()
		{
			try
			{
				Type type = GameApis.DayCycleType ?? ReflectionHelper.FindWishType("DayCycle");
				if (type == null)
				{
					return null;
				}
				object singletonInstance = ReflectionHelper.GetSingletonInstance(type);
				if (singletonInstance != null)
				{
					object instanceValue = ReflectionHelper.GetInstanceValue(singletonInstance, "Season");
					if (instanceValue != null)
					{
						return instanceValue.ToString();
					}
					instanceValue = ReflectionHelper.GetInstanceValue(singletonInstance, "season");
					if (instanceValue != null)
					{
						return instanceValue.ToString();
					}
					instanceValue = ReflectionHelper.GetInstanceValue(singletonInstance, "CurrentSeason");
					if (instanceValue != null)
					{
						return instanceValue.ToString();
					}
				}
				return null;
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogDebug((object)("[BirthrightRunner] Failed to read current season: " + ex.Message));
				}
				return null;
			}
		}

		public static bool IsInMine()
		{
			string text = SceneHelpers.GetCurrentSceneName().ToLowerInvariant();
			if (!text.Contains("mine") && !text.Contains("dungeon") && !text.Contains("cave") && !text.Contains("underground") && !text.Contains("nelvari"))
			{
				return text.Contains("withergate");
			}
			return true;
		}

		public static bool IsDaytime()
		{
			float currentHour = GetCurrentHour();
			if (currentHour < 0f)
			{
				return true;
			}
			if (currentHour >= 6f)
			{
				return currentHour < 18f;
			}
			return false;
		}

		public static float GetPlayerHPRatio()
		{
			try
			{
				Player instance = Player.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					return 1f;
				}
				float maxHealth = instance.MaxHealth;
				if (maxHealth <= 0f)
				{
					return 1f;
				}
				return ReflectionHelper.TryGetValue(instance, "health", maxHealth) / maxHealth;
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogDebug((object)("[BirthrightRunner] Failed to read player HP ratio: " + ex.Message));
				}
				return 1f;
			}
		}

		private void CheckAbilityToggleHotkey()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (TextInputFocusGuard.ShouldDeferModHotkeys(Plugin.Log) || !Input.GetKeyDown(Plugin.StaticAbilityToggleKey))
				{
					return;
				}
				RacialBonusManager racialBonusManager = Plugin.GetRacialBonusManager();
				if (racialBonusManager == null)
				{
					return;
				}
				Race? playerRace = racialBonusManager.GetPlayerRace();
				if (!playerRace.HasValue)
				{
					RaceDetectionService.RetryRaceDetection();
					playerRace = racialBonusManager.GetPlayerRace();
				}
				if (!playerRace.HasValue)
				{
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogInfo((object)"[AbilityToggle] F9 pressed but race is not detected yet; waiting for Player/GameSave (try again shortly).");
					}
					return;
				}
				RaceDetectionService.RetryRaceDetection();
				playerRace = racialBonusManager.GetPlayerRace();
				if (!playerRace.HasValue)
				{
					ManualLogSource log2 = Plugin.Log;
					if (log2 != null)
					{
						log2.LogInfo((object)"[AbilityToggle] F9 pressed but race is not detected yet after retry; try again shortly.");
					}
					return;
				}
				Race value = playerRace.Value;
				Race race = ElementalVariantResolver.ResolveRaceForActiveAbilityToggle(value);
				if (race != value)
				{
					ManualLogSource log3 = Plugin.Log;
					if (log3 != null)
					{
						log3.LogInfo((object)$"[AbilityToggle] Body-resolved race for F9: {race} (cached was {value}).");
					}
				}
				race = ElementalVariantResolver.ResolveElementalAbilityRace(race);
				string activeAbilityForRace = GetActiveAbilityForRace(race);
				if (activeAbilityForRace == null)
				{
					ManualLogSource log4 = Plugin.Log;
					if (log4 != null)
					{
						log4.LogInfo((object)$"[AbilityToggle] No toggleable active ability for cached={value} resolved={race} (F9).");
					}
					return;
				}
				bool flag = ActiveAbilityManager.ToggleRuntime(activeAbilityForRace);
				string abilityDisplayName = GetAbilityDisplayName(activeAbilityForRace);
				GameApis.SendGameNotification(abilityDisplayName + ": " + (flag ? "ON" : "OFF"));
				ManualLogSource log5 = Plugin.Log;
				if (log5 != null)
				{
					log5.LogInfo((object)("[AbilityToggle] " + (flag ? "ENABLED" : "DISABLED") + " " + abilityDisplayName + " (key=" + activeAbilityForRace + ") | " + $"storedRace={value} abilityRace={race} | " + $"ToggleKey={Plugin.StaticAbilityToggleKey}"));
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log6 = Plugin.Log;
				if (log6 != null)
				{
					log6.LogWarning((object)("[BirthrightRunner] Toggle hotkey error: " + ex.Message));
				}
			}
		}

		private static string GetActiveAbilityForRace(Race race)
		{
			return race switch
			{
				Race.FireElemental => "InfernalForge", 
				Race.WaterElemental => "TidalBlessing", 
				Race.Angel => "FontOfLight", 
				Race.Demon => "SoulHarvest", 
				Race.Elemental => "InfernalForge", 
				_ => null, 
			};
		}

		private static string GetAbilityDisplayName(string abilityKey)
		{
			return abilityKey switch
			{
				"InfernalForge" => "Infernal Forge", 
				"TidalBlessing" => "Tidal Blessing", 
				"FontOfLight" => "Font of Light", 
				"SoulHarvest" => "Soul Harvest", 
				_ => abilityKey, 
			};
		}
	}
	internal static class BonusTransferRules
	{
		private readonly struct Rule
		{
			public readonly Race TargetRace;

			public readonly Race SourceRace;

			public readonly BonusType BonusType;

			public Rule(Race targetRace, Race sourceRace, BonusType bonusType)
			{
				TargetRace = targetRace;
				SourceRace = sourceRace;
				BonusType = bonusType;
			}
		}

		private static readonly List<Rule> Rules = new List<Rule>();

		internal static void RebuildFromConfig(string raw)
		{
			Rules.Clear();
			if (string.IsNullOrWhiteSpace(raw))
			{
				return;
			}
			string[] array = raw.Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length != 0)
				{
					string[] array2 = text.Split(new char[1] { '|' });
					Race result;
					Race result2;
					BonusType result3;
					if (array2.Length != 3)
					{
						Plugin.Log.LogWarning((object)("[BonusTransfer] Skip invalid rule (need Target|Source|BonusType): \"" + text + "\""));
					}
					else if (!Enum.TryParse<Race>(array2[0].Trim(), ignoreCase: true, out result) || !Enum.TryParse<Race>(array2[1].Trim(), ignoreCase: true, out result2) || !Enum.TryParse<BonusType>(array2[2].Trim(), ignoreCase: true, out result3))
					{
						Plugin.Log.LogWarning((object)("[BonusTransfer] Skip rule with unknown Race/BonusType: \"" + text + "\""));
					}
					else
					{
						Rules.Add(new Rule(result, result2, result3));
					}
				}
			}
			Plugin.Log.LogInfo((object)$"[BonusTransfer] Loaded {Rules.Count} rule(s)");
		}

		internal static float GetTransferredPercent(RacialBonusManager manager, Race currentRace, BonusType type)
		{
			if (Rules.Count == 0 || manager == null || RacialConfig.EnableBonusTransfers == null || !RacialConfig.EnableBonusTransfers.Value)
			{
				return 0f;
			}
			float num = 0f;
			foreach (Rule rule in Rules)
			{
				if (rule.TargetRace != currentRace || rule.BonusType != type)
				{
					continue;
				}
				foreach (RacialBonus item in manager.GetBonusesForRace(rule.SourceRace))
				{
					if (item.Type == type)
					{
						num += item.Value;
						break;
					}
				}
			}
			return num;
		}
	}
	internal static class GameApis
	{
		internal static Type TileManagerType;

		internal static object TileManagerInstance;

		internal static MethodInfo IsWateredMethod;

		internal static MethodInfo WaterTileMethod;

		internal static MethodInfo IsHoedOrWateredMethod;

		internal static FieldInfo FarmingDataField;

		internal static FieldInfo FarmingTileMapField;

		internal static MethodInfo WorldToCellMethod;

		internal static object GridInstance;

		internal static MethodInfo GridCellToWorldMethod;

		private static bool _wishCachesInitialized;

		private static MethodInfo _cachedGetAmountMethod;

		private static MethodInfo _cachedRemoveItemMethod;

		private static bool _inventoryMethodsCached;

		private static object _notificationStackInstance;

		private static MethodInfo _sendNotificationMethod;

		private static bool _notificationInitialized;

		private static bool _reflectionInitialized;

		private static bool _reflectionInitFailed;

		private static int _reflectionInitAttempts;

		private const int MaxReflectionInitAttempts = 3;

		private static FieldInfo _cachedItemIdField;

		private static PropertyInfo _cachedItemIdProperty;

		private static MethodInfo _cachedItemIdMethod;

		private static MethodInfo _cachedAddItemIntMethod;

		private static int _cachedAddItemArgCount;

		internal static Type CropType { get; private set; }

		internal static Type DayCycleType { get; private set; }

		internal static void EnsureWishCachesInitialized()
		{
			if (_wishCachesInitialized)
			{
				return;
			}
			_wishCachesInitialized = true;
			try
			{
				CropType = ReflectionHelper.FindWishType("Crop");
				DayCycleType = ReflectionHelper.FindWishType("DayCycle");
				if (CropType != null)
				{
					Plugin.Log.LogInfo((object)"[GameApis] Found Crop type");
				}
				if (DayCycleType != null)
				{
					Plugin.Log.LogInfo((object)"[GameApis] Found DayCycle type");
				}
				CacheTileManager();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[GameApis] Wish cache init failed: " + ex.Message));
			}
		}

		internal static void ResetWishCachesInitializedFlag()
		{
			_wishCachesInitialized = false;
		}

		private static void CacheTileManager()
		{
			try
			{
				TileManagerType = ReflectionHelper.FindWishType("TileManager");
				if (TileManagerType == null)
				{
					Plugin.Log.LogWarning((object)"[GameApis] TileManager type not found");
					return;
				}
				IsWateredMethod = TileManagerType.GetMethod("IsWatered", new Type[1] { typeof(Vector2Int) });
				WaterTileMethod = TileManagerType.GetMethod("Water", new Type[2]
				{
					typeof(Vector2Int),
					typeof(short)
				});
				IsHoedOrWateredMethod = TileManagerType.GetMethod("IsHoedOrWatered", new Type[1] { typeof(Vector2Int) });
				FarmingDataField = TileManagerType.GetField("farmingData", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				FarmingTileMapField = TileManagerType.GetField("farmingTileMap", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				Plugin.Log.LogInfo((object)("[GameApis] TileManager cached — " + $"IsWatered:{IsWateredMethod != null}, Water:{WaterTileMethod != null}, " + $"farmingData:{FarmingDataField != null}"));
				TryGetTileManagerInstance();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[GameApis] TileManager cache failed: " + ex.Message));
			}
		}

		internal static void TryGetTileManagerInstance()
		{
			if (TileManagerInstance != null)
			{
				object tileManagerInstance = TileManagerInstance;
				Object val = (Object)((tileManagerInstance is Object) ? tileManagerInstance : null);
				if (val == null || !(val == (Object)null))
				{
					return;
				}
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogInfo((object)"[GameApis] TileManager instance stale — clearing");
				}
				TileManagerInstance = null;
			}
			if (TileManagerType == null)
			{
				return;
			}
			try
			{
				TileManagerInstance = ReflectionHelper.GetSingletonInstance(TileManagerType);
				if (TileManagerInstance != null)
				{
					ManualLogSource log2 = Plugin.Log;
					if (log2 != null)
					{
						log2.LogInfo((object)"[GameApis] TileManager instance acquired");
					}
					if (WorldToCellMethod == null && FarmingTileMapField != null)
					{
						try
						{
							object value = FarmingTileMapField.GetValue(TileManagerInstance);
							if (value != null)
							{
								WorldToCellMethod = value.GetType().GetMethod("WorldToCell", new Type[1] { typeof(Vector3) });
								ManualLogSource log3 = Plugin.Log;
								if (log3 != null)
								{
									log3.LogInfo((object)("[GameApis] WorldToCell: " + ((WorldToCellMethod != null) ? "ok" : "missing")));
								}
							}
						}
						catch (Exception ex)
						{
							ManualLogSource log4 = Plugin.Log;
							if (log4 != null)
							{
								log4.LogWarning((object)("[GameApis] WorldToCell cache failed: " + ex.Message));
							}
						}
					}
					if (GridInstance != null || !(FarmingTileMapField != null))
					{
						return;
					}
					try
					{
						object value2 = FarmingTileMapField.GetValue(TileManagerInstance);
						if (value2 == null)
						{
							return;
						}
						GridInstance = value2.GetType().GetProperty("layoutGrid")?.GetValue(value2);
						if (GridInstance != null)
						{
							GridCellToWorldMethod = GridInstance.GetType().GetMethod("CellToWorld", new Type[1] { typeof(Vector3Int) });
							ManualLogSource log5 = Plugin.Log;
							if (log5 != null)
							{
								log5.LogInfo((object)("[GameApis] Grid CellToWorld: " + ((GridCellToWorldMethod != null) ? "ok" : "missing")));
							}
						}
						return;
					}
					catch (Exception ex2)
					{
						ManualLogSource log6 = Plugin.Log;
						if (log6 != null)
						{
							log6.LogWarning((object)("[GameApis] Grid cache failed: " + ex2.Message));
						}
						return;
					}
				}
				ManualLogSource log7 = Plugin.Log;
				if (log7 != null)
				{
					log7.LogInfo((object)"[GameApis] TileManager singleton null");
				}
			}
			catch (Exception ex3)
			{
				ManualLogSource log8 = Plugin.Log;
				if (log8 != null)
				{
					log8.LogWarning((object)("[GameApis] TileManager instance lookup failed: " + ex3.Message));
				}
			}
		}

		private static void ResetFarmingCaches()
		{
			TileManagerInstance = null;
			WorldToCellMethod = null;
			GridInstance = null;
			GridCellToWorldMethod = null;
			_cachedGetAmountMethod = null;
			_cachedRemoveItemMethod = null;
			_inventoryMethodsCached = false;
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogDebug((object)"[GameApis] Farming + inventory reflection reset");
			}
		}

		internal static void ResetFarmingTileAndInventoryScanState()
		{
			ResetFarmingCaches();
			ResetWishCachesInitializedFlag();
		}

		internal static void CacheInventoryMethods(object inventory)
		{
			try
			{
				Type type = inventory.GetType();
				_cachedGetAmountMethod = type.GetMethod("GetAmount", new Type[1] { typeof(int) }) ?? type.GetMethod("GetItemAmount", new Type[1] { typeof(int) });
				_cachedRemoveItemMethod = type.GetMethod("RemoveAll", new Type[1] { typeof(int) });
				Plugin.Log.LogInfo((object)("[GameApis] Inventory methods — GetAmount:" + (_cachedGetAmountMethod?.Name ?? "null") + ", Remove:" + (_cachedRemoveItemMethod?.Name ?? "null")));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[GameApis] CacheInventoryMethods failed: " + ex.Message));
			}
		}

		internal static void EnsureInventoryMethodsCached(object inventory)
		{
			if (!_inventoryMethodsCached)
			{
				CacheInventoryMethods(inventory);
				_inventoryMethodsCached = true;
			}
		}

		internal static int GetInventoryAmount(object inventory, int itemId)
		{
			try
			{
				if (_cachedGetAmountMethod != null && _cachedGetAmountMethod.Invoke(inventory, new object[1] { itemId }) is int result)
				{
					return result;
				}
				object obj = ReflectionHelper.InvokeMethod(inventory, "GetAmount", itemId);
				if (obj is int)
				{
					return (int)obj;
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)$"[GameApis] GetInventoryAmount failed for {itemId}: {ex.Message}");
				}
			}
			return 0;
		}

		internal static bool RemoveInventoryItem(object inventory, int itemId, int amount)
		{
			try
			{
				if (_cachedRemoveItemMethod == null)
				{
					return false;
				}
				int inventoryAmount = GetInventoryAmount(inventory, itemId);
				if (inventoryAmount < amount)
				{
					return false;
				}
				int num = inventoryAmount - amount;
				_cachedRemoveItemMethod.Invoke(inventory, new object[1] { itemId });
				if (num > 0)
				{
					AddInventoryItem(inventory, itemId, num);
				}
				return true;
			}
			catch (Exception ex)
			{
				Exception ex2 = ex.InnerException ?? ex;
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[GameApis] RemoveInventoryItem: " + ex2.Message));
				}
				return false;
			}
		}

		internal static void AddInventoryItem(object inventory, int itemId, int amount)
		{
			try
			{
				MethodInfo addItemIntMethod = GetAddItemIntMethod(inventory);
				if (addItemIntMethod != null)
				{
					InvokeAddItem(addItemIntMethod, inventory, itemId, amount, notify: false);
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogError((object)("[GameApis] AddInventoryItem failed: " + ex.Message));
				}
			}
		}

		internal static void ResetAllApiCaches()
		{
			ResetItemAndNotificationCaches();
			ResetFarmingTileAndInventoryScanState();
		}

		private static void InitializeNotificationSystem()
		{
			if (_notificationInitialized)
			{
				return;
			}
			_notificationInitialized = true;
			try
			{
				Type type = AccessTools.TypeByName("Wish.NotificationStack");
				if (type == null)
				{
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogWarning((object)"[GameApis] NotificationStack type not found");
					}
					return;
				}
				_sendNotificationMethod = AccessTools.Method(type, "SendNotification", new Type[5]
				{
					typeof(string),
					typeof(int),
					typeof(int),
					typeof(bool),
					typeof(bool)
				}, (Type[])null);
				if (_sendNotificationMethod == null)
				{
					ManualLogSource log2 = Plugin.Log;
					if (log2 != null)
					{
						log2.LogWarning((object)"[GameApis] SendNotification(string,int,int,bool,bool) not found");
					}
					return;
				}
				TryGetNotificationInstance(type);
				ManualLogSource log3 = Plugin.Log;
				if (log3 != null)
				{
					log3.LogInfo((object)("[GameApis] Notification system initialized" + ((_notificationStackInstance != null) ? " (instance ready)" : " (instance deferred)")));
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log4 = Plugin.Log;
				if (log4 != null)
				{
					log4.LogWarning((object)("[GameApis] Notification init failed: " + ex.Message));
				}
			}
		}

		private static void TryGetNotificationInstance(Type notifStackType = null)
		{
			if (_notificationStackInstance != null)
			{
				return;
			}
			try
			{
				if (notifStackType == null)
				{
					notifStackType = AccessTools.TypeByName("Wish.NotificationStack");
				}
				if (notifStackType == null)
				{
					return;
				}
				Type type = AccessTools.TypeByName("Wish.SingletonBehaviour`1");
				if (type != null)
				{
					PropertyInfo propertyInfo = AccessTools.Property(type.MakeGenericType(notifStackType), "Instance");
					if (propertyInfo != null)
					{
						_notificationStackInstance = propertyInfo.GetValue(null);
						if (_notificationStackInstance != null)
						{
							return;
						}
					}
				}
				_notificationStackInstance = ReflectionHelper.GetSingletonInstance(notifStackType);
				if (_notificationStackInstance == null)
				{
					MethodInfo method = typeof(Object).GetMethod("FindObjectOfType", Type.EmptyTypes);
					if (method != null)
					{
						_notificationStackInstance = method.MakeGenericMethod(notifStackType).Invoke(null, null);
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[GameApis] Notification instance lookup failed: " + ex.Message));
				}
			}
		}

		internal static void SendGameNotification(string text, int id = 0, int amount = 0, bool unique = false, bool error = false)
		{
			try
			{
				if (!_notificationInitialized)
				{
					InitializeNotificationSystem();
				}
				if (!(_sendNotificationMethod == null))
				{
					if (_notificationStackInstance == null)
					{
						TryGetNotificationInstance();
					}
					if (_notificationStackInstance != null)
					{
						_sendNotificationMethod.Invoke(_notificationStackInstance, new object[5] { text, id, amount, unique, error });
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[GameApis] Notification send failed: " + ex.Message));
				}
			}
		}

		private static void InitializeReflectionCache()
		{
			if (_reflectionInitialized || _reflectionInitFailed)
			{
				return;
			}
			_reflectionInitAttempts++;
			if (_reflectionInitAttempts > 3)
			{
				_reflectionInitFailed = true;
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogError((object)$"[GameApis] Reflection init failed after {3} attempts - giving up");
				}
				return;
			}
			try
			{
				Type type = AccessTools.TypeByName("Wish.Item");
				if (type == null)
				{
					ManualLogSource log2 = Plugin.Log;
					if (log2 != null)
					{
						log2.LogWarning((object)$"[GameApis] Could not find Wish.Item type (attempt {_reflectionInitAttempts}/{3})");
					}
					return;
				}
				string[] array = new string[6] { "id", "_id", "itemId", "_itemId", "ItemId", "m_id" };
				foreach (string text in array)
				{
					_cachedItemIdField = AccessTools.Field(type, text);
					if (_cachedItemIdField != null)
					{
						break;
					}
				}
				array = new string[6] { "id", "Id", "ID", "ItemID", "itemId", "ItemId" };
				foreach (string text2 in array)
				{
					_cachedItemIdProperty = AccessTools.Property(type, text2);
					if (_cachedItemIdProperty != null)
					{
						break;
					}
				}
				array = new string[5] { "ID", "GetID", "GetId", "GetItemID", "GetItemId" };
				foreach (string text3 in array)
				{
					_cachedItemIdMethod = AccessTools.Method(type, text3, (Type[])null, (Type[])null);
					if (_cachedItemIdMethod != null)
					{
						break;
					}
				}
				if (_cachedItemIdField == null && _cachedItemIdProperty == null && _cachedItemIdMethod == null)
				{
					ManualLogSource log3 = Plugin.Log;
					if (log3 != null)
					{
						log3.LogWarning((object)$"[GameApis] ALL Item ID lookups failed (attempt {_reflectionInitAttempts}/{3})! Members containing 'id':");
					}
					MemberInfo[] members = type.GetMembers(ReflectionHelper.AllBindingFlags);
					foreach (MemberInfo memberInfo in members)
					{
						if (memberInfo.Name.IndexOf("id", StringComparison.OrdinalIgnoreCase) >= 0)
						{
							ManualLogSource log4 = Plugin.Log;
							if (log4 != null)
							{
								log4.LogWarning((object)$"  - {memberInfo.MemberType} {memberInfo.Name}");
							}
						}
					}
				}
				else
				{
					_reflectionInitialized = true;
					ManualLogSource log5 = Plugin.Log;
					if (log5 != null)
					{
						log5.LogInfo((object)("[GameApis] Item ID reflection ready - field:" + (_cachedItemIdField?.Name ?? "null") + ", prop:" + (_cachedItemIdProperty?.Name ?? "null") + ", method:" + (_cachedItemIdMethod?.Name ?? "null")));
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log6 = Plugin.Log;
				if (log6 != null)
				{
					log6.LogWarning((object)$"[GameApis] Reflection cache init failed (attempt {_reflectionInitAttempts}): {ex.Message}");
				}
			}
		}

		internal static int GetItemId(object item)
		{
			if (item == null)
			{
				return -1;
			}
			InitializeReflectionCache();
			try
			{
				if (_cachedItemIdField != null && _cachedItemIdField.GetValue(item) is int result)
				{
					return result;
				}
				if (_cachedItemIdProperty != null && _cachedItemIdProperty.GetValue(item) is int result2)
				{
					return result2;
				}
				if (_cachedItemIdMethod != null && _cachedItemIdMethod.Invoke(item, null) is int result3)
				{
					return result3;
				}
				return GetItemIdSlow(item);
			}
			catch (Exception ex)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogDebug((object)("[GameApis] GetItemId failed: " + ex.Message));
				}
				return -1;
			}
		}

		private static int GetItemIdSlow(object item)
		{
			try
			{
				Type type = item.GetType();
				FieldInfo[] fields = type.GetFields(ReflectionHelper.AllBindingFlags);
				foreach (FieldInfo fieldInfo in fields)
				{
					if (!(fieldInfo.FieldType == typeof(int)) || fieldInfo.Name.IndexOf("id", StringComparison.OrdinalIgnoreCase) < 0)
					{
						continue;
					}
					int num = (int)fieldInfo.GetValue(item);
					if (num > 0)
					{
						_cachedItemIdField = fieldInfo;
						ManualLogSource log = Plugin.Log;
						if (log != null)
						{
							log.LogInfo((object)$"[GameApis] Slow path item ID via field '{fieldInfo.Name}' = {num}");
						}
						return num;
					}
				}
				PropertyInfo[] properties = type.GetProperties(ReflectionHelper.AllBindingFlags);
				foreach (PropertyInfo propertyInfo in properties)
				{
					if (!(propertyInfo.PropertyType == typeof(int)) || !propertyInfo.CanRead || propertyInfo.Name.IndexOf("id", StringComparison.OrdinalIgnoreCase) < 0)
					{
						continue;
					}
					int num2 = (int)propertyInfo.GetValue(item);
					if (num2 > 0)
					{
						_cachedItemIdProperty = propertyInfo;
						ManualLogSource log2 = Plugin.Log;
						if (log2 != null)
						{
							log2.LogInfo((object)$"[GameApis] Slow path item ID via property '{propertyInfo.Name}' = {num2}");
						}
						return num2;
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log3 = Plugin.Log;
				if (log3 != null)
				{
					log3.LogDebug((object)("[GameApis] GetItemIdSlow failed: " + ex.Message));
				}
			}
			return -1;
		}

		internal static MethodInfo GetAddItemIntMethod(object inventory)
		{
			if (_cachedAddItemIntMethod != null)
			{
				return _cachedAddItemIntMethod;
			}
			try
			{
				Type type = inventory.GetType();
				_cachedAddItemIntMethod = AccessTools.Method(type, "AddItem", new Type[3]
				{
					typeof(int),
					typeof(int),
					typeof(bool)
				}, (Type[])null);
				if (_cachedAddItemIntMethod != null)
				{
					_cachedAddItemArgCount = 3;
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogInfo((object)"[GameApis] Cached Inventory.AddItem(int, int, bool)");
					}
					return _cachedAddItemIntMethod;
				}
				_cachedAddItemIntMethod = AccessTools.Method(type, "AddItem", new Type[2]
				{
					typeof(int),
					typeof(int)
				}, (Type[])null);
				if (_cachedAddItemIntMethod != null)
				{
					_cachedAddItemArgCount = 2;
					ManualLogSource log2 = Plugin.Log;
					if (log2 != null)
					{
						log2.LogInfo((object)"[GameApis] Cached Inventory.AddItem(int, int)");
					}
					return _cachedAddItemIntMethod;
				}
				ManualLogSource log3 = Plugin.Log;
				if (log3 != null)
				{
					log3.LogWarning((object)"[GameApis] Could not find Inventory.AddItem(int,...) overloads:");
				}
				MethodInfo[] methods = type.GetMethods(ReflectionHelper.AllBindingFlags);
				foreach (MethodInfo methodInfo in methods)
				{
					if (methodInfo.Name == "AddItem")
					{
						ParameterInfo[] parameters = methodInfo.GetParameters();
						string text = string.Join(", ", Array.ConvertAll(parameters, (ParameterInfo p) => p.ParameterType.Name + " " + p.Name));
						ManualLogSource log4 = Plugin.Log;
						if (log4 != null)
						{
							log4.LogWarning((object)("  - AddItem(" + text + ")"));
						}
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log5 = Plugin.Log;
				if (log5 != null)
				{
					log5.LogWarning((object)("[GameApis] Failed to cache AddItem method: " + ex.Message));
				}
			}
			return _cachedAddItemIntMethod;
		}

		internal static bool InvokeAddItem(MethodInfo addMethod, object inventory, int itemId, int amount, bool notify)
		{
			try
			{
				if (_cachedAddItemArgCount == 3)
				{
					addMethod.Invoke(inventory, new object[3] { itemId, amount, notify });
					return true;
				}
				if (_cachedAddItemArgCount == 2)
				{
					addMethod.Invoke(inventory, new object[2] { itemId, amount });
					return true;
				}
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogError((object)$"[GameApis] InvokeAddItem: bad arg count {_cachedAddItemArgCount}");
				}
				return false;
			}
			catch (Exception ex)
			{
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogError((object)$"[GameApis] InvokeAddItem threw: {ex.Message}. Item {itemId} x{amount}");
				}
				return false;
			}
		}

		internal static void ResetItemAndNotificationCaches()
		{
			_notificationStackInstance = null;
			_notificationInitialized = false;
			_sendNotificationMethod = null;
			_reflectionInitialized = false;
			_reflectionInitFailed = false;
			_reflectionInitAttempts = 0;
			_cachedAddItemIntMethod = null;
			_cachedAddItemArgCount = 0;
			_cachedItemIdField = null;
			_cachedItemIdProperty = null;
			_cachedItemIdMethod = null;
			ManualLogSource log = Plugin.Log;
			if (log != null)
			{
				log.LogDebug((object)"[GameApis] Item + notification caches reset");
			}
		}
	}
	[BepInPlugin("com.azraelgodking.havensbirthright", "Haven's Birthright", "2.2.2")]
	public class Plugin : BaseUnityPlugin
	{
		internal static KeyCode StaticAbilityToggleKey = (KeyCode)290;

		internal static KeyCode StaticReloadConfigKey = (KeyCode)293;

		private Harmony _harmony;

		private RacialBonusManager _racialBonusManager;

		private ConfigEntry<bool> _checkForUpdates;

		private ConfigEntry<KeyCode> _reloadConfigKey;

		private BirthrightRunner _runner;

		private EventHandler _updateKeybindsHandler;

		private EventHandler _rebuildCrossRaceRulesHandler;

		private bool _applicationQuitting;

		public static Plugin Instance { get; private set; }

		public static ManualLogSource Log { get; private set; }

		public static ConfigFile ConfigFile { get; private set; }

		public static bool CriticalBirthrightHarmonyIncomplete { get; private set; }

		private void Awake()
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Expected O, but got Unknown
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e4: Expected O, but got Unknown
			//IL_02ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fa: Expected O, but got Unknown
			//IL_03bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c8: Expected O, but got Unknown
			//IL_0578: Unknown result type (might be due to invalid IL or missing references)
			//IL_0596: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			ConfigFile = CreateNamedConfig();
			ConfigFileHelper.ReplacePluginConfig((BaseUnityPlugin)(object)this, ConfigFile, (Action<string>)Log.LogWarning);
			Log.LogInfo((object)"Loading Haven's Birthright v2.2.2");
			try
			{
				RacialConfig.Initialize(ConfigFile);
				AbilityConfig.Initialize(ConfigFile);
				StaticAbilityToggleKey = AbilityConfig.ActiveAbilityToggleKey.Value;
				_checkForUpdates = ConfigFile.Bind<bool>("Updates", "CheckForUpdates", true, "Check for mod updates on startup");
				_reloadConfigKey = ConfigFile.Bind<KeyCode>("General", "ReloadConfigKey", (KeyCode)293, "Key to reload config from file (edit the .cfg file, then press this key in-game to apply)");
				StaticAbilityToggleKey = AbilityConfig.ActiveAbilityToggleKey.Value;
				StaticReloadConfigKey = _reloadConfigKey.Value;
				SubscribeConfigChanged();
				_racialBonusManager = new RacialBonusManager();
				_runner = PersistentRunnerBase.CreateRunner<BirthrightRunner>();
				Log.LogInfo((object)"BirthrightRunner created for active abilities");
				_harmony = new Harmony("com.azraelgodking.havensbirthright");
				try
				{
					Type typeFromHandle = typeof(Player);
					Log.LogInfo((object)("Player type: " + typeFromHandle.FullName + " from " + typeFromHandle.Assembly.GetName().Name));
					PatchMethod(typeFromHandle, "InitializeAsOwner", typeof(PlayerPatches), "OnPlayerInitialized", null, essential: true);
					PatchMethod(typeFromHandle, "Initialize", typeof(PlayerPatches), "OnPlayerInitialize", Type.EmptyTypes, essential: true);
					Type type = AccessTools.TypeByName("Wish.GameSave");
					if (type != null)
					{
						MethodInfo methodInfo = AccessTools.Method(type, "LoadCharacter", new Type[1] { typeof(int) }, (Type[])null);
						if (methodInfo != null)
						{
							_harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(PlayerPatches), "OnGameSaveLoadCharacter", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
							Log.LogInfo((object)"Patched GameSave.LoadCharacter (race/actives reset on character load)");
						}
						else
						{
							Log.LogWarning((object)"Could not find GameSave.LoadCharacter(int) — character-switch reset may be incomplete");
						}
					}
					PatchMethod(typeFromHandle, "GetStat", typeof(StatPatches), "ModifyGetStat", new Type[1] { typeof(StatType) }, essential: true);
					PatchMethodPrefix(typeFromHandle, "ReceiveDamage", typeof(CombatPatches), "ModifyDamageReceived", null, essential: true);
					PatchMethod(typeFromHandle, "ReceiveDamage", typeof(CombatPatches), "OnDamageReceivedPostfix", null, essential: true);
					Type type2 = AccessTools.TypeByName("Wish.NPCAI");
					if (type2 != null)
					{
						MethodInfo methodInfo2 = AccessTools.Method(type2, "AddRelationship", new Type[3]
						{
							typeof(float),
							typeof(float),
							typeof(bool)
						}, (Type[])null);
						if (methodInfo2 != null)
						{
							MethodInfo methodInfo3 = AccessTools.Method(typeof(EconomyPatches), "ModifyRelationshipGain", (Type[])null, (Type[])null);
							_harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(methodInfo3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
							Log.LogInfo((object)"Successfully patched NPCAI.AddRelationship");
						}
						else
						{
							Log.LogWarning((object)"Could not find NPCAI.AddRelationship - relationship bonuses will not work");
						}
					}
					else
					{
						Log.LogWarning((object)"Could not find NPCAI type - relationship bonuses will not work");
					}
					PatchShopBuyItemForDiscount();
					PatchMethodPrefix(typeFromHandle, "AddMana", typeof(AbilityPatches), "OnPlayerAddManaPrefix", null, essential: true);
					Type type3 = AccessTools.TypeByName("Wish.EnemyAI");
					if (type3 != null)
					{
						MethodInfo methodInfo4 = AccessTools.Method(type3, "Die", new Type[1] { typeof(bool) }, (Type[])null);
						if (methodInfo4 != null)
						{
							MethodInfo methodInfo5 = AccessTools.Method(typeof(SoulHarvestPatches), "OnEnemyDiePostfix", (Type[])null, (Type[])null);
							if (methodInfo5 != null)
							{
								_harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(methodInfo5), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
								Log.LogInfo((object)"Successfully patched EnemyAI.Die (Soul Harvest)");
							}
						}
					}
					IEnumerable<MethodBase> patchedMethods = _harmony.GetPatchedMethods();
					int num = 0;
					foreach (MethodBase item in patchedMethods)
					{
						Log.LogInfo((object)("Patched: " + item.DeclaringType?.Name + "." + item.Name));
						num++;
					}
					Log.LogInfo((object)$"Total methods patched: {num}");
					if (CriticalBirthrightHarmonyIncomplete)
					{
						Log.LogError((object)"[Haven's Birthright] One or more essential Harmony patches failed — racial stat/combat hooks are disabled for this session. Check the log above for 'Could not find method' / patch errors.");
					}
				}
				catch (Exception arg)
				{
					Log.LogError((object)$"Harmony patching failed: {arg}");
					CriticalBirthrightHarmonyIncomplete = true;
				}
				if (_checkForUpdates.Value)
				{
					VersionChecker.CheckForUpdate("com.azraelgodking.havensbirthright", "2.2.2", Log, delegate(VersionChecker.VersionCheckResult result)
					{
						result.NotifyUpdateAvailable(Log);
					});
				}
				Log.LogInfo((object)"Haven's Birthright loaded successfully!");
				Log.LogInfo((object)("Active abilities: " + (AbilityConfig.EnableActiveAbilities.Value ? "ENABLED" : "DISABLED")));
				Log.LogInfo((object)("Racial drawbacks: " + (AbilityConfig.EnableRacialDrawbacks.Value ? "ENABLED" : "DISABLED")));
				Log.LogInfo((object)("Conditional synergies: " + (AbilityConfig.EnableConditionalSynergies.Value ? "ENABLED" : "DISABLED")));
				Log.LogInfo((object)$"Ability toggle key: {StaticAbilityToggleKey}");
				Log.LogInfo((object)$"Config reload key: {StaticReloadConfigKey} (edit .cfg then press in-game to apply)");
			}
			catch (Exception arg2)
			{
				Log.LogError((object)string.Format("Failed to load {0}: {1}", "Haven's Birthright", arg2));
			}
		}

		private void SubscribeConfigChanged()
		{
			_updateKeybindsHandler = delegate
			{
				//IL_0005: 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_0015: 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)
				StaticAbilityToggleKey = AbilityConfig.ActiveAbilityToggleKey.Value;
				StaticReloadConfigKey = _reloadConfigKey.Value;
			};
			AbilityConfig.ActiveAbilityToggleKey.SettingChanged += _updateKeybindsHandler;
			_reloadConfigKey.SettingChanged += _updateKeybindsHandler;
			_rebuildCrossRaceRulesHandler = delegate
			{
				BonusTransferRules.RebuildFromConfig((RacialConfig.CrossRaceBonusRules != null) ? RacialConfig.CrossRaceBonusRules.Value : "");
			};
			RacialConfig.CrossRaceBonusRules.SettingChanged += _rebuildCrossRaceRulesHandler;
			RacialConfig.EnableBonusTransfers.SettingChanged += _rebuildCrossRaceRulesHandler;
		}

		public void ReloadConfig()
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				ConfigFile.Reload();
				RacialConfig.Initialize(ConfigFile);
				AbilityConfig.Initialize(ConfigFile);
				StaticAbilityToggleKey = AbilityConfig.ActiveAbilityToggleKey.Value;
				StaticReloadConfigKey = _reloadConfigKey.Value;
				_racialBonusManager = new RacialBonusManager();
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogInfo((object)"[Haven's Birthright] Config reloaded from file");
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log2 = Log;
				if (log2 != null)
				{
					log2.LogError((object)("[Haven's Birthright] Config reload failed: " + ex.Message));
				}
			}
		}

		private static ConfigFile CreateNamedConfig()
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Expected O, but got Unknown
			string text = Path.Combine(Paths.ConfigPath, "HavensBirthright.cfg");
			string text2 = Path.Combine(Paths.ConfigPath, "com.azraelgodking.havensbirthright.cfg");
			try
			{
				if (!File.Exists(text) && File.Exists(text2))
				{
					File.Copy(text2, text);
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogWarning((object)("[Config] Migration to HavensBirthright.cfg failed: " + ex.Message));
				}
			}
			return new ConfigFile(text, true);
		}

		private void OnApplicationQuit()
		{
			_applicationQuitting = true;
		}

		private void OnDestroy()
		{
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (_updateKeybindsHandler != null)
				{
					AbilityConfig.ActiveAbilityToggleKey.SettingChanged -= _updateKeybindsHandler;
					_reloadConfigKey.SettingChanged -= _updateKeybindsHandler;
				}
				if (_rebuildCrossRaceRulesHandler != null)
				{
					RacialConfig.CrossRaceBonusRules.SettingChanged -= _rebuildCrossRaceRulesHandler;
					RacialConfig.EnableBonusTransfers.SettingChanged -= _rebuildCrossRaceRulesHandler;
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogDebug((object)("[Lifecycle] Config handler teardown encountered an issue: " + ex.Message));
				}
			}
			Scene activeScene = SceneManager.GetActiveScene();
			string text = ((Scene)(ref activeScene)).name ?? string.Empty;
			string text2 = text.ToLowerInvariant();
			if (_applicationQuitting || !Application.isPlaying || text2.Contains("menu") || text2.Contains("title"))
			{
				ManualLogSource log2 = Log;
				if (log2 != null)
				{
					log2.LogInfo((object)("[Lifecycle] Plugin OnDestroy during expected teardown (scene: " + text + ")"));
				}
			}
			else
			{
				ManualLogSource log3 = Log;
				if (log3 != null)
				{
					log3.LogWarning((object)("[Lifecycle] Plugin OnDestroy outside expected teardown (scene: " + text + ")"));
				}
			}
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void PatchMethod(Type targetType, string methodName, Type patchType, string patchMethodName, Type[] parameters = null, bool essential = false)
		{
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Expected O, but got Unknown
			try
			{
				MethodInfo methodInfo = ((parameters == null) ? AccessTools.Method(targetType, methodName, (Type[])null, (Type[])null) : AccessTools.Method(targetType, methodName, parameters, (Type[])null));
				if (methodInfo == null)
				{
					Log.LogWarning((object)("Could not find method " + targetType.Name + "." + methodName));
					if (essential)
					{
						CriticalBirthrightHarmonyIncomplete = true;
					}
					return;
				}
				MethodInfo methodInfo2 = AccessTools.Method(patchType, patchMethodName, (Type[])null, (Type[])null);
				if (methodInfo2 == null)
				{
					Log.LogWarning((object)("Could not find patch method " + patchType.Name + "." + patchMethodName));
					if (essential)
					{
						CriticalBirthrightHarmonyIncomplete = true;
					}
				}
				else
				{
					_harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Log.LogInfo((object)("Successfully patched " + targetType.Name + "." + methodName));
				}
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Failed to patch " + targetType.Name + "." + methodName + ": " + ex.Message));
				if (essential)
				{
					CriticalBirthrightHarmonyIncomplete = true;
				}
			}
		}

		private void PatchMethodPrefix(Type targetType, string methodName, Type patchType, string patchMethodName, Type[] parameters = null, bool essential = false)
		{
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Expected O, but got Unknown
			try
			{
				MethodInfo methodInfo = ((parameters == null) ? AccessTools.Method(targetType, methodName, (Type[])null, (Type[])null) : AccessTools.Method(targetType, methodName, parameters, (Type[])null));
				if (methodInfo == null)
				{
					Log.LogWarning((object)("Could not find method " + target