using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using BoplFixedMath; using HarmonyLib; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("BoplRandomModifiers")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("HP")] [assembly: AssemblyProduct("BoplRandomModifiers")] [assembly: AssemblyCopyright("Copyright © HP 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("39dabfd8-111f-485b-bfc1-8b4ec0a92f8b")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] namespace BoplRandomModifiers; [BepInPlugin("com.neriook.boplrandommodifiers", "Bopl Random Modifiers", "1.0.0")] public class Plugin : BaseUnityPlugin { internal static ManualLogSource logger; private void Awake() { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Expected O, but got Unknown //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Expected O, but got Unknown //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Expected O, but got Unknown //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Expected O, but got Unknown //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Expected O, but got Unknown logger = ((BaseUnityPlugin)this).Logger; DelayedRerollRunner.Initialize(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"================================="); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Bopl Random Modifiers loaded!"); ((BaseUnityPlugin)this).Logger.LogInfo((object)"================================="); Harmony val = new Harmony("com.neriook.boplrandommodifiers"); val.Patch((MethodBase)AccessTools.Method(typeof(GameSessionHandler), "SpawnPlayers", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Patches), "SpawnPlayers_Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.Method(typeof(SlimeController), "AddAdditionalAbility", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "AddAdditionalAbility_Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.Method(typeof(Ability), "ExitAbility", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "ExitAbility_Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.Method(typeof(Teleport), "CastAbility", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "TeleportCast_Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.Method(typeof(HookshotInstant), "UseAbility", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "HookshotUse_Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.Method(typeof(Invisibility), "OnInvisibiltyEnded", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "OnInvisibiltyEnded_Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Ability patches installed!"); } } public class DelayedRerollRunner : MonoBehaviour { private class PendingReroll { public SlimeController controller; public int index; public string key; public float executeAt; } private static DelayedRerollRunner instance; private readonly List pending = new List(); private static DelayedRerollRunner GetOrCreate() { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown if ((Object)(object)instance != (Object)null) { return instance; } GameObject val = GameObject.Find("BoplRandomModifiers_DelayedRerollRunner"); if ((Object)(object)val != (Object)null) { instance = val.GetComponent(); if ((Object)(object)instance != (Object)null) { return instance; } } GameObject val2 = new GameObject("BoplRandomModifiers_DelayedRerollRunner"); Object.DontDestroyOnLoad((Object)(object)val2); instance = val2.AddComponent(); Plugin.logger.LogInfo((object)"Delayed reroll runner created."); return instance; } public static void Initialize() { GetOrCreate(); } public static void Schedule(SlimeController controller, int index, string key, float delay) { DelayedRerollRunner orCreate = GetOrCreate(); PendingReroll pendingReroll = new PendingReroll(); pendingReroll.controller = controller; pendingReroll.index = index; pendingReroll.key = key; pendingReroll.executeAt = Time.unscaledTime + delay; orCreate.pending.Add(pendingReroll); Plugin.logger.LogInfo((object)("Reroll scheduled in " + delay + " seconds.")); } private void Update() { if (pending.Count == 0) { return; } float unscaledTime = Time.unscaledTime; for (int num = pending.Count - 1; num >= 0; num--) { PendingReroll pendingReroll = pending[num]; if (!(unscaledTime < pendingReroll.executeAt)) { pending.RemoveAt(num); try { if ((Object)(object)pendingReroll.controller == (Object)null) { Patches.ClearPendingReroll(pendingReroll.key); } else { Patches.ExecuteDelayedReroll(pendingReroll.controller, pendingReroll.index, pendingReroll.key); } } catch (Exception ex) { Plugin.logger.LogError((object)("Delayed reroll error: " + ex)); Patches.ClearPendingReroll(pendingReroll.key); } } } } } public static class Patches { private static readonly Dictionary> randomSlots = new Dictionary>(); private static readonly Dictionary useCounts = new Dictionary(); private static readonly HashSet pendingRerolls = new HashSet(); private static readonly Dictionary lastCountedRope = new Dictionary(); private static readonly FieldInfo hookAbilityField = AccessTools.Field(typeof(HookshotInstant), "instantAbility"); private static readonly FieldInfo hookRopeBodyField = AccessTools.Field(typeof(HookshotInstant), "ropeBody"); private static readonly FieldInfo hookFiredRopeNumberField = AccessTools.Field(typeof(HookshotInstant), "firedRopeNumber"); private static readonly FieldInfo invisAbilityField = AccessTools.Field(typeof(Invisibility), "ia"); public static void SpawnPlayers_Prefix() { randomSlots.Clear(); useCounts.Clear(); pendingRerolls.Clear(); lastCountedRope.Clear(); List list = PlayerHandler.Get().PlayerList(); for (int i = 0; i < list.Count; i++) { Player val = list[i]; HashSet hashSet = new HashSet(); for (int j = 0; j < val.Abilities.Count; j++) { GameObject val2 = val.Abilities[j]; if (!((Object)(object)val2 == (Object)null)) { RandomAbility component = val2.GetComponent(); if ((Object)(object)component != (Object)null) { hashSet.Add(j); Plugin.logger.LogInfo((object)("Player " + val.Id + " slot " + j + " IS RANDOM")); } } } if (hashSet.Count > 0) { randomSlots[val.Id] = hashSet; useCounts[val.Id] = new int[val.Abilities.Count]; } } } public static void AddAdditionalAbility_Postfix(SlimeController __instance) { if ((Object)(object)__instance == (Object)null) { return; } int playerNumber = __instance.playerNumber; if (!randomSlots.ContainsKey(playerNumber)) { return; } int num = 2; if (randomSlots[playerNumber].Contains(num)) { if (!useCounts.ContainsKey(playerNumber)) { useCounts[playerNumber] = new int[__instance.abilities.Count]; } if (num < useCounts[playerNumber].Length) { useCounts[playerNumber][num] = 0; string text = playerNumber + ":" + num; pendingRerolls.Remove(text); lastCountedRope.Remove(text); Plugin.logger.LogInfo((object)"Reset counter for pickup slot."); } } } public static void ExitAbility_Postfix(Ability __instance, AbilityExitInfo exitInfo) { if ((Object)(object)__instance == (Object)null) { return; } SlimeController slimeController = ((AbilityMonoBehaviour)__instance).GetSlimeController(); if ((Object)(object)slimeController == (Object)null) { return; } int num = slimeController.abilities.IndexOf((AbilityMonoBehaviour)(object)__instance); if (num == -1) { return; } AbilityMonoBehaviour val = slimeController.abilities[num]; if ((Object)(object)val != (Object)null) { if ((Object)(object)((Component)val).GetComponent() != (Object)null) { return; } string text = ((Object)((Component)val).gameObject).name.ToLower(); if (text.Contains("teleport")) { return; } } CountRandomAbilityUse(slimeController, num, hookshotUse: false); } public static void HookshotUse_Postfix(HookshotInstant __instance) { if ((Object)(object)__instance == (Object)null) { return; } if (hookAbilityField == null) { Plugin.logger.LogError((object)"Could not find HookshotInstant.instantAbility"); return; } if (hookFiredRopeNumberField == null) { Plugin.logger.LogError((object)"Could not find HookshotInstant.firedRopeNumber"); return; } object? value = hookAbilityField.GetValue(__instance); InstantAbility val = (InstantAbility)((value is InstantAbility) ? value : null); if ((Object)(object)val == (Object)null) { return; } SlimeController slimeController = ((AbilityMonoBehaviour)val).GetSlimeController(); if ((Object)(object)slimeController == (Object)null) { return; } int num = slimeController.abilities.IndexOf((AbilityMonoBehaviour)(object)val); if (num < 0) { return; } int playerNumber = slimeController.playerNumber; if (!randomSlots.ContainsKey(playerNumber) || !randomSlots[playerNumber].Contains(num)) { return; } string text = playerNumber + ":" + num; if (pendingRerolls.Contains(text)) { return; } object value2 = hookFiredRopeNumberField.GetValue(__instance); if (value2 == null) { Plugin.logger.LogWarning((object)"Hookshot firedRopeNumber was null."); return; } uint num2; try { num2 = (uint)value2; } catch (Exception ex) { Plugin.logger.LogError((object)("Failed to read firedRopeNumber: " + ex)); return; } if (lastCountedRope.TryGetValue(text, out var value3) && value3 == num2) { Plugin.logger.LogInfo((object)("Ignoring same hookshot rope: " + num2)); return; } lastCountedRope[text] = num2; Plugin.logger.LogInfo((object)"================================="); Plugin.logger.LogInfo((object)"===== NEW HOOKSHOT ROPE ====="); Plugin.logger.LogInfo((object)("Player: " + playerNumber)); Plugin.logger.LogInfo((object)("Slot: " + num)); Plugin.logger.LogInfo((object)("Rope number: " + num2)); Plugin.logger.LogInfo((object)"Counting hookshot use."); Plugin.logger.LogInfo((object)"================================="); CountRandomAbilityUse(slimeController, num, hookshotUse: true); } public static void TeleportCast_Postfix(Teleport __instance) { if ((Object)(object)__instance == (Object)null) { return; } InstantAbility component = ((Component)__instance).GetComponent(); if ((Object)(object)component == (Object)null) { return; } SlimeController slimeController = ((AbilityMonoBehaviour)component).GetSlimeController(); if (!((Object)(object)slimeController == (Object)null)) { int num = slimeController.abilities.IndexOf((AbilityMonoBehaviour)(object)component); if (num >= 0) { CountTeleportPress(slimeController, num); } } } private static void CountTeleportPress(SlimeController controller, int index) { if ((Object)(object)controller == (Object)null) { return; } int playerNumber = controller.playerNumber; if (!randomSlots.ContainsKey(playerNumber) || !randomSlots[playerNumber].Contains(index)) { return; } if (!useCounts.ContainsKey(playerNumber)) { useCounts[playerNumber] = new int[controller.abilities.Count]; } if (index < useCounts[playerNumber].Length) { useCounts[playerNumber][index]++; int num = useCounts[playerNumber][index]; Plugin.logger.LogInfo((object)("===== TELEPORT PRESS " + num + " / 4 =====")); if (num >= 4) { useCounts[playerNumber][index] = 0; ScheduleReroll(controller, index, wasHookshot: false); } } } public static void OnInvisibiltyEnded_Postfix(Invisibility __instance) { if ((Object)(object)__instance == (Object)null) { return; } object? value = invisAbilityField.GetValue(__instance); InstantAbility val = (InstantAbility)((value is InstantAbility) ? value : null); if ((Object)(object)val == (Object)null) { return; } SlimeController slimeController = ((AbilityMonoBehaviour)val).GetSlimeController(); if (!((Object)(object)slimeController == (Object)null)) { int num = slimeController.abilities.IndexOf((AbilityMonoBehaviour)(object)val); if (num >= 0) { CountRandomAbilityUse(slimeController, num, hookshotUse: false); } } } private static void CountRandomAbilityUse(SlimeController controller, int index, bool hookshotUse) { if ((Object)(object)controller == (Object)null) { return; } int playerNumber = controller.playerNumber; if (!randomSlots.ContainsKey(playerNumber) || !randomSlots[playerNumber].Contains(index)) { return; } if (!useCounts.ContainsKey(playerNumber)) { useCounts[playerNumber] = new int[controller.abilities.Count]; } if (index < 0 || index >= useCounts[playerNumber].Length) { return; } string item = playerNumber + ":" + index; if (!pendingRerolls.Contains(item)) { useCounts[playerNumber][index]++; int num = useCounts[playerNumber][index]; int num2 = 2; bool flag = IsTeslaCoil(controller, index); bool flag2 = IsBlackHole(controller, index); if (flag) { num2 = 3; } if (flag2) { num2 = 1; } if (IsHookshot(controller, index)) { num2 = 2; } Plugin.logger.LogInfo((object)("RANDOM SLOT " + index + " USE #" + num + " / " + num2)); if (num >= num2) { useCounts[playerNumber][index] = 0; Plugin.logger.LogInfo((object)"===== ABILITY USE LIMIT REACHED ====="); ScheduleReroll(controller, index, hookshotUse); } } } private static void ScheduleReroll(SlimeController controller, int index, bool wasHookshot) { if (!((Object)(object)controller == (Object)null)) { int playerNumber = controller.playerNumber; string text = playerNumber + ":" + index; if (!pendingRerolls.Contains(text)) { pendingRerolls.Add(text); float delay = (wasHookshot ? 1f : 0.25f); Plugin.logger.LogInfo((object)"===== REROLL SCHEDULED ====="); Plugin.logger.LogInfo((object)("Slot: " + index + " | Delay: " + delay)); DelayedRerollRunner.Schedule(controller, index, text, delay); } } } private static bool IsHookshot(SlimeController controller, int index) { if ((Object)(object)controller == (Object)null) { return false; } if (index < 0 || index >= controller.abilities.Count) { return false; } AbilityMonoBehaviour val = controller.abilities[index]; if ((Object)(object)val == (Object)null) { return false; } return (Object)(object)((Component)val).GetComponent() != (Object)null; } private static bool IsTeslaCoil(SlimeController controller, int index) { if ((Object)(object)controller == (Object)null) { return false; } if (index < 0 || index >= controller.abilities.Count) { return false; } AbilityMonoBehaviour val = controller.abilities[index]; if ((Object)(object)val == (Object)null) { return false; } return ((Object)((Component)val).gameObject).name.ToLower().Contains("tesla"); } private static bool IsBlackHole(SlimeController controller, int index) { if ((Object)(object)controller == (Object)null) { return false; } if (index < 0 || index >= controller.abilities.Count) { return false; } AbilityMonoBehaviour val = controller.abilities[index]; if ((Object)(object)val == (Object)null) { return false; } string text = ((Object)((Component)val).gameObject).name.ToLower(); return text.Contains("black") && text.Contains("hole"); } public static void ExecuteDelayedReroll(SlimeController controller, int index, string rerollKey) { try { if (!((Object)(object)controller == (Object)null) && index >= 0 && index < controller.abilities.Count) { Plugin.logger.LogInfo((object)"===== EXECUTING REROLL ====="); ChangeAbility(controller, index); } } catch (Exception ex) { Plugin.logger.LogError((object)("Reroll error: " + ex)); } finally { ClearPendingReroll(rerollKey); } } public static void ClearPendingReroll(string key) { pendingRerolls.Remove(key); lastCountedRope.Remove(key); } private static void ChangeAbility(SlimeController controller, int index) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)controller == (Object)null || index < 0 || index >= controller.abilities.Count || index >= controller.AbilityReadyIndicators.Length) { return; } Sprite val = null; AbilityReadyIndicator val2 = controller.AbilityReadyIndicators[index]; if ((Object)(object)val2 != (Object)null) { val = val2.GetPrimarySprite(); } NamedSprite randomAbilityPrefab = RandomAbility.GetRandomAbilityPrefab(controller.abilityIconsFull, controller.abilityIconsDemo, val); GameObject associatedGameObject = randomAbilityPrefab.associatedGameObject; if ((Object)(object)associatedGameObject == (Object)null) { Plugin.logger.LogWarning((object)"Random ability prefab was null."); return; } Plugin.logger.LogInfo((object)("New random ability prefab: " + ((Object)associatedGameObject).name)); AbilityMonoBehaviour val3 = controller.abilities[index]; GameObject val4 = (((Object)(object)val3 != (Object)null) ? ((Component)val3).gameObject : null); AbilityMonoBehaviour component = FixTransform.InstantiateFixed(associatedGameObject, Vec2.zero, Fix.Zero).GetComponent(); if ((Object)(object)component == (Object)null) { Plugin.logger.LogError((object)"Failed to create new ability."); return; } GameObject gameObject = ((Component)component).gameObject; gameObject.SetActive(false); controller.abilities[index] = component; Player player = PlayerHandler.Get().GetPlayer(controller.playerNumber); if (player != null && index < player.CurrentAbilities.Count) { player.CurrentAbilities[index] = gameObject; } if ((Object)(object)val2 != (Object)null) { val2.SetSprite(randomAbilityPrefab.sprite, true); val2.ResetAnimation(); } if ((Object)(object)val4 != (Object)null && (Object)(object)val4 != (Object)(object)gameObject) { try { Updater.DestroyFix(val4); } catch { Object.Destroy((Object)(object)val4); } } string text = controller.playerNumber + ":" + index; lastCountedRope.Remove(text); pendingRerolls.Remove(text); AudioManager.Get().Play("abilityPickup"); Plugin.logger.LogInfo((object)"===== REROLL COMPLETE ====="); Plugin.logger.LogInfo((object)("Slot " + index + " is now " + ((Object)gameObject).name)); } }