Decompiled source of GTFOPause v0.1.1

GTFOPause.dll

Decompiled 8 hours ago
using System;
using System.Collections.Generic;
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.Configuration;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using CellMenu;
using GTFO.API;
using GTFOPause.Net;
using HarmonyLib;
using Il2CppSystem.Collections.Generic;
using Microsoft.CodeAnalysis;
using SNetwork;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("GTFOPause")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.1.0")]
[assembly: AssemblyInformationalVersion("0.1.1+8108b790a1c68400d1ad77fe8493f97988df8ec0")]
[assembly: AssemblyProduct("GTFOPause")]
[assembly: AssemblyTitle("GTFOPause")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.1.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;
		}
	}
}
namespace GTFOPause
{
	internal static class Logger
	{
		private static ManualLogSource s_log;

		public static void Setup(ManualLogSource log)
		{
			s_log = log;
		}

		public static void Info(object data)
		{
			s_log.LogInfo(data);
		}

		public static void Warn(object data)
		{
			s_log.LogWarning(data);
		}

		public static void Error(object data)
		{
			s_log.LogError(data);
		}

		public static void Debug(object data)
		{
			s_log.LogDebug(data);
		}
	}
	internal static class Notify
	{
		private const int BannerPriority = 100;

		private static bool s_bannerShown;

		public static string PausedBy { get; set; }

		public static void Event(string message)
		{
			Logger.Info(message);
			if (PauseConfig.ShowNotifications.Value)
			{
				PlayerGuiLayer playerLayer = GuiManager.PlayerLayer;
				if (playerLayer != null && !((Object)(object)playerLayer.m_gameEventLog == (Object)null))
				{
					playerLayer.m_gameEventLog.AddLogItem(message, (eGameEventChatLogType)2);
				}
			}
		}

		public static void UpdateBanner()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			bool flag = PauseState.IsPaused && PauseConfig.ShowNotifications.Value && !PauseRules.IsMenu(FocusStateManager.CurrentState);
			if (!flag && !s_bannerShown)
			{
				return;
			}
			InteractionGuiLayer interactionLayer = GuiManager.InteractionLayer;
			if (interactionLayer != null)
			{
				if (flag)
				{
					interactionLayer.SetMessage(BannerText(), (ePUIMessageStyle)1, 100);
					interactionLayer.MessageVisible = true;
					HideIntroBoxes(interactionLayer.m_message);
				}
				else
				{
					interactionLayer.MessageVisible = false;
				}
				s_bannerShown = flag;
			}
		}

		private static void HideIntroBoxes(PUI_InteractionPrompt prompt)
		{
			if (!((Object)(object)prompt == (Object)null))
			{
				if ((Object)(object)prompt.m_whiteBox != (Object)null && prompt.m_whiteBox.activeSelf)
				{
					prompt.m_whiteBox.SetActive(false);
				}
				if ((Object)(object)prompt.m_whiteBoxWide != (Object)null && prompt.m_whiteBoxWide.activeSelf)
				{
					prompt.m_whiteBoxWide.SetActive(false);
				}
			}
		}

		private static string BannerText()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			string text = ((object)PauseConfig.PauseHotkey.Value/*cast due to .constrained prefix*/).ToString();
			if (PausedBy != null)
			{
				return $"PAUSED by {PausedBy} - press {text} to resume";
			}
			return "PAUSED - press " + text + " to resume";
		}
	}
	internal static class PauseConfig
	{
		public static ConfigEntry<bool> PauseOnMenu { get; private set; }

		public static ConfigEntry<KeyCode> PauseHotkey { get; private set; }

		public static ConfigEntry<bool> ShowNotifications { get; private set; }

		public static ConfigEntry<bool> AllowMultiplayerPause { get; private set; }

		public static void Bind(ConfigFile config)
		{
			PauseOnMenu = config.Bind<bool>("General", "PauseOnMenu", false, "Solo only: also pause while the Esc menu is open, like Risk of Rain 2 singleplayer. Off by default: the hotkey is the pause and Esc is just the menu.");
			PauseHotkey = config.Bind<KeyCode>("General", "PauseHotkey", (KeyCode)289, "Toggles the pause. Solo: pauses without opening the menu. Multiplayer: asks the host to pause everyone.");
			ShowNotifications = config.Bind<bool>("General", "ShowNotifications", true, "Show the PAUSED banner and the paused/resumed lines in the game event log.");
			AllowMultiplayerPause = config.Bind<bool>("Multiplayer", "AllowMultiplayerPause", true, "Host only: let any player with GTFOPause pause the whole squad. Every human player needs the mod. Turn off to keep pausing solo-only.");
		}
	}
	internal class PauseController : MonoBehaviour
	{
		public PauseController(IntPtr ptr)
			: base(ptr)
		{
		}

		private void Update()
		{
			try
			{
				PauseLogic.Tick();
			}
			catch (Exception data)
			{
				Logger.Error(data);
			}
		}

		private void LateUpdate()
		{
			try
			{
				Notify.UpdateBanner();
			}
			catch (Exception data)
			{
				Logger.Error(data);
			}
		}
	}
	internal static class PauseLogic
	{
		private static int s_lastHumanCount = -1;

		private static ulong s_lastMaster;

		public static void OnFocusStateChanged(eFocusState oldState, eFocusState newState)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Invalid comparison between Unknown and I4
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			if (!PauseRules.InLevel)
			{
				return;
			}
			if ((int)newState == 17)
			{
				ForceResume("popup opened");
			}
			else if (PauseRules.IsMenu(newState))
			{
				if (PauseConfig.PauseOnMenu.Value && PauseRules.IsSolo)
				{
					PauseState.Add(PauseReason.Menu);
				}
			}
			else if (PauseRules.IsMenu(oldState))
			{
				PauseState.Remove(PauseReason.Menu);
			}
		}

		public static void OnGameStateChanged(eGameStateName oldState, eGameStateName newState)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			ForceResume($"game state {oldState} -> {newState}");
		}

		public static void OnCheckpointReloaded()
		{
			ForceResume("checkpoint reloaded");
		}

		public static void Tick()
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			WatchSession();
			PauseNet.Tick();
			if (Input.GetKeyDown(PauseConfig.PauseHotkey.Value) && PauseRules.HotkeyAllowedIn(FocusStateManager.CurrentState))
			{
				OnHotkey();
			}
		}

		public static void ForceResume(string why)
		{
			if (PauseState.Reasons != PauseReason.None)
			{
				Logger.Info("Force resume: " + why);
				PauseNet.OnLocalForceResume();
				PauseState.Clear();
			}
		}

		private static void OnHotkey()
		{
			if (!PauseRules.InLevel)
			{
				return;
			}
			if (PauseRules.IsSolo)
			{
				if (PauseState.IsPaused)
				{
					PauseState.Clear();
				}
				else
				{
					PauseState.Add(PauseReason.Hotkey);
				}
			}
			else
			{
				PauseNet.OnHotkey();
			}
		}

		private static void WatchSession()
		{
			int num = PauseRules.HumanCount();
			ulong num2 = ((SNet.HasMaster && (Object)(object)SNet.Master != (Object)null) ? SNet.Master.Lookup : 0);
			bool flag = num != s_lastHumanCount || num2 != s_lastMaster;
			s_lastHumanCount = num;
			s_lastMaster = num2;
			if (PauseState.IsPaused)
			{
				if (!PauseRules.InLevel)
				{
					ForceResume("left the level");
				}
				else if (flag)
				{
					ForceResume("players or host changed");
				}
			}
		}
	}
	internal static class PauseRules
	{
		public static bool InLevel => (int)GameStateManager.CurrentStateName == 10;

		public static bool IsSolo
		{
			get
			{
				if (SNet.IsMaster)
				{
					return HumanCount() <= 1;
				}
				return false;
			}
		}

		public static int HumanCount()
		{
			List<SNet_Player> lobbyPlayers = SNet.LobbyPlayers;
			if (lobbyPlayers == null)
			{
				return 0;
			}
			int num = 0;
			for (int i = 0; i < lobbyPlayers.Count; i++)
			{
				SNet_Player val = lobbyPlayers[i];
				if ((Object)(object)val != (Object)null && !val.IsBot)
				{
					num++;
				}
			}
			return num;
		}

		public static bool IsMenu(eFocusState state)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			return (int)state == 2;
		}

		public static bool HotkeyAllowedIn(eFocusState state)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Invalid comparison between Unknown and I4
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Invalid comparison between Unknown and I4
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Invalid comparison between Unknown and I4
			if ((int)state <= 5)
			{
				if ((int)state == 3 || (int)state == 5)
				{
					goto IL_0017;
				}
			}
			else if ((int)state == 8 || (int)state == 14)
			{
				goto IL_0017;
			}
			bool flag = false;
			goto IL_001d;
			IL_0017:
			flag = true;
			goto IL_001d;
			IL_001d:
			return !flag;
		}

		public static bool BlocksInputIn(eFocusState state)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Invalid comparison between Unknown and I4
			if ((int)state == 4 || (int)state == 6 || (int)state == 15)
			{
				return true;
			}
			return false;
		}

		public static bool AllowedWhilePaused(InputAction action)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Invalid comparison between Unknown and I4
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Invalid comparison between Unknown and I4
			if ((int)action == 36 || action - 55 <= 2)
			{
				return true;
			}
			return false;
		}
	}
	[Flags]
	internal enum PauseReason
	{
		None = 0,
		Menu = 1,
		Hotkey = 2,
		Remote = 4
	}
	internal static class PauseState
	{
		private static PauseReason s_reasons;

		private static float s_savedTimeScale = 1f;

		public static bool IsPaused { get; private set; }

		public static PauseReason Reasons => s_reasons;

		public static float FrozenExpeditionTime { get; private set; }

		public static void Add(PauseReason reason)
		{
			Set(s_reasons | reason);
		}

		public static void Remove(PauseReason reason)
		{
			Set(s_reasons & ~reason);
		}

		public static void Clear()
		{
			Set(PauseReason.None);
		}

		private static void Set(PauseReason reasons)
		{
			s_reasons = reasons;
			bool flag = reasons != PauseReason.None;
			if (flag != IsPaused)
			{
				IsPaused = flag;
				if (flag)
				{
					s_savedTimeScale = ((Time.timeScale > 0f) ? Time.timeScale : 1f);
					Time.timeScale = 0f;
					FrozenExpeditionTime = Clock.ExpeditionProgressionTime;
					SetAudioSuspended(suspend: true);
					Logger.Info($"Paused ({reasons})");
				}
				else
				{
					Time.timeScale = s_savedTimeScale;
					SetAudioSuspended(suspend: false);
					Logger.Info("Resumed");
				}
			}
		}

		private static void SetAudioSuspended(bool suspend)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Invalid comparison between Unknown and I4
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				AKRESULT val = (suspend ? AkSoundEngine.Suspend(false) : AkSoundEngine.WakeupFromSuspend());
				if ((int)val != 1)
				{
					Logger.Warn($"Wwise {(suspend ? "Suspend" : "WakeupFromSuspend")} returned {val}");
				}
			}
			catch (Exception value)
			{
				Logger.Error($"Wwise {(suspend ? "Suspend" : "WakeupFromSuspend")} failed: {value}");
			}
		}
	}
	[BepInPlugin("mrwave.GTFOPause", "GTFOPause", "0.1.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BasePlugin
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static FocusStateChangedDelegate <0>__OnFocusStateChanged;

			public static GameStateChangedDelegate <1>__OnGameStateChanged;

			public static Action <2>__OnCheckpointReloaded;
		}

		public const string Guid = "mrwave.GTFOPause";

		public const string Name = "GTFOPause";

		public const string Version = "0.1.1";

		public override void Load()
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected O, but got Unknown
			//IL_0061: 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)
			//IL_006c: Expected O, but got Unknown
			Logger.Setup(((BasePlugin)this).Log);
			PauseConfig.Bind(((BasePlugin)this).Config);
			new Harmony("mrwave.GTFOPause").PatchAll();
			((BasePlugin)this).AddComponent<PauseController>();
			PauseNet.Register();
			object obj = <>O.<0>__OnFocusStateChanged;
			if (obj == null)
			{
				FocusStateChangedDelegate val = PauseLogic.OnFocusStateChanged;
				<>O.<0>__OnFocusStateChanged = val;
				obj = (object)val;
			}
			EventAPI.OnFocusStateChanged += (FocusStateChangedDelegate)obj;
			object obj2 = <>O.<1>__OnGameStateChanged;
			if (obj2 == null)
			{
				GameStateChangedDelegate val2 = PauseLogic.OnGameStateChanged;
				<>O.<1>__OnGameStateChanged = val2;
				obj2 = (object)val2;
			}
			EventAPI.OnGameStateChanged += (GameStateChangedDelegate)obj2;
			EventAPI.OnCheckpointReloaded += PauseLogic.OnCheckpointReloaded;
			Logger.Info("GTFOPause 0.1.1 loaded");
		}
	}
}
namespace GTFOPause.Patches
{
	[HarmonyPatch(typeof(InputMapper))]
	internal static class InputPatches
	{
		private static bool ShouldBlock(InputAction action)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			if (PauseState.IsPaused && !PauseRules.AllowedWhilePaused(action))
			{
				return PauseRules.BlocksInputIn(FocusStateManager.CurrentState);
			}
			return false;
		}

		[HarmonyPatch("DoGetButton")]
		[HarmonyPrefix]
		private static bool DoGetButton(InputAction action, ref bool __result)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			return Pass(action, ref __result);
		}

		[HarmonyPatch("DoGetButtonDown")]
		[HarmonyPrefix]
		private static bool DoGetButtonDown(InputAction action, ref bool __result)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			return Pass(action, ref __result);
		}

		[HarmonyPatch("DoGetButtonUp")]
		[HarmonyPrefix]
		private static bool DoGetButtonUp(InputAction action, ref bool __result)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			return Pass(action, ref __result);
		}

		[HarmonyPatch("DoGetAxis")]
		[HarmonyPrefix]
		private static bool DoGetAxis(InputAction action, ref float __result)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			if (!ShouldBlock(action))
			{
				return true;
			}
			__result = 0f;
			return false;
		}

		private static bool Pass(InputAction action, ref bool result)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			if (!ShouldBlock(action))
			{
				return true;
			}
			result = false;
			return false;
		}
	}
	[HarmonyPatch(typeof(CM_TimedButton))]
	internal static class MenuPatches
	{
		[HarmonyPatch("OnHoverUpdate")]
		[HarmonyPrefix]
		private static void OnHoverUpdatePrefix(out float __state)
		{
			__state = UseRealDelta();
		}

		[HarmonyPatch("OnHoverUpdate")]
		[HarmonyPostfix]
		private static void OnHoverUpdatePostfix(float __state)
		{
			RestoreDelta(__state);
		}

		[HarmonyPatch("Update")]
		[HarmonyPrefix]
		private static void UpdatePrefix(out float __state)
		{
			__state = UseRealDelta();
		}

		[HarmonyPatch("Update")]
		[HarmonyPostfix]
		private static void UpdatePostfix(float __state)
		{
			RestoreDelta(__state);
		}

		private static float UseRealDelta()
		{
			if (!PauseState.IsPaused)
			{
				return -1f;
			}
			float delta = Clock.Delta;
			Clock.Delta = Mathf.Min(Time.unscaledDeltaTime, Time.maximumDeltaTime);
			return delta;
		}

		private static void RestoreDelta(float saved)
		{
			if (saved >= 0f)
			{
				Clock.Delta = saved;
			}
		}
	}
	[HarmonyPatch]
	internal static class SessionPatches
	{
		[HarmonyPatch(typeof(SNet_SyncManager), "SessionCommand")]
		[HarmonyPrefix]
		private static void SessionCommand(eSessionCommandType type)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			PauseLogic.ForceResume($"session command {type}");
		}

		[HarmonyPatch(typeof(SNet_SessionHub), "LeaveHub")]
		[HarmonyPrefix]
		private static void LeaveHub()
		{
			PauseLogic.ForceResume("leaving the session");
		}
	}
	[HarmonyPatch(typeof(GS_InLevel), "Update")]
	internal static class TimerPatches
	{
		private static void Postfix()
		{
			if (PauseState.IsPaused)
			{
				Clock.ExpeditionProgressionTime = PauseState.FrozenExpeditionTime;
			}
		}
	}
}
namespace GTFOPause.Net
{
	internal static class PauseNet
	{
		private struct pHello
		{
			public uint Protocol;
		}

		private struct pRequest
		{
			public byte WantPaused;
		}

		private struct pState
		{
			public byte Paused;

			public ulong By;

			public uint Seq;
		}

		private struct pDeny
		{
			public byte Reason;

			public ulong Player;
		}

		private enum DenyReason : byte
		{
			Disabled = 1,
			MissingMod,
			NotInLevel,
			Cooldown,
			ProtocolMismatch
		}

		private const string HelloEvent = "GTFOPause.Hello";

		private const string RequestEvent = "GTFOPause.Request";

		private const string StateEvent = "GTFOPause.State";

		private const string DenyEvent = "GTFOPause.Deny";

		private const uint Protocol = 1u;

		private const float ResendInterval = 2f;

		private const float WatchdogTimeout = 6f;

		private const float ToggleCooldown = 1f;

		private static readonly HashSet<ulong> s_moddedPlayers = new HashSet<ulong>();

		private static readonly HashSet<ulong> s_lobbyScratch = new HashSet<ulong>();

		private static bool s_squadPaused;

		private static ulong s_squadPausedBy;

		private static uint s_seq;

		private static float s_nextResend;

		private static float s_lastToggle = float.NegativeInfinity;

		private static ulong s_helloMaster;

		private static bool s_hostHasMod;

		private static ulong s_lastSender;

		private static uint s_lastSeq;

		private static uint? s_dismissedSeq;

		private static float s_lastStateTime;

		private static float Now => Time.realtimeSinceStartup;

		private static ulong LocalLookup
		{
			get
			{
				if (!SNet.HasLocalPlayer)
				{
					return 0uL;
				}
				return SNet.LocalPlayer.Lookup;
			}
		}

		public static void Register()
		{
			NetworkAPI.RegisterEvent<pHello>("GTFOPause.Hello", (Action<ulong, pHello>)OnHello);
			NetworkAPI.RegisterEvent<pRequest>("GTFOPause.Request", (Action<ulong, pRequest>)OnRequest);
			NetworkAPI.RegisterEvent<pState>("GTFOPause.State", (Action<ulong, pState>)OnState);
			NetworkAPI.RegisterEvent<pDeny>("GTFOPause.Deny", (Action<ulong, pDeny>)OnDeny);
		}

		public static void Tick()
		{
			if (SNet.IsMaster)
			{
				HostTick();
			}
			else
			{
				ClientTick();
			}
		}

		public static void OnHotkey()
		{
			if (SNet.IsMaster)
			{
				HostToggle(LocalLookup, !s_squadPaused);
				return;
			}
			if (!s_hostHasMod)
			{
				Notify.Event("The host doesn't have GTFOPause");
				return;
			}
			bool flag = PauseState.Reasons.HasFlag(PauseReason.Remote);
			s_dismissedSeq = null;
			NetworkAPI.InvokeEvent<pRequest>("GTFOPause.Request", new pRequest
			{
				WantPaused = ((!flag) ? ((byte)1) : ((byte)0))
			}, SNet.Master, (SNet_ChannelType)2);
		}

		public static void OnLocalForceResume()
		{
			Notify.PausedBy = null;
			if (SNet.IsMaster)
			{
				if (s_squadPaused)
				{
					SetSquadPaused(paused: false, LocalLookup, applyLocally: false);
				}
			}
			else if (PauseState.Reasons.HasFlag(PauseReason.Remote))
			{
				s_dismissedSeq = s_lastSeq;
			}
		}

		private static void HostTick()
		{
			PruneModdedPlayers();
			if (s_squadPaused && Now >= s_nextResend)
			{
				Broadcast();
			}
		}

		private static void OnHello(ulong sender, pHello hello)
		{
			if (SNet.IsMaster)
			{
				if (hello.Protocol != 1)
				{
					Logger.Warn($"Player {sender} runs GTFOPause protocol {hello.Protocol}, expected {1}");
					SendTo(sender, "GTFOPause.Deny", new pDeny
					{
						Reason = 5
					});
				}
				else
				{
					s_moddedPlayers.Add(sender);
					Logger.Info(NameOf(sender) + " has GTFOPause");
					SendTo(sender, "GTFOPause.State", CurrentState());
				}
			}
		}

		private static void OnRequest(ulong sender, pRequest request)
		{
			if (SNet.IsMaster)
			{
				HostToggle(sender, request.WantPaused != 0);
			}
		}

		private static void HostToggle(ulong requester, bool wantPaused)
		{
			ulong lookup;
			if (!PauseRules.InLevel)
			{
				Deny(requester, DenyReason.NotInLevel, 0uL);
			}
			else if (!PauseConfig.AllowMultiplayerPause.Value)
			{
				Deny(requester, DenyReason.Disabled, 0uL);
			}
			else if (wantPaused == s_squadPaused)
			{
				if (requester != LocalLookup)
				{
					SendTo(requester, "GTFOPause.State", CurrentState());
				}
			}
			else if (wantPaused && TryFindUnmoddedHuman(out lookup))
			{
				Deny(requester, DenyReason.MissingMod, lookup);
			}
			else if (Now - s_lastToggle < 1f)
			{
				Deny(requester, DenyReason.Cooldown, 0uL);
			}
			else
			{
				s_lastToggle = Now;
				SetSquadPaused(wantPaused, requester, applyLocally: true);
			}
		}

		private static void SetSquadPaused(bool paused, ulong by, bool applyLocally)
		{
			s_squadPaused = paused;
			s_squadPausedBy = by;
			s_seq++;
			if (applyLocally)
			{
				ApplyRemote(paused, by);
			}
			Broadcast();
		}

		private static void Broadcast()
		{
			NetworkAPI.InvokeEvent<pState>("GTFOPause.State", CurrentState(), (SNet_ChannelType)2);
			s_nextResend = Now + 2f;
		}

		private static pState CurrentState()
		{
			return new pState
			{
				Paused = (s_squadPaused ? ((byte)1) : ((byte)0)),
				By = s_squadPausedBy,
				Seq = s_seq
			};
		}

		private static bool TryFindUnmoddedHuman(out ulong lookup)
		{
			lookup = 0uL;
			ulong localLookup = LocalLookup;
			List<SNet_Player> lobbyPlayers = SNet.LobbyPlayers;
			if (lobbyPlayers == null)
			{
				return false;
			}
			for (int i = 0; i < lobbyPlayers.Count; i++)
			{
				SNet_Player val = lobbyPlayers[i];
				if (!((Object)(object)val == (Object)null) && !val.IsBot && val.Lookup != localLookup && !s_moddedPlayers.Contains(val.Lookup))
				{
					lookup = val.Lookup;
					return true;
				}
			}
			return false;
		}

		private static void PruneModdedPlayers()
		{
			if (s_moddedPlayers.Count == 0)
			{
				return;
			}
			s_lobbyScratch.Clear();
			List<SNet_Player> lobbyPlayers = SNet.LobbyPlayers;
			if (lobbyPlayers != null)
			{
				for (int i = 0; i < lobbyPlayers.Count; i++)
				{
					if ((Object)(object)lobbyPlayers[i] != (Object)null)
					{
						s_lobbyScratch.Add(lobbyPlayers[i].Lookup);
					}
				}
			}
			s_moddedPlayers.RemoveWhere((ulong lookup) => !s_lobbyScratch.Contains(lookup));
		}

		private static void Deny(ulong requester, DenyReason reason, ulong player = 0uL)
		{
			pDeny pDeny = new pDeny
			{
				Reason = (byte)reason,
				Player = player
			};
			if (requester == LocalLookup)
			{
				OnDeny(requester, pDeny);
			}
			else
			{
				SendTo(requester, "GTFOPause.Deny", pDeny);
			}
		}

		private static void ClientTick()
		{
			if (!PauseRules.InLevel || !SNet.HasMaster || (Object)(object)SNet.Master == (Object)null)
			{
				s_helloMaster = 0uL;
				return;
			}
			ulong lookup = SNet.Master.Lookup;
			if (lookup != s_helloMaster)
			{
				s_helloMaster = lookup;
				s_hostHasMod = false;
				NetworkAPI.InvokeEvent<pHello>("GTFOPause.Hello", new pHello
				{
					Protocol = 1u
				}, SNet.Master, (SNet_ChannelType)2);
			}
			if (PauseState.Reasons.HasFlag(PauseReason.Remote) && Now - s_lastStateTime > 6f)
			{
				PauseLogic.ForceResume("no word from the host");
			}
		}

		private static void OnState(ulong sender, pState state)
		{
			if (SNet.IsMaster || !SNet.HasMaster || (Object)(object)SNet.Master == (Object)null || sender != SNet.Master.Lookup)
			{
				return;
			}
			s_hostHasMod = true;
			s_lastStateTime = Now;
			if (sender != s_lastSender || state.Seq >= s_lastSeq)
			{
				s_lastSender = sender;
				s_lastSeq = state.Seq;
				bool flag = state.Paused != 0;
				if ((!flag || (state.Seq != s_dismissedSeq && PauseRules.InLevel)) && flag != PauseState.Reasons.HasFlag(PauseReason.Remote))
				{
					ApplyRemote(flag, state.By);
				}
			}
		}

		private static void OnDeny(ulong sender, pDeny deny)
		{
			switch ((DenyReason)deny.Reason)
			{
			case DenyReason.Disabled:
				Notify.Event("Multiplayer pause is disabled by the host");
				break;
			case DenyReason.MissingMod:
				Notify.Event(NameOf(deny.Player) + " doesn't have GTFOPause");
				break;
			case DenyReason.ProtocolMismatch:
				Notify.Event("Your GTFOPause version doesn't match the host's");
				break;
			case DenyReason.NotInLevel:
			case DenyReason.Cooldown:
				break;
			}
		}

		private static void ApplyRemote(bool paused, ulong by)
		{
			string text = NameOf(by);
			if (paused)
			{
				Notify.PausedBy = text;
				PauseState.Add(PauseReason.Remote);
				Notify.Event(text + " paused the game");
			}
			else
			{
				Notify.PausedBy = null;
				PauseState.Remove(PauseReason.Remote);
				Notify.Event(text + " resumed the game");
			}
		}

		private static void SendTo<T>(ulong lookup, string eventName, T payload) where T : struct
		{
			SNet_Player val = default(SNet_Player);
			if (SNet.TryGetPlayer(lookup, ref val) && (Object)(object)val != (Object)null)
			{
				NetworkAPI.InvokeEvent<T>(eventName, payload, val, (SNet_ChannelType)2);
			}
		}

		private static string NameOf(ulong lookup)
		{
			SNet_Player val = default(SNet_Player);
			if (!SNet.TryGetPlayer(lookup, ref val) || !((Object)(object)val != (Object)null))
			{
				return "Someone";
			}
			return val.NickName;
		}
	}
}