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();
}
}
}