Decompiled source of SentryAmmoGuard v1.0.0

BepInEx/plugins/SentryAmmoGuard/SentryAmmoGuard.dll

Decompiled 21 hours ago
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
namespace HawChow.SentryAmmoGuard;

[BepInPlugin("HawChow.SentryAmmoGuard", "SentryAmmoGuard", "1.0.0")]
public sealed class Plugin : BasePlugin
{
	internal static ManualLogSource Logger;

	public override void Load()
	{
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Expected O, but got Unknown
		Logger = ((BasePlugin)this).Log;
		new Harmony("HawChow.SentryAmmoGuard").PatchAll();
		ManualLogSource logger = Logger;
		bool flag = default(bool);
		BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(86, 2, ref flag);
		if (flag)
		{
			((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("SentryAmmoGuard");
			((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ");
			((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("1.0.0");
			((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" loaded; SentryGunSemi targeting is gated by live sentry ammo and valid target state.");
		}
		logger.LogInfo(val);
	}
}
internal static class PluginInfo
{
	internal const string Guid = "HawChow.SentryAmmoGuard";

	internal const string Name = "SentryAmmoGuard";

	internal const string Version = "1.0.0";
}
internal readonly struct DetectionDecision
{
	internal bool RunVanillaDetection { get; }

	internal bool ClearCachedTarget { get; }

	internal DetectionDecision(bool runVanillaDetection, bool clearCachedTarget)
	{
		RunVanillaDetection = runVanillaDetection;
		ClearCachedTarget = clearCachedTarget;
	}
}
internal static class SentryAmmoPolicy
{
	internal static DetectionDecision ForDetection(bool isSniperSentry, float ammo)
	{
		bool flag = isSniperSentry && !HasUsableAmmo(ammo);
		return new DetectionDecision(!flag, flag);
	}

	internal static bool ShouldAllowFire(bool isSniperSentry, float ammo, bool hasValidTarget)
	{
		if (isSniperSentry)
		{
			return HasUsableAmmo(ammo) && hasValidTarget;
		}
		return true;
	}

	internal static bool HasUsableAmmo(float ammo)
	{
		if (float.IsFinite(ammo))
		{
			return ammo > 0f;
		}
		return false;
	}

	internal static bool ShouldRunDetectionAfterStateReadError(bool isSniperSentry)
	{
		return !isSniperSentry;
	}

	internal static bool ShouldAllowFireAfterStateReadError(bool isSniperSentry)
	{
		return !isSniperSentry;
	}
}
internal static class SentryTargetingAccess
{
	internal static bool IsSniper(SentryGunInstance sentry)
	{
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Invalid comparison between Unknown and I4
		if ((Object)(object)sentry == (Object)null || sentry.m_firing == null)
		{
			return false;
		}
		SentryGunInstance_Firing_Bullets val = ((Il2CppObjectBase)sentry.m_firing).TryCast<SentryGunInstance_Firing_Bullets>();
		if ((Object)(object)val != (Object)null)
		{
			return (int)val.m_fireMode == 10;
		}
		return false;
	}

	internal static bool ClearCachedTarget(SentryGunInstance_Detection detection)
	{
		if ((Object)(object)detection == (Object)null)
		{
			return false;
		}
		if (!detection.HasTarget && !((Object)(object)detection.Target != (Object)null) && !((Object)(object)detection.TargetAimTrans != (Object)null) && !detection.TargetIsTagged)
		{
			return false;
		}
		detection.Target = null;
		detection.HasTarget = false;
		detection.TargetIsTagged = false;
		detection.TargetAimTrans = null;
		return true;
	}

	internal static bool HasValidTarget(SentryGunInstance sentry)
	{
		if ((Object)(object)sentry == (Object)null || sentry.m_detection == null)
		{
			return false;
		}
		SentryGunInstance_Detection val = ((Il2CppObjectBase)sentry.m_detection).TryCast<SentryGunInstance_Detection>();
		if ((Object)(object)val != (Object)null && val.HasTarget)
		{
			return (Object)(object)val.Target != (Object)null;
		}
		return false;
	}
}
[HarmonyPatch(typeof(SentryGunInstance_Detection), "UpdateDetection")]
internal static class SniperSentryDetectionPatch
{
	private static bool _reportedError;

	[HarmonyPrefix]
	private static bool Prefix(SentryGunInstance_Detection __instance)
	{
		bool flag = false;
		try
		{
			object obj;
			if (__instance == null)
			{
				obj = null;
			}
			else
			{
				iSentrygunInstanceCore core = __instance.m_core;
				obj = ((core != null) ? ((Il2CppObjectBase)core).TryCast<SentryGunInstance>() : null);
			}
			SentryGunInstance val = (SentryGunInstance)obj;
			flag = SentryTargetingAccess.IsSniper(val);
			DetectionDecision detectionDecision = SentryAmmoPolicy.ForDetection(flag, ((Object)(object)val == (Object)null) ? float.NaN : val.Ammo);
			if (detectionDecision.ClearCachedTarget && SentryTargetingAccess.ClearCachedTarget(__instance))
			{
				ManualLogSource logger = Plugin.Logger;
				if (logger != null)
				{
					logger.LogInfo((object)"[SentryAmmoGuard] Sniper Sentry target cleared at zero ammo; detection is paused until ammo is restored.");
				}
			}
			return detectionDecision.RunVanillaDetection;
		}
		catch (Exception error)
		{
			ReportError(error);
			if (flag)
			{
				try
				{
					SentryTargetingAccess.ClearCachedTarget(__instance);
				}
				catch
				{
				}
			}
			return SentryAmmoPolicy.ShouldRunDetectionAfterStateReadError(flag);
		}
	}

	internal static void ReportError(Exception error)
	{
		if (!_reportedError)
		{
			_reportedError = true;
			ManualLogSource logger = Plugin.Logger;
			if (logger != null)
			{
				logger.LogError((object)("Sentry guard could not read current game state; a classified sniper uses the fail-closed path. " + error));
			}
		}
	}
}
[HarmonyPatch(typeof(SentryGunInstance), "WantToFire")]
internal static class SniperSentryFireGuardPatch
{
	[HarmonyPostfix]
	private static void Postfix(SentryGunInstance __instance, ref bool __result)
	{
		if (!__result)
		{
			return;
		}
		bool flag = false;
		try
		{
			flag = SentryTargetingAccess.IsSniper(__instance);
			if (flag)
			{
				__result = SentryAmmoPolicy.ShouldAllowFire(flag, __instance.Ammo, SentryTargetingAccess.HasValidTarget(__instance));
			}
		}
		catch (Exception error)
		{
			SniperSentryDetectionPatch.ReportError(error);
			__result = SentryAmmoPolicy.ShouldAllowFireAfterStateReadError(flag) & __result;
		}
	}
}