using BepInEx; using BepInEx.Unity.IL2CPP; using HarmonyLib; using System; using System.Reflection; using static MonopolyPokerHelper.LoggerHelper; namespace MonopolyPokerHelper { /// /// Main BepInEx plugin entry point for Monopoly Poker helper features. /// Sets up Harmony patches and periodically runs feature handlers. /// [BepInPlugin(MyPluginInfo.PLUGIN_GUID, MyPluginInfo.PLUGIN_NAME, MyPluginInfo.PLUGIN_VERSION)] public class Plugin : BasePlugin { private static Harmony _harmony; // Accumulated time since last handler run. private static float _elapsed; // How often to run handlers after startup, in seconds. private const float CHECK_INTERVAL = 0.25f; // Initial delay before any logic runs, in seconds. private const float INITIAL_DELAY = 15f; // Ensures the initial delay logic only runs once. private static bool _initialDelayDone; // Ensures reflection is resolved successfully before handlers execute. public static bool IsResolved { get; private set; } public static bool _pluginDisabled; /// /// BepInEx entry point. Sets up Harmony patches and logs plugin startup. /// public override void Load() { LoggerHelper.Log = base.Log; _harmony = new Harmony(MyPluginInfo.PLUGIN_GUID); // Redundant, BepInEx does this automatically //LogInfo($"{MyPluginInfo.PLUGIN_NAME} v{MyPluginInfo.PLUGIN_VERSION} loading..."); var updatePatch = new HarmonyMethod(typeof(Plugin).GetMethod( nameof(UpdatePostfix), BindingFlags.Static | BindingFlags.NonPublic)); PatchMethod("SocialLayerManager", "Update", updatePatch, isPostfix: true); DistractionHandler.Load(); BankrupcyRefillHandler.Load(); LogInfo($"Plugin {MyPluginInfo.PLUGIN_GUID} is loaded!"); } /// /// Resolves all reflection-based handles used by handlers. /// Must succeed before periodic updates start running. /// private static bool ResolveReflection() { if (IsResolved) return true; LogInfo("Resolving reflection handles..."); BoardwalkHandler.ResolveReflection(); FreeDailyCurrencyHandler.ResolveReflection(); // ClaimMissionHandler.ResolveReflection(); IsResolved = BoardwalkHandler.IsResolved && FreeDailyCurrencyHandler.IsResolved // ClaimMissionHandler.IsResolved ; if (IsResolved) { LogInfo("Reflection handles for Monopoly Poker helper resolved successfully."); } return IsResolved; } /// /// Harmony postfix for SocialLayerManager.Update. /// Throttles execution, enforces an initial delay, /// ensures reflection is ready, then invokes feature handlers. /// private static void UpdatePostfix() { if (_pluginDisabled) return; float dt = UnityEngine.Time.deltaTime; if (!_initialDelayDone) { _elapsed += dt; if (_elapsed < INITIAL_DELAY) return; _initialDelayDone = true; _elapsed = CHECK_INTERVAL; LogInfo("Initial start delay complete"); } if (!ResolveReflection()) { DisablePlugin("Failed to resolve reflection handles"); return; } _elapsed += dt; if (_elapsed < CHECK_INTERVAL) return; _elapsed = 0f; try { //LogInfo("Running periodic handlers..."); SafeInvoke(() => BoardwalkHandler.Update(dt), nameof(BoardwalkHandler.Update)); SafeInvoke(FreeDailyCurrencyHandler.Update, nameof(FreeDailyCurrencyHandler.Update)); // SafeInvoke(() => ClaimMissionHandler.Update(dt), nameof(ClaimMissionHandler.Update)); } catch (Exception ex) { LogError($"UpdatePostfix error: {ex.Message}"); } } /// /// Safely invokes a handler action and logs any exceptions with the given name. /// Use when you want individual handler failures not to affect others. /// private static void SafeInvoke(Action action, string name) { try { action(); } catch (Exception ex) { LogError($"Error invoking handler {name}: {ex}"); } } /// /// Finds a target type and patches all methods with the given name /// using the provided HarmonyMethod as prefix or postfix. /// /// Name of the target type to search for. /// Name of the methods to patch on that type. /// Harmony patch method (prefix or postfix). /// True to apply as postfix, false for prefix. public static bool PatchMethod( string typeName, string methodName, HarmonyMethod patch, bool isPostfix = false, Type[] parameterTypes = null) { Type targetType = ReflectionHelper.FindType(typeName); if (targetType == null) { LogError($" Type not found: {typeName}"); return false; } int patched = 0; if (parameterTypes != null) { var method = targetType.GetMethod( methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly, null, parameterTypes, null); if (method == null) { LogError($"No method named '{methodName}' with specified parameters found on {typeName}"); return false; } if (isPostfix) _harmony.Patch(method, postfix: patch); else _harmony.Patch(method, prefix: patch); patched = 1; } else { var methods = targetType.GetMethods( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly); foreach (var method in methods) { if (method.Name != methodName) continue; if (isPostfix) _harmony.Patch(method, postfix: patch); else _harmony.Patch(method, prefix: patch); patched++; } } if (patched == 0) { LogError($"No methods named '{methodName}' found on {typeName}"); return false; } LogInfo($"Patched {patched} method(s): {typeName}.{methodName}"); return true; } public static void PatchMethod(string typeName, string methodName, MethodInfo patchMethod, bool isPostfix = false) { if (patchMethod == null) { LogWarn($"Patch method is null for {typeName}.{methodName}"); return; } PatchMethod(typeName, methodName, new HarmonyMethod(patchMethod), isPostfix); } private static void DisablePlugin(string reason) { if (_pluginDisabled) return; _pluginDisabled = true; LogError($"Disabling plugin: {reason}"); try { _harmony?.UnpatchSelf(); } catch (Exception e) { LogError($"Error during plugin unpatching: {e}"); } } } }