Decompiled source of RandomReroll v1.0.0

RandomReroll.dll

Decompiled 2 days ago
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<PendingReroll> pending = new List<PendingReroll>();

	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<DelayedRerollRunner>();
			if ((Object)(object)instance != (Object)null)
			{
				return instance;
			}
		}
		GameObject val2 = new GameObject("BoplRandomModifiers_DelayedRerollRunner");
		Object.DontDestroyOnLoad((Object)(object)val2);
		instance = val2.AddComponent<DelayedRerollRunner>();
		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<int, HashSet<int>> randomSlots = new Dictionary<int, HashSet<int>>();

	private static readonly Dictionary<int, int[]> useCounts = new Dictionary<int, int[]>();

	private static readonly HashSet<string> pendingRerolls = new HashSet<string>();

	private static readonly Dictionary<string, uint> lastCountedRope = new Dictionary<string, uint>();

	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<Player> list = PlayerHandler.Get().PlayerList();
		for (int i = 0; i < list.Count; i++)
		{
			Player val = list[i];
			HashSet<int> hashSet = new HashSet<int>();
			for (int j = 0; j < val.Abilities.Count; j++)
			{
				GameObject val2 = val.Abilities[j];
				if (!((Object)(object)val2 == (Object)null))
				{
					RandomAbility component = val2.GetComponent<RandomAbility>();
					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<HookshotInstant>() != (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<InstantAbility>();
		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<HookshotInstant>() != (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<AbilityMonoBehaviour>();
		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));
	}
}