using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Pigeon.Movement;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Interactions;
[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("Sparroh")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0")]
[assembly: AssemblyProduct("GrenadeMacros")]
[assembly: AssemblyTitle("GrenadeMacros")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
public static class ConfigManager
{
public enum ActivationMode
{
None,
Hold,
Toggle
}
private const float DebounceSeconds = 0.25f;
private static ConfigFile config;
private static ManualLogSource logger;
private static FileSystemWatcher configWatcher;
private static volatile bool reloadPending;
private static float lastReloadTime;
public static ConfigEntry<ActivationMode> IncendiaryMode { get; private set; }
public static ConfigEntry<ActivationMode> VoltaicMode { get; private set; }
public static ConfigEntry<ActivationMode> AcidMode { get; private set; }
public static ConfigEntry<bool> DebugThrowInput { get; private set; }
public static bool DebugEnabled
{
get
{
if (DebugThrowInput != null)
{
return DebugThrowInput.Value;
}
return false;
}
}
public static void Initialize(ConfigFile configFile, ManualLogSource log)
{
config = configFile;
logger = log;
IncendiaryMode = config.Bind<ActivationMode>("General", "Incendiary Mode", ActivationMode.None, "Activation mode for incendiary grenades. Hold: prevent auto-activation, Toggle: toggle on/off, None: default");
VoltaicMode = config.Bind<ActivationMode>("General", "Shock Mode", ActivationMode.Toggle, "Activation mode for voltaic (shock) grenades. Hold: prevent auto-activation, Toggle: toggle on/off, None: default");
AcidMode = config.Bind<ActivationMode>("General", "Acid Mode", ActivationMode.None, "Activation mode for acid grenades. Hold: prevent auto-activation, Toggle: toggle on/off, None: default");
DebugThrowInput = config.Bind<bool>("Debug", "Debug Throw Input", true, "Log throw input / toggle decisions to BepInEx log (Info). Turn off when done debugging.");
IncendiaryMode.SettingChanged += OnSettingChanged;
VoltaicMode.SettingChanged += OnSettingChanged;
AcidMode.SettingChanged += OnSettingChanged;
try
{
SetupFileWatcher();
}
catch (Exception ex)
{
logger.LogError((object)("Error setting up config file watcher: " + ex.Message));
}
}
public static void Tick()
{
if (!reloadPending || Time.unscaledTime - lastReloadTime < 0.25f)
{
return;
}
reloadPending = false;
lastReloadTime = Time.unscaledTime;
try
{
config.Reload();
ThrowablePatches.ResetToggles();
logger.LogInfo((object)"Config reloaded from disk.");
}
catch (Exception ex)
{
logger.LogError((object)("Error reloading config: " + ex.Message));
}
}
public static void Dispose()
{
if (IncendiaryMode != null)
{
IncendiaryMode.SettingChanged -= OnSettingChanged;
}
if (VoltaicMode != null)
{
VoltaicMode.SettingChanged -= OnSettingChanged;
}
if (AcidMode != null)
{
AcidMode.SettingChanged -= OnSettingChanged;
}
if (configWatcher != null)
{
configWatcher.EnableRaisingEvents = false;
configWatcher.Changed -= OnConfigFileChanged;
configWatcher.Created -= OnConfigFileChanged;
configWatcher.Renamed -= OnConfigFileChanged;
configWatcher.Dispose();
configWatcher = null;
}
}
private static void SetupFileWatcher()
{
configWatcher = new FileSystemWatcher(Paths.ConfigPath, "sparroh.grenademacros.cfg");
configWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite;
configWatcher.Changed += OnConfigFileChanged;
configWatcher.Created += OnConfigFileChanged;
configWatcher.Renamed += OnConfigFileChanged;
configWatcher.EnableRaisingEvents = true;
}
private static void OnConfigFileChanged(object sender, FileSystemEventArgs e)
{
reloadPending = true;
}
private static void OnSettingChanged(object sender, EventArgs e)
{
ThrowablePatches.ResetToggles();
}
}
[BepInPlugin("sparroh.grenademacros", "GrenadeMacros", "1.1.0")]
[MycoMod(/*Could not decode attribute arguments.*/)]
public class GrenadeMacrosPlugin : BaseUnityPlugin
{
public const string PluginGUID = "sparroh.grenademacros";
public const string PluginName = "GrenadeMacros";
public const string PluginVersion = "1.1.0";
internal static ManualLogSource Logger;
private Harmony harmony;
private void Awake()
{
//IL_0021: Unknown result type (might be due to invalid IL or missing references)
//IL_002b: Expected O, but got Unknown
Logger = ((BaseUnityPlugin)this).Logger;
ConfigManager.Initialize(((BaseUnityPlugin)this).Config, Logger);
harmony = new Harmony("sparroh.grenademacros");
try
{
harmony.PatchAll(typeof(ThrowablePatches));
Logger.LogInfo((object)"Harmony patches applied.");
}
catch (Exception ex)
{
Logger.LogError((object)("Error applying patches: " + ex.Message));
}
Logger.LogInfo((object)"GrenadeMacros v1.1.0 loaded successfully.");
}
private void Update()
{
ConfigManager.Tick();
}
private void OnDestroy()
{
ConfigManager.Dispose();
Harmony obj = harmony;
if (obj != null)
{
obj.UnpatchSelf();
}
}
}
public static class ThrowablePatches
{
public static bool incendiaryToggle;
public static bool voltaicToggle;
public static bool acidToggle;
private static bool isAutoThrow;
private static bool togglePressPending;
private static float togglePressStartTime;
private static Player togglePressPlayer;
private static object togglePressEquipped;
private const float ToggleTapMaxDuration = 0.35f;
private static readonly FieldInfo CooldownDataField = AccessTools.Field(typeof(Throwable), "cooldownData");
private static readonly FieldInfo PlayerField = AccessTools.Field(typeof(Throwable), "player");
private static readonly MethodInfo TryThrowMethod = AccessTools.Method(typeof(Player), "TryThrow", (Type[])null, (Type[])null);
private static readonly FieldInfo OnThrowHeldField = AccessTools.Field(typeof(Player), "OnThrowHeld");
public static void ResetToggles()
{
incendiaryToggle = false;
voltaicToggle = false;
acidToggle = false;
ClearTogglePress("ResetToggles");
}
private static void LogDebug(string message)
{
if (ConfigManager.DebugEnabled)
{
GrenadeMacrosPlugin.Logger.LogInfo((object)("[GrenadeMacros] " + message));
}
}
private static string InteractionName(CallbackContext context)
{
if (((CallbackContext)(ref context)).interaction == null)
{
return "null";
}
if (((CallbackContext)(ref context)).interaction is TapInteraction)
{
return "Tap";
}
if (((CallbackContext)(ref context)).interaction is HoldInteraction)
{
return "Hold";
}
if (((CallbackContext)(ref context)).interaction is PressInteraction)
{
return "Press";
}
return ((object)((CallbackContext)(ref context)).interaction).GetType().Name;
}
private static string PhaseName(CallbackContext context)
{
if (((CallbackContext)(ref context)).started)
{
return "Started";
}
if (((CallbackContext)(ref context)).performed)
{
return "Performed";
}
if (((CallbackContext)(ref context)).canceled)
{
return "Canceled";
}
return "Unknown";
}
private static string ToggleSummary()
{
return $"I={incendiaryToggle} V={voltaicToggle} A={acidToggle}";
}
private static void ClearTogglePress(string reason)
{
if (togglePressPending)
{
LogDebug($"ClearPending reason={reason} age={Time.unscaledTime - togglePressStartTime:F3}s");
}
togglePressPending = false;
togglePressPlayer = null;
togglePressEquipped = null;
togglePressStartTime = 0f;
}
private static bool TryGetGrenadeMode(object equippedThrow, out ConfigManager.ActivationMode mode)
{
mode = ConfigManager.ActivationMode.None;
if (equippedThrow == null)
{
return false;
}
switch (equippedThrow.GetType().Name)
{
case "IncendiaryGrenade":
mode = ConfigManager.IncendiaryMode.Value;
return true;
case "VoltaicGrenade":
mode = ConfigManager.VoltaicMode.Value;
return true;
case "AcidGrenade":
mode = ConfigManager.AcidMode.Value;
return true;
default:
return false;
}
}
private static bool TryGetToggleState(string typeName, out bool toggleState)
{
switch (typeName)
{
case "IncendiaryGrenade":
toggleState = incendiaryToggle;
return true;
case "VoltaicGrenade":
toggleState = voltaicToggle;
return true;
case "AcidGrenade":
toggleState = acidToggle;
return true;
default:
toggleState = false;
return false;
}
}
private static void FlipToggle(object equippedThrow, string reason)
{
if (equippedThrow == null)
{
LogDebug("FlipToggle skipped (equipped=null) reason=" + reason);
return;
}
string name = equippedThrow.GetType().Name;
switch (name)
{
case "IncendiaryGrenade":
incendiaryToggle = !incendiaryToggle;
LogDebug($"FlipToggle {name} -> {incendiaryToggle} reason={reason}");
break;
case "VoltaicGrenade":
voltaicToggle = !voltaicToggle;
LogDebug($"FlipToggle {name} -> {voltaicToggle} reason={reason}");
break;
case "AcidGrenade":
acidToggle = !acidToggle;
LogDebug($"FlipToggle {name} -> {acidToggle} reason={reason}");
break;
default:
LogDebug("FlipToggle unknown type=" + name + " reason=" + reason);
break;
}
}
private static IGear GetEquippedThrowable(Player player)
{
IGear[] gear = player.Gear;
if (gear == null || gear.Length <= 3)
{
return null;
}
return gear[3];
}
private static void InvokeTryThrow(Player player, string reason)
{
if (TryThrowMethod == null)
{
LogDebug("InvokeTryThrow aborted (method null) reason=" + reason);
return;
}
LogDebug("InvokeTryThrow reason=" + reason);
isAutoThrow = true;
try
{
TryThrowMethod.Invoke(player, null);
}
finally
{
isAutoThrow = false;
}
}
private static void AutoThrow(Player player, Throwable throwable, string reason)
{
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
//IL_002b: Unknown result type (might be due to invalid IL or missing references)
//IL_0066: Unknown result type (might be due to invalid IL or missing references)
if (!(CooldownDataField == null) && !(TryThrowMethod == null))
{
CooldownData val = (CooldownData)CooldownDataField.GetValue(throwable);
if (!((CooldownData)(ref val)).IsCharged)
{
LogDebug("AutoThrow skip not-charged type=" + ((object)throwable).GetType().Name + " reason=" + reason);
return;
}
LogDebug($"AutoThrow type={((object)throwable).GetType().Name} charge={val.charge:F2} reason={reason}");
InvokeTryThrow(player, reason);
}
}
private static bool HasOnThrowHeldSubscriber(Player player)
{
if (OnThrowHeldField == null)
{
return false;
}
return OnThrowHeldField.GetValue(player) != null;
}
private static void BeginTogglePress(Player player, object equipped)
{
togglePressPending = true;
togglePressStartTime = Time.unscaledTime;
togglePressPlayer = player;
togglePressEquipped = equipped;
LogDebug("BeginPending equipped=" + (equipped?.GetType().Name ?? "null") + " " + $"threshold={0.35f:F2}s {ToggleSummary()}");
}
private static void CompleteToggleTap(string reason)
{
if (!togglePressPending)
{
LogDebug("CompleteToggleTap skipped (no pending) reason=" + reason);
return;
}
object equippedThrow = togglePressEquipped;
float num = Time.unscaledTime - togglePressStartTime;
ClearTogglePress("complete:" + reason);
LogDebug($"CompleteToggleTap age={num:F3}s reason={reason}");
FlipToggle(equippedThrow, reason);
}
private static void ResolveTogglePressOnRelease(Player player)
{
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
//IL_001f: Unknown result type (might be due to invalid IL or missing references)
if (!togglePressPending || (Object)(object)togglePressPlayer != (Object)(object)player)
{
return;
}
PlayerActions player2 = PlayerInput.Controls.Player;
bool num = ((PlayerActions)(ref player2)).Throw.IsPressed();
float num2 = Time.unscaledTime - togglePressStartTime;
if (!num)
{
if (num2 <= 0.35f)
{
LogDebug($"PendingRelease FLIP age={num2:F3}s isPressed=false");
CompleteToggleTap("release-before-hold-threshold");
}
else
{
LogDebug($"PendingRelease CLEAR (too long) age={num2:F3}s threshold={0.35f:F2}s");
ClearTogglePress("release-after-threshold");
}
}
}
[HarmonyPatch(typeof(Player), "OnThrowPressed")]
[HarmonyPrefix]
private static bool OnThrowPressedPrefix(Player __instance, CallbackContext context)
{
//IL_0048: 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_0084: Unknown result type (might be due to invalid IL or missing references)
//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
try
{
if (!((NetworkBehaviour)__instance).IsOwner)
{
LogDebug("OnThrowPressed skip not-owner");
return true;
}
if (isAutoThrow)
{
LogDebug("OnThrowPressed skip isAutoThrow");
return true;
}
IGear equippedThrowable = GetEquippedThrowable(__instance);
ConfigManager.ActivationMode mode;
bool flag = TryGetGrenadeMode(equippedThrowable, out mode);
PlayerActions player = PlayerInput.Controls.Player;
bool flag2 = ((PlayerActions)(ref player)).Throw.IsPressed();
float num = (togglePressPending ? (Time.unscaledTime - togglePressStartTime) : 0f);
LogDebug($"OnThrowPressed phase={PhaseName(context)} performed={((CallbackContext)(ref context)).performed} canceled={((CallbackContext)(ref context)).canceled} " + $"started={((CallbackContext)(ref context)).started} interaction={InteractionName(context)} duration={((CallbackContext)(ref context)).duration:F3} " + string.Format("throwIsPressed={0} equipped={1} ", flag2, ((object)equippedThrowable)?.GetType().Name ?? "null") + $"hasMode={flag} mode={mode} pending={togglePressPending} pendingAge={num:F3} " + $"onThrowHeld={HasOnThrowHeldSubscriber(__instance)} {ToggleSummary()}");
if (flag)
{
switch (mode)
{
case ConfigManager.ActivationMode.None:
break;
case ConfigManager.ActivationMode.Hold:
ClearTogglePress("enter-hold-mode-input");
LogDebug("decision=hold-mode-block");
return false;
default:
LogDebug("decision=pass-vanilla (not toggle)");
return true;
case ConfigManager.ActivationMode.Toggle:
switch ((((CallbackContext)(ref context)).interaction is TapInteraction) ? 1 : (((CallbackContext)(ref context)).performed ? 2 : 0))
{
case 1:
if (!((CallbackContext)(ref context)).performed)
{
ClearTogglePress("tap-started");
LogDebug("decision=ignore (Tap started, wait for performed)");
return false;
}
ClearTogglePress("tap-performed");
FlipToggle(equippedThrowable, "tap-performed");
LogDebug("decision=flip-toggle (Tap performed)");
return false;
case 2:
ClearTogglePress("hold-performed");
if (HasOnThrowHeldSubscriber(__instance))
{
LogDebug("decision=pass-OnThrowHeld");
return true;
}
InvokeTryThrow(__instance, "hold-manual");
LogDebug("decision=manual-throw (Hold performed)");
return false;
default:
ClearTogglePress("non-tap-started");
LogDebug("decision=block-started (kind=0)");
return false;
}
}
}
LogDebug("decision=pass-vanilla (mode none/unknown)");
return true;
}
catch (Exception ex)
{
GrenadeMacrosPlugin.Logger.LogError((object)("Error in OnThrowPressedPrefix: " + ex.Message));
return true;
}
}
[HarmonyPatch(typeof(Player), "TryThrow")]
[HarmonyPrefix]
private static bool TryThrowPrefix(Player __instance)
{
try
{
if (isAutoThrow)
{
LogDebug("TryThrow allow isAutoThrow");
return true;
}
IGear equippedThrowable = GetEquippedThrowable(__instance);
if (!TryGetGrenadeMode(equippedThrowable, out var mode))
{
LogDebug("TryThrow allow unknown-equipped=" + (((object)equippedThrowable)?.GetType().Name ?? "null"));
return true;
}
if (mode == ConfigManager.ActivationMode.Toggle || mode == ConfigManager.ActivationMode.Hold)
{
LogDebug($"TryThrow BLOCK mode={mode} equipped={((object)equippedThrowable)?.GetType().Name} {ToggleSummary()}");
return false;
}
LogDebug($"TryThrow allow mode={mode}");
return true;
}
catch (Exception ex)
{
GrenadeMacrosPlugin.Logger.LogError((object)("Error in TryThrowPrefix: " + ex.Message));
return true;
}
}
[HarmonyPatch(typeof(Player), "Update")]
[HarmonyPostfix]
private static void PlayerUpdatePostfix(Player __instance)
{
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_0038: Unknown result type (might be due to invalid IL or missing references)
try
{
if (!((NetworkBehaviour)__instance).IsOwner || isAutoThrow)
{
return;
}
if (togglePressPending && (Object)(object)togglePressPlayer == (Object)(object)__instance)
{
ResolveTogglePressOnRelease(__instance);
}
PlayerActions player = PlayerInput.Controls.Player;
if (!((PlayerActions)(ref player)).Throw.IsPressed())
{
return;
}
IGear equippedThrowable = GetEquippedThrowable(__instance);
if (TryGetGrenadeMode(equippedThrowable, out var mode) && mode == ConfigManager.ActivationMode.Hold)
{
Throwable val = (Throwable)(object)((equippedThrowable is Throwable) ? equippedThrowable : null);
if (val != null)
{
AutoThrow(__instance, val, "hold-mode-update");
}
}
}
catch (Exception ex)
{
GrenadeMacrosPlugin.Logger.LogError((object)("Error in PlayerUpdatePostfix: " + ex.Message));
}
}
[HarmonyPatch(typeof(Throwable), "HandleCooldown")]
[HarmonyPrefix]
private static bool HandleCooldownPrefix(Throwable __instance, float charge)
{
//IL_0059: Unknown result type (might be due to invalid IL or missing references)
//IL_005e: Unknown result type (might be due to invalid IL or missing references)
try
{
string name = ((object)__instance).GetType().Name;
if (!TryGetGrenadeMode(__instance, out var mode) || mode == ConfigManager.ActivationMode.None)
{
return true;
}
if (CooldownDataField == null)
{
return true;
}
switch (mode)
{
case ConfigManager.ActivationMode.Hold:
return true;
default:
return true;
case ConfigManager.ActivationMode.Toggle:
{
CooldownData val = (CooldownData)CooldownDataField.GetValue(__instance);
if (!((CooldownData)(ref val)).IsCharged)
{
return true;
}
if (!TryGetToggleState(name, out var toggleState) || !toggleState)
{
return true;
}
if (PlayerField == null)
{
return true;
}
object? value = PlayerField.GetValue(__instance);
Player val2 = (Player)((value is Player) ? value : null);
if ((Object)(object)val2 == (Object)null || !((NetworkBehaviour)val2).IsOwner)
{
return true;
}
if ((object)GetEquippedThrowable(val2) != __instance)
{
return true;
}
AutoThrow(val2, __instance, "toggle-handle-cooldown");
return false;
}
}
}
catch (Exception ex)
{
GrenadeMacrosPlugin.Logger.LogError((object)("Error in HandleCooldownPrefix: " + ex.Message));
return true;
}
}
}
namespace GrenadeMacros
{
public static class MyPluginInfo
{
public const string PLUGIN_GUID = "GrenadeMacros";
public const string PLUGIN_NAME = "GrenadeMacros";
public const string PLUGIN_VERSION = "1.1.0";
}
}
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
internal sealed class IgnoresAccessChecksToAttribute : Attribute
{
public IgnoresAccessChecksToAttribute(string assemblyName)
{
}
}
}