Decompiled source of HavensBirthright v2.2.2
HavensBirthright.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
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