Decompiled source of Host Pause v0.2.1

BepInEx\plugins\HostPauseMod.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mirror;
using Steamworks.Data;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Localization.Components;
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("HostPauseMod")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("HostPauseMod")]
[assembly: AssemblyTitle("HostPauseMod")]
[assembly: AssemblyVersion("1.0.0.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 HostPauseMod
{
	[BepInPlugin("codex.superbattlegolf.hostpause", "Host Pause", "0.2.1")]
	public sealed class HostPausePlugin : BaseUnityPlugin
	{
		private sealed class RigidbodySnapshot
		{
			public Rigidbody Body;

			public bool IsKinematic;

			public bool UseGravity;

			public bool DetectCollisions;

			public Vector3 LinearVelocity;

			public Vector3 AngularVelocity;
		}

		[HarmonyPatch(typeof(PauseMenu), "Awake")]
		private static class PauseMenuAwakePatch
		{
			private static void Postfix(PauseMenu __instance)
			{
				instance?.AddPauseButton(__instance);
			}
		}

		[HarmonyPatch(typeof(PauseMenu), "OnEnable")]
		private static class PauseMenuOnEnablePatch
		{
			private static void Postfix(PauseMenu __instance)
			{
				if ((Object)(object)instance?.pauseButton != (Object)null)
				{
					RefreshPauseMenuButtonList(__instance);
					FitPauseMenuButtonStack(__instance);
					instance.RefreshPauseButton();
				}
			}
		}

		[HarmonyPatch(typeof(BNetworkManager), "OnStartClient")]
		private static class NetworkStartClientPatch
		{
			private static void Postfix()
			{
				lastObservedLobbyPauseVersion = string.Empty;
				nextLobbyPollTime = 0f;
			}
		}

		[HarmonyPatch(typeof(BNetworkManager), "OnStopServer")]
		private static class NetworkStopServerPatch
		{
			private static void Prefix()
			{
				pendingRemotePause = false;
				PublishSteamLobbyPauseState(paused: false);
				ForceLocalResume();
			}
		}

		[HarmonyPatch(typeof(BNetworkManager), "OnStopClient")]
		private static class NetworkStopClientPatch
		{
			private static void Prefix()
			{
				lastObservedLobbyPauseVersion = string.Empty;
				nextLobbyPollTime = 0f;
				pendingRemotePause = false;
				ForceLocalResume();
			}
		}

		[HarmonyPatch(typeof(PlayerMovement), "Update")]
		private static class PlayerMovementUpdatePatch
		{
			private static bool Prefix()
			{
				return !isPaused;
			}
		}

		[HarmonyPatch(typeof(PlayerMovement), "FixedUpdate")]
		private static class PlayerMovementFixedUpdatePatch
		{
			private static bool Prefix()
			{
				return !isPaused;
			}
		}

		[HarmonyPatch(typeof(GolfBall), "UpdatePhysics")]
		private static class GolfBallUpdatePhysicsPatch
		{
			private static bool Prefix()
			{
				return !isPaused;
			}
		}

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

		private const string PluginGuid = "codex.superbattlegolf.hostpause";

		private const string PluginName = "Host Pause";

		private const string PluginVersion = "0.2.1";

		private const string LobbyPauseStateKey = "codex_hostpause_state";

		private const string LobbyPauseVersionKey = "codex_hostpause_version";

		private static HostPausePlugin instance;

		private static bool isPaused;

		private static string lastObservedLobbyPauseVersion = string.Empty;

		private static float nextLobbyPollTime;

		private static bool pendingRemotePause;

		private static bool pauseMenuStackAdjusted;

		private static Vector3 pauseMenuOriginalScale;

		private static Vector2 pauseMenuOriginalAnchoredPosition;

		private static bool pauseBannerApplied;

		private static bool aheadBannerWasVisibleBeforePause;

		private static readonly List<RigidbodySnapshot> frozenRigidbodies = new List<RigidbodySnapshot>();

		private static readonly Dictionary<TMP_Text, string> pauseBannerOriginalTexts = new Dictionary<TMP_Text, string>();

		private static readonly List<LocalizeStringEvent> pauseBannerDisabledLocalizers = new List<LocalizeStringEvent>();

		private Harmony harmony;

		private Button pauseButton;

		private TMP_Text pauseButtonLabel;

		private void Awake()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			instance = this;
			harmony = new Harmony("codex.superbattlegolf.hostpause");
			harmony.PatchAll();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Host Pause 0.2.1 loaded.");
		}

		private void OnDestroy()
		{
			ForceLocalResume();
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			if (instance == this)
			{
				instance = null;
			}
		}

		private void Update()
		{
			if (isPaused)
			{
				if (!CanApplyPauseState())
				{
					ForceLocalResume();
					return;
				}
				MaintainFrozenRigidbodies();
				ShowPauseBanner();
			}
			PollSteamLobbyPauseState();
			TryApplyPendingRemotePause();
			if (isPaused && !NetworkServer.active && !NetworkClient.active)
			{
				ForceLocalResume();
			}
			RefreshPauseButton();
		}

		private static void PollSteamLobbyPauseState()
		{
			if (NetworkServer.active || !NetworkClient.active || !NetworkClient.isConnected || NetworkClient.connection == null || !((NetworkConnection)NetworkClient.connection).isAuthenticated || !CanApplyPauseState() || Time.unscaledTime < nextLobbyPollTime)
			{
				return;
			}
			nextLobbyPollTime = Time.unscaledTime + 0.5f;
			Lobby val = default(Lobby);
			if (BNetworkManager.TryGetSteamLobby(ref val))
			{
				string data = ((Lobby)(ref val)).GetData("codex_hostpause_version");
				if (!string.IsNullOrEmpty(data) && !(data == lastObservedLobbyPauseVersion))
				{
					lastObservedLobbyPauseVersion = data;
					ApplyRemotePause(((Lobby)(ref val)).GetData("codex_hostpause_state") == "1");
				}
			}
		}

		private static void TogglePauseFromPauseMenu()
		{
			if (!NetworkServer.active)
			{
				return;
			}
			bool flag = !isPaused;
			if (flag && !CanApplyPauseState())
			{
				HostPausePlugin hostPausePlugin = instance;
				if (hostPausePlugin != null)
				{
					((BaseUnityPlugin)hostPausePlugin).Logger.LogWarning((object)"Ignoring pause request because the game is not in a playable match state.");
				}
			}
			else
			{
				PauseMenu.Unpause();
				ApplyPauseLocal(flag);
				PublishSteamLobbyPauseState(flag);
			}
		}

		private static void PublishSteamLobbyPauseState(bool paused)
		{
			Lobby val = default(Lobby);
			if (NetworkServer.active && BNetworkManager.TryGetSteamLobby(ref val))
			{
				((Lobby)(ref val)).SetData("codex_hostpause_state", paused ? "1" : "0");
				((Lobby)(ref val)).SetData("codex_hostpause_version", Time.unscaledTime.ToString("R"));
			}
		}

		private static void ApplyPauseLocal(bool paused)
		{
			if (paused)
			{
				if (!CanApplyPauseState())
				{
					pendingRemotePause = true;
					ForceLocalResume();
					return;
				}
				if (!isPaused)
				{
					FreezeRigidbodies();
					ShowPauseBanner();
				}
				isPaused = true;
			}
			else
			{
				pendingRemotePause = false;
				isPaused = false;
				RestoreRigidbodies();
				RestorePauseBanner();
			}
			instance?.RefreshPauseButton();
		}

		private static void ApplyRemotePause(bool paused)
		{
			if (!paused)
			{
				pendingRemotePause = false;
				ApplyPauseLocal(paused: false);
			}
			else if (!CanApplyPauseState())
			{
				pendingRemotePause = true;
				ForceLocalResume();
			}
			else
			{
				pendingRemotePause = false;
				ApplyPauseLocal(paused: true);
			}
		}

		private static void TryApplyPendingRemotePause()
		{
			if (pendingRemotePause && !isPaused && CanApplyPauseState())
			{
				pendingRemotePause = false;
				ApplyPauseLocal(paused: true);
			}
		}

		private static void ForceLocalResume()
		{
			isPaused = false;
			RestoreRigidbodies();
			RestorePauseBanner();
			instance?.RefreshPauseButton();
		}

		private static bool CanApplyPauseState()
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Invalid comparison between Unknown and I4
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Invalid comparison between Unknown and I4
			if (!NetworkServer.active && (!NetworkClient.active || NetworkClient.connection == null || !((NetworkConnection)NetworkClient.connection).isAuthenticated))
			{
				return false;
			}
			if (!SingletonBehaviour<PauseMenu>.HasInstance)
			{
				return false;
			}
			if ((Object)(object)GameManager.LocalPlayerInfo == (Object)null || (Object)(object)GameManager.LocalPlayerMovement == (Object)null)
			{
				return false;
			}
			MatchState matchState = CourseManager.MatchState;
			if ((int)matchState >= 1)
			{
				return (int)matchState < 6;
			}
			return false;
		}

		private static void ShowPauseBanner()
		{
			AheadOfBallMessage aheadOfBallMessage = GetAheadOfBallMessage();
			if ((Object)(object)aheadOfBallMessage == (Object)null)
			{
				return;
			}
			if (!pauseBannerApplied)
			{
				aheadBannerWasVisibleBeforePause = GetAheadBannerVisible(aheadOfBallMessage);
				pauseBannerOriginalTexts.Clear();
				pauseBannerDisabledLocalizers.Clear();
				LocalizeStringEvent[] componentsInChildren = ((Component)aheadOfBallMessage).GetComponentsInChildren<LocalizeStringEvent>(true);
				foreach (LocalizeStringEvent val in componentsInChildren)
				{
					if (((Behaviour)val).enabled)
					{
						((Behaviour)val).enabled = false;
						pauseBannerDisabledLocalizers.Add(val);
					}
				}
				TMP_Text[] componentsInChildren2 = ((Component)aheadOfBallMessage).GetComponentsInChildren<TMP_Text>(true);
				foreach (TMP_Text val2 in componentsInChildren2)
				{
					pauseBannerOriginalTexts[val2] = val2.text;
					val2.text = "Game Paused";
				}
				pauseBannerApplied = true;
			}
			else
			{
				foreach (TMP_Text key in pauseBannerOriginalTexts.Keys)
				{
					if ((Object)(object)key != (Object)null)
					{
						key.text = "Game Paused";
					}
				}
			}
			AheadOfBallMessage.Show();
		}

		private static void RestorePauseBanner()
		{
			if (!pauseBannerApplied)
			{
				return;
			}
			foreach (KeyValuePair<TMP_Text, string> pauseBannerOriginalText in pauseBannerOriginalTexts)
			{
				if ((Object)(object)pauseBannerOriginalText.Key != (Object)null)
				{
					pauseBannerOriginalText.Key.text = pauseBannerOriginalText.Value;
				}
			}
			foreach (LocalizeStringEvent pauseBannerDisabledLocalizer in pauseBannerDisabledLocalizers)
			{
				if ((Object)(object)pauseBannerDisabledLocalizer != (Object)null)
				{
					((Behaviour)pauseBannerDisabledLocalizer).enabled = true;
				}
			}
			pauseBannerOriginalTexts.Clear();
			pauseBannerDisabledLocalizers.Clear();
			pauseBannerApplied = false;
			if (aheadBannerWasVisibleBeforePause)
			{
				AheadOfBallMessage.Show();
			}
			else
			{
				AheadOfBallMessage.Hide();
			}
		}

		private static AheadOfBallMessage GetAheadOfBallMessage()
		{
			if (SingletonBehaviour<AheadOfBallMessage>.HasInstance)
			{
				return SingletonBehaviour<AheadOfBallMessage>.Instance;
			}
			return Object.FindFirstObjectByType<AheadOfBallMessage>((FindObjectsInactive)1);
		}

		private static bool GetAheadBannerVisible(AheadOfBallMessage banner)
		{
			object obj = AccessTools.Field(typeof(AheadOfBallMessage), "isVisible")?.GetValue(banner);
			bool flag = default(bool);
			int num;
			if (obj is bool)
			{
				flag = (bool)obj;
				num = 1;
			}
			else
			{
				num = 0;
			}
			return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
		}

		private static void FreezeRigidbodies()
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			frozenRigidbodies.Clear();
			Rigidbody[] array = Object.FindObjectsByType<Rigidbody>((FindObjectsSortMode)0);
			foreach (Rigidbody val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					frozenRigidbodies.Add(new RigidbodySnapshot
					{
						Body = val,
						IsKinematic = val.isKinematic,
						UseGravity = val.useGravity,
						DetectCollisions = val.detectCollisions,
						LinearVelocity = val.linearVelocity,
						AngularVelocity = val.angularVelocity
					});
					FreezeRigidbody(val);
				}
			}
		}

		private static void MaintainFrozenRigidbodies()
		{
			foreach (RigidbodySnapshot frozenRigidbody in frozenRigidbodies)
			{
				if ((Object)(object)frozenRigidbody.Body != (Object)null)
				{
					FreezeRigidbody(frozenRigidbody.Body);
				}
			}
		}

		private static void FreezeRigidbody(Rigidbody body)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			body.linearVelocity = Vector3.zero;
			body.angularVelocity = Vector3.zero;
			body.isKinematic = true;
		}

		private static void RestoreRigidbodies()
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			foreach (RigidbodySnapshot frozenRigidbody in frozenRigidbodies)
			{
				Rigidbody body = frozenRigidbody.Body;
				if (!((Object)(object)body == (Object)null))
				{
					body.useGravity = frozenRigidbody.UseGravity;
					body.detectCollisions = frozenRigidbody.DetectCollisions;
					body.isKinematic = frozenRigidbody.IsKinematic;
					if (!body.isKinematic)
					{
						body.linearVelocity = frozenRigidbody.LinearVelocity;
						body.angularVelocity = frozenRigidbody.AngularVelocity;
					}
				}
			}
			frozenRigidbodies.Clear();
		}

		private void AddPauseButton(PauseMenu pauseMenu)
		{
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Expected O, but got Unknown
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Expected O, but got Unknown
			if ((Object)(object)pauseMenu == (Object)null || (Object)(object)pauseMenu.menuButtonContainer == (Object)null || (Object)(object)pauseButton != (Object)null)
			{
				return;
			}
			Button val = FindPauseButtonTemplate(pauseMenu);
			if ((Object)(object)val == (Object)null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find a pause menu button to clone for Host Pause.");
				return;
			}
			GameObject val2 = Object.Instantiate<GameObject>(((Component)val).gameObject, ((Component)val).transform.parent);
			((Object)val2).name = "HostPauseButton";
			val2.transform.SetSiblingIndex(((Component)val).transform.GetSiblingIndex() + 1);
			LocalizeStringEvent[] componentsInChildren = val2.GetComponentsInChildren<LocalizeStringEvent>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				((Behaviour)componentsInChildren[i]).enabled = false;
			}
			pauseButton = val2.GetComponent<Button>();
			pauseButton.onClick = new ButtonClickedEvent();
			ButtonClickedEvent onClick = pauseButton.onClick;
			object obj = <>O.<0>__TogglePauseFromPauseMenu;
			if (obj == null)
			{
				UnityAction val3 = TogglePauseFromPauseMenu;
				<>O.<0>__TogglePauseFromPauseMenu = val3;
				obj = (object)val3;
			}
			((UnityEvent)onClick).AddListener((UnityAction)obj);
			pauseButtonLabel = val2.GetComponentInChildren<TMP_Text>(true);
			RefreshPauseButton();
			RefreshPauseMenuButtonList(pauseMenu);
			FitPauseMenuButtonStack(pauseMenu);
		}

		private static Button FindPauseButtonTemplate(PauseMenu pauseMenu)
		{
			Button[] componentsInChildren = pauseMenu.menuButtonContainer.GetComponentsInChildren<Button>(true);
			Button[] array = componentsInChildren;
			foreach (Button val in array)
			{
				TMP_Text componentInChildren = ((Component)val).GetComponentInChildren<TMP_Text>(true);
				if ((Object)(object)componentInChildren != (Object)null && componentInChildren.text.Trim().Equals("Settings", StringComparison.OrdinalIgnoreCase))
				{
					return val;
				}
			}
			int num = Array.IndexOf(componentsInChildren, pauseMenu.exitGameButton);
			if (num > 0)
			{
				return componentsInChildren[num - 1];
			}
			if (!((Object)(object)pauseMenu.exitGameButton != (Object)null))
			{
				return pauseMenu.resumeButton;
			}
			return pauseMenu.exitGameButton;
		}

		private static void RefreshPauseMenuButtonList(PauseMenu pauseMenu)
		{
			Button[] componentsInChildren = pauseMenu.menuButtonContainer.GetComponentsInChildren<Button>(true);
			AccessTools.Field(typeof(PauseMenu), "allButtons")?.SetValue(pauseMenu, componentsInChildren);
			AccessTools.Method(typeof(PauseMenu), "UpdateButtonOffsets", (Type[])null, (Type[])null)?.Invoke(pauseMenu, null);
		}

		private static void FitPauseMenuButtonStack(PauseMenu pauseMenu)
		{
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: 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_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			RectTransform component = pauseMenu.menuButtonContainer.GetComponent<RectTransform>();
			if (!((Object)(object)component == (Object)null))
			{
				if (!pauseMenuStackAdjusted)
				{
					pauseMenuOriginalScale = ((Transform)component).localScale;
					pauseMenuOriginalAnchoredPosition = component.anchoredPosition;
					pauseMenuStackAdjusted = true;
				}
				((Transform)component).localScale = new Vector3(pauseMenuOriginalScale.x * 0.88f, pauseMenuOriginalScale.y * 0.88f, pauseMenuOriginalScale.z);
				component.anchoredPosition = pauseMenuOriginalAnchoredPosition + new Vector2(0f, 26f);
			}
		}

		private void RefreshPauseButton()
		{
			if (!((Object)(object)pauseButton == (Object)null))
			{
				bool active = NetworkServer.active;
				((Selectable)pauseButton).interactable = active;
				if ((Object)(object)pauseButtonLabel != (Object)null)
				{
					pauseButtonLabel.text = (active ? GetHostPauseButtonLabel() : "Host Pause Only");
				}
			}
		}

		private static string GetHostPauseButtonLabel()
		{
			if (isPaused)
			{
				return "Resume Game";
			}
			return "Pause Game";
		}
	}
}