Decompiled source of HavensBirthright v1.2.0
HavensBirthright.dll
Decompiled 6 hours 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.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.Configuration; using BepInEx.Logging; using HarmonyLib; using HavensBirthright.Abilities; using HavensBirthright.Patches; using Microsoft.CodeAnalysis; using SunhavenMods.Shared; using UnityEngine; using UnityEngine.Networking; using UnityEngine.SceneManagement; 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+c7976283ee1309608b00428a169c130e784344a0")] [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.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 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; } } private class VersionCheckRunner : MonoBehaviour { public void StartCheck(string pluginGuid, string currentVersion, Action<VersionCheckResult> onComplete) { ((MonoBehaviour)this).StartCoroutine(CheckVersionCoroutine(pluginGuid, currentVersion, onComplete)); } 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; LogWarning(result.ErrorMessage); onComplete?.Invoke(result); Object.Destroy((Object)(object)((Component)this).gameObject); yield break; } try { string text = www.downloadHandler.text; string pattern = "\"" + Regex.Escape(pluginGuid) + "\"\\s*:\\s*\\{([^}]+)\\}"; Match match = Regex.Match(text, pattern, RegexOptions.Singleline); if (!match.Success) { result.Success = false; result.ErrorMessage = "Mod '" + pluginGuid + "' not found in versions.json"; LogWarning(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"; LogWarning(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) { Log("Update available for " + result.ModName + ": " + currentVersion + " -> " + result.LatestVersion); } else { Log(result.ModName + " is up to date (v" + currentVersion + ")"); } } catch (Exception ex) { result.Success = false; result.ErrorMessage = "Parse error: " + ex.Message; LogError(result.ErrorMessage); } } finally { ((IDisposable)www)?.Dispose(); } onComplete?.Invoke(result); Object.Destroy((Object)(object)((Component)this).gameObject); } private string ExtractJsonString(string json, string key) { string pattern = "\"" + key + "\"\\s*:\\s*(?:\"([^\"]*)\"|null)"; Match match = Regex.Match(json, pattern); if (!match.Success) { return null; } return match.Groups[1].Value; } } private const string VersionsUrl = "https://azraelgodking.github.io/SunhavenMod/versions.json"; private static ManualLogSource _logger; 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) _logger = logger; VersionCheckRunner versionCheckRunner = new GameObject("VersionChecker").AddComponent<VersionCheckRunner>(); Object.DontDestroyOnLoad((Object)(object)((Component)versionCheckRunner).gameObject); versionCheckRunner.StartCheck(pluginGuid, currentVersion, onComplete); } 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'); string[] array = v1.Split(new char[1] { '.' }); string[] array2 = v2.Split(new char[1] { '.' }); int num = Math.Max(array.Length, array2.Length); for (int i = 0; i < num; i++) { int result; int num2 = ((i < array.Length && int.TryParse(array[i], out result)) ? result : 0); int result2; int num3 = ((i < array2.Length && int.TryParse(array2[i], out result2)) ? result2 : 0); if (num2 < num3) { return -1; } if (num2 > num3) { return 1; } } return 0; } internal static void Log(string message) { ManualLogSource logger = _logger; if (logger != null) { logger.LogInfo((object)("[VersionChecker] " + message)); } } internal static void LogWarning(string message) { ManualLogSource logger = _logger; if (logger != null) { logger.LogWarning((object)("[VersionChecker] " + message)); } } internal static void LogError(string message) { ManualLogSource logger = _logger; if (logger != null) { logger.LogError((object)("[VersionChecker] " + message)); } } } 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 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) { Type type = AccessTools.TypeByName(typeName); if (type != null) { return type; } for (int i = 0; i < namespaces.Length; i++) { type = AccessTools.TypeByName(namespaces[i] + "." + typeName); if (type != null) { return type; } } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { type = assembly.GetTypes().FirstOrDefault((Type t) => t.Name == typeName || t.FullName == typeName); 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; } PropertyInfo property = type.GetProperty(memberName, AllBindingFlags); if (property != null && property.GetMethod != null) { return property.GetValue(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 = ""; 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 GameObject val = new GameObject("[" + typeof(T).Name + "]") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)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_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: 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"); } } 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() { LogWarning("[" + RunnerName + "] OnDestroy called - this should NOT happen!"); } } 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"; } } } namespace HavensBirthright { public class BirthrightRunner : PersistentRunnerBase { private float _outdoorTime; private float _lastTidalBlessingCheck; private float _lastInfernalForgeCheck; private bool _apiCacheInitialized; private Type _cropType; private Type _dayCycleType; private Type _tileManagerType; private object _tileManagerInstance; private MethodInfo _isWateredMethod; private MethodInfo _waterTileMethod; private MethodInfo _isHoedOrWateredMethod; private FieldInfo _farmingDataField; private FieldInfo _farmingTileMapField; private MethodInfo _worldToCellMethod; private object _gridInstance; private MethodInfo _gridCellToWorldMethod; private bool _tidalBlessingDiagLogged; private MethodInfo _cachedGetAmountMethod; private MethodInfo _cachedRemoveItemMethod; private bool _inventoryMethodsCached; private static float _cachedHPRatio = 1f; private static string _cachedSeason = null; private static bool _cachedIsDaytime = true; private static bool _cachedIsInMine = false; private static float _cachedQuickLearnerBonus = 0f; private static bool _cacheValid = false; protected override string RunnerName => "BirthrightRunner"; public static float CachedHPRatio => _cachedHPRatio; public static string CachedSeason => _cachedSeason; public static bool CachedIsDaytime => _cachedIsDaytime; public static bool CachedIsInMine => _cachedIsInMine; public static float CachedQuickLearnerBonus => _cachedQuickLearnerBonus; public static bool IsCacheValid => _cacheValid; protected override void OnUpdate() { if (!SceneHelpers.IsInGame()) { return; } CheckAbilityToggleHotkey(); if (!RacialConfig.EnableRacialBonuses.Value) { return; } RacialBonusManager racialBonusManager = Plugin.GetRacialBonusManager(); if (racialBonusManager == null) { return; } Race? playerRace = racialBonusManager.GetPlayerRace(); if (!playerRace.HasValue) { PlayerPatches.RetryRaceDetection(); playerRace = racialBonusManager.GetPlayerRace(); if (!playerRace.HasValue) { return; } } if (!_apiCacheInitialized) { InitializeApiCache(); _apiCacheInitialized = true; } UpdateStatCache(playerRace.Value); if (AbilityConfig.EnableActiveAbilities.Value) { if (playerRace.Value == Race.AmariBird && AbilityConfig.EnableTailwind.Value) { UpdateTailwind(); } if (playerRace.Value == Race.WaterElemental && AbilityConfig.EnableTidalBlessing.Value) { UpdateTidalBlessing(); } if ((playerRace.Value == Race.FireElemental || playerRace.Value == Race.Elemental) && AbilityConfig.EnableInfernalForge.Value) { UpdateInfernalForge(); } } } protected override void OnMenuTransition() { ActiveAbilityManager.ResetAll(); _outdoorTime = 0f; _lastInfernalForgeCheck = 0f; _apiCacheInitialized = false; _inventoryMethodsCached = false; _cachedGetAmountMethod = null; _cachedRemoveItemMethod = null; _tileManagerInstance = null; _worldToCellMethod = null; _gridInstance = null; _gridCellToWorldMethod = null; ResetStatCache(); AbilityPatches.ResetNotificationCache(); AbilityPatches.ResetReflectionCache(); Plugin.GetRacialBonusManager()?.ClearPlayerRace(); PlayerPatches.ResetRaceDetection(); } protected override void OnGameTransition() { _outdoorTime = 0f; _lastInfernalForgeCheck = 0f; _apiCacheInitialized = false; _inventoryMethodsCached = false; _cachedGetAmountMethod = null; _cachedRemoveItemMethod = null; _tileManagerInstance = null; _worldToCellMethod = null; _gridInstance = null; _gridCellToWorldMethod = null; ResetStatCache(); AbilityPatches.ResetNotificationCache(); AbilityPatches.ResetReflectionCache(); } private static void ResetStatCache() { _cacheValid = false; _cachedHPRatio = 1f; _cachedSeason = null; _cachedIsDaytime = true; _cachedIsInMine = false; _cachedQuickLearnerBonus = 0f; } 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 InitializeApiCache() { try { _cropType = ReflectionHelper.FindWishType("Crop"); _dayCycleType = ReflectionHelper.FindWishType("DayCycle"); if (_cropType != null) { Plugin.Log.LogInfo((object)"[BirthrightRunner] Found Crop type for Tidal Blessing"); } if (_dayCycleType != null) { Plugin.Log.LogInfo((object)"[BirthrightRunner] Found DayCycle type for synergies"); } CacheTileManager(); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[BirthrightRunner] API cache init failed: " + ex.Message)); } } private void CacheTileManager() { try { _tileManagerType = ReflectionHelper.FindWishType("TileManager"); if (_tileManagerType == null) { Plugin.Log.LogWarning((object)"[BirthrightRunner] TileManager type not found — Tidal Blessing will use fallback"); 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)("[BirthrightRunner] TileManager cached — " + $"IsWatered:{_isWateredMethod != null}, Water:{_waterTileMethod != null}, " + $"IsHoedOrWatered:{_isHoedOrWateredMethod != null}, " + $"farmingData:{_farmingDataField != null}, farmingTileMap:{_farmingTileMapField != null}")); TryGetTileManagerInstance(); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[BirthrightRunner] TileManager cache failed: " + ex.Message)); } } private 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)"[BirthrightRunner] TileManager instance was stale (destroyed Unity object) — 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)"[BirthrightRunner] 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)("[BirthrightRunner] WorldToCell method: " + ((_worldToCellMethod != null) ? "found" : "NOT found"))); } } else { ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogInfo((object)"[BirthrightRunner] farmingTileMap is null on TileManager instance"); } } } catch (Exception ex) { ManualLogSource log5 = Plugin.Log; if (log5 != null) { log5.LogWarning((object)("[BirthrightRunner] Failed to cache WorldToCell: " + 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 log6 = Plugin.Log; if (log6 != null) { log6.LogInfo((object)("[BirthrightRunner] Grid cached — CellToWorld: " + ((_gridCellToWorldMethod != null) ? "found" : "NOT found"))); } } else { ManualLogSource log7 = Plugin.Log; if (log7 != null) { log7.LogInfo((object)"[BirthrightRunner] layoutGrid is null — distance filtering will be disabled"); } } return; } catch (Exception ex2) { ManualLogSource log8 = Plugin.Log; if (log8 != null) { log8.LogWarning((object)("[BirthrightRunner] Failed to cache Grid: " + ex2.Message)); } return; } } ManualLogSource log9 = Plugin.Log; if (log9 != null) { log9.LogInfo((object)"[BirthrightRunner] TileManager instance NOT available (singleton returned null)"); } } catch (Exception ex3) { ManualLogSource log10 = Plugin.Log; if (log10 != null) { log10.LogWarning((object)("[BirthrightRunner] TileManager instance lookup failed: " + ex3.Message)); } } } private void UpdateStatCache(Race race) { try { _cachedIsInMine = IsInMine(); if (_dayCycleType != null) { object singletonInstance = ReflectionHelper.GetSingletonInstance(_dayCycleType); if (singletonInstance != null) { float num = ReflectionHelper.TryGetValue(singletonInstance, "Hour", -1f); if (num < 0f) { num = ReflectionHelper.TryGetValue(singletonInstance, "CurrentHour", -1f); } if (num < 0f) { num = ReflectionHelper.TryGetValue(singletonInstance, "currentHour", -1f); } if (num < 0f) { int num2 = ReflectionHelper.TryGetValue(singletonInstance, "Hour", -1); if (num2 >= 0) { num = num2; } } _cachedIsDaytime = num < 0f || (num >= 6f && num < 18f); object instanceValue = ReflectionHelper.GetInstanceValue(singletonInstance, "Season"); if (instanceValue == null) { instanceValue = ReflectionHelper.GetInstanceValue(singletonInstance, "season"); } if (instanceValue == null) { instanceValue = ReflectionHelper.GetInstanceValue(singletonInstance, "CurrentSeason"); } _cachedSeason = instanceValue?.ToString(); } } Player instance = Player.Instance; if ((Object)(object)instance != (Object)null) { float maxHealth = instance.MaxHealth; if (maxHealth > 0f) { _cachedHPRatio = ReflectionHelper.TryGetValue(instance, "health", maxHealth) / maxHealth; } else { _cachedHPRatio = 1f; } if (race == Race.Human && AbilityConfig.EnableQuickLearner != null && AbilityConfig.EnableQuickLearner.Value) { _cachedQuickLearnerBonus = CalculateQuickLearnerBonus(instance); } else { _cachedQuickLearnerBonus = 0f; } } _cacheValid = true; } catch (Exception ex) { Plugin.Log.LogWarning((object)("[BirthrightRunner] Cache update error: " + ex.Message)); _cacheValid = true; } } private static float CalculateQuickLearnerBonus(Player player) { try { int value = AbilityConfig.QuickLearnerSkillThreshold.Value; int num = 0; float num2 = ReflectionHelper.TryGetValue(player, "FarmingSkillLevel", 0f); float num3 = ReflectionHelper.TryGetValue(player, "MiningSkillLevel", 0f); float num4 = ReflectionHelper.TryGetValue(player, "FishingSkillLevel", 0f); float num5 = ReflectionHelper.TryGetValue(player, "ExplorationSkillLevel", 0f); if (num2 >= (float)value) { num++; } if (num3 >= (float)value) { num++; } if (num4 >= (float)value) { num++; } if (num5 >= (float)value) { num++; } try { object instanceValue = ReflectionHelper.GetInstanceValue(player, "Professions"); if (instanceValue != null) { Type type = ReflectionHelper.FindWishType("ProfessionType"); if (type != null) { object key = Enum.Parse(type, "Crafting"); if (instanceValue is IDictionary dictionary && dictionary.Contains(key) && ReflectionHelper.TryGetValue(dictionary[key], "level", 0) >= value) { num++; } } } } catch { } return Mathf.Min((float)num * AbilityConfig.QuickLearnerBonusPerSkill.Value, AbilityConfig.QuickLearnerMaxBonus.Value); } catch { return 0f; } } 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_0319: Unknown result type (might be due to invalid IL or missing references) //IL_032e: 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=" + ((_tileManagerType != null) ? "ok" : "NULL") + ", instance=" + ((_tileManagerInstance != null) ? "ok" : "NULL"))); } ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogInfo((object)("[TidalBlessing] Methods: Water=" + ((_waterTileMethod != null) ? "ok" : "NULL") + ", farmingData field=" + ((_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 (_farmingDataField != null && _tileManagerInstance != null) { try { if (_farmingDataField.GetValue(_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 { } } ManualLogSource log7 = Plugin.Log; if (log7 != null) { log7.LogInfo((object)("[TidalBlessing] Scene: " + SceneHelpers.GetCurrentSceneName())); } ManualLogSource log8 = Plugin.Log; if (log8 != null) { log8.LogInfo((object)$"[TidalBlessing] Player world pos: ({((Component)instance).transform.position.x:F1}, {((Component)instance).transform.position.y:F1})"); } ManualLogSource log9 = Plugin.Log; if (log9 != null) { log9.LogInfo((object)("[TidalBlessing] Grid distance filter: " + ((_gridCellToWorldMethod != null && _gridInstance != null) ? "enabled (radius=1)" : "disabled (no Grid)"))); } ManualLogSource log10 = Plugin.Log; if (log10 != null) { log10.LogInfo((object)"[TidalBlessing] === END DIAGNOSTIC ==="); } } if (num2 <= AbilityConfig.TidalBlessingHPThreshold.Value) { return; } if (_tileManagerType != null && _isWateredMethod != null && _waterTileMethod != null) { UpdateTidalBlessingViaTileManager(instance, maxHealth, num); return; } ManualLogSource log11 = Plugin.Log; if (log11 != null) { log11.LogInfo((object)"[TidalBlessing] Using fallback Crop approach (TileManager methods not available)"); } UpdateTidalBlessingViaCropObjects(instance, maxHealth, num); } catch (Exception ex) { Plugin.Log.LogWarning((object)("[TidalBlessing] Error: " + ex.Message)); ManualLogSource log12 = Plugin.Log; if (log12 != null) { log12.LogWarning((object)("[TidalBlessing] Stack: " + ex.StackTrace)); } } } } private void UpdateTidalBlessingViaTileManager(Player player, float maxHP, float currentHP) { //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_02a1: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) object tileManagerInstance = _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"); } _tileManagerInstance = null; } if (_tileManagerInstance == null) { TryGetTileManagerInstance(); } if (_tileManagerInstance == null) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)"[TidalBlessing] TileManager instance not available — skipping this cycle"); } } else { if (_farmingDataField == null || !(_farmingDataField.GetValue(_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 = _gridCellToWorldMethod != null && _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)_gridCellToWorldMethod.Invoke(_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 = _waterTileMethod.Invoke(_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_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: 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_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) if (_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(_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; } if (!_inventoryMethodsCached) { CacheInventoryMethods(instanceValue); _inventoryMethodsCached = true; } 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 = AbilityPatches.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 = 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 (!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 = AbilityPatches.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"); } 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); AbilityPatches.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 CacheInventoryMethods(object inventory) { try { Type type = inventory.GetType(); _cachedGetAmountMethod = type.GetMethod("GetAmount", new Type[1] { typeof(int) }); if (_cachedGetAmountMethod == null) { _cachedGetAmountMethod = type.GetMethod("GetItemAmount", new Type[1] { typeof(int) }); } _cachedRemoveItemMethod = type.GetMethod("RemoveAll", new Type[1] { typeof(int) }); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[InfernalForge] Inventory methods cached - GetAmount:" + (_cachedGetAmountMethod?.Name ?? "null") + ", RemoveItem:" + (_cachedRemoveItemMethod?.Name ?? "null"))); } } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)("[InfernalForge] Failed to cache inventory methods: " + ex.Message)); } } } private 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)$"[InfernalForge] GetAmount failed for item {itemId}: {ex.Message}"); } } return 0; } private bool RemoveInventoryItem(object inventory, int itemId, int amount) { try { if (_cachedRemoveItemMethod == null) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)$"[InfernalForge] No cached RemoveAll method — cannot remove {amount}x item {itemId}"); } return false; } int inventoryAmount = GetInventoryAmount(inventory, itemId); if (inventoryAmount < amount) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)$"[InfernalForge] Not enough items: have {inventoryAmount}, need {amount} of item {itemId}"); } 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 log3 = Plugin.Log; if (log3 != null) { log3.LogWarning((object)$"[InfernalForge] RemoveItem failed for {amount}x item {itemId}: {ex2.GetType().Name}: {ex2.Message}"); } return false; } } private void AddInventoryItem(object inventory, int itemId, int amount) { try { MethodInfo addItemIntMethod = AbilityPatches.GetAddItemIntMethod(inventory); if (addItemIntMethod != null) { AbilityPatches.InvokeAddItem(addItemIntMethod, inventory, itemId, amount, notify: false); } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)$"[InfernalForge] Failed to return {amount}x item {itemId}: {ex.Message}"); } } } public static float GetCurrentHour() { try { Type type = 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 { return -1f; } } public static string GetCurrentSeason() { try { Type type = 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 { 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 { return 1f; } } private void CheckAbilityToggleHotkey() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) try { if (!Input.GetKeyDown(Plugin.StaticAbilityToggleKey)) { return; } RacialBonusManager racialBonusManager = Plugin.GetRacialBonusManager(); if (racialBonusManager == null) { return; } Race? playerRace = racialBonusManager.GetPlayerRace(); if (!playerRace.HasValue) { return; } string activeAbilityForRace = GetActiveAbilityForRace(playerRace.Value); if (activeAbilityForRace == null) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)$"[BirthrightRunner] No toggleable active ability for race {playerRace.Value}"); } return; } bool flag = ActiveAbilityManager.ToggleRuntime(activeAbilityForRace); string abilityDisplayName = GetAbilityDisplayName(activeAbilityForRace); AbilityPatches.SendGameNotification(abilityDisplayName + ": " + (flag ? "ON" : "OFF")); ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)("[BirthrightRunner] Toggled " + abilityDisplayName + ": " + (flag ? "ON" : "OFF"))); } } catch (Exception ex) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogWarning((object)("[BirthrightRunner] Toggle hotkey error: " + ex.Message)); } } } private static string GetActiveAbilityForRace(Race race) { return race switch { Race.FireElemental => "InfernalForge", Race.WaterElemental => "TidalBlessing", _ => null, }; } private static string GetAbilityDisplayName(string abilityKey) { if (!(abilityKey == "InfernalForge")) { if (abilityKey == "TidalBlessing") { return "Tidal Blessing"; } return abilityKey; } return "Infernal Forge"; } } [BepInPlugin("com.azraelgodking.havensbirthright", "Haven's Birthright", "1.2.0")] public class Plugin : BaseUnityPlugin { internal static KeyCode StaticAbilityToggleKey = (KeyCode)290; private Harmony _harmony; private RacialBonusManager _racialBonusManager; private ConfigEntry<bool> _checkForUpdates; private BirthrightRunner _runner; public static Plugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } public static ConfigFile ConfigFile { get; private set; } private void Awake() { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: 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_00a6: Expected O, but got Unknown //IL_03b2: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; ConfigFile = ((BaseUnityPlugin)this).Config; Log.LogInfo((object)"Loading Haven's Birthright v1.2.0"); try { RacialConfig.Initialize(((BaseUnityPlugin)this).Config); AbilityConfig.Initialize(((BaseUnityPlugin)this).Config); StaticAbilityToggleKey = AbilityConfig.ActiveAbilityToggleKey.Value; _checkForUpdates = ((BaseUnityPlugin)this).Config.Bind<bool>("Updates", "CheckForUpdates", true, "Check for mod updates on startup"); _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"); PatchMethod(typeFromHandle, "Initialize", typeof(PlayerPatches), "OnPlayerInitialize", Type.EmptyTypes); PatchMethod(typeFromHandle, "GetStat", typeof(StatPatches), "ModifyGetStat", new Type[1] { typeof(StatType) }); PatchMethodPrefix(typeFromHandle, "ReceiveDamage", typeof(CombatPatches), "ModifyDamageReceived"); PatchMethod(typeFromHandle, "ReceiveDamage", typeof(CombatPatches), "OnDamageReceivedPostfix"); Type type = AccessTools.TypeByName("Wish.NPCAI"); if (type != null) { PatchMethodPrefix(type, "AddFriendship", typeof(EconomyPatches), "ModifyRelationshipGain", new Type[1] { typeof(int) }); } else { Log.LogWarning((object)"Could not find NPCAI type - relationship bonuses will not work"); } Type type2 = AccessTools.TypeByName("Wish.ShopMenu"); if (type2 != null) { PatchMethodPrefix(type2, "BuyItem", typeof(EconomyPatches), "ModifyBuyPrice"); } else { Log.LogWarning((object)"Could not find ShopMenu type - shop discounts will not work"); } PatchMethodPrefix(typeFromHandle, "AddMana", typeof(AbilityPatches), "OnPlayerAddManaPrefix"); 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}"); } catch (Exception arg) { Log.LogError((object)$"Harmony patching failed: {arg}"); } if (_checkForUpdates.Value) { VersionChecker.CheckForUpdate("com.azraelgodking.havensbirthright", "1.2.0", 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}"); } catch (Exception arg2) { Log.LogError((object)string.Format("Failed to load {0}: {1}", "Haven's Birthright", arg2)); } } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private void PatchMethod(Type targetType, string methodName, Type patchType, string patchMethodName, Type[] parameters = null) { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0095: 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)); 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)); return; } _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)); } } private void PatchMethodPrefix(Type targetType, string methodName, Type patchType, string patchMethodName, Type[] parameters = null) { //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0098: 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)); 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)); return; } _harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)("Successfully patched " + targetType.Name + "." + methodName + " (prefix)")); } catch (Exception ex) { Log.LogError((object)("Failed to patch " + targetType.Name + "." + methodName + ": " + ex.Message)); } } public static RacialBonusManager GetRacialBonusManager() { return Instance?._racialBonusManager; } public static void EnsureRunner() { if ((Object)(object)Instance == (Object)null) { return; } if (!((Object)(object)Instance._runner == (Object)null)) { Object runner = (Object)(object)Instance._runner; if (runner == null || !(runner == (Object)null)) { return; } } Instance._runner = PersistentRunnerBase.CreateRunner<BirthrightRunner>(); ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"[Plugin] BirthrightRunner recreated after destruction"); } } } public static class PluginInfo { public const string PLUGIN_GUID = "com.azraelgodking.havensbirthright"; public const string PLUGIN_NAME = "Haven's Birthright"; public const string PLUGIN_VERSION = "1.2.0"; } public enum Race { Human, Elf, Angel, Demon, Elemental, FireElemental, WaterElemental, Amari, AmariCat, AmariDog, AmariBird, AmariAquatic, AmariReptile, Naga } public enum ElementalVariant { None, Fire, Water } public enum BonusType { MeleeStrength, MagicPower, Defense, CriticalChance, AttackSpeed, FarmingSpeed, CropQuality, CropYield, WateringEfficiency, MiningSpeed, MiningYield, WoodcuttingSpeed, WoodcuttingYield, FishingSpeed, FishingLuck, ForagingChance, CraftingSpeed, CraftingQuality, RelationshipGain, ShopDiscount, MovementSpeed, MaxHealth, MaxMana, ManaRegen, HealthRegen, ExperienceGain, GoldFind, LuckBonus, DodgeChance, FishingMinigameSpeed, WoodcuttingDamage, MiningDamage, SpellAttackSpeed, AirSkipChance, CommunityTokenGain, TripleGoldChance } public class RacialBonus { public BonusType Type { get; set; } public float Value { get; set; } public bool IsPercentage { get; set; } public string Description { get; set; } public RacialBonus(BonusType type, float value, bool isPercentage, string description) { Type = type; Value = value; IsPercentage = isPercentage; Description = description; } public string GetFormattedValue() { if (IsPercentage) { if (!(Value >= 0f)) { return $"{Value}%"; } return $"+{Value}%"; } if (!(Value >= 0f)) { return $"{Value}"; } return $"+{Value}"; } } public class RacialBonusManager { private Dictionary<Race, List<RacialBonus>> _racialBonuses; private Race? _currentPlayerRace; private int _cachedGameSubRace = -1; public RacialBonusManager() { _racialBonuses = new Dictionary<Race, List<RacialBonus>>(); InitializeDefaultBonuses(); } private void InitializeDefaultBonuses() { _racialBonuses[Race.Human] = new List<RacialBonus> { new RacialBonus(BonusType.ExperienceGain, RacialConfig.HumanExpBonus.Value, isPercentage: true, "Adaptable: Gain experience faster"), new RacialBonus(BonusType.RelationshipGain, RacialConfig.HumanRelationshipBonus.Value, isPercentage: true, "Charismatic: Build relationships faster"), new RacialBonus(BonusType.ShopDiscount, RacialConfig.HumanShopDiscount.Value, isPercentage: true, "Silver Tongue: Small discount at shops"), new RacialBonus(BonusType.CommunityTokenGain, RacialConfig.HumanCommunityTokenBonus.Value, isPercentage: true, "Community Leader: Earn more community tokens") }; _racialBonuses[Race.Elf] = new List<RacialBonus> { new RacialBonus(BonusType.FarmingSpeed, RacialConfig.ElfFarmingBonus.Value, isPercentage: true, "Nature's Touch: Farm crops faster"), new RacialBonus(BonusType.CropQuality, RacialConfig.ElfCropQualityBonus.Value, isPercentage: true, "Green Thumb: Higher chance for quality crops"), new RacialBonus(BonusType.ForagingChance, RacialConfig.ElfForagingBonus.Value, isPercentage: true, "Forest Walker: Find more foragables"), new RacialBonus(BonusType.ManaRegen, RacialConfig.ElfManaRegenBonus.Value, isPercentage: true, "Arcane Heritage: Faster mana regeneration"), new RacialBonus(BonusType.WoodcuttingDamage, RacialConfig.ElfWoodcuttingDamageBonus.Value, isPercentage: true, "Timber Master: Deal more damage to trees") }; _racialBonuses[Race.Angel] = new List<RacialBonus> { new RacialBonus(BonusType.MaxMana, RacialConfig.AngelMaxManaBonus.Value, isPercentage: true, "Divine Reservoir: Increased maximum mana"), new RacialBonus(BonusType.MagicPower, RacialConfig.AngelMagicBonus.Value, isPercentage: true, "Holy Light: Enhanced magic damage"), new RacialBonus(BonusType.HealthRegen, RacialConfig.AngelHealthRegenBonus.Value, isPercentage: true, "Blessed Recovery: Faster health regeneration"), new RacialBonus(BonusType.LuckBonus, RacialConfig.AngelLuckBonus.Value, isPercentage: true, "Fortune's Favor: Blessed with good luck"), new RacialBonus(BonusType.SpellAttackSpeed, RacialConfig.AngelSpellAttackSpeedBonus.Value, isPercentage: true, "Divine Haste: Cast spells faster") }; _racialBonuses[Race.Demon] = new List<RacialBonus> { new RacialBonus(BonusType.MeleeStrength, RacialConfig.DemonMeleeBonus.Value, isPercentage: true, "Infernal Might: Increased melee damage"), new RacialBonus(BonusType.CriticalChance, RacialConfig.DemonCritBonus.Value, isPercentage: true, "Ruthless: Higher critical hit chance"), new RacialBonus(BonusType.MaxHealth, RacialConfig.DemonHealthBonus.Value, isPercentage: true, "Hellforged Vitality: Increased maximum health"), new RacialBonus(BonusType.GoldFind, RacialConfig.DemonGoldBonus.Value, isPercentage: true, "Greed: Find more gold"), new RacialBonus(BonusType.TripleGoldChance, RacialConfig.DemonTripleGoldBonus.Value, isPercentage: true, "Avarice: Chance for triple gold drops"), new RacialBonus(BonusType.MiningDamage, RacialConfig.DemonMiningDamageBonus.Value, isPercentage: true, "Hellfire Pick: Deal more damage to rocks") }; _racialBonuses[Race.FireElemental] = new List<RacialBonus> { new RacialBonus(BonusType.MeleeStrength, RacialConfig.FireElementalMeleeBonus.Value, isPercentage: true, "Burning Fury: Increased melee damage"), new RacialBonus(BonusType.MagicPower, RacialConfig.FireElementalMagicBonus.Value, isPercentage: true, "Inferno: Enhanced magic damage"), new RacialBonus(BonusType.AttackSpeed, RacialConfig.FireElementalAttackSpeedBonus.Value, isPercentage: true, "Wildfire: Faster attack speed"), new RacialBonus(BonusType.CriticalChance, RacialConfig.FireElementalCritBonus.Value, isPercentage: true, "Scorching Strike: Higher critical hit chance") }; _racialBonuses[Race.WaterElemental] = new List<RacialBonus> { new RacialBonus(BonusType.Defense, RacialConfig.WaterElementalDefenseBonus.Value, isPercentage: true, "Tidal Shield: Increased defense"), new RacialBonus(BonusType.HealthRegen, RacialConfig.WaterElementalHealthRegenBonus.Value, isPercentage: true, "Healing Waters: Faster health regeneration"), new RacialBonus(BonusType.ManaRegen, RacialConfig.WaterElementalManaRegenBonus.Value, isPercentage: true, "Flowing Spirit: Faster mana regeneration"), new RacialBonus(BonusType.FishingLuck, RacialConfig.WaterElementalFishingBonus.Value, isPercentage: true, "Aquatic Kinship: Better fishing luck"), new RacialBonus(BonusType.FishingMinigameSpeed, RacialConfig.WaterElementalFishingMinigameBonus.Value, isPercentage: true, "Water Mastery: Faster fishing minigame"), new RacialBonus(BonusType.AirSkipChance, RacialConfig.WaterElementalAirSkipBonus.Value, isPercentage: true, "Breathless: No need for air underwater") }; _racialBonuses[Race.Elemental] = new List<RacialBonus> { new RacialBonus(BonusType.MiningSpeed, RacialConfig.ElementalMiningSpeedBonus.Value, isPercentage: true, "Stone Affinity: Mine faster"), new RacialBonus(BonusType.MiningYield, RacialConfig.ElementalMiningYieldBonus.Value, isPercentage: true, "Earth's Bounty: Chance for extra ore"), new RacialBonus(BonusType.MagicPower, RacialConfig.ElementalMagicBonus.Value, isPercentage: true, "Elemental Mastery: Enhanced magic damage"), new RacialBonus(BonusType.Defense, RacialConfig.ElementalDefenseBonus.Value, isPercentage: true, "Hardened Form: Increased defense") }; _racialBonuses[Race.Amari] = new List<RacialBonus> { new RacialBonus(BonusType.MovementSpeed, RacialConfig.AmariSpeedBonus.Value, isPercentage: true, "Swift Paws: Move faster"), new RacialBonus(BonusType.AttackSpeed, RacialConfig.AmariAttackSpeedBonus.Value, isPercentage: true, "Predator's Reflexes: Attack faster"), new RacialBonus(BonusType.CraftingSpeed, RacialConfig.AmariCraftingBonus.Value, isPercentage: true, "Skilled Artisan: Craft items faster"), new RacialBonus(BonusType.WoodcuttingSpeed, RacialConfig.AmariWoodcuttingBonus.Value, isPercentage: true, "Forest Hunter: Chop trees faster") }; _racialBonuses[Race.AmariCat] = new List<RacialBonus> { new RacialBonus(BonusType.MovementSpeed, RacialConfig.AmariCatSpeedBonus.Value, isPercentage: true, "Feline Grace: Move faster"), new RacialBonus(BonusType.AttackSpeed, RacialConfig.AmariCatAttackSpeedBonus.Value, isPercentage: true, "Quick Pounce: Attack faster"), new RacialBonus(BonusType.CriticalChance, RacialConfig.AmariCatCritBonus.Value, isPercentage: true, "Predator's Strike: Higher critical hit chance"), new RacialBonus(BonusType.DodgeChance, RacialConfig.AmariCatDodgeBonus.Value, isPercentage: true, "Nine Lives: Higher chance to dodge attacks") }; _racialBonuses[Race.AmariDog] = new List<RacialBonus> { new RacialBonus(BonusType.MaxHealth, RacialConfig.AmariDogHealthBonus.Value, isPercentage: true, "Loyal Heart: Increased maximum health"), new RacialBonus(BonusType.Defense, RacialConfig.AmariDogDefenseBonus.Value, isPercentage: true, "Guardian's Resolve: Increased defense"), new RacialBonus(BonusType.RelationshipGain, RacialConfig.AmariDogRelationshipBonus.Value, isPercentage: true, "Best Friend: Build relationships faster"), new RacialBonus(BonusType.ExperienceGain, RacialConfig.AmariDogExpBonus.Value, isPercentage: true, "Eager Learner: Gain experience faster") }; _racialBonuses[Race.AmariBird] = new List<RacialBonus> { new RacialBonus(BonusType.MovementSpeed, RacialConfig.AmariBirdSpeedBonus.Value, isPercentage: true, "Wind Rider: Move faster"), new RacialBonus(BonusType.ForagingChance, RacialConfig.AmariBirdForagingBonus.Value, isPercentage: true, "Keen Eye: Find more foragables"), new RacialBonus(BonusType.ManaRegen, RacialConfig.AmariBirdManaRegenBonus.Value, isPercentage: true, "Sky Spirit: Faster mana regeneration"), new RacialBonus(BonusType.DodgeChance, RacialConfig.AmariBirdDodgeBonus.Value, isPercentage: true, "Evasive Flight: Higher chance to dodge attacks") }; _racialBonuses[Race.AmariAquatic] = new List<RacialBonus> { new RacialBonus(BonusType.FishingSpeed, RacialConfig.AmariAquaticFishingSpeedBonus.Value, isPercentage: true, "Water Born: Fish faster"), new RacialBonus(BonusType.FishingLuck, RacialConfig.AmariAquaticFishingLuckBonus.Value, isPercentage: true, "Tidal Blessing: Better fishing luck"), new RacialBonus(BonusType.ManaRegen, RacialConfig.AmariAquaticManaRegenBonus.Value, isPercentage: true, "Flowing Spirit: Faster mana regeneration"), new RacialBonus(BonusType.HealthRegen, RacialConfig.AmariAquaticHealthRegenBonus.Value, isPercentage: true, "Healing Waters: Faster health regeneration"), new RacialBonus(BonusType.FishingMinigameSpeed, RacialConfig.AmariAquaticFishingMinigameBonus.Value, isPercentage: true, "Aquatic Instinct: Faster fishing minigame"), new RacialBonus(BonusType.AirSkipChance, RacialConfig.AmariAquaticAirSkipBonus.Value, isPercentage: true, "Amphibious: Chance to skip air requirement") }; _racialBonuses[Race.AmariReptile] = new List<RacialBonus> { new RacialBonus(BonusType.Defense, RacialConfig.AmariReptileDefenseBonus.Value, isPercentage: true, "Scaled Hide: Increased defense"), new RacialBonus(BonusType.MeleeStrength, RacialConfig.AmariReptileMeleeBonus.Value, isPercentage: true, "Primal Strength: Increased melee damage"), new RacialBonus(BonusType.MaxHealth, RacialConfig.AmariReptileHealthBonus.Value, isPercentage: true, "Cold Blood: Increased maximum health"), new RacialBonus(BonusType.MiningSpeed, RacialConfig.AmariReptileMiningBonus.Value, isPercentage: true, "Burrow Instinct: Mine faster") }; _racialBonuses[Race.Naga] = new List<RacialBonus> { new RacialBonus(BonusType.FishingSpeed, RacialConfig.NagaFishingSpeedBonus.Value, isPercentage: true, "Aquatic Nature: Fish faster"), new RacialBonus(BonusType.FishingLuck, RacialConfig.NagaFishingLuckBonus.Value, isPercentage: true, "Sea's Blessing: Better fishing luck"), new RacialBonus(BonusType.Defense, RacialConfig.NagaDefenseBonus.Value, isPercentage: true, "Scaled Hide: Increased defense"), new RacialBonus(BonusType.ManaRegen, RacialConfig.NagaManaRegenBonus.Value, isPercentage: true, "Tidal Magic: Faster mana regeneration"), new RacialBonus(BonusType.AirSkipChance, RacialConfig.NagaAirSkipBonus.Value, isPercentage: true, "Serpent's Breath: Chance to skip air requirement") }; Plugin.Log.LogInfo((object)$"Initialized racial bonuses for {_racialBonuses.Count} races"); } public void SetPlayerRace(Race race) { _currentPlayerRace = race; Plugin.Log.LogInfo((object)$"Player race set to: {race}"); } public void SetPlayerRace(Race race, ElementalVariant variant) { if (race == Race.Elemental) { _currentPlayerRace = variant switch { ElementalVariant.Fire => Race.FireElemental, ElementalVariant.Water => Race.WaterElemental, _ => Race.Elemental, }; } else { _currentPlayerRace = race; } Plugin.Log.LogInfo((object)$"Player race set to: {_currentPlayerRace} (variant: {variant})"); } public void SetPlayerRace(Race race, int gameSubRace) { _currentPlayerRace = race; _cachedGameSubRace = gameSubRace; Plugin.Log.LogInfo((object)$"Player race set to: {race} (cached SubRace: {gameSubRace})"); } public bool IsElemental() { if (_currentPlayerRace.GetValueOrDefault() != Race.Elemental && _currentPlayerRace.GetValueOrDefault() != Race.FireElemental) { return _currentPlayerRace.GetValueOrDefault() == Race.WaterElemental; } return true; } public bool IsAmari() { if (_currentPlayerRace.GetValueOrDefault() != Race.Amari && _currentPlayerRace.GetValueOrDefault() != Race.AmariCat && _currentPlayerRace.GetValueOrDefault() != Race.AmariDog && _currentPlayerRace.GetValueOrDefault() != Race.AmariBird && _currentPlayerRace.GetValueOrDefault() != Race.AmariAquatic) { return _currentPlayerRace.GetValueOrDefault() == Race.AmariReptile; } return true; } public bool IsNaga() { return _currentPlayerRace.GetValueOrDefault() == Race.Naga; } public int GetCachedSubRace() { return _cachedGameSubRace; } public void ClearPlayerRace() { _currentPlayerRace = null; _cachedGameSubRace = -1; Plugin.Log.LogDebug((object)"[RacialBonusManager] Player race cleared"); } public Race? GetPlayerRace() { return _currentPlayerRace; } public List<RacialBonus> GetBonusesForRace(Race race) { if (_racialBonuses.TryGetValue(race, out var value)) { return value; } return new List<RacialBonus>(); } public List<RacialBonus> GetCurrentPlayerBonuses() { if (_currentPlayerRace.HasValue) { return GetBonusesForRace(_currentPlayerRace.Value); } return new List<RacialBonus>(); } public float GetBonusValue(BonusType type) { if (!_currentPlayerRace.HasValue) { return 0f; } foreach (RacialBonus item in GetBonusesForRace(_currentPlayerRace.Value)) { if (item.Type == type) { return item.Value; } } return 0f; } public float ApplyBonus(float baseValue, BonusType type) { float bonusValue = GetBonusValue(type); if (bonusValue == 0f) { return baseValue; } return baseValue * (1f + bonusValue / 100f); } public bool HasBonus(BonusType type) { return GetBonusValue(type) != 0f; } public void RefreshBonuses() { InitializeDefaultBonuses(); Plugin.Log.LogInfo((object)"Racial bonuses refreshed from config"); } } public static class RacialConfig { public static ConfigEntry<float> HumanExpBonus; public static ConfigEntry<float> HumanRelationshipBonus; public static ConfigEntry<float> HumanShopDiscount; public static ConfigEntry<float> ElfFarmingBonus; public static ConfigEntry<float> ElfCropQualityBonus; public static ConfigEntry<float> ElfForagingBonus; public static ConfigEntry<float> ElfManaRegenBonus; public static ConfigEntry<float> AngelMaxManaBonus; public static ConfigEntry<float> AngelMagicBonus; public static ConfigEntry<float> AngelHealthRegenBonus; public static ConfigEntry<float> AngelLuckBonus; public static ConfigEntry<float> DemonMeleeBonus; public static ConfigEntry<float> DemonCritBonus; public static ConfigEntry<float> DemonHealthBonus; public static ConfigEntry<float> DemonGoldBonus; public static ConfigEntry<float> FireElementalMeleeBonus; public static ConfigEntry<float> FireElementalMagicBonus; public static ConfigEntry<float> FireElementalAttackSpeedBonus; public static ConfigEntry<float> FireElementalCritBonus; public static ConfigEntry<float> WaterElementalDefenseBonus; public static ConfigEntry<float> WaterElementalHealthRegenBonus; public static ConfigEntry<float> WaterElementalManaRegenBonus; public static ConfigEntry<float> WaterElementalFishingBonus; public static ConfigEntry<float> ElementalMiningSpeedBonus; public static ConfigEntry<float> ElementalMiningYieldBonus; public static ConfigEntry<float> ElementalMagicBonus; public static ConfigEntry<float> ElementalDefenseBonus; public static ConfigEntry<float> AmariSpeedBonus; public static ConfigEntry<float> AmariAttackSpeedBonus; public static ConfigEntry<float> AmariCraftingBonus; public static ConfigEntry<float> AmariWoodcuttingBonus; public static ConfigEntry<float> AmariCatSpeedBonus; public static ConfigEntry<float> AmariCatAttackSpeedBonus; public static ConfigEntry<float> AmariCatCritBonus; public static ConfigEntry<float> AmariCatDodgeBonus; public static ConfigEntry<float> AmariDogHealthBonus; public static ConfigEntry<float> AmariDogDefenseBonus; public static ConfigEntry<float> AmariDogRelationshipBonus; public static ConfigEntry<float> AmariDogExpBonus; public static ConfigEntry<float> AmariBirdSpeedBonus; public static ConfigEntry<float> AmariBirdForagingBonus; public static ConfigEntry<float> AmariBirdManaRegenBonus; public static ConfigEntry<float> AmariBirdDodgeBonus; public static ConfigEntry<float> AmariAquaticFishingSpeedBonus; public static ConfigEntry<float> AmariAquaticFishingLuckBonus; public static ConfigEntry<float> AmariAquaticManaRegenBonus; public static ConfigEntry<float> AmariAquaticHealthRegenBonus; public static ConfigEntry<float> AmariReptileDefenseBonus; public static ConfigEntry<float> AmariReptileMeleeBonus; public static ConfigEntry<float> AmariReptileHealthBonus; public static ConfigEntry<float> AmariReptileMiningBonus; public static ConfigEntry<float> NagaFishingSpeedBonus; public static ConfigEntry<float> NagaFishingLuckBonus; public static ConfigEntry<float> NagaDefenseBonus; public static ConfigEntry<float> NagaManaRegenBonus; public static ConfigEntry<float> NagaAirSkipBonus; public static ConfigEntry<float> ElfWoodcuttingDamageBonus; public static ConfigEntry<float> HumanCommunityTokenBonus; public static ConfigEntry<float> DemonTripleGoldBonus; public static ConfigEntry<float> DemonMiningDamageBonus; public static ConfigEntry<float> AngelSpellAttackSpeedBonus; public static ConfigEntry<float> WaterElementalFishingMinigameBonus; public static ConfigEntry<float> WaterElementalAirSkipBonus; public static ConfigEntry<float> AmariAquaticFishingMinigameBonus; public static ConfigEntry<float> AmariAquaticAirSkipBonus; public static ConfigEntry<bool> EnableRacialBonuses; public static ConfigEntry<bool> ShowBonusNotifications; public static void Initialize(ConfigFile config) { EnableRacialBonuses = config.Bind<bool>("General", "EnableRacialBonuses", true, "Enable or disable all racial bonuses"); ShowBonusNotifications = config.Bind<bool>("General", "ShowBonusNotifications", true, "Show notifications when racial bonuses are applied"); HumanExpBonus = config.Bind<float>("Human", "ExperienceBonus", 10f, "Percentage bonus to experience gain"); HumanRelationshipBonus = config.Bind<float>("Human", "RelationshipBonus", 15f, "Percentage bonus to relationship point gain"); HumanShopDiscount = config.Bind<float>("Human", "ShopDiscount", 5f, "Percentage discount at shops"); ElfFarmingBonus = config.Bind<float>("Elf", "FarmingSpeedBonus", 15f, "Percentage bonus to farming speed"); ElfCropQualityBonus = config.Bind<float>("Elf", "CropQualityBonus", 20f, "Percentage bonus to crop quality chance"); ElfForagingBonus = config.Bind<float>("Elf", "ForagingBonus", 25f, "Percentage bonus to foraging find chance"); ElfManaRegenBonus = config.Bind<float>("Elf", "ManaRegenBonus", 15f, "Percentage bonus to mana regeneration"); AngelMaxManaBonus = config.Bind<float>("Angel", "MaxManaBonus", 20f, "Percentage bonus to maximum mana"); AngelMagicBonus = config.Bind<float>("Angel", "MagicPowerBonus", 15f, "Percentage bonus to magic damage"); AngelHealthRegenBonus = config.Bind<float>("Angel", "HealthRegenBonus", 25f, "Percentage bonus to health regeneration"); AngelLuckBonus = config.Bind<float>("Angel", "LuckBonus", 10f, "Percentage bonus to luck"); DemonMeleeBonus = config.Bind<float>("Demon", "MeleeDamageBonus", 20f, "Percentage bonus to melee damage"); DemonCritBonus = config.Bind<float>("Demon", "CriticalChanceBonus", 15f, "Percentage bonus to critical hit chance"); DemonHealthBonus = config.Bind<float>("Demon", "MaxHealthBonus", 15f, "Percentage bonus to maximum health"); DemonGoldBonus = config.Bind<float>("Demon", "GoldFindBonus", 20f, "Percentage bonus to gold drops"); FireElementalMeleeBonus = config.Bind<float>("Fire Elemental", "MeleeDamageBonus", 15f, "Percentage bonus to melee damage"); FireElementalMagicBonus = config.Bind<float>("Fire Elemental", "MagicPowerBonus", 20f, "Percentage bonus to magic damage"); FireElementalAttackSpeedBonus = config.Bind<float>("Fire Elemental", "AttackSpeedBonus", 10f, "Percentage bonus to attack speed"); FireElementalCritBonus = config.Bind<float>("Fire Elemental", "CriticalChanceBonus", 15f, "Percentage bonus to critical hit chance"); WaterElementalDefenseBonus = config.Bind<float>("Water Elemental", "DefenseBonus", 20f, "Percentage bonus to defense"); WaterElementalHealthRegenBonus = config.Bind<float>("Water Elemental", "HealthRegenBonus", 20f, "Percentage bonus to health regeneration"); WaterElementalManaRegenBonus = config.Bind<float>("Water Elemental", "ManaRegenBonus", 25f, "Percentage bonus to mana regeneration"); WaterElementalFishingBonus = config.Bind<float>("Water Elemental", "FishingLuckBonus", 20f, "Percentage bonus to fishing luck"); ElementalMiningSpeedBonus = config.Bind<float>("Elemental (Generic)", "MiningSpeedBonus", 20f, "Percentage bonus to mining speed (used if Fire/Water variant cannot be detected)"); ElementalMiningYieldBonus = config.Bind<float>("Elemental (Generic)", "MiningYieldBonus", 15f, "Percentage bonus to mining yield"); ElementalMagicBonus = config.Bind<float>("Elemental (Generic)", "MagicPowerBonus", 10f, "Percentage bonus to magic damage"); ElementalDefenseBonus = config.Bind<float>("Elemental (Generic)", "DefenseBonus", 15f, "Percentage bonus to defense"); AmariSpeedBonus = config.Bind<float>("Amari (Generic)", "MovementSpeedBonus", 15f, "Percentage bonus to movement speed (used if variant cannot be detected)"); AmariAttackSpeedBonus = config.Bind<float>("Amari (Generic)", "AttackSpeedBonus", 15f, "Percentage bonus to attack speed"); AmariCraftingBonus = config.Bind<float>("Amari (Generic)", "CraftingSpeedBonus", 20f, "Percentage bonus to crafting speed"); AmariWoodcuttingBonus = config.Bind<float>("Amari (Generic)", "WoodcuttingSpeedBonus", 15f, "Percentage bonus to woodcutting speed"); AmariCatSpeedBonus = config.Bind<float>("Amari Cat", "MovementSpeedBonus", 20f, "Percentage bonus to movement speed"); AmariCatAttackSpeedBonus = config.Bind<float>("Amari Cat", "AttackSpeedBonus", 20f, "Percentage bonus to attack speed"); AmariCatCritBonus = config.Bind<float>("Amari Cat", "CriticalChanceBonus", 15f, "Percentage bonus to critical hit chance"); AmariCatDodgeBonus = config.Bind<float>("Amari Cat", "DodgeChanceBonus", 10f, "Percentage bonus to dodge chance"); AmariDogHealthBonus = config.Bind<float>("Amari Dog", "MaxHealthBonus", 20f, "Percentage bonus to maximum health"); AmariDogDefenseBonus = config.Bind<float>("Amari Dog", "DefenseBonus", 15f, "Percentage bonus to defense"); AmariDogRelationshipBonus = config.Bind<float>("Amari Dog", "RelationshipBonus", 25f, "Percentage bonus to relationship point gain"); AmariDogExpBonus = config.Bind<float>("Amari Dog", "ExperienceBonus", 10f, "Percentage bonus to experience gain"); AmariBirdSpeedBonus = config.Bind<float>("Amari Bird", "MovementSpeedBonus", 25f, "Percentage bonus to movement speed"); AmariBirdForagingBonus = config.Bind<float>("Amari Bird", "ForagingBonus", 25f, "Percentage bonus to foraging find chance"); AmariBirdManaRegenBonus = config.Bind<float>("Amari Bird", "ManaRegenBonus", 15f, "Percentage bonus to mana regeneration"); AmariBirdDodgeBonus = config.Bind<float>("Amari Bird", "DodgeChanceBonus", 15f, "Percentage bonus to dodge chance"); AmariAquaticFishingSpeedBonus = config.Bind<float>("Amari Aquatic", "FishingSpeedBonus", 25f, "Percentage bonus to fishing speed"); AmariAquaticFishingLuckBonus = config.Bind<float>("Amari Aquatic", "FishingLuckBonus", 25f, "Percentage bonus to fishing luck"); AmariAquaticManaRegenBonus = config.Bind<float>("Amari Aquatic", "ManaRegenBonus", 15f, "Percentage bonus to mana regeneration"); AmariAquaticHealthRegenBonus = config.Bind<float>("Amari Aquatic", "HealthRegenBonus", 15f, "Percentage bonus to health regeneration"); AmariReptileDefenseBonus = config.Bind<float>("Amari Reptile", "DefenseBonus", 25f, "Percentage bonus to defense"); AmariReptileMeleeBonus = config.Bind<float>("Amari Reptile", "MeleeDamageBonus", 15f, "Percentage bonus to melee damage"); AmariReptileHealthBonus = config.Bind<float>("Amari Reptile", "MaxHealthBonus", 15f, "Percentage bonus to maximum health"); AmariReptileMiningBonus = config.Bind<float>("Amari Reptile", "MiningSpeedBonus", 20f, "Percentage bonus to mining speed"); NagaFishingSpeedBonus = config.Bind<float>("Naga", "FishingSpeedBonus", 25f, "Percentage bonus to fishing speed"); NagaFishingLuckBonus = config.Bind<float>("Naga", "FishingLuckBonus", 20f, "Percentage bonus to fishing luck"); NagaDefenseBonus = config.Bind<float>("Naga", "DefenseBonus", 10f, "Percentage bonus to defense"); NagaManaRegenBonus = config.Bind<float>("Naga", "ManaRegenBonus", 15f, "Percentage bonus to mana regeneration"); NagaAirSkipBonus = config.Bind<float>("Naga", "AirSkipChanceBonus", 15f, "Percentage chance to skip air requirement while swimming/fishing"); ElfWoodcuttingDamageBonus = config.Bind<float>("Elf", "WoodcuttingDamageBonus", 15f, "Percentage bonus to tree damage"); HumanCommunityTokenBonus = config.Bind<float>("Human", "CommunityTokenBonus", 10f, "Percentage bonus to daily community token gain"); DemonTripleGoldBonus = config.Bind<float>("Demon", "TripleGoldChanceBonus", 10f, "Percentage chance for triple gold drops"); DemonMiningDamageBonus = config.Bind<float>("Demon", "MiningDamageBonus", 15f, "Percentage bonus to mining damage"); AngelSpellAttackSpeedBonus = config.Bind<float>("Angel", "SpellAttackSpeedBonus", 15f, "Percentage bonus to spell casting speed"); WaterElementalFishingMinigameBonus = config.Bind<float>("Water Elemental", "FishingMinigameSpeedBonus", 20f, "Percentage bonus to fishing minigame speed"); WaterElementalAirSkipBonus = config.Bind<float>("Water Elemental", "AirSkipChanceBonus", 20f, "Percentage chance to skip air requirement while swimming/fishing"); AmariAquaticFishingMinigameBonus = config.Bind<float>("Amari Aquatic", "FishingMinigameSpeedBonus", 15f, "Percentage bonus to fishing minigame speed"); AmariAquaticAirSkipBonus = config.Bind<float>("Amari Aquatic", "AirSkipChanceBonus", 15f, "Percentage chance to skip air requirement while swimming/fishing"); Plugin.Log.LogInfo((object)"Configuration initialized"); } } } namespace HavensBirthright.Patches { public static class AbilityPatches { internal static readonly Dictionary<int, int> OreToBarMap = new Dictionary<int, int> { { 1100, 1200 }, { 1101, 1201 }, { 1102, 1202 }, { 1103, 1203 }, { 1104, 1204 }, { 1105, 1205 }, { 1107, 1206 }, { 1108, 1207 } }; internal static readonly Dictionary<int, float> OreManaCostMap = new Dictionary<int, float> { { 1100, 1f }, { 1101, 2f }, { 1105, 3f }, { 1102, 4f }, { 1103, 5f }, { 1104, 6f }, { 1107, 7f }, { 1108, 8f } }; private static bool _reflectionInitialized; private static bool _reflectionInitFailed; private static int _reflectionInitAttempts; private const int MAX_REFLECTION_INIT_ATTEMPTS = 3; private static FieldInfo _cachedItemIdField; private static PropertyInfo _cachedItemIdProperty; private static MethodInfo _cachedItemIdMethod; private static MethodInfo _cachedAddItemIntMethod; private static int _cachedAddItemArgCount; private static bool _isSmeltingItem; private static bool _genericElementalWarningLogged; private static object _notificationStackInstance; private static MethodInfo _sendNotificationMethod; private static bool _notificationInitialized; private static bool IsVerbose { get { if (AbilityConfig.InfernalForgeVerboseLogging != null) { return AbilityConfig.InfernalForgeVerboseLogging.Value; } return false; } } 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)"[AbilityPatches] 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)"[AbilityPatches] SendNotification(string,int,int,bool,bool) not found"); } return; } TryGetNotificationInstance(type); ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogInfo((object)("[AbilityPatches] Notification system initialized" + ((_notificationStackInstance != null) ? " (instance ready)" : " (instance deferred)"))); } } catch (Exception ex) { ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogWarning((object)("[AbilityPatches] 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)("[AbilityPatches] 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)("[AbilityPatches] 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)$"[AbilityPatches] 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)$"[AbilityPatches] 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)$"[AbilityPatches] ALL Item ID lookups failed (attempt {_reflectionInitAttempts}/{3})! Enumerating Wish.Item members containing 'id':"); } MemberInfo[] members = type.GetMembers(ReflectionHelper.AllBindingFlags); foreach (MemberInfo memberInfo in members) { if (memberInfo.Name.IndexOf("id", StringComparison.OrdinalI