Decompiled source of SingleplayerPause v0.1.10

BepInEx/plugins/HowToFish.SingleplayerPause/HowToFish.SingleplayerPause.dll

Decompiled an hour ago
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using BepInEx;
using FishNet;
using FishNet.Managing.Server;
using FishNet.Managing.Timing;
using HarmonyLib;
using UnityEngine;

[assembly: AssemblyDescription("Freezes gameplay while the native pause menu is open in strict singleplayer sessions only.")]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyCompany("evansvl")]
[assembly: CompilationRelaxations(8)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace HowToFish.SingleplayerPause;

[BepInPlugin("community.howtofish.singleplayerpause", "Singleplayer Pause", "0.1.10")]
public sealed class Plugin : BaseUnityPlugin
{
	public const string PluginGuid = "community.howtofish.singleplayerpause";

	public const string PluginName = "Singleplayer Pause";

	public const string PluginVersion = "0.1.10";

	private const float MinimumSafePauseDelay = 0.08f;

	private static readonly FieldInfo HasCoolDownField = AccessTools.Field(typeof(Weapon), "_hasCoolDown");

	private static readonly FieldInfo IsReloadingField = AccessTools.Field(typeof(Weapon), "_isReloading");

	private static readonly FieldInfo QueueReloadField = AccessTools.Field(typeof(Weapon), "_queueReload");

	private static readonly FieldInfo QueuedShootField = AccessTools.Field(typeof(Weapon), "_queuedShoot");

	private static readonly FieldInfo DisabledBeforeShootAnimField = AccessTools.Field(typeof(Weapon), "_disabledBeforeShootAnim");

	private static readonly FieldInfo HoldingFireInputField = AccessTools.Field(typeof(Weapon), "_holdingFireInput");

	private static readonly FieldInfo HandHoldRightField = AccessTools.Field(typeof(PlayerHands), "_curHandHoldAmountRight");

	private static readonly FieldInfo HandHoldLeftField = AccessTools.Field(typeof(PlayerHands), "_curHandHoldAmountLeft");

	private static readonly FieldInfo InHandHolderField = AccessTools.Field(typeof(Item), "_inHandHolder");

	private static readonly FieldInfo OutOfHandHolderField = AccessTools.Field(typeof(Item), "_outOfHandHolder");

	private static readonly FieldInfo RodLineField = AccessTools.Field(typeof(FishingRod), "_line");

	private static readonly FieldInfo RodDisabledField = AccessTools.Field(typeof(FishingRod), "_isDisabled");

	private static Plugin _instance;

	private bool _ownsFreeze;

	private bool _awaitingPositiveDeltaAfterResume;

	private float _restoreTimeScale = 1f;

	private float _pauseDetectedAt = -1f;

	private bool _loggedWeaponDelay;

	private Tool _lastRepairedTool;

	private float _nextDiagnosticAt;

	private string _lastDiagnosticState;

	private Harmony _harmony;

	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("community.howtofish.singleplayerpause");
		PatchPrefix(typeof(TimeManager), "IncreaseTick", "IncreaseTickPrefix");
		PatchPrefix(typeof(PlayerToolMovement), "LateUpdate", "PlayerToolMovementLateUpdatePrefix");
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Loaded. Strict-singleplayer pause freezes Unity time and FishNet simulation ticks while guarding held-tool sway from zero-delta-time updates.");
	}

	private void PatchPrefix(Type targetType, string targetName, string hookName)
	{
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_0052: Expected O, but got Unknown
		MethodInfo methodInfo = AccessTools.Method(targetType, targetName, (Type[])null, (Type[])null);
		MethodInfo methodInfo2 = AccessTools.Method(typeof(TimeManagerHooks), hookName, (Type[])null, (Type[])null);
		if (methodInfo == null || methodInfo2 == null)
		{
			throw new MissingMethodException(targetType.FullName, targetName);
		}
		_harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
	}

	private void Update()
	{
		if (PauseManager.IsPaused && IsStrictSingleplayerWorld())
		{
			if (_ownsFreeze)
			{
				Freeze();
				return;
			}
			if (_pauseDetectedAt < 0f)
			{
				_pauseDetectedAt = Time.realtimeSinceStartup;
				LogRuntimeState("pause-request", force: true);
			}
			if (Time.realtimeSinceStartup - _pauseDetectedAt >= 0.08f && !HasActiveHeldToolTransition())
			{
				Freeze();
			}
			return;
		}
		_pauseDetectedAt = -1f;
		_loggedWeaponDelay = false;
		_lastRepairedTool = null;
		if (_ownsFreeze)
		{
			LogRuntimeState("pause-release", force: true);
		}
		_lastDiagnosticState = null;
		Restore();
		if (_awaitingPositiveDeltaAfterResume && Time.deltaTime > 1E-06f)
		{
			_awaitingPositiveDeltaAfterResume = false;
		}
	}

	private bool HasActiveHeldToolTransition()
	{
		Player localPlayer = Player.LocalPlayer;
		if ((Object)(object)localPlayer == (Object)null || (Object)(object)localPlayer.Holding == (Object)null)
		{
			return false;
		}
		Item heldItem = localPlayer.Holding.HeldItem;
		Tool val = (Tool)(object)((heldItem is Tool) ? heldItem : null);
		PlayerToolMovement toolMovement = localPlayer.ToolMovement;
		if ((Object)(object)toolMovement != (Object)null)
		{
			Tool currentTool = toolMovement.CurrentTool;
			if ((Object)(object)currentTool != (Object)(object)val || ((Object)(object)currentTool != (Object)null && toolMovement.HoldPercent < 0.999f))
			{
				LogToolDelay();
				return true;
			}
		}
		Weapon val2 = (Weapon)(object)((val is Weapon) ? val : null);
		if ((Object)(object)val2 == (Object)null)
		{
			return false;
		}
		bool flag = ReadBool(HasCoolDownField, val2) || ReadBool(IsReloadingField, val2) || ReadBool(QueueReloadField, val2) || ReadBool(QueuedShootField, val2) || ReadBool(DisabledBeforeShootAnimField, val2) || ReadBool(HoldingFireInputField, val2);
		if (flag)
		{
			LogToolDelay();
		}
		return flag;
	}

	private void LogToolDelay()
	{
		if (!_loggedWeaponDelay)
		{
			_loggedWeaponDelay = true;
			((BaseUnityPlugin)this).Logger.LogDebug((object)"Pause freeze delayed until the held tool finishes its equip, shot, or reload transition.");
		}
	}

	private static bool ReadBool(FieldInfo field, Weapon weapon)
	{
		if (field != null)
		{
			return (bool)field.GetValue(weapon);
		}
		return false;
	}

	private static bool IsStrictSingleplayerWorld()
	{
		if (MainMenuManager.IsInMenu)
		{
			return false;
		}
		if ((Object)(object)ConnectionManager.Instance == (Object)null || ConnectionManager.IsUsingSteam)
		{
			return false;
		}
		if (!InstanceFinder.IsHostStarted)
		{
			return false;
		}
		ServerManager serverManager = InstanceFinder.ServerManager;
		if ((Object)(object)serverManager == (Object)null || !serverManager.Started || serverManager.Clients == null)
		{
			return false;
		}
		if (serverManager.Clients.Count != 1)
		{
			return false;
		}
		return (Object)(object)Player.LocalPlayer != (Object)null;
	}

	private void Freeze()
	{
		if (!_ownsFreeze)
		{
			_restoreTimeScale = Time.timeScale;
			_ownsFreeze = true;
			_loggedWeaponDelay = false;
			((BaseUnityPlugin)this).Logger.LogDebug((object)"Singleplayer pause freeze acquired.");
		}
		if (!Mathf.Approximately(Time.timeScale, 0f))
		{
			Time.timeScale = 0f;
		}
		MaintainHeldToolPresentation();
		LogRuntimeState("paused", force: false);
	}

	private void MaintainHeldToolPresentation()
	{
		Player localPlayer = Player.LocalPlayer;
		if ((Object)(object)localPlayer == (Object)null || (Object)(object)localPlayer.Holding == (Object)null)
		{
			return;
		}
		Item heldItem = localPlayer.Holding.HeldItem;
		Tool val = (Tool)(object)((heldItem is Tool) ? heldItem : null);
		if (!((Object)(object)val == (Object)null) && !((Object)(object)val.HandsMesh == (Object)null))
		{
			bool flag = !val.HandsMesh.enabled;
			if (val.TryActivateAnimatedHands(localPlayer) && flag && (Object)(object)_lastRepairedTool != (Object)(object)val)
			{
				_lastRepairedTool = val;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Restored paused animated-hands renderer for held tool " + ((object)val).GetType().Name + "."));
			}
		}
	}

	private void LogRuntimeState(string phase, bool force)
	{
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (force || !(realtimeSinceStartup < _nextDiagnosticAt))
		{
			_nextDiagnosticAt = realtimeSinceStartup + 0.25f;
			string text = DescribeRuntimeState();
			if (force || !string.Equals(text, _lastDiagnosticState, StringComparison.Ordinal))
			{
				_lastDiagnosticState = text;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("STATE " + phase + " | " + text));
			}
		}
	}

	private static string DescribeRuntimeState()
	{
		//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0289: Unknown result type (might be due to invalid IL or missing references)
		Player localPlayer = Player.LocalPlayer;
		if ((Object)(object)localPlayer == (Object)null)
		{
			return "player=null timeScale=" + Time.timeScale;
		}
		PlayerHolding holding = localPlayer.Holding;
		Item val = (((Object)(object)holding == (Object)null) ? null : holding.HeldItem);
		Tool val2 = (Tool)(object)((val is Tool) ? val : null);
		PlayerToolMovement toolMovement = localPlayer.ToolMovement;
		Tool val3 = (((Object)(object)toolMovement == (Object)null) ? null : toolMovement.CurrentTool);
		StringBuilder stringBuilder = new StringBuilder(384);
		stringBuilder.Append("timeScale=").Append(Time.timeScale);
		stringBuilder.Append(" deltaTime=").Append(Time.deltaTime);
		stringBuilder.Append(" paused=").Append(PauseManager.IsPaused);
		stringBuilder.Append(" afk=").Append(localPlayer.IsAfk);
		stringBuilder.Append(" held=").Append(ObjectLabel((Object)(object)val));
		stringBuilder.Append(" currentTool=").Append(ObjectLabel((Object)(object)val3));
		stringBuilder.Append(" sameTool=").Append((Object)(object)val2 == (Object)(object)val3);
		if ((Object)(object)val != (Object)null)
		{
			stringBuilder.Append(" holderLocal=").Append((Object)(object)val.Holder == (Object)(object)localPlayer);
			stringBuilder.Append(" syncedHolderLocal=").Append((Object)(object)val.SyncedHolder == (Object)(object)localPlayer);
			stringBuilder.Append(" inventory=").Append(val.IsInInventory);
			stringBuilder.Append(" active=").Append(((Component)val).gameObject.activeInHierarchy);
			stringBuilder.Append(" pos=").Append(VectorLabel(((Component)val).transform.position));
			AppendGameObjectState(stringBuilder, " inHand", InHandHolderField, val);
			AppendGameObjectState(stringBuilder, " outHand", OutOfHandHolderField, val);
		}
		if ((Object)(object)toolMovement != (Object)null)
		{
			stringBuilder.Append(" holdPercent=").Append(toolMovement.HoldPercent.ToString("0.000"));
		}
		if ((Object)(object)val2 != (Object)null)
		{
			Renderer handsMesh = val2.HandsMesh;
			stringBuilder.Append(" handsMesh=").Append(((Object)(object)handsMesh == (Object)null) ? "null" : (handsMesh.enabled + "/" + ((Component)handsMesh).gameObject.activeInHierarchy));
			stringBuilder.Append(" swayPos=").Append(((Object)(object)val2.SwayTransform == (Object)null) ? "null" : VectorLabel(val2.SwayTransform.position));
		}
		PlayerHands hands = localPlayer.Hands;
		if ((Object)(object)hands != (Object)null)
		{
			stringBuilder.Append(" handR=").Append(ReadFloat(HandHoldRightField, hands).ToString("0.000"));
			stringBuilder.Append(" handL=").Append(ReadFloat(HandHoldLeftField, hands).ToString("0.000"));
		}
		FishingRod val4 = (FishingRod)(object)((val2 is FishingRod) ? val2 : null);
		if ((Object)(object)val4 != (Object)null)
		{
			LineRenderer val5 = (LineRenderer)((RodLineField == null) ? null : /*isinst with value type is only supported in some contexts*/);
			stringBuilder.Append(" rodDisabled=").Append(ReadObjectBool(RodDisabledField, val4));
			stringBuilder.Append(" line=").Append(((Object)(object)val5 == (Object)null) ? "null" : (((Renderer)val5).enabled + "/" + ((Component)val5).gameObject.activeInHierarchy));
		}
		return stringBuilder.ToString();
	}

	private static string ObjectLabel(Object value)
	{
		if (!(value == (Object)null))
		{
			return ((object)value).GetType().Name + "#" + value.GetInstanceID();
		}
		return "null";
	}

	private static string VectorLabel(Vector3 value)
	{
		return value.x.ToString("0.00") + "," + value.y.ToString("0.00") + "," + value.z.ToString("0.00");
	}

	private static float ReadFloat(FieldInfo field, object instance)
	{
		if (!(field == null) && instance != null)
		{
			return (float)field.GetValue(instance);
		}
		return float.NaN;
	}

	private static bool ReadObjectBool(FieldInfo field, object instance)
	{
		if (field != null && instance != null)
		{
			return (bool)field.GetValue(instance);
		}
		return false;
	}

	private static void AppendGameObjectState(StringBuilder value, string label, FieldInfo field, Item item)
	{
		GameObject val = (GameObject)((field == null) ? null : /*isinst with value type is only supported in some contexts*/);
		value.Append(label).Append('=').Append(((Object)(object)val == (Object)null) ? "null" : (val.activeSelf + "/" + val.activeInHierarchy));
	}

	private void Restore()
	{
		if (_ownsFreeze)
		{
			if (Mathf.Approximately(Time.timeScale, 0f))
			{
				Time.timeScale = _restoreTimeScale;
			}
			_ownsFreeze = false;
			_awaitingPositiveDeltaAfterResume = true;
			((BaseUnityPlugin)this).Logger.LogDebug((object)"Singleplayer pause freeze released.");
		}
	}

	internal static bool AllowSimulationTick()
	{
		Plugin instance = _instance;
		if ((Object)(object)instance == (Object)null)
		{
			return true;
		}
		return !instance._ownsFreeze;
	}

	internal static bool AllowHeldToolFrameUpdate()
	{
		Plugin instance = _instance;
		if (!((Object)(object)instance == (Object)null))
		{
			if (!instance._ownsFreeze)
			{
				return !instance._awaitingPositiveDeltaAfterResume;
			}
			return false;
		}
		return true;
	}

	private void OnDisable()
	{
		_pauseDetectedAt = -1f;
		Restore();
		_awaitingPositiveDeltaAfterResume = false;
	}

	private void OnDestroy()
	{
		Restore();
		_awaitingPositiveDeltaAfterResume = false;
		if (_harmony != null)
		{
			_harmony.UnpatchSelf();
		}
		if ((Object)(object)_instance == (Object)(object)this)
		{
			_instance = null;
		}
	}

	private void OnApplicationQuit()
	{
		Restore();
	}
}
internal static class TimeManagerHooks
{
	internal static bool IncreaseTickPrefix()
	{
		return Plugin.AllowSimulationTick();
	}

	internal static bool PlayerToolMovementLateUpdatePrefix()
	{
		return Plugin.AllowHeldToolFrameUpdate();
	}
}