Decompiled source of NoPitStops v0.8.1

BepInEx/plugins/NoPitStops/NoPitStops.dll

Decompiled 10 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Extensions;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mirror;
using NoPitStops.GiveUp;
using NoPitStops.Patches;
using NoPitStops.UI;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("NoPitStops")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.8.1.0")]
[assembly: AssemblyInformationalVersion("0.8.1+d714dee1f04624388c3cf520916753476a775b1f")]
[assembly: AssemblyProduct("NoPitStops")]
[assembly: AssemblyTitle("NoPitStops")]
[assembly: AssemblyVersion("0.8.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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;
		}
	}
}
namespace NoPitStops
{
	[BepInPlugin("com.saltedbyte.nopitstops", "NoPitStops", "0.8.0")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.saltedbyte.nopitstops";

		public const string PluginName = "NoPitStops";

		public const string PluginVersion = "0.8.0";

		internal static ManualLogSource Log = null;

		internal static ConfigEntry<bool> AutoVoteEnabled = null;

		internal static ConfigEntry<float> AutoVoteDelay = null;

		internal static ConfigEntry<bool> ForceVoteHotkey = null;

		internal static ConfigEntry<bool> FastCountdownEnabled = null;

		internal static ConfigEntry<float> CountdownSeconds = null;

		internal static ConfigEntry<bool> PlayerThresholdEnabled = null;

		internal static ConfigEntry<float> PlayerThresholdPercent = null;

		internal static ConfigEntry<bool> SoloTrigger = null;

		internal static ConfigEntry<bool> SkipPitOnFullLoss = null;

		internal static ConfigEntry<bool> ShowToasts = null;

		internal static ConfigEntry<bool> GiveUpEnabled = null;

		internal static ConfigEntry<float> GiveUpThreshold = null;

		private static readonly Type[] PatchClasses = new Type[7]
		{
			typeof(SkipUI_AutoVote),
			typeof(AllPlayersTriggerZone_FastCountdown),
			typeof(AllPlayersTriggerZone_PlayerThreshold),
			typeof(GameManager_SkipLoseStateScene),
			typeof(EscapeMenu_TrackOpen),
			typeof(EscapeMenu_AddGiveUpButton),
			typeof(CustomNetworkManager_HandlerInit)
		};

		private void Awake()
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			BindConfig();
			Log.LogInfo((object)"NoPitStops v0.8.0 loaded.");
			Toast.Bootstrap();
			GiveUpVote.InstallSerializers();
			Harmony val = new Harmony("com.saltedbyte.nopitstops");
			int num = 0;
			Type[] patchClasses = PatchClasses;
			foreach (Type type in patchClasses)
			{
				try
				{
					val.CreateClassProcessor(type).Patch();
					Log.LogInfo((object)("  ✔ patched " + type.Name));
					num++;
				}
				catch (Exception arg)
				{
					Log.LogError((object)$"  ✘ FAILED to patch {type.Name}: {arg}");
				}
			}
			Log.LogInfo((object)$"Harmony patches applied: {num}/{PatchClasses.Length}");
			if (ShowToasts.Value)
			{
				Toast.Show($"No Pit Stops loaded — {num}/{PatchClasses.Length} patches active");
			}
		}

		private void BindConfig()
		{
			AutoVoteEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("AutoVote", "Enabled", true, "Auto-vote skip whenever SkipUI becomes available (end-of-day, ending sequence, etc.). When all clients run the mod, the skip fires the moment SkipUI appears.");
			AutoVoteDelay = ((BaseUnityPlugin)this).Config.Bind<float>("AutoVote", "DelaySeconds", 0.5f, "Delay between SkipUI appearing and the auto-vote being sent. Lets you visually confirm the skip is happening.");
			ForceVoteHotkey = ((BaseUnityPlugin)this).Config.Bind<bool>("Hotkeys", "EnableF9ForceVote", true, "Press F9 to force-send a skip vote on any active SkipUI in the scene (debug/manual fallback).");
			FastCountdownEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("FastCountdown", "Enabled", true, "Shorten the 'wait for all players to gather, then countdown' delay on AllPlayersTriggerZone (the car, the pit-entry, etc.). Host-side only.");
			CountdownSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("FastCountdown", "Seconds", 0.25f, "How long the countdown should be once the player threshold is met. Clamped to a minimum of 0.05s. Only ever shortens.");
			PlayerThresholdEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("PlayerThreshold", "Enabled", true, "Override the 'every player must be in the trigger zone' gate. When disabled, vanilla all-players-required logic is used. Host-side only.");
			PlayerThresholdPercent = ((BaseUnityPlugin)this).Config.Bind<float>("PlayerThreshold", "PercentRequired", 0.5f, "Fraction (0..1) of players who must be in the zone for the countdown to start. 0.5 = half. Always rounds up and requires at least 1.");
			SoloTrigger = ((BaseUnityPlugin)this).Config.Bind<bool>("PlayerThreshold", "SoloTrigger", false, "If true, only one player needs to be in the zone (overrides PercentRequired).");
			SkipPitOnFullLoss = ((BaseUnityPlugin)this).Config.Bind<bool>("SkipPit", "Enabled", true, "Hard-skip the LoseStateScene cutscene (the body-shredder 'pit' on a full loss). Host-side only.");
			ShowToasts = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "ShowToasts", true, "Display in-game toast notifications (top-right of screen) whenever a skip event fires.");
			GiveUpEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("GiveUp", "Enabled", true, "Show a 'Give Up Day' button in the in-game escape menu. Clicking it casts a vote; when the threshold is met across all players, the day ends immediately (calls GameManager.ProgressGame).");
			GiveUpThreshold = ((BaseUnityPlugin)this).Config.Bind<float>("GiveUp", "Threshold", 1f, "Fraction (0..1) of players who must vote 'Give Up' for the day to end. 1.0 = unanimous (default), 0.5 = half, etc. Always rounds up; requires at least 1.");
		}

		private void Update()
		{
			Keyboard current = Keyboard.current;
			if (current == null)
			{
				return;
			}
			if (((ButtonControl)current[(Key)101]).wasPressedThisFrame)
			{
				Log.LogInfo((object)"[NoPitStops] F8 → toast self-test");
				Toast.Show("F8 self-test — mod is alive");
			}
			if (ForceVoteHotkey.Value && ((ButtonControl)current[(Key)102]).wasPressedThisFrame)
			{
				SkipUI val = Object.FindAnyObjectByType<SkipUI>();
				if ((Object)(object)val != (Object)null)
				{
					Log.LogInfo((object)"[NoPitStops] F9 → force-vote on active SkipUI");
					Toast.Show("F9: force-voting skip");
					SkipUI_AutoVote.TryRequestSkip(val);
				}
				else
				{
					Log.LogInfo((object)"[NoPitStops] F9 pressed but no active SkipUI");
					Toast.Show("F9: no SkipUI in scene");
				}
			}
		}
	}
	internal sealed class Toast : MonoBehaviour
	{
		private struct Entry
		{
			public string Text;

			public float ExpiresAt;
		}

		private static Toast _instance;

		private readonly List<Entry> _entries = new List<Entry>();

		private GUIStyle _style;

		public static void Bootstrap()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			if (!((Object)(object)_instance != (Object)null))
			{
				GameObject val = new GameObject("NoPitStops.Toast");
				Object.DontDestroyOnLoad((Object)val);
				_instance = val.AddComponent<Toast>();
			}
		}

		public static void Show(string text, float seconds = 3f)
		{
			if ((Object)(object)_instance == (Object)null)
			{
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogDebug((object)("[Toast pre-init] " + text));
				}
			}
			else
			{
				_instance._entries.Add(new Entry
				{
					Text = text,
					ExpiresAt = Time.unscaledTime + seconds
				});
			}
		}

		private void Update()
		{
			float unscaledTime = Time.unscaledTime;
			for (int num = _entries.Count - 1; num >= 0; num--)
			{
				if (unscaledTime >= _entries[num].ExpiresAt)
				{
					_entries.RemoveAt(num);
				}
			}
		}

		private void OnGUI()
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			if (_entries.Count != 0)
			{
				EnsureStyle();
				float num = 14f;
				for (int i = 0; i < _entries.Count; i++)
				{
					GUI.Box(new Rect((float)Screen.width - 420f - 14f, num, 420f, 32f), _entries[i].Text, _style);
					num += 36f;
				}
			}
		}

		private void EnsureStyle()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//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_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: 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)
			//IL_0068: Expected O, but got Unknown
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Expected O, but got Unknown
			if (_style == null)
			{
				Texture2D val = new Texture2D(1, 1);
				val.SetPixel(0, 0, new Color(0f, 0f, 0f, 0.78f));
				val.Apply();
				GUIStyle val2 = new GUIStyle(GUI.skin.box)
				{
					fontSize = 15,
					alignment = (TextAnchor)3,
					padding = new RectOffset(12, 12, 6, 6)
				};
				val2.normal.textColor = new Color(0.95f, 0.95f, 0.95f, 1f);
				val2.normal.background = val;
				_style = val2;
			}
		}
	}
}
namespace NoPitStops.UI
{
	internal class GiveUpButtonController : MonoBehaviour
	{
		public GameObject ButtonGo;

		public Button Button;

		public TMP_Text[] TmpLabels;

		public Text[] UiLabels;

		private void Update()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Invalid comparison between Unknown and I4
			//IL_020b: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0220: Unknown result type (might be due to invalid IL or missing references)
			//IL_022c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0238: Unknown result type (might be due to invalid IL or missing references)
			//IL_0247: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ButtonGo == (Object)null)
			{
				return;
			}
			GameManager instance = NetworkSingleton<GameManager>.Instance;
			bool flag = Plugin.GiveUpEnabled.Value && (Object)(object)instance != (Object)null && (int)instance.state == 1;
			if (ButtonGo.activeSelf != flag)
			{
				ButtonGo.SetActive(flag);
			}
			if (!flag)
			{
				return;
			}
			string text;
			bool flag2;
			if (GiveUpVote.LocalVoted)
			{
				text = $"VOTED — waiting ({GiveUpVote.CurrentVotes}/{Math.Max(1, GiveUpVote.CurrentNeeded)})";
				flag2 = false;
			}
			else if (GiveUpVote.CurrentVotes > 0)
			{
				text = $"GIVE UP DAY ({GiveUpVote.CurrentVotes}/{GiveUpVote.CurrentNeeded})";
				flag2 = true;
			}
			else
			{
				text = "GIVE UP DAY";
				flag2 = true;
			}
			if ((Object)(object)Button != (Object)null && ((Selectable)Button).interactable != flag2)
			{
				((Selectable)Button).interactable = flag2;
			}
			if (TmpLabels != null)
			{
				for (int i = 0; i < TmpLabels.Length; i++)
				{
					TMP_Text val = TmpLabels[i];
					if (!((Object)(object)val == (Object)null))
					{
						if (val.text != text)
						{
							val.text = text;
						}
						if (val.maxVisibleCharacters < text.Length)
						{
							val.maxVisibleCharacters = int.MaxValue;
						}
						if (((Graphic)val).color.a < 0.99f)
						{
							((Graphic)val).color = new Color(((Graphic)val).color.r, ((Graphic)val).color.g, ((Graphic)val).color.b, 1f);
						}
						CanvasRenderer canvasRenderer = ((Graphic)val).canvasRenderer;
						if ((Object)(object)canvasRenderer != (Object)null && canvasRenderer.GetAlpha() < 0.99f)
						{
							canvasRenderer.SetAlpha(1f);
						}
					}
				}
			}
			if (UiLabels == null)
			{
				return;
			}
			for (int j = 0; j < UiLabels.Length; j++)
			{
				Text val2 = UiLabels[j];
				if (!((Object)(object)val2 == (Object)null))
				{
					if (val2.text != text)
					{
						val2.text = text;
					}
					if (((Graphic)val2).color.a < 0.99f)
					{
						((Graphic)val2).color = new Color(((Graphic)val2).color.r, ((Graphic)val2).color.g, ((Graphic)val2).color.b, 1f);
					}
					CanvasRenderer canvasRenderer2 = ((Graphic)val2).canvasRenderer;
					if ((Object)(object)canvasRenderer2 != (Object)null && canvasRenderer2.GetAlpha() < 0.99f)
					{
						canvasRenderer2.SetAlpha(1f);
					}
				}
			}
		}
	}
}
namespace NoPitStops.Patches
{
	[HarmonyPatch]
	internal static class AllPlayersTriggerZone_FastCountdown
	{
		private static readonly FieldInfo DelayField = AccessTools.Field(typeof(AllPlayersTriggerZone), "delayBeforeEvent");

		[HarmonyPostfix]
		[HarmonyPatch(typeof(AllPlayersTriggerZone), "OnStartClient")]
		private static void Postfix_OnStartClient(AllPlayersTriggerZone __instance)
		{
			if ((Object)(object)__instance == (Object)null || !Plugin.FastCountdownEnabled.Value)
			{
				return;
			}
			if (DelayField == null)
			{
				Plugin.Log.LogError((object)"[NoPitStops] AllPlayersTriggerZone.delayBeforeEvent missing — game updated?");
				return;
			}
			float num = (float)DelayField.GetValue(__instance);
			float num2 = Mathf.Max(0.05f, Plugin.CountdownSeconds.Value);
			if (!(num2 >= num))
			{
				DelayField.SetValue(__instance, num2);
				string text = $"Countdown '{((Object)((Component)__instance).gameObject).name}' {num:0.##}s → {num2:0.##}s";
				Plugin.Log.LogInfo((object)("[NoPitStops] " + text));
				if (Plugin.ShowToasts.Value)
				{
					Toast.Show(text);
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class AllPlayersTriggerZone_PlayerThreshold
	{
		private static readonly FieldInfo IsActiveField = AccessTools.Field(typeof(AllPlayersTriggerZone), "isActive");

		private static readonly FieldInfo HasTriggeredField = AccessTools.Field(typeof(AllPlayersTriggerZone), "_hasTriggered");

		private static readonly FieldInfo CheckColliderField = AccessTools.Field(typeof(AllPlayersTriggerZone), "checkCollider");

		private static readonly FieldInfo IntervalField = AccessTools.Field(typeof(AllPlayersTriggerZone), "colliderCheckInterval");

		private static readonly FieldInfo LastCheckTimeField = AccessTools.Field(typeof(AllPlayersTriggerZone), "_lastCheckTime");

		private static readonly FieldInfo CountdownRoutineField = AccessTools.Field(typeof(AllPlayersTriggerZone), "_countdownRoutine");

		private static readonly MethodInfo RpcUpdateCountdownText = AccessTools.Method(typeof(AllPlayersTriggerZone), "RpcUpdateCountdownText", (Type[])null, (Type[])null);

		private static readonly MethodInfo CountdownRoutineMethod = AccessTools.Method(typeof(AllPlayersTriggerZone), "CountdownRoutine", (Type[])null, (Type[])null);

		[HarmonyPrefix]
		[HarmonyPatch(typeof(AllPlayersTriggerZone), "CheckPlayers")]
		private static bool Prefix(AllPlayersTriggerZone __instance)
		{
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Expected O, but got Unknown
			if (!Plugin.PlayerThresholdEnabled.Value)
			{
				return true;
			}
			if (IsActiveField == null || HasTriggeredField == null || CheckColliderField == null || IntervalField == null || LastCheckTimeField == null || CountdownRoutineField == null || RpcUpdateCountdownText == null || CountdownRoutineMethod == null)
			{
				Plugin.Log.LogError((object)"[NoPitStops] AllPlayersTriggerZone reflection setup failed; falling back to vanilla logic");
				return true;
			}
			bool num = (bool)IsActiveField.GetValue(__instance);
			bool flag = (bool)HasTriggeredField.GetValue(__instance);
			LocalManager instance = MonoSingleton<LocalManager>.Instance;
			if (!num || flag || (Object)(object)instance == (Object)null || instance.players == null || instance.players.Count <= 0)
			{
				return false;
			}
			float num2 = (float)LastCheckTimeField.GetValue(__instance);
			float num3 = (float)IntervalField.GetValue(__instance);
			if (Time.time - num2 < num3)
			{
				return false;
			}
			LastCheckTimeField.SetValue(__instance, Time.time);
			Bounds bounds = ((Collider)CheckColliderField.GetValue(__instance)).bounds;
			int count = instance.players.Count;
			int num4 = 0;
			foreach (PlayerReferences player in instance.players)
			{
				if (((Bounds)(ref bounds)).Contains(player.transform.position))
				{
					num4++;
				}
			}
			int num5 = (Plugin.SoloTrigger.Value ? 1 : Mathf.Max(1, Mathf.CeilToInt(Plugin.PlayerThresholdPercent.Value * (float)count)));
			Coroutine val = (Coroutine)CountdownRoutineField.GetValue(__instance);
			if (num4 < num5)
			{
				if (val != null)
				{
					((MonoBehaviour)__instance).StopCoroutine(val);
					CountdownRoutineField.SetValue(__instance, null);
					RpcUpdateCountdownText.Invoke(__instance, new object[1] { false });
				}
				return false;
			}
			if (val == null)
			{
				IEnumerator enumerator2 = (IEnumerator)CountdownRoutineMethod.Invoke(__instance, null);
				Coroutine value = ((MonoBehaviour)__instance).StartCoroutine(enumerator2);
				CountdownRoutineField.SetValue(__instance, value);
				Plugin.Log.LogDebug((object)$"[NoPitStops] Threshold met on '{((Object)((Component)__instance).gameObject).name}' ({num4}/{count} ≥ {num5}); countdown started");
			}
			return false;
		}
	}
	[HarmonyPatch]
	internal static class CustomNetworkManager_HandlerInit
	{
		[HarmonyPostfix]
		[HarmonyPatch(typeof(CustomNetworkManager), "OnStartServer")]
		private static void Postfix_OnStartServer()
		{
			GiveUpVote.RegisterServerHandlers();
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(CustomNetworkManager), "OnClientConnect")]
		private static void Postfix_OnClientConnect()
		{
			GiveUpVote.RegisterClientHandlers();
		}
	}
	[HarmonyPatch]
	internal static class EscapeMenu_AddGiveUpButton
	{
		private struct RectTransformSnapshot
		{
			public Vector2 AnchorMin;

			public Vector2 AnchorMax;

			public Vector2 Pivot;

			public Vector2 SizeDelta;

			public Vector2 AnchoredPosition;
		}

		private struct LabelSnapshot
		{
			public RectTransformSnapshot Rect;

			public int SiblingIndex;

			public LayoutElementSnapshot LayoutElement;
		}

		private class LayoutElementSnapshot
		{
			public float MinWidth;

			public float MinHeight;

			public float PreferredWidth;

			public float PreferredHeight;

			public float FlexibleWidth;

			public float FlexibleHeight;
		}

		[CompilerGenerated]
		private static class <>O
		{
			public static UnityAction <0>__SendLocalVote;
		}

		private const string GiveUpButtonName = "NoPitStops_GiveUpButton";

		private const string LabelName = "NoPitStops_Label";

		private const string ButtonText = "GIVE UP DAY";

		[HarmonyPostfix]
		[HarmonyPatch(typeof(EscapeMenu), "Awake")]
		private static void Postfix(EscapeMenu __instance)
		{
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Expected O, but got Unknown
			if (!Plugin.GiveUpEnabled.Value)
			{
				return;
			}
			try
			{
				if ((Object)(object)((Component)__instance).GetComponent<GiveUpButtonController>() != (Object)null)
				{
					return;
				}
				Button[] componentsInChildren = ((Component)__instance).GetComponentsInChildren<Button>(true);
				if (componentsInChildren.Length == 0)
				{
					Plugin.Log.LogWarning((object)"[NoPitStops] EscapeMenu has no child Button — Give Up button skipped");
					return;
				}
				Button val = null;
				Button[] array = componentsInChildren;
				foreach (Button val2 in array)
				{
					if ((Object)(object)((Component)val2).GetComponentInChildren<TMP_Text>(true) != (Object)null)
					{
						val = val2;
						break;
					}
				}
				if ((Object)(object)val == (Object)null)
				{
					Plugin.Log.LogWarning((object)("[NoPitStops] No TMP-bearing button in EscapeMenu; falling back to '" + ((Object)componentsInChildren[0]).name + "' (label may be unstyled)"));
					val = componentsInChildren[0];
				}
				Transform parent = ((Component)val).transform.parent;
				GameObject val3 = Object.Instantiate<GameObject>(((Component)val).gameObject, parent);
				((Object)val3).name = "NoPitStops_GiveUpButton";
				val3.transform.SetSiblingIndex(parent.childCount - 1);
				Button component = val3.GetComponent<Button>();
				ClearPersistentListeners((UnityEvent)(object)component.onClick);
				((UnityEventBase)component.onClick).RemoveAllListeners();
				ButtonClickedEvent onClick = component.onClick;
				object obj = <>O.<0>__SendLocalVote;
				if (obj == null)
				{
					UnityAction val4 = GiveUpVote.SendLocalVote;
					<>O.<0>__SendLocalVote = val4;
					obj = (object)val4;
				}
				((UnityEvent)onClick).AddListener((UnityAction)obj);
				TMP_Text val5 = ReplaceLabelChild(val3);
				GiveUpButtonController giveUpButtonController = ((Component)__instance).gameObject.AddComponent<GiveUpButtonController>();
				giveUpButtonController.ButtonGo = val3;
				giveUpButtonController.Button = component;
				giveUpButtonController.TmpLabels = (TMP_Text[])((!((Object)(object)val5 != (Object)null)) ? ((Array)Array.Empty<TMP_Text>()) : ((Array)new TMP_Text[1] { val5 }));
				giveUpButtonController.UiLabels = Array.Empty<Text>();
				Plugin.Log.LogInfo((object)("[NoPitStops] Give Up button injected (template='" + ((Object)val).name + "', fresh-label-replaced)"));
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"[NoPitStops] Failed to inject Give Up button: {arg}");
			}
		}

		private static TMP_Text ReplaceLabelChild(GameObject buttonGo)
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_020a: Unknown result type (might be due to invalid IL or missing references)
			TMP_Text componentInChildren = buttonGo.GetComponentInChildren<TMP_Text>(true);
			if ((Object)(object)componentInChildren == (Object)null)
			{
				Plugin.Log.LogWarning((object)"[NoPitStops] Cloned button has no TMP child — building label from scratch with no font carry-over");
				return CreateFreshLabel(buttonGo.transform, null, 24f, (TextAlignmentOptions)514, default(LabelSnapshot));
			}
			GameObject gameObject = ((Component)componentInChildren).gameObject;
			RectTransform component = gameObject.GetComponent<RectTransform>();
			TMP_FontAsset font = componentInChildren.font;
			float fontSize = componentInChildren.fontSize;
			TextAlignmentOptions alignment = componentInChildren.alignment;
			Transform parent = gameObject.transform.parent;
			int siblingIndex = gameObject.transform.GetSiblingIndex();
			RectTransformSnapshot rect = new RectTransformSnapshot
			{
				AnchorMin = (Vector2)(((Object)(object)component != (Object)null) ? component.anchorMin : new Vector2(0.5f, 0.5f)),
				AnchorMax = (Vector2)(((Object)(object)component != (Object)null) ? component.anchorMax : new Vector2(0.5f, 0.5f)),
				Pivot = (Vector2)(((Object)(object)component != (Object)null) ? component.pivot : new Vector2(0.5f, 0.5f)),
				SizeDelta = (Vector2)(((Object)(object)component != (Object)null) ? component.sizeDelta : new Vector2(200f, 50f)),
				AnchoredPosition = (((Object)(object)component != (Object)null) ? component.anchoredPosition : Vector2.zero)
			};
			LayoutElementSnapshot layoutElement = null;
			LayoutElement component2 = gameObject.GetComponent<LayoutElement>();
			if ((Object)(object)component2 != (Object)null)
			{
				layoutElement = new LayoutElementSnapshot
				{
					MinWidth = component2.minWidth,
					MinHeight = component2.minHeight,
					PreferredWidth = component2.preferredWidth,
					PreferredHeight = component2.preferredHeight,
					FlexibleWidth = component2.flexibleWidth,
					FlexibleHeight = component2.flexibleHeight
				};
			}
			Plugin.Log.LogInfo((object)$"[NoPitStops]   Replacing label child '{((Object)gameObject).name}' (font={((font != null) ? ((Object)font).name : null)}, size={fontSize})");
			Object.DestroyImmediate((Object)(object)gameObject);
			LabelSnapshot layoutSnapshot = new LabelSnapshot
			{
				Rect = rect,
				SiblingIndex = siblingIndex,
				LayoutElement = layoutElement
			};
			return CreateFreshLabel(parent, font, fontSize, alignment, layoutSnapshot);
		}

		private static TMP_Text CreateFreshLabel(Transform parent, TMP_FontAsset font, float fontSize, TextAlignmentOptions alignment, LabelSnapshot layoutSnapshot)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("NoPitStops_Label", new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(parent, false);
			if (layoutSnapshot.SiblingIndex >= 0)
			{
				val.transform.SetSiblingIndex(layoutSnapshot.SiblingIndex);
			}
			RectTransform component = val.GetComponent<RectTransform>();
			RectTransformSnapshot rect = layoutSnapshot.Rect;
			if (rect.SizeDelta != Vector2.zero || rect.AnchorMin != Vector2.zero || rect.AnchorMax != Vector2.zero)
			{
				component.anchorMin = rect.AnchorMin;
				component.anchorMax = rect.AnchorMax;
				component.pivot = rect.Pivot;
				component.sizeDelta = rect.SizeDelta;
				component.anchoredPosition = rect.AnchoredPosition;
			}
			else
			{
				component.anchorMin = Vector2.zero;
				component.anchorMax = Vector2.one;
				component.offsetMin = Vector2.zero;
				component.offsetMax = Vector2.zero;
			}
			TextMeshProUGUI val2 = val.AddComponent<TextMeshProUGUI>();
			if ((Object)(object)font != (Object)null)
			{
				((TMP_Text)val2).font = font;
			}
			((TMP_Text)val2).text = "GIVE UP DAY";
			((TMP_Text)val2).fontSize = ((fontSize > 0f) ? fontSize : 24f);
			((TMP_Text)val2).alignment = alignment;
			((Graphic)val2).color = Color.white;
			((TMP_Text)val2).textWrappingMode = (TextWrappingModes)0;
			((TMP_Text)val2).overflowMode = (TextOverflowModes)0;
			((Graphic)val2).raycastTarget = false;
			if (layoutSnapshot.LayoutElement != null)
			{
				LayoutElement obj = val.AddComponent<LayoutElement>();
				obj.minWidth = layoutSnapshot.LayoutElement.MinWidth;
				obj.minHeight = layoutSnapshot.LayoutElement.MinHeight;
				obj.preferredWidth = layoutSnapshot.LayoutElement.PreferredWidth;
				obj.preferredHeight = layoutSnapshot.LayoutElement.PreferredHeight;
				obj.flexibleWidth = layoutSnapshot.LayoutElement.FlexibleWidth;
				obj.flexibleHeight = layoutSnapshot.LayoutElement.FlexibleHeight;
			}
			try
			{
				((TMP_Text)val2).ForceMeshUpdate(true, true);
			}
			catch
			{
			}
			return (TMP_Text)(object)val2;
		}

		private static void ClearPersistentListeners(UnityEvent ev)
		{
			if (ev == null)
			{
				return;
			}
			try
			{
				FieldInfo field = typeof(UnityEventBase).GetField("m_PersistentCalls", BindingFlags.Instance | BindingFlags.NonPublic);
				if (!(field == null))
				{
					object value = field.GetValue(ev);
					value?.GetType().GetMethod("Clear", BindingFlags.Instance | BindingFlags.Public)?.Invoke(value, null);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[NoPitStops] Couldn't clear persistent listeners: " + ex.Message));
			}
		}
	}
	[HarmonyPatch]
	internal static class EscapeMenu_TrackOpen
	{
		private static readonly FieldInfo IsOpenField = AccessTools.Field(typeof(EscapeMenu), "_isOpen");

		public static bool IsOpen { get; private set; }

		public static EscapeMenu Current { get; private set; }

		[HarmonyPostfix]
		[HarmonyPatch(typeof(EscapeMenu), "ToggleEscapeMenu")]
		private static void Postfix_Toggle(EscapeMenu __instance)
		{
			if (!(IsOpenField == null))
			{
				IsOpen = (bool)IsOpenField.GetValue(__instance);
				Current = __instance;
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(EscapeMenu), "OnDisable")]
		private static void Postfix_OnDisable(EscapeMenu __instance)
		{
			if ((Object)(object)Current == (Object)(object)__instance)
			{
				IsOpen = false;
				Current = null;
			}
		}
	}
	[HarmonyPatch]
	internal static class GameManager_SkipLoseStateScene
	{
		private const string LoseScene = "LoseStateScene";

		private const string HomeScene = "HomeScene";

		[HarmonyPrefix]
		[HarmonyPatch(typeof(NetworkManager), "ServerChangeScene", new Type[] { typeof(string) })]
		private static void Prefix(ref string newSceneName)
		{
			if (!Plugin.SkipPitOnFullLoss.Value || newSceneName != "LoseStateScene" || !NetworkServer.active)
			{
				return;
			}
			Plugin.Log.LogInfo((object)"[NoPitStops] Full-loss detected; redirecting 'LoseStateScene' → 'HomeScene' and resetting save");
			if (Plugin.ShowToasts.Value)
			{
				Toast.Show("PIT SKIP — redirecting LoseStateScene → HomeScene");
			}
			SaveManager instance = NetworkSingleton<SaveManager>.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				try
				{
					instance.ResetCurrentSaveToDefaults();
					instance.LoadGame();
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("[NoPitStops] Save reset during pit-skip failed: " + ex.Message + ". Letting scene change proceed anyway."));
				}
			}
			else
			{
				Plugin.Log.LogWarning((object)"[NoPitStops] SaveManager singleton not available; skipping save reset (game may have stale data on next round)");
			}
			newSceneName = "HomeScene";
		}
	}
	[HarmonyPatch]
	internal static class SkipUI_AutoVote
	{
		private static readonly MethodInfo RequestSkipMethod = AccessTools.Method(typeof(SkipUI), "RequestSkip", (Type[])null, (Type[])null);

		[HarmonyPostfix]
		[HarmonyPatch(typeof(SkipUI), "SetSkippableForLocal")]
		private static void OnLocalSkipEnabled(SkipUI __instance)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.AutoVoteEnabled.Value)
			{
				Plugin.Log.LogDebug((object)"[NoPitStops] AutoVote disabled in config; ignoring SkipUI ready");
				return;
			}
			Scene scene = ((Component)__instance).gameObject.scene;
			string name = ((Scene)(ref scene)).name;
			Plugin.Log.LogInfo((object)$"[NoPitStops] SkipUI local-ready in scene '{name}'; auto-vote in {Plugin.AutoVoteDelay.Value:0.##}s");
			((MonoBehaviour)__instance).StartCoroutine(VoteAfterDelay(__instance));
		}

		private static IEnumerator VoteAfterDelay(SkipUI instance)
		{
			yield return (object)new WaitForSeconds(Plugin.AutoVoteDelay.Value);
			TryRequestSkip(instance);
		}

		internal static void TryRequestSkip(SkipUI instance)
		{
			if ((Object)(object)instance == (Object)null)
			{
				Plugin.Log.LogDebug((object)"[NoPitStops] SkipUI was destroyed before vote could send");
				return;
			}
			if (!NetworkClient.active)
			{
				Plugin.Log.LogDebug((object)"[NoPitStops] NetworkClient inactive — not sending vote");
				return;
			}
			if (RequestSkipMethod == null)
			{
				Plugin.Log.LogError((object)"[NoPitStops] SkipUI.RequestSkip not found via reflection — game updated?");
				return;
			}
			try
			{
				RequestSkipMethod.Invoke(instance, new object[1]);
				Plugin.Log.LogInfo((object)"[NoPitStops] Skip vote sent to server");
				if (Plugin.ShowToasts.Value)
				{
					Toast.Show("Auto-voted skip");
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[NoPitStops] RequestSkip invoke failed: " + ex.Message));
				if (Plugin.ShowToasts.Value)
				{
					Toast.Show("Auto-vote FAILED: " + ex.Message);
				}
			}
		}
	}
}
namespace NoPitStops.GiveUp
{
	[StructLayout(LayoutKind.Sequential, Size = 1)]
	public struct GiveUpRequestMsg : NetworkMessage
	{
	}
	public struct GiveUpUpdateMsg : NetworkMessage
	{
		public int Votes;

		public int Total;

		public int Needed;

		public bool Passed;
	}
	internal static class GiveUpVote
	{
		private static readonly HashSet<int> ServerVotedConnIds = new HashSet<int>();

		private static GameState _lastObservedState = (GameState)0;

		public static int CurrentVotes;

		public static int CurrentTotal;

		public static int CurrentNeeded;

		public static bool VotePassed;

		public static bool LocalVoted;

		public static void InstallSerializers()
		{
			Writer<GiveUpRequestMsg>.write = delegate
			{
			};
			Reader<GiveUpRequestMsg>.read = (NetworkReader _) => default(GiveUpRequestMsg);
			Writer<GiveUpUpdateMsg>.write = delegate(NetworkWriter writer, GiveUpUpdateMsg msg)
			{
				NetworkWriterExtensions.WriteInt(writer, msg.Votes);
				NetworkWriterExtensions.WriteInt(writer, msg.Total);
				NetworkWriterExtensions.WriteInt(writer, msg.Needed);
				NetworkWriterExtensions.WriteBool(writer, msg.Passed);
			};
			Reader<GiveUpUpdateMsg>.read = delegate(NetworkReader reader)
			{
				GiveUpUpdateMsg result = default(GiveUpUpdateMsg);
				result.Votes = NetworkReaderExtensions.ReadInt(reader);
				result.Total = NetworkReaderExtensions.ReadInt(reader);
				result.Needed = NetworkReaderExtensions.ReadInt(reader);
				result.Passed = NetworkReaderExtensions.ReadBool(reader);
				return result;
			};
			Plugin.Log.LogInfo((object)"[NoPitStops] GiveUp serializers installed");
		}

		public static void RegisterServerHandlers()
		{
			try
			{
				NetworkServer.ReplaceHandler<GiveUpRequestMsg>((Action<NetworkConnectionToClient, GiveUpRequestMsg>)OnServerVote, true);
				Plugin.Log.LogInfo((object)"[NoPitStops] GiveUp server handler registered");
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"[NoPitStops] GiveUp server handler registration failed: {arg}");
			}
		}

		public static void RegisterClientHandlers()
		{
			try
			{
				NetworkClient.ReplaceHandler<GiveUpUpdateMsg>((Action<GiveUpUpdateMsg>)OnClientUpdate, true);
				Plugin.Log.LogInfo((object)"[NoPitStops] GiveUp client handler registered");
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"[NoPitStops] GiveUp client handler registration failed: {arg}");
			}
		}

		private static void OnServerVote(NetworkConnectionToClient conn, GiveUpRequestMsg _)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Invalid comparison between Unknown and I4
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			GameManager instance = NetworkSingleton<GameManager>.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				Plugin.Log.LogWarning((object)"[NoPitStops] GiveUp vote dropped — GameManager singleton not available");
				return;
			}
			if ((int)instance.state != 1)
			{
				Plugin.Log.LogInfo((object)$"[NoPitStops] GiveUp vote dropped — state is {instance.state}, not Game");
				return;
			}
			if (_lastObservedState != instance.state)
			{
				ServerVotedConnIds.Clear();
				_lastObservedState = instance.state;
			}
			ServerVotedConnIds.Add(conn.connectionId);
			int valueOrDefault = (MonoSingleton<LocalManager>.Instance?.players?.Count).GetValueOrDefault(1);
			int num = Math.Max(1, Mathf.CeilToInt(Plugin.GiveUpThreshold.Value * (float)valueOrDefault));
			bool flag = ServerVotedConnIds.Count >= num;
			GiveUpUpdateMsg giveUpUpdateMsg = default(GiveUpUpdateMsg);
			giveUpUpdateMsg.Votes = ServerVotedConnIds.Count;
			giveUpUpdateMsg.Total = valueOrDefault;
			giveUpUpdateMsg.Needed = num;
			giveUpUpdateMsg.Passed = flag;
			GiveUpUpdateMsg giveUpUpdateMsg2 = giveUpUpdateMsg;
			NetworkServer.SendToAll<GiveUpUpdateMsg>(giveUpUpdateMsg2, 0, false);
			Plugin.Log.LogInfo((object)$"[NoPitStops] GiveUp vote {giveUpUpdateMsg2.Votes}/{giveUpUpdateMsg2.Needed} (total {giveUpUpdateMsg2.Total}) — passed:{flag}");
			if (flag)
			{
				ServerVotedConnIds.Clear();
				instance.ProgressGame();
				Plugin.Log.LogInfo((object)"[NoPitStops] GiveUp PASSED — calling GameManager.ProgressGame()");
			}
		}

		private static void OnClientUpdate(GiveUpUpdateMsg msg)
		{
			CurrentVotes = msg.Votes;
			CurrentTotal = msg.Total;
			CurrentNeeded = msg.Needed;
			VotePassed = msg.Passed;
			if (Plugin.ShowToasts.Value)
			{
				Toast.Show(msg.Passed ? $"GIVE UP — vote passed ({msg.Votes}/{msg.Needed})" : $"Give Up vote: {msg.Votes}/{msg.Needed} (of {msg.Total})");
			}
			if (msg.Passed)
			{
				LocalVoted = false;
				CurrentVotes = 0;
				CurrentNeeded = 0;
			}
		}

		public static void SendLocalVote()
		{
			if (!NetworkClient.active)
			{
				Toast.Show("Give Up: not connected");
				return;
			}
			if (LocalVoted)
			{
				Toast.Show("Give Up: already voted");
				return;
			}
			try
			{
				NetworkClient.Send<GiveUpRequestMsg>(default(GiveUpRequestMsg), 0);
				LocalVoted = true;
				Toast.Show("Give Up vote sent");
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[NoPitStops] GiveUp send failed: " + ex.Message));
				Toast.Show("Give Up failed: " + ex.Message);
			}
		}
	}
}