Decompiled source of Manual Extraction Confirm v1.1.0

ManualExtractionConfirm.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Logging;
using ExtractionConfirm.Patches;
using HarmonyLib;
using Photon.Pun;
using UnityEngine;

[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace ExtractionConfirm
{
	[BepInPlugin("yourname.extractionconfirm", "Extraction Confirm", "2.1.1")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "yourname.extractionconfirm";

		public const string PluginName = "Extraction Confirm";

		public const string PluginVersion = "2.1.1";

		internal static ManualLogSource Log;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			_harmony = new Harmony("yourname.extractionconfirm");
			PatchOne(typeof(StateSetGatePatch));
			PatchOne(typeof(ExtractionPointUpdatePatch));
			BreakGuard.TryPatch(_harmony);
			HalloweenCompat.TryPatch(_harmony);
			Fields.ValidateOrLog();
			Log.LogInfo((object)string.Format("{0} v{1} loaded.", "Extraction Confirm", "2.1.1"));
		}

		private void PatchOne(Type patchClass)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				new PatchClassProcessor(_harmony, patchClass).Patch();
				Log.LogInfo((object)$"Extraction Confirm: patched {patchClass.Name}.");
			}
			catch (Exception ex)
			{
				Log.LogError((object)($"Extraction Confirm: FAILED to patch {patchClass.Name} — " + "that feature is off, the rest of the mod still runs."));
				Log.LogError((object)ex.ToString());
			}
		}

		private void OnDestroy()
		{
			if (_harmony != null)
			{
				_harmony.UnpatchSelf();
			}
		}

		internal static bool IsHost()
		{
			if (!PhotonNetwork.IsConnected)
			{
				return true;
			}
			return PhotonNetwork.InRoom && PhotonNetwork.IsMasterClient;
		}
	}
	internal static class Fields
	{
		private static readonly FieldInfo EpCurrentState = AccessTools.Field(typeof(ExtractionPoint), "currentState");

		private static readonly FieldInfo EpIsShop = AccessTools.Field(typeof(ExtractionPoint), "isShop");

		private static readonly FieldInfo EpHaulCurrent = AccessTools.Field(typeof(ExtractionPoint), "haulCurrent");

		private static readonly FieldInfo EpHaulGoal = AccessTools.Field(typeof(ExtractionPoint), "haulGoal");

		private static readonly FieldInfo EpPlatform = AccessTools.Field(typeof(ExtractionPoint), "platform");

		private static readonly FieldInfo EpHaulGoalScreen = AccessTools.Field(typeof(ExtractionPoint), "haulGoalScreen");

		private static readonly FieldInfo EpTubeScreenText = AccessTools.Field(typeof(ExtractionPoint), "tubeScreenText");

		private static readonly FieldInfo EpEmojiScreen = AccessTools.Field(typeof(ExtractionPoint), "emojiScreen");

		private static readonly MethodInfo EpStateSet = AccessTools.Method(typeof(ExtractionPoint), "StateSet", new Type[1] { typeof(State) }, (Type[])null);

		private static readonly FieldInfo TstInstance = AccessTools.Field(typeof(TruckScreenText), "instance");

		private static readonly FieldInfo TstChargeChatLoop = AccessTools.Field(typeof(TruckScreenText), "chargeChatLoop");

		private static readonly FieldInfo TstResultFail = AccessTools.Field(typeof(TruckScreenText), "chatMessageResultFail");

		private static readonly FieldInfo PgoGrabbed = AccessTools.Field(typeof(PhysGrabObject), "grabbed");

		public static State CurrentState(ExtractionPoint p)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			if (EpCurrentState == null || (Object)(object)p == (Object)null)
			{
				return (State)0;
			}
			return (State)EpCurrentState.GetValue(p);
		}

		public static bool IsShop(ExtractionPoint p)
		{
			if (EpIsShop == null || (Object)(object)p == (Object)null)
			{
				return false;
			}
			return (bool)EpIsShop.GetValue(p);
		}

		public static bool QuotaReached(ExtractionPoint p)
		{
			if (EpHaulCurrent == null || EpHaulGoal == null || (Object)(object)p == (Object)null)
			{
				return false;
			}
			int num = (int)EpHaulCurrent.GetValue(p);
			int num2 = (int)EpHaulGoal.GetValue(p);
			return num >= num2;
		}

		public static void CallStateSet(ExtractionPoint p, State state)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			if (!(EpStateSet == null) && !((Object)(object)p == (Object)null))
			{
				EpStateSet.Invoke(p, new object[1] { state });
			}
		}

		public static bool IsGrabbed(PhysGrabObject o)
		{
			if (PgoGrabbed == null || (Object)(object)o == (Object)null)
			{
				return false;
			}
			return (bool)PgoGrabbed.GetValue(o);
		}

		public static Transform GetPlatform(ExtractionPoint p)
		{
			if (EpPlatform == null || (Object)(object)p == (Object)null)
			{
				return null;
			}
			object? value = EpPlatform.GetValue(p);
			return (Transform)((value is Transform) ? value : null);
		}

		private static Transform ComponentTransform(FieldInfo f, ExtractionPoint p)
		{
			if (f == null || (Object)(object)p == (Object)null)
			{
				return null;
			}
			object? value = f.GetValue(p);
			Component val = (Component)((value is Component) ? value : null);
			return (!((Object)(object)val == (Object)null)) ? val.transform : null;
		}

		public static Transform GetHaulGoalScreenTransform(ExtractionPoint p)
		{
			return ComponentTransform(EpHaulGoalScreen, p);
		}

		public static Transform GetTubeScreenTextTransform(ExtractionPoint p)
		{
			return ComponentTransform(EpTubeScreenText, p);
		}

		public static Transform GetEmojiScreenTransform(ExtractionPoint p)
		{
			return ComponentTransform(EpEmojiScreen, p);
		}

		private static object TruckScreenInstance()
		{
			if (TstInstance == null)
			{
				return null;
			}
			return TstInstance.GetValue(null);
		}

		public static Sound GetTruckChargeSound()
		{
			object obj = TruckScreenInstance();
			if (obj == null || TstChargeChatLoop == null)
			{
				return null;
			}
			object? value = TstChargeChatLoop.GetValue(obj);
			return (Sound)((value is Sound) ? value : null);
		}

		public static Sound GetTruckDenySound()
		{
			object obj = TruckScreenInstance();
			if (obj == null || TstResultFail == null)
			{
				return null;
			}
			object? value = TstResultFail.GetValue(obj);
			return (Sound)((value is Sound) ? value : null);
		}

		public static void ValidateOrLog()
		{
			Warn(EpCurrentState, "ExtractionPoint.currentState");
			Warn(EpIsShop, "ExtractionPoint.isShop");
			Warn(EpHaulCurrent, "ExtractionPoint.haulCurrent");
			Warn(EpHaulGoal, "ExtractionPoint.haulGoal");
			Warn(EpEmojiScreen, "ExtractionPoint.emojiScreen");
			Warn(EpStateSet, "ExtractionPoint.StateSet(State)");
			Warn(TstInstance, "TruckScreenText.instance");
			Warn(TstChargeChatLoop, "TruckScreenText.chargeChatLoop");
			Warn(TstResultFail, "TruckScreenText.chatMessageResultFail");
			Warn(PgoGrabbed, "PhysGrabObject.grabbed");
		}

		private static void Warn(object member, string name)
		{
			if (member == null)
			{
				Plugin.Log.LogWarning((object)$"Extraction Confirm: could not resolve {name} — that feature is disabled.");
			}
		}
	}
	internal class HiddenState
	{
		public GameObject SpawnedObject;

		public PhysGrabObject GrabObject;

		public float? HoldStartTime;

		public bool Despawned;

		public AudioSource HoldSound;

		public float LastSpawnAttempt = float.NegativeInfinity;

		public float LastGrabbedTime = float.NegativeInfinity;

		public bool AwaitingRelease;
	}
	internal static class ConfirmTracker
	{
		public static readonly Dictionary<ExtractionPoint, HiddenState> States = new Dictionary<ExtractionPoint, HiddenState>();

		private static readonly HashSet<ExtractionPoint> AllowNextSuccess = new HashSet<ExtractionPoint>();

		public static HiddenState GetOrCreate(ExtractionPoint point)
		{
			if ((Object)(object)point == (Object)null)
			{
				return null;
			}
			if (!States.TryGetValue(point, out var value))
			{
				value = new HiddenState();
				States[point] = value;
			}
			return value;
		}

		public static void Allow(ExtractionPoint point)
		{
			if ((Object)(object)point != (Object)null)
			{
				AllowNextSuccess.Add(point);
			}
		}

		public static bool ConsumeAllow(ExtractionPoint point)
		{
			if ((Object)(object)point == (Object)null)
			{
				return false;
			}
			return AllowNextSuccess.Remove(point);
		}
	}
	internal static class ConfirmHandle
	{
		private static readonly List<GameObject> Roots = new List<GameObject>();

		private static readonly HashSet<PhysGrabObject> GrabObjects = new HashSet<PhysGrabObject>();

		private static readonly List<Collider> Colliders = new List<Collider>();

		public static int ColliderCount
		{
			[CompilerGenerated]
			get
			{
				return Colliders.Count;
			}
		}

		public static void Register(GameObject root, PhysGrabObject grabObject)
		{
			if ((Object)(object)root == (Object)null)
			{
				return;
			}
			if (!Roots.Contains(root))
			{
				Roots.Add(root);
			}
			if ((Object)(object)grabObject != (Object)null)
			{
				GrabObjects.Add(grabObject);
			}
			Collider[] componentsInChildren = root.GetComponentsInChildren<Collider>(true);
			foreach (Collider val in componentsInChildren)
			{
				if ((Object)(object)val != (Object)null && !Colliders.Contains(val))
				{
					Colliders.Add(val);
				}
			}
		}

		public static void Unregister(GameObject root)
		{
			if (!((Object)(object)root == (Object)null))
			{
				Roots.Remove(root);
				Colliders.RemoveAll((Collider c) => (Object)(object)c == (Object)null || IsUnder(((Component)c).transform, root.transform));
				GrabObjects.RemoveWhere((PhysGrabObject o) => (Object)(object)o == (Object)null || IsUnder(((Component)o).transform, root.transform));
			}
		}

		private static bool IsUnder(Transform t, Transform root)
		{
			return (Object)(object)t != (Object)null && (Object)(object)root != (Object)null && t.IsChildOf(root);
		}

		public static bool IsCollider(Collider c)
		{
			if ((Object)(object)c == (Object)null)
			{
				return false;
			}
			if (Colliders.Contains(c))
			{
				return true;
			}
			return IsTransform(((Component)c).transform);
		}

		public static bool IsGrabObject(PhysGrabObject o)
		{
			if ((Object)(object)o == (Object)null)
			{
				return false;
			}
			if (GrabObjects.Contains(o))
			{
				return true;
			}
			return IsTransform(((Component)o).transform);
		}

		public static bool IsTransform(Transform t)
		{
			if ((Object)(object)t == (Object)null || Roots.Count == 0)
			{
				return false;
			}
			for (int i = 0; i < Roots.Count; i++)
			{
				GameObject val = Roots[i];
				if ((Object)(object)val != (Object)null && t.IsChildOf(val.transform))
				{
					return true;
				}
			}
			return false;
		}

		public static void MaskColliders(List<Collider> masked)
		{
			masked.Clear();
			for (int i = 0; i < Colliders.Count; i++)
			{
				Collider val = Colliders[i];
				if (!((Object)(object)val == (Object)null) && val.enabled)
				{
					val.enabled = false;
					masked.Add(val);
				}
			}
		}

		public static void RestoreColliders(List<Collider> masked)
		{
			for (int i = 0; i < masked.Count; i++)
			{
				Collider val = masked[i];
				if ((Object)(object)val != (Object)null)
				{
					val.enabled = true;
				}
			}
			masked.Clear();
		}

		public static bool EnsureCollidersEnabled()
		{
			bool result = false;
			MeshCollider val2 = default(MeshCollider);
			for (int i = 0; i < Colliders.Count; i++)
			{
				Collider val = Colliders[i];
				if (!((Object)(object)val == (Object)null) && !val.enabled)
				{
					object obj = ((val is MeshCollider) ? val : null);
					if (obj != null)
					{
						val2 = (MeshCollider)obj;
						obj = val2;
					}
					if (obj == null || val2.convex)
					{
						val.enabled = true;
						result = true;
					}
				}
			}
			return result;
		}
	}
	internal static class HiddenSpawner
	{
		private const string HiddenResourcePath = "enemies/Enemy - Hidden";

		private const float GrabHitboxWidenFactor = 3f;

		private const bool DisablePhysicalCollision = false;

		public static GameObject Spawn(Vector3 position, Quaternion rotation, out PhysGrabObject grabObject)
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: 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)
			grabObject = null;
			GameObject val = null;
			if (PhotonNetwork.IsConnected && PhotonNetwork.InRoom)
			{
				val = PhotonNetwork.Instantiate("enemies/Enemy - Hidden", position, rotation, (byte)0, (object[])null);
			}
			else
			{
				GameObject val2 = Resources.Load<GameObject>("enemies/Enemy - Hidden");
				if ((Object)(object)val2 == (Object)null)
				{
					Plugin.Log.LogError((object)string.Format("Extraction Confirm: could not load {0}.", "enemies/Enemy - Hidden"));
					return null;
				}
				val = Object.Instantiate<GameObject>(val2, position, rotation);
			}
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			Neutralize(val);
			grabObject = val.GetComponentInChildren<PhysGrabObject>(true);
			if ((Object)(object)grabObject == (Object)null)
			{
				Plugin.Log.LogWarning((object)"Extraction Confirm: spawned confirm handle has no PhysGrabObject.");
			}
			ConfirmHandle.Register(val, grabObject);
			return val;
		}

		private static void Neutralize(GameObject root)
		{
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
			MonoBehaviour[] componentsInChildren = root.GetComponentsInChildren<MonoBehaviour>(true);
			foreach (MonoBehaviour val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && !(val is PhysGrabObject) && !(val is PhotonView))
				{
					((Behaviour)val).enabled = false;
				}
			}
			AudioSource[] componentsInChildren2 = root.GetComponentsInChildren<AudioSource>(true);
			foreach (AudioSource val2 in componentsInChildren2)
			{
				if (!((Object)(object)val2 == (Object)null))
				{
					val2.Stop();
					val2.volume = 0f;
					val2.mute = true;
					val2.playOnAwake = false;
					((Behaviour)val2).enabled = false;
				}
			}
			Rigidbody[] componentsInChildren3 = root.GetComponentsInChildren<Rigidbody>(true);
			foreach (Rigidbody val3 in componentsInChildren3)
			{
				if (!((Object)(object)val3 == (Object)null))
				{
					val3.isKinematic = true;
				}
			}
			HashSet<Transform> hashSet = new HashSet<Transform>();
			List<CapsuleCollider> list = new List<CapsuleCollider>();
			Collider[] componentsInChildren4 = root.GetComponentsInChildren<Collider>(true);
			MeshCollider val5 = default(MeshCollider);
			CapsuleCollider val6 = default(CapsuleCollider);
			foreach (Collider val4 in componentsInChildren4)
			{
				if ((Object)(object)val4 == (Object)null)
				{
					continue;
				}
				object obj = ((val4 is MeshCollider) ? val4 : null);
				if (obj != null)
				{
					val5 = (MeshCollider)obj;
					obj = val5;
				}
				if (obj != null && !val5.convex)
				{
					((Collider)val5).enabled = false;
					hashSet.Add(((Component)val5).transform);
					continue;
				}
				object obj2 = ((val4 is CapsuleCollider) ? val4 : null);
				if (obj2 != null)
				{
					val6 = (CapsuleCollider)obj2;
					obj2 = val6;
				}
				if (obj2 != null)
				{
					list.Add(val6);
				}
			}
			foreach (CapsuleCollider item in list)
			{
				BoxCollider val7 = ((Component)item).gameObject.AddComponent<BoxCollider>();
				val7.center = item.center;
				val7.size = new Vector3(item.radius * 2f * 3f, item.height, item.radius * 2f);
			}
			Renderer[] componentsInChildren5 = root.GetComponentsInChildren<Renderer>(true);
			foreach (Renderer val8 in componentsInChildren5)
			{
				if (!((Object)(object)val8 == (Object)null) && !hashSet.Contains(((Component)val8).transform))
				{
					val8.enabled = true;
				}
			}
		}

		public static void Despawn(GameObject spawned)
		{
			if ((Object)(object)spawned == (Object)null)
			{
				return;
			}
			ConfirmHandle.Unregister(spawned);
			if (PhotonNetwork.IsConnected && PhotonNetwork.InRoom)
			{
				PhotonView component = spawned.GetComponent<PhotonView>();
				if ((Object)(object)component != (Object)null && component.IsMine)
				{
					PhotonNetwork.Destroy(spawned);
					return;
				}
			}
			Object.Destroy((Object)(object)spawned);
		}
	}
	internal static class HalloweenCompat
	{
		private static readonly List<Collider> Masked = new List<Collider>();

		private static bool _patched;

		public static bool IsMasking
		{
			[CompilerGenerated]
			get
			{
				return Masked.Count > 0;
			}
		}

		public static void TryPatch(Harmony harmony)
		{
			if (_patched || harmony == null)
			{
				return;
			}
			Type type = AccessTools.TypeByName("ExtractionHalloweenTower");
			if (type == null)
			{
				Plugin.Log.LogInfo((object)"Extraction Confirm: no Halloween tower in this build — stacking compatibility not needed.");
				return;
			}
			try
			{
				MethodInfo methodInfo = AccessTools.Method(type, "TowerEvaluate", (Type[])null, (Type[])null);
				if (methodInfo != null)
				{
					MethodInfo methodInfo2 = methodInfo;
					HarmonyMethod val = Hook("MaskPrefix");
					HarmonyMethod val2 = Hook("UnmaskFinalizer");
					harmony.Patch((MethodBase)methodInfo2, val, (HarmonyMethod)null, (HarmonyMethod)null, val2, (HarmonyMethod)null);
				}
				else
				{
					Plugin.Log.LogWarning((object)"Extraction Confirm: ExtractionHalloweenTower.TowerEvaluate not found — valuable stacking may misbehave.");
				}
				MethodInfo methodInfo3 = AccessTools.Method(type, "TowerReject", (Type[])null, (Type[])null);
				if (methodInfo3 != null)
				{
					harmony.Patch((MethodBase)methodInfo3, Hook("TowerRejectPrefix"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				Type type2 = AccessTools.TypeByName("ExtractionHalloweenContact");
				if (type2 != null)
				{
					MethodInfo methodInfo4 = AccessTools.Method(type2, "SupportUpdate", (Type[])null, (Type[])null);
					if (methodInfo4 != null)
					{
						harmony.Patch((MethodBase)methodInfo4, Hook("SupportUpdatePrefix"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					}
				}
				_patched = true;
				Plugin.Log.LogInfo((object)"Extraction Confirm: Halloween stacking compatibility applied.");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)"Extraction Confirm: failed to apply Halloween stacking compatibility.");
				Plugin.Log.LogError((object)ex.ToString());
			}
		}

		private static HarmonyMethod Hook(string name)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			return new HarmonyMethod(AccessTools.Method(typeof(HalloweenCompat), name, (Type[])null, (Type[])null));
		}

		private static void MaskPrefix()
		{
			if (Masked.Count > 0)
			{
				ConfirmHandle.RestoreColliders(Masked);
			}
			if (ConfirmHandle.ColliderCount != 0)
			{
				ConfirmHandle.MaskColliders(Masked);
			}
		}

		private static void UnmaskFinalizer()
		{
			ConfirmHandle.RestoreColliders(Masked);
		}

		private static bool TowerRejectPrefix(object __0, ref bool __result)
		{
			object obj = ((__0 is PhysGrabObject) ? __0 : null);
			PhysGrabObject val = default(PhysGrabObject);
			if (obj != null)
			{
				val = (PhysGrabObject)obj;
				obj = val;
			}
			if (obj != null && ConfirmHandle.IsGrabObject(val))
			{
				__result = false;
				return false;
			}
			return true;
		}

		private static bool SupportUpdatePrefix(Collision __0)
		{
			if (__0 == null)
			{
				return true;
			}
			return !ConfirmHandle.IsCollider(__0.collider);
		}
	}
	internal static class BreakGuard
	{
		private static readonly string[] Targets = new string[5] { "Break", "BreakHeavy", "BreakMedium", "BreakLight", "BreakRPC" };

		public static void TryPatch(Harmony harmony)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Expected O, but got Unknown
			if (harmony == null)
			{
				return;
			}
			HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(BreakGuard), "SkipForConfirmHandle", (Type[])null, (Type[])null));
			int num = 0;
			string[] targets = Targets;
			foreach (string text in targets)
			{
				try
				{
					MethodInfo methodInfo = AccessTools.Method(typeof(PhysGrabObjectImpactDetector), text, (Type[])null, (Type[])null);
					if (methodInfo == null)
					{
						Plugin.Log.LogWarning((object)$"Extraction Confirm: PhysGrabObjectImpactDetector.{text} not found, skipping.");
						continue;
					}
					harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					num++;
				}
				catch (Exception ex)
				{
					Plugin.Log.LogError((object)$"Extraction Confirm: could not guard PhysGrabObjectImpactDetector.{text}: {ex.Message}");
				}
			}
			Plugin.Log.LogInfo((object)$"Extraction Confirm: break guard applied to {num}/{Targets.Length} methods.");
		}

		private static bool SkipForConfirmHandle(PhysGrabObjectImpactDetector __instance)
		{
			if ((Object)(object)__instance == (Object)null)
			{
				return true;
			}
			return !ConfirmHandle.IsTransform(((Component)__instance).transform);
		}
	}
}
namespace ExtractionConfirm.Patches
{
	[HarmonyPatch(typeof(ExtractionPoint), "Update")]
	internal static class ExtractionPointUpdatePatch
	{
		private const float HoldSeconds = 2f;

		private const float SpawnRetrySeconds = 1f;

		private const float ReleaseGraceSeconds = 0.3f;

		private const float StartPitch = 1f;

		private const float EndPitch = 2f;

		private static readonly Vector3 SpawnOffset = new Vector3(0f, -1.25f, 0f);

		private static void Postfix(ExtractionPoint __instance)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Invalid comparison between Unknown and I4
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Invalid comparison between Unknown and I4
			try
			{
				if ((Object)(object)__instance == (Object)null || !Plugin.IsHost() || Fields.IsShop(__instance))
				{
					return;
				}
				HiddenState orCreate = ConfirmTracker.GetOrCreate(__instance);
				if (orCreate == null)
				{
					return;
				}
				State val = Fields.CurrentState(__instance);
				if ((int)val == 7)
				{
					if ((Object)(object)orCreate.SpawnedObject != (Object)null)
					{
						StopHoldSound(orCreate);
						HiddenSpawner.Despawn(orCreate.SpawnedObject);
						orCreate.SpawnedObject = null;
						orCreate.GrabObject = null;
						orCreate.Despawned = true;
					}
				}
				else
				{
					if ((int)val != 2)
					{
						return;
					}
					if ((Object)(object)orCreate.SpawnedObject == (Object)null && !orCreate.Despawned)
					{
						TrySpawn(__instance, orCreate);
						return;
					}
					if (!HalloweenCompat.IsMasking && ConfirmHandle.EnsureCollidersEnabled())
					{
						Plugin.Log.LogWarning((object)"Extraction Confirm: re-enabled confirm handle colliders that were left disabled.");
					}
					UpdateHold(__instance, orCreate);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Extraction Confirm: ExtractionPointUpdatePatch threw: " + ex));
			}
		}

		private static void TrySpawn(ExtractionPoint point, HiddenState state)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			if (Time.time - state.LastSpawnAttempt < 1f)
			{
				return;
			}
			state.LastSpawnAttempt = Time.time;
			Transform emojiScreenTransform = Fields.GetEmojiScreenTransform(point);
			if ((Object)(object)emojiScreenTransform == (Object)null)
			{
				Plugin.Log.LogWarning((object)"Extraction Confirm: no emoji screen on this point yet, retrying.");
				return;
			}
			Vector3 val = emojiScreenTransform.position + SpawnOffset;
			PhysGrabObject grabObject;
			GameObject val2 = HiddenSpawner.Spawn(val, emojiScreenTransform.rotation, out grabObject);
			if (!((Object)(object)val2 == (Object)null))
			{
				state.SpawnedObject = val2;
				state.GrabObject = grabObject;
				state.HoldStartTime = null;
				state.AwaitingRelease = false;
				Plugin.Log.LogInfo((object)$"Extraction Confirm: confirm handle placed at {val}.");
			}
		}

		private static void UpdateHold(ExtractionPoint point, HiddenState state)
		{
			if ((Object)(object)state.GrabObject == (Object)null)
			{
				return;
			}
			bool flag = Fields.IsGrabbed(state.GrabObject);
			if (flag)
			{
				state.LastGrabbedTime = Time.time;
			}
			if (!flag && !(Time.time - state.LastGrabbedTime <= 0.3f))
			{
				state.HoldStartTime = null;
				state.AwaitingRelease = false;
				StopHoldSound(state);
			}
			else
			{
				if (state.AwaitingRelease)
				{
					return;
				}
				if (!state.HoldStartTime.HasValue)
				{
					state.HoldStartTime = Time.time;
					Plugin.Log.LogInfo((object)"Extraction Confirm: confirm hold started.");
					StartHoldSound(state);
				}
				float num = Time.time - state.HoldStartTime.Value;
				if ((Object)(object)state.HoldSound != (Object)null)
				{
					state.HoldSound.pitch = Mathf.Lerp(1f, 2f, Mathf.Clamp01(num / 2f));
				}
				if (!(num < 2f))
				{
					StopHoldSound(state);
					state.HoldStartTime = null;
					if (Fields.QuotaReached(point))
					{
						Plugin.Log.LogInfo((object)"Extraction Confirm: confirmed, starting extraction.");
						ConfirmTracker.Allow(point);
						Fields.CallStateSet(point, (State)3);
					}
					else
					{
						Plugin.Log.LogInfo((object)"Extraction Confirm: quota not met, denying.");
						state.AwaitingRelease = true;
						PlayDenySound(state);
						Fields.CallStateSet(point, (State)5);
					}
				}
			}
		}

		private static void StartHoldSound(HiddenState state)
		{
			StopHoldSound(state);
			if ((Object)(object)state.SpawnedObject == (Object)null)
			{
				return;
			}
			Sound truckChargeSound = Fields.GetTruckChargeSound();
			if (truckChargeSound != null)
			{
				AudioClip val = null;
				if (truckChargeSound.Sounds != null && truckChargeSound.Sounds.Length > 0)
				{
					val = truckChargeSound.Sounds[0];
				}
				if (!((Object)(object)val == (Object)null))
				{
					AudioSource val2 = state.SpawnedObject.AddComponent<AudioSource>();
					val2.clip = val;
					val2.loop = true;
					val2.spatialBlend = 1f;
					val2.volume = Mathf.Max(truckChargeSound.Volume, 0.7f);
					val2.pitch = 1f;
					val2.Play();
					state.HoldSound = val2;
				}
			}
		}

		private static void StopHoldSound(HiddenState state)
		{
			if (!((Object)(object)state.HoldSound == (Object)null))
			{
				state.HoldSound.Stop();
				Object.Destroy((Object)(object)state.HoldSound);
				state.HoldSound = null;
			}
		}

		private static void PlayDenySound(HiddenState state)
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			Sound truckDenySound = Fields.GetTruckDenySound();
			if (truckDenySound != null && !((Object)(object)state.SpawnedObject == (Object)null))
			{
				AudioClip val = null;
				if (truckDenySound.Sounds != null && truckDenySound.Sounds.Length > 0)
				{
					val = truckDenySound.Sounds[0];
				}
				if (!((Object)(object)val == (Object)null))
				{
					AudioSource.PlayClipAtPoint(val, state.SpawnedObject.transform.position, Mathf.Max(truckDenySound.Volume, 0.7f));
				}
			}
		}
	}
	[HarmonyPatch(typeof(ExtractionPoint), "StateSet")]
	internal static class StateSetGatePatch
	{
		private static bool Prefix(ExtractionPoint __instance, State newState)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Invalid comparison between Unknown and I4
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Invalid comparison between Unknown and I4
			try
			{
				if (!Plugin.IsHost())
				{
					return true;
				}
				if ((int)newState != 3)
				{
					return true;
				}
				if (Fields.IsShop(__instance))
				{
					return true;
				}
				if ((int)Fields.CurrentState(__instance) != 2)
				{
					return true;
				}
				if (ConfirmTracker.ConsumeAllow(__instance))
				{
					return true;
				}
				Plugin.Log.LogInfo((object)"Quota reached automatically — holding for manual confirm instead.");
				return false;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Extraction Confirm: StateSetGatePatch threw, letting original run: " + ex));
				return true;
			}
		}
	}
}