Decompiled source of PermanentCombo v1.0.0

PermanentCombo.dll

Decompiled 2 days ago
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Reptile;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("PermanentCombo")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("PermanentCombo")]
[assembly: AssemblyTitle("PermanentCombo")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace PermanentCombo;

internal static class ComboPatches
{
	internal const int TotalPatches = 2;

	private static ManualLogSource log;

	private static FieldRef<Player, bool> isAIRef;

	private static FieldRef<Player, float> baseScoreRef;

	private static FieldRef<Player, float> scoreMultiplierRef;

	private static FieldRef<WorldHandler, Encounter> currentEncounterRef;

	private static Player retainedPlayer;

	private static bool suppressRecord;

	private static StageProgress guardedProgress;

	private static float guardedHighestCombo;

	private static bool landComboPatched;

	private static bool exceptionLogged;

	internal static int Apply(Harmony harmony, ManualLogSource logger)
	{
		log = logger;
		try
		{
			isAIRef = AccessTools.FieldRefAccess<Player, bool>("isAI");
			baseScoreRef = AccessTools.FieldRefAccess<Player, float>("baseScore");
			scoreMultiplierRef = AccessTools.FieldRefAccess<Player, float>("scoreMultiplier");
			currentEncounterRef = AccessTools.FieldRefAccess<WorldHandler, Encounter>("currentEncounter");
		}
		catch (Exception ex)
		{
			log.LogError((object)("Could not resolve required game fields (game update changed them?). No patches applied; the game runs unmodified. Details: " + ex));
			return 0;
		}
		int num = 0;
		if (TryPatch(harmony, typeof(StageProgress), "SetHighestCombo", new Type[1] { typeof(float) }, "SetHighestCombo_Prefix", null))
		{
			num++;
		}
		else
		{
			log.LogWarning((object)"SetHighestCombo patch failed; records are still protected by the snapshot/restore guard.");
		}
		landComboPatched = TryPatch(harmony, typeof(Player), "LandCombo", Type.EmptyTypes, "LandCombo_Prefix", "LandCombo_Finalizer");
		if (landComboPatched)
		{
			num++;
		}
		else
		{
			log.LogWarning((object)"Without the LandCombo patch, combos end normally (mod has no effect).");
		}
		return num;
	}

	private static bool TryPatch(Harmony harmony, Type type, string methodName, Type[] args, string prefix, string finalizer)
	{
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		string text = type.Name + "." + methodName;
		try
		{
			MethodInfo methodInfo = AccessTools.DeclaredMethod(type, methodName, args, (Type[])null);
			if (methodInfo == null)
			{
				log.LogError((object)("Patch FAILED: " + text + " not found in Assembly-CSharp."));
				return false;
			}
			harmony.Patch((MethodBase)methodInfo, (prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(ComboPatches), prefix, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (finalizer == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(ComboPatches), finalizer, (Type[])null), (HarmonyMethod)null);
			log.LogInfo((object)("Patched " + text + " (prefix" + ((finalizer != null) ? " + finalizer" : string.Empty) + ")."));
			return true;
		}
		catch (Exception arg)
		{
			log.LogError((object)$"Patch FAILED: {text}: {arg}");
			return false;
		}
	}

	private static bool IsLocalHuman(Player p)
	{
		if ((Object)(object)p != (Object)null)
		{
			return !isAIRef.Invoke(p);
		}
		return false;
	}

	private static bool EncounterActive()
	{
		WorldHandler instance = WorldHandler.instance;
		if ((Object)(object)instance != (Object)null)
		{
			return (Object)(object)currentEncounterRef.Invoke(instance) != (Object)null;
		}
		return false;
	}

	private static bool ShouldRetainNow()
	{
		if (PermanentComboPlugin.RuntimeEnabled)
		{
			if (PermanentComboPlugin.PauseDuringEncounters)
			{
				return !EncounterActive();
			}
			return true;
		}
		return false;
	}

	private static bool IsRetained(Player p)
	{
		if ((Object)(object)p != (Object)null && retainedPlayer == p)
		{
			return p.IsComboing();
		}
		return false;
	}

	private static void FoldMultiplier(Player p)
	{
		float num = scoreMultiplierRef.Invoke(p);
		if (!(num <= 1f))
		{
			float num2 = baseScoreRef.Invoke(p) * num;
			if (!float.IsNaN(num2) && !float.IsInfinity(num2) && !(num2 <= 0f))
			{
				baseScoreRef.Invoke(p) = num2;
				scoreMultiplierRef.Invoke(p) = 1f;
			}
		}
	}

	private static void BeginRecordGuard()
	{
		suppressRecord = true;
		guardedProgress = null;
		try
		{
			Core instance = Core.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				SaveManager saveManager = instance.SaveManager;
				if (saveManager == null)
				{
					obj = null;
				}
				else
				{
					SaveSlotData currentSaveSlot = saveManager.CurrentSaveSlot;
					obj = ((currentSaveSlot != null) ? currentSaveSlot.GetCurrentStageProgress() : null);
				}
			}
			StageProgress val = (StageProgress)obj;
			if (val != null)
			{
				guardedProgress = val;
				guardedHighestCombo = val.highestCombo;
			}
		}
		catch (Exception e)
		{
			ReportOnce("BeginRecordGuard", e);
		}
	}

	private static void EndRecordGuard()
	{
		if (suppressRecord || guardedProgress != null)
		{
			suppressRecord = false;
			StageProgress val = guardedProgress;
			guardedProgress = null;
			if (val != null && val.highestCombo != guardedHighestCombo)
			{
				log.LogInfo((object)$"Highest-combo record restored to {guardedHighestCombo:0} (the game tried to set {val.highestCombo:0} from a retained combo).");
				val.highestCombo = guardedHighestCombo;
			}
		}
	}

	private static void ReportOnce(string where, Exception e)
	{
		if (!exceptionLogged)
		{
			exceptionLogged = true;
			ManualLogSource obj = log;
			if (obj != null)
			{
				obj.LogError((object)$"Exception in {where} (further exceptions suppressed): {e}");
			}
		}
	}

	private static bool LandCombo_Prefix(Player __instance)
	{
		try
		{
			if (!IsLocalHuman(__instance) || !__instance.IsComboing())
			{
				return true;
			}
			if (ShouldRetainNow())
			{
				__instance.RefillComboTimeOut();
				if (!PermanentComboPlugin.PreserveMultiplier)
				{
					FoldMultiplier(__instance);
				}
				if (retainedPlayer != __instance)
				{
					retainedPlayer = __instance;
					log.LogInfo((object)$"Combo retained (base {baseScoreRef.Invoke(__instance):0} x {scoreMultiplierRef.Invoke(__instance):0}).");
				}
				return false;
			}
			if (IsRetained(__instance) && !PermanentComboPlugin.RecordRetainedCombos)
			{
				BeginRecordGuard();
				log.LogInfo((object)($"Banking retained combo ({baseScoreRef.Invoke(__instance) * scoreMultiplierRef.Invoke(__instance):0}) " + "without updating the highest-combo record (RecordRetainedCombos = false)."));
			}
		}
		catch (Exception e)
		{
			ReportOnce("LandCombo prefix", e);
		}
		return true;
	}

	private static Exception LandCombo_Finalizer(Player __instance, Exception __exception)
	{
		try
		{
			EndRecordGuard();
			if ((Object)(object)__instance != (Object)null && retainedPlayer == __instance && !__instance.IsComboing())
			{
				retainedPlayer = null;
			}
		}
		catch (Exception e)
		{
			ReportOnce("LandCombo finalizer", e);
		}
		return __exception;
	}

	private static bool SetHighestCombo_Prefix()
	{
		return !suppressRecord;
	}

	internal static void OnFrame()
	{
		if (!landComboPatched)
		{
			return;
		}
		try
		{
			Player val = retainedPlayer;
			if ((Object)(object)val == (Object)null)
			{
				retainedPlayer = null;
			}
			else if (!val.IsComboing())
			{
				retainedPlayer = null;
			}
			else if (PermanentComboPlugin.PauseDuringEncounters && EncounterActive())
			{
				log.LogInfo((object)"Challenge started: banking the retained combo so the challenge starts clean.");
				val.LandCombo();
			}
		}
		catch (Exception e)
		{
			ReportOnce("OnFrame", e);
		}
	}

	internal static string ResetCurrentCombo()
	{
		if (!landComboPatched)
		{
			return null;
		}
		try
		{
			WorldHandler instance = WorldHandler.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return null;
			}
			Player currentPlayer = instance.GetCurrentPlayer();
			if (!IsLocalHuman(currentPlayer))
			{
				return null;
			}
			if (EncounterActive())
			{
				return "PermanentCombo: reset is disabled during challenges";
			}
			if (!currentPlayer.IsComboing())
			{
				return "PermanentCombo: no combo to reset";
			}
			BeginRecordGuard();
			try
			{
				currentPlayer.DropCombo();
			}
			finally
			{
				EndRecordGuard();
			}
			if (retainedPlayer == currentPlayer)
			{
				retainedPlayer = null;
			}
			log.LogInfo((object)"Combo reset by hotkey (discarded, no record written).");
			return "PermanentCombo: combo reset";
		}
		catch (Exception e)
		{
			ReportOnce("ResetCurrentCombo", e);
			return null;
		}
	}
}
[BepInPlugin("brc.permanentcombo", "PermanentCombo", "1.0.0")]
[BepInProcess("Bomb Rush Cyberfunk.exe")]
public sealed class PermanentComboPlugin : BaseUnityPlugin
{
	public const string PluginGuid = "brc.permanentcombo";

	public const string PluginName = "PermanentCombo";

	public const string PluginVersion = "1.0.0";

	private const string Section = "Permanent Combo";

	internal static ManualLogSource Log;

	internal static ConfigEntry<bool> EnabledConfig;

	internal static ConfigEntry<KeyboardShortcut> ToggleKeyConfig;

	internal static ConfigEntry<KeyboardShortcut> ResetKeyConfig;

	internal static ConfigEntry<bool> AllowManualResetConfig;

	internal static ConfigEntry<bool> PreserveMultiplierConfig;

	internal static ConfigEntry<bool> PauseDuringEncountersConfig;

	internal static ConfigEntry<bool> RecordRetainedCombosConfig;

	internal static bool RuntimeEnabled;

	private Harmony harmony;

	private string toastText;

	private float toastUntil;

	internal static bool PreserveMultiplier
	{
		get
		{
			if (PreserveMultiplierConfig != null)
			{
				return PreserveMultiplierConfig.Value;
			}
			return true;
		}
	}

	internal static bool PauseDuringEncounters
	{
		get
		{
			if (PauseDuringEncountersConfig != null)
			{
				return PauseDuringEncountersConfig.Value;
			}
			return true;
		}
	}

	internal static bool RecordRetainedCombos
	{
		get
		{
			if (RecordRetainedCombosConfig != null)
			{
				return RecordRetainedCombosConfig.Value;
			}
			return false;
		}
	}

	private void Awake()
	{
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_0073: Unknown result type (might be due to invalid IL or missing references)
		//IL_0132: Unknown result type (might be due to invalid IL or missing references)
		//IL_013c: Expected O, but got Unknown
		//IL_0198: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
		Log = ((BaseUnityPlugin)this).Logger;
		EnabledConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Permanent Combo", "Enabled", true, "Mod is on when the game starts.");
		ToggleKeyConfig = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Permanent Combo", "ToggleKey", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Key to turn the mod on/off.");
		ResetKeyConfig = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Permanent Combo", "ResetKey", new KeyboardShortcut((KeyCode)288, Array.Empty<KeyCode>()), "Key to reset your current combo.");
		AllowManualResetConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Permanent Combo", "AllowManualReset", true, "Allow the reset key.");
		PreserveMultiplierConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Permanent Combo", "PreserveMultiplier", true, "Keep the combo multiplier while idle.");
		PauseDuringEncountersConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Permanent Combo", "PauseDuringEncounters", true, "Use normal combo rules during challenges.");
		RecordRetainedCombosConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Permanent Combo", "RecordRetainedCombos", false, "Let kept combos count toward your highest-combo record.");
		RuntimeEnabled = EnabledConfig.Value;
		EnabledConfig.SettingChanged += delegate
		{
			SetEnabled(EnabledConfig.Value, "config");
		};
		harmony = new Harmony("brc.permanentcombo");
		int num = ComboPatches.Apply(harmony, Log);
		Log.LogInfo((object)(string.Format("{0} {1} loaded: {2}/{3} patches applied. ", "PermanentCombo", "1.0.0", num, 2) + $"Enabled={RuntimeEnabled}, ToggleKey={ToggleKeyConfig.Value}, ResetKey={ResetKeyConfig.Value}, " + $"AllowManualReset={AllowManualResetConfig.Value}, PreserveMultiplier={PreserveMultiplierConfig.Value}, " + $"PauseDuringEncounters={PauseDuringEncountersConfig.Value}, RecordRetainedCombos={RecordRetainedCombosConfig.Value}"));
	}

	private void Update()
	{
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0052: Unknown result type (might be due to invalid IL or missing references)
		KeyboardShortcut value;
		if (ToggleKeyConfig != null)
		{
			value = ToggleKeyConfig.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				SetEnabled(!RuntimeEnabled, "hotkey");
			}
		}
		if (AllowManualResetConfig != null && AllowManualResetConfig.Value && ResetKeyConfig != null)
		{
			value = ResetKeyConfig.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				string text = ComboPatches.ResetCurrentCombo();
				if (text != null)
				{
					Toast(text);
				}
			}
		}
		ComboPatches.OnFrame();
	}

	private void SetEnabled(bool value, string source)
	{
		if (RuntimeEnabled != value)
		{
			RuntimeEnabled = value;
			string text = "PermanentCombo " + (value ? "ON" : "OFF");
			Log.LogInfo((object)(text + " (via " + source + ")" + (value ? string.Empty : ": normal combo rules apply again; a retained combo banks the next time the game would bank it.")));
			Toast(text);
		}
	}

	private void Toast(string text)
	{
		toastText = text;
		toastUntil = Time.unscaledTime + 2f;
	}

	private void OnGUI()
	{
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		if (toastText != null && !(Time.unscaledTime > toastUntil))
		{
			GUI.Label(new Rect(20f, 50f, 500f, 30f), toastText);
		}
	}

	private void OnDestroy()
	{
		Harmony obj = harmony;
		if (obj != null)
		{
			obj.UnpatchSelf();
		}
	}
}