Decompiled source of GrenadeMacros v1.0.2

BepInEx/plugins/GrenadeMacros.dll

Decompiled 2 weeks ago
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)
		{
		}
	}
}