Decompiled source of EnhancedSpectator v0.2.0

EnhancedSpectator.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Dissonance;
using Dissonance.Integrations.Unity_NFGO;
using EnhancedSpectator.Config;
using EnhancedSpectator.Features;
using EnhancedSpectator.Features.FloatingHead;
using EnhancedSpectator.Features.ModelInspection;
using EnhancedSpectator.Features.PlayerStateSync;
using EnhancedSpectator.Features.Spectator;
using EnhancedSpectator.Features.SpectatorPresence;
using EnhancedSpectator.Features.VoiceActivity;
using EnhancedSpectator.Features.VoiceDiagnostics;
using EnhancedSpectator.Features.VoiceRouting;
using EnhancedSpectator.GameInterop;
using EnhancedSpectator.Logging;
using EnhancedSpectator.Networking;
using EnhancedSpectator.Patching;
using EnhancedSpectator.Runtime;
using GameNetcodeStuff;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Steamworks;
using Steamworks.Data;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.Events;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("EnhancedSpectator")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+12c7eaf60f9d1e976f15c11fdf911b1e41c5140e")]
[assembly: AssemblyProduct("EnhancedSpectator")]
[assembly: AssemblyTitle("EnhancedSpectator")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[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.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 EnhancedSpectator
{
	[BepInPlugin("Auuueser.EnhancedSpectator", "Enhanced Spectator", "0.2.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private FeatureBootstrapper? _featureBootstrapper;

		private PatchBootstrapper? _patchBootstrapper;

		private bool _applicationQuitting;

		private bool _shutdownComplete;

		private void Awake()
		{
			RuntimeConnectionState.Reset();
			ModLog.Initialize(((BaseUnityPlugin)this).Logger);
			ModLog.Info("Enhanced Spectator 0.2.0 starting.");
			Application.quitting += OnApplicationQuitting;
			EnhancedSpectatorConfig enhancedSpectatorConfig = EnhancedSpectatorConfig.Bind(((BaseUnityPlugin)this).Config);
			ModLog.SetDebugEnabled(enhancedSpectatorConfig.EnableDebugLogging.Value);
			if (enhancedSpectatorConfig.EnableDebugLogging.Value)
			{
				PluginDiagnostics.LogPluginBinaryHash(((BaseUnityPlugin)this).Info.Location);
			}
			_featureBootstrapper = new FeatureBootstrapper(enhancedSpectatorConfig);
			_featureBootstrapper.Initialize();
			_patchBootstrapper = new PatchBootstrapper();
			_patchBootstrapper.Register();
			EnhancedSpectatorRuntimeDriver.Install(_featureBootstrapper, Shutdown);
			ModLog.Info("Local spectator freecam initialized.");
		}

		private void OnDestroy()
		{
			if (!_applicationQuitting)
			{
				EnhancedSpectatorRuntimeDriver.EnsureInstalled();
				ModLog.Warning("Plugin component destroyed before application quit; preserving runtime driver and patches.");
			}
			else
			{
				Shutdown();
			}
		}

		private void OnApplicationQuitting()
		{
			_applicationQuitting = true;
			RuntimeConnectionState.MarkApplicationQuitting();
			Shutdown();
		}

		private void Shutdown()
		{
			if (!_shutdownComplete)
			{
				_shutdownComplete = true;
				RuntimeConnectionState.MarkPluginShuttingDown();
				Application.quitting -= OnApplicationQuitting;
				EnhancedSpectatorRuntimeDriver.BeginShutdown();
				_patchBootstrapper?.Dispose();
				_featureBootstrapper?.Dispose();
				ModLog.Info("Local spectator freecam shut down.");
			}
		}
	}
	public static class PluginMetadata
	{
		public const string Guid = "Auuueser.EnhancedSpectator";

		public const string Name = "Enhanced Spectator";

		public const string Version = "0.2.0";
	}
}
namespace EnhancedSpectator.Runtime
{
	public sealed class EnhancedSpectatorRuntimeDriver : MonoBehaviour
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static UnityAction<Scene, LoadSceneMode> <0>__OnSceneLoaded;

			public static UnityAction<Scene> <1>__OnSceneUnloaded;

			public static CameraCallback <2>__OnCameraPreCull;

			public static Action<ScriptableRenderContext, Camera> <3>__OnBeginCameraRendering;
		}

		private static FeatureBootstrapper? _featureBootstrapper;

		private static Action? _shutdown;

		private static EnhancedSpectatorRuntimeDriver? _instance;

		private static bool _applicationQuitting;

		private static bool _sceneHookRegistered;

		private static int _lastCameraTickFrame = -1;

		private static int _lastCameraTickInstanceId = int.MinValue;

		private bool _intentionalDestroy;

		public static void Install(FeatureBootstrapper featureBootstrapper, Action shutdown)
		{
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Expected O, but got Unknown
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Expected O, but got Unknown
			_featureBootstrapper = featureBootstrapper ?? throw new ArgumentNullException("featureBootstrapper");
			_shutdown = shutdown ?? throw new ArgumentNullException("shutdown");
			_applicationQuitting = false;
			if (!_sceneHookRegistered)
			{
				SceneManager.sceneLoaded += OnSceneLoaded;
				SceneManager.sceneUnloaded += OnSceneUnloaded;
				CameraCallback onPreCull = Camera.onPreCull;
				object obj = <>O.<2>__OnCameraPreCull;
				if (obj == null)
				{
					CameraCallback val = OnCameraPreCull;
					<>O.<2>__OnCameraPreCull = val;
					obj = (object)val;
				}
				Camera.onPreCull = (CameraCallback)Delegate.Combine((Delegate?)(object)onPreCull, (Delegate?)obj);
				RenderPipelineManager.beginCameraRendering += OnBeginCameraRendering;
				_sceneHookRegistered = true;
			}
			EnsureInstalled();
		}

		public static void EnsureInstalled()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			if (!_applicationQuitting && _featureBootstrapper != null && !((Object)(object)_instance != (Object)null))
			{
				GameObject val = new GameObject("Enhanced Spectator Runtime");
				Object.DontDestroyOnLoad((Object)val);
				_instance = val.AddComponent<EnhancedSpectatorRuntimeDriver>();
			}
		}

		public static void BeginShutdown()
		{
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Expected O, but got Unknown
			//IL_006a: 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_0075: Expected O, but got Unknown
			_applicationQuitting = true;
			RuntimeConnectionState.MarkPluginShuttingDown();
			if (_sceneHookRegistered)
			{
				SceneManager.sceneLoaded -= OnSceneLoaded;
				SceneManager.sceneUnloaded -= OnSceneUnloaded;
				CameraCallback onPreCull = Camera.onPreCull;
				object obj = <>O.<2>__OnCameraPreCull;
				if (obj == null)
				{
					CameraCallback val = OnCameraPreCull;
					<>O.<2>__OnCameraPreCull = val;
					obj = (object)val;
				}
				Camera.onPreCull = (CameraCallback)Delegate.Remove((Delegate?)(object)onPreCull, (Delegate?)obj);
				RenderPipelineManager.beginCameraRendering -= OnBeginCameraRendering;
				_sceneHookRegistered = false;
			}
			if ((Object)(object)_instance != (Object)null)
			{
				_instance._intentionalDestroy = true;
				Object.Destroy((Object)(object)((Component)_instance).gameObject);
				_instance = null;
			}
			_featureBootstrapper = null;
			_shutdown = null;
			_lastCameraTickFrame = -1;
			_lastCameraTickInstanceId = int.MinValue;
		}

		private void Update()
		{
			_featureBootstrapper?.Tick();
		}

		private void LateUpdate()
		{
			_featureBootstrapper?.LateTick();
		}

		private void OnGUI()
		{
			_featureBootstrapper?.GuiTick();
		}

		private void OnApplicationQuit()
		{
			_applicationQuitting = true;
			RuntimeConnectionState.MarkApplicationQuitting();
			_shutdown?.Invoke();
			_shutdown = null;
		}

		private void OnDestroy()
		{
			if ((Object)(object)_instance == (Object)(object)this)
			{
				_instance = null;
			}
			if (!_applicationQuitting && !_intentionalDestroy)
			{
				ModLog.Warning("Runtime driver destroyed before application quit; it will be recreated on the next plugin or scene lifecycle event.");
			}
		}

		private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			RuntimeConnectionState.MarkSceneTransition();
			EnsureInstalled();
		}

		private static void OnSceneUnloaded(Scene scene)
		{
			RuntimeConnectionState.MarkSceneTransition();
		}

		private static void OnCameraPreCull(Camera camera)
		{
			TryCameraPreCullTick(camera);
		}

		private static void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
		{
			TryCameraPreCullTick(camera);
		}

		private static void TryCameraPreCullTick(Camera camera)
		{
			if (_featureBootstrapper != null && !((Object)(object)camera == (Object)null))
			{
				int frameCount = Time.frameCount;
				int instanceID = ((Object)camera).GetInstanceID();
				if (_lastCameraTickFrame != frameCount || _lastCameraTickInstanceId != instanceID)
				{
					_lastCameraTickFrame = frameCount;
					_lastCameraTickInstanceId = instanceID;
					_featureBootstrapper.CameraPreCullTick(camera);
				}
			}
		}
	}
	public interface IRuntimeCameraPreCullTickable
	{
		void CameraPreCullTick(Camera camera);
	}
	public interface IRuntimeGuiTickable
	{
		void GuiTick();
	}
	public interface IRuntimeLateTickable
	{
		void LateTick();
	}
	public interface IRuntimeTickable
	{
		void Tick();
	}
	public static class RuntimeConnectionState
	{
		private const float SceneTransitionUnsafeSeconds = 0.5f;

		private static bool _applicationQuitting;

		private static bool _pluginShuttingDown;

		private static float _unsafeUntilRealtime;

		public static void Reset()
		{
			_applicationQuitting = false;
			_pluginShuttingDown = false;
			_unsafeUntilRealtime = 0f;
		}

		public static void MarkApplicationQuitting()
		{
			_applicationQuitting = true;
			MarkUnsafeWindow();
		}

		public static void MarkPluginShuttingDown()
		{
			_pluginShuttingDown = true;
			MarkUnsafeWindow();
		}

		public static void MarkSceneTransition()
		{
			MarkUnsafeWindow();
		}

		public static bool CanUseModNetworking(out string reason)
		{
			if (IsLifecycleUnsafe(out reason))
			{
				return false;
			}
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null)
			{
				reason = "NetworkManager unavailable";
				return false;
			}
			if (singleton.ShutdownInProgress)
			{
				reason = "NetworkManager shutdown in progress";
				return false;
			}
			if (!singleton.IsListening)
			{
				reason = "NetworkManager is not listening";
				return false;
			}
			if (!singleton.IsClient && !singleton.IsHost)
			{
				reason = "not connected as client or host";
				return false;
			}
			if (!singleton.IsConnectedClient && !singleton.IsHost)
			{
				reason = "local client is not connected";
				return false;
			}
			if (singleton.CustomMessagingManager == null)
			{
				reason = "CustomMessagingManager unavailable";
				return false;
			}
			reason = string.Empty;
			return true;
		}

		public static bool CanRunLocalDiagnostics(out string reason)
		{
			if (IsLifecycleUnsafe(out reason))
			{
				return false;
			}
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton != (Object)null && singleton.ShutdownInProgress)
			{
				reason = "NetworkManager shutdown in progress";
				return false;
			}
			reason = string.Empty;
			return true;
		}

		public static bool CanRepairVanillaPlayerState(out string reason)
		{
			if (IsLifecycleUnsafe(out reason))
			{
				return false;
			}
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton != (Object)null && singleton.ShutdownInProgress)
			{
				reason = "NetworkManager shutdown in progress";
				return false;
			}
			reason = string.Empty;
			return true;
		}

		public static bool ShouldSkipVanillaSpectatorTargetSwitch(out string reason)
		{
			if (IsLifecycleUnsafe(out reason))
			{
				return true;
			}
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null)
			{
				reason = "NetworkManager unavailable";
				return true;
			}
			if (singleton.ShutdownInProgress)
			{
				reason = "NetworkManager shutdown in progress";
				return true;
			}
			if (!singleton.IsListening || (!singleton.IsClient && !singleton.IsHost))
			{
				reason = "network is disconnected";
				return true;
			}
			reason = string.Empty;
			return false;
		}

		private static bool IsLifecycleUnsafe(out string reason)
		{
			if (_applicationQuitting)
			{
				reason = "application is quitting";
				return true;
			}
			if (_pluginShuttingDown)
			{
				reason = "plugin is shutting down";
				return true;
			}
			if (Time.realtimeSinceStartup < _unsafeUntilRealtime)
			{
				reason = "scene transition unsafe window";
				return true;
			}
			reason = string.Empty;
			return false;
		}

		private static void MarkUnsafeWindow()
		{
			float num = Time.realtimeSinceStartup + 0.5f;
			if (num > _unsafeUntilRealtime)
			{
				_unsafeUntilRealtime = num;
			}
		}
	}
}
namespace EnhancedSpectator.Patching
{
	public interface IPatchModule
	{
		void Register(Harmony harmony);

		void Unregister(Harmony harmony);
	}
	public sealed class PatchBootstrapper : IDisposable
	{
		private readonly Harmony _harmony = new Harmony("Auuueser.EnhancedSpectator");

		private readonly List<IPatchModule> _modules = new List<IPatchModule>
		{
			new SpectatorLifecyclePatchModule()
		};

		private bool _registered;

		public void Register()
		{
			if (_registered)
			{
				return;
			}
			foreach (IPatchModule module in _modules)
			{
				module.Register(_harmony);
			}
			_registered = true;
			ModLog.Debug("Patch modules registered.");
		}

		public void Dispose()
		{
			if (_registered)
			{
				for (int num = _modules.Count - 1; num >= 0; num--)
				{
					_modules[num].Unregister(_harmony);
				}
				_harmony.UnpatchSelf();
				_registered = false;
				ModLog.Debug("Patch modules unregistered.");
			}
		}
	}
	public sealed class SpectatorLifecyclePatchModule : IPatchModule
	{
		[HarmonyPatch(typeof(PlayerControllerB), "KillPlayer", new Type[]
		{
			typeof(Vector3),
			typeof(bool),
			typeof(CauseOfDeath),
			typeof(int),
			typeof(Vector3),
			typeof(bool)
		})]
		private static class PlayerKillPatch
		{
			private static void Postfix(PlayerControllerB __instance)
			{
				if (IsLocalPlayerOrOwner(__instance))
				{
					SpectatorLifecycleEvents.Raise(SpectatorLifecycleEventKind.PlayerDied);
				}
			}
		}

		[HarmonyPatch(typeof(PlayerControllerB), "SetSpectatedPlayerEffects", new Type[] { typeof(bool) })]
		private static class SpectatedPlayerEffectsPatch
		{
			private static void Postfix(PlayerControllerB __instance)
			{
				if (IsLocalSpectatorContext(__instance))
				{
					SpectatorLifecycleEvents.Raise(SpectatorLifecycleEventKind.SpectatedPlayerEffectsApplied);
				}
			}
		}

		[HarmonyPatch(typeof(StartOfRound), "SwitchCamera", new Type[] { typeof(Camera) })]
		private static class SwitchCameraPatch
		{
			private static void Postfix()
			{
				SpectatorLifecycleEvents.Raise(SpectatorLifecycleEventKind.CameraSwitched);
			}
		}

		[HarmonyPatch(typeof(StartOfRound), "SetSpectateCameraToGameOverMode", new Type[]
		{
			typeof(bool),
			typeof(PlayerControllerB)
		})]
		private static class GameOverSpectateModePatch
		{
			private static void Postfix()
			{
				SpectatorLifecycleEvents.Raise(SpectatorLifecycleEventKind.GameOverOverrideChanged);
			}
		}

		[HarmonyPatch(typeof(StartOfRound), "ReviveDeadPlayers")]
		private static class ReviveDeadPlayersPatch
		{
			private static void Postfix()
			{
				SpectatorLifecycleEvents.Raise(SpectatorLifecycleEventKind.Revived);
			}
		}

		[HarmonyPatch(typeof(StartOfRound), "OnPlayerConnectedClientRpc", new Type[]
		{
			typeof(ulong),
			typeof(int),
			typeof(ulong[]),
			typeof(int),
			typeof(int),
			typeof(int),
			typeof(int),
			typeof(int),
			typeof(int),
			typeof(int),
			typeof(bool)
		})]
		private static class PlayerConnectedPatch
		{
			private static int _nextDebugFrame;

			private static void Postfix()
			{
				RestoreLocalDeadSpectatorStateAfterPlayerJoin();
			}

			private static void RestoreLocalDeadSpectatorStateAfterPlayerJoin()
			{
				if (!RuntimeConnectionState.CanRepairVanillaPlayerState(out string reason))
				{
					LogDebug("Skipped local spectator state restore after player connect: " + reason + ".");
					return;
				}
				StartOfRound instance = StartOfRound.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					return;
				}
				PlayerControllerB localPlayerController = instance.localPlayerController;
				if (!((Object)(object)localPlayerController == (Object)null) && IsLocalPlayerOrOwner(localPlayerController) && localPlayerController.isPlayerDead && localPlayerController.isPlayerControlled)
				{
					localPlayerController.isPlayerControlled = false;
					Camera spectateCamera = instance.spectateCamera;
					if ((Object)(object)spectateCamera != (Object)null && (Object)(object)instance.activeCamera != (Object)(object)spectateCamera)
					{
						instance.SwitchCamera(spectateCamera);
					}
					LogDebug("Restored local dead spectator state after vanilla player-connect sync.");
				}
			}

			private static void LogDebug(string message)
			{
				if (Time.frameCount >= _nextDebugFrame)
				{
					_nextDebugFrame = Time.frameCount + 120;
					ModLog.Debug(message);
				}
			}
		}

		[HarmonyPatch(typeof(PlayerControllerB), "SpectateNextPlayer", new Type[] { typeof(bool) })]
		private static class SpectateNextPlayerPatch
		{
			private static int _nextSuppressDebugFrame;

			private static int _nextUnsafeDebugFrame;

			private static bool Prefix(PlayerControllerB __instance)
			{
				if (!TryGetLocalDeadSpectatorContext(__instance, out string reason))
				{
					if (reason.Length > 0 && Time.frameCount >= _nextUnsafeDebugFrame)
					{
						_nextUnsafeDebugFrame = Time.frameCount + 120;
						ModLog.Debug("Vanilla spectator target switch suppression skipped: " + reason + ".");
					}
					return true;
				}
				if (RuntimeConnectionState.ShouldSkipVanillaSpectatorTargetSwitch(out string reason2))
				{
					if (Time.frameCount >= _nextUnsafeDebugFrame)
					{
						_nextUnsafeDebugFrame = Time.frameCount + 120;
						ModLog.Debug("Skipped vanilla spectator target switch during unsafe lifecycle: " + reason2 + ".");
					}
					return false;
				}
				if (!TryGetLocalSpectatorContext(__instance, out string reason3))
				{
					if (reason3.Length > 0 && Time.frameCount >= _nextUnsafeDebugFrame)
					{
						_nextUnsafeDebugFrame = Time.frameCount + 120;
						ModLog.Debug("Vanilla spectator target switch suppression skipped: " + reason3 + ".");
					}
					return true;
				}
				if (SpectatorVanillaInputGuard.ShouldSuppressTargetSwitchInput(out string reason4))
				{
					if (Time.frameCount >= _nextSuppressDebugFrame)
					{
						_nextSuppressDebugFrame = Time.frameCount + 120;
						ModLog.Debug("Suppressed vanilla spectator target switch while " + ((reason4.Length > 0) ? reason4 : "quick menu is open") + ".");
					}
					return false;
				}
				return true;
			}

			private static bool TryGetLocalDeadSpectatorContext(PlayerControllerB player, out string reason)
			{
				reason = string.Empty;
				if ((Object)(object)player == (Object)null)
				{
					reason = "patch instance unavailable";
					return false;
				}
				StartOfRound instance = StartOfRound.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					reason = "round unavailable";
					return false;
				}
				PlayerControllerB localPlayerController = instance.localPlayerController;
				if ((Object)(object)localPlayerController == (Object)null)
				{
					reason = "local player unavailable";
					return false;
				}
				if (!IsLocalPlayerOrOwner(player, localPlayerController))
				{
					return false;
				}
				if (!localPlayerController.isPlayerDead)
				{
					return false;
				}
				return true;
			}

			private static bool TryGetLocalSpectatorContext(PlayerControllerB player, out string reason)
			{
				reason = string.Empty;
				if ((Object)(object)player == (Object)null)
				{
					reason = "patch instance unavailable";
					return false;
				}
				StartOfRound instance = StartOfRound.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					reason = "round unavailable";
					return false;
				}
				PlayerControllerB localPlayerController = instance.localPlayerController;
				if ((Object)(object)localPlayerController == (Object)null)
				{
					reason = "local player unavailable";
					return false;
				}
				if (!IsLocalPlayerOrOwner(player, localPlayerController))
				{
					return false;
				}
				if (!localPlayerController.isPlayerDead)
				{
					return false;
				}
				if ((Object)(object)instance.spectateCamera == (Object)null || (Object)(object)localPlayerController.spectatedPlayerScript == (Object)null)
				{
					reason = "spectator camera or target unavailable";
					return false;
				}
				return true;
			}
		}

		[HarmonyPatch(typeof(PlayerControllerB), "Interact_performed", new Type[] { typeof(CallbackContext) })]
		private static class InteractPerformedPatch
		{
			private static int _nextSuppressDebugFrame;

			private static bool Prefix(PlayerControllerB __instance)
			{
				if (!ShouldSuppressQuickMenuGameplayInput(__instance, out string reason))
				{
					return true;
				}
				LogQuickMenuGameplaySuppression("interact", reason, ref _nextSuppressDebugFrame);
				return false;
			}
		}

		[HarmonyPatch(typeof(PlayerControllerB), "ActivateItem_performed", new Type[] { typeof(CallbackContext) })]
		private static class ActivateItemPerformedPatch
		{
			private static int _nextSuppressDebugFrame;

			private static bool Prefix(PlayerControllerB __instance)
			{
				if (!ShouldSuppressQuickMenuGameplayInput(__instance, out string reason))
				{
					return true;
				}
				LogQuickMenuGameplaySuppression("activate item", reason, ref _nextSuppressDebugFrame);
				return false;
			}
		}

		public void Register(Harmony harmony)
		{
			harmony.CreateClassProcessor(typeof(PlayerKillPatch)).Patch();
			harmony.CreateClassProcessor(typeof(SpectatedPlayerEffectsPatch)).Patch();
			harmony.CreateClassProcessor(typeof(SwitchCameraPatch)).Patch();
			harmony.CreateClassProcessor(typeof(GameOverSpectateModePatch)).Patch();
			harmony.CreateClassProcessor(typeof(ReviveDeadPlayersPatch)).Patch();
			harmony.CreateClassProcessor(typeof(PlayerConnectedPatch)).Patch();
			harmony.CreateClassProcessor(typeof(SpectateNextPlayerPatch)).Patch();
			harmony.CreateClassProcessor(typeof(InteractPerformedPatch)).Patch();
			harmony.CreateClassProcessor(typeof(ActivateItemPerformedPatch)).Patch();
		}

		public void Unregister(Harmony harmony)
		{
		}

		private static bool IsLocalSpectatorContext(PlayerControllerB player)
		{
			if (IsLocalPlayerOrOwner(player))
			{
				return true;
			}
			PlayerControllerB localPlayer = GetLocalPlayer();
			if ((Object)(object)localPlayer != (Object)null && localPlayer.isPlayerDead)
			{
				return (Object)(object)localPlayer.spectatedPlayerScript == (Object)(object)player;
			}
			return false;
		}

		private static bool IsLocalPlayerOrOwner(PlayerControllerB player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return false;
			}
			if ((Object)(object)GetLocalPlayer() == (Object)(object)player)
			{
				return true;
			}
			if (!((NetworkBehaviour)player).IsOwner)
			{
				return ((NetworkBehaviour)player).IsLocalPlayer;
			}
			return true;
		}

		private static bool IsLocalPlayerOrOwner(PlayerControllerB player, PlayerControllerB localPlayer)
		{
			if ((Object)(object)player == (Object)null)
			{
				return false;
			}
			if ((Object)(object)localPlayer == (Object)(object)player)
			{
				return true;
			}
			if (!((NetworkBehaviour)player).IsOwner)
			{
				return ((NetworkBehaviour)player).IsLocalPlayer;
			}
			return true;
		}

		private static PlayerControllerB? GetLocalPlayer()
		{
			StartOfRound instance = StartOfRound.Instance;
			if (!((Object)(object)instance != (Object)null))
			{
				return null;
			}
			return instance.localPlayerController;
		}

		private static bool ShouldSuppressQuickMenuGameplayInput(PlayerControllerB player, out string reason)
		{
			reason = string.Empty;
			if (!SpectatorVanillaInputGuard.ShouldSuppressGameplayInteractInput() && !IsLocalQuickMenuOpen())
			{
				return false;
			}
			PlayerControllerB localPlayer = GetLocalPlayer();
			if ((Object)(object)localPlayer == (Object)null)
			{
				reason = "local player unavailable";
				return false;
			}
			if (!IsLocalPlayerOrOwner(player, localPlayer))
			{
				return false;
			}
			reason = "quick menu is open";
			return true;
		}

		private static bool IsLocalQuickMenuOpen()
		{
			PlayerControllerB localPlayer = GetLocalPlayer();
			if ((Object)(object)localPlayer != (Object)null && (Object)(object)localPlayer.quickMenuManager != (Object)null)
			{
				return localPlayer.quickMenuManager.isMenuOpen;
			}
			return false;
		}

		private static void LogQuickMenuGameplaySuppression(string inputName, string reason, ref int nextSuppressDebugFrame)
		{
			if (Time.frameCount >= nextSuppressDebugFrame)
			{
				nextSuppressDebugFrame = Time.frameCount + 120;
				ModLog.Debug("Suppressed local " + inputName + " input while " + reason + ".");
			}
		}
	}
}
namespace EnhancedSpectator.Networking
{
	public sealed class EnhancedSpectatorNetworkService : IEnhancedSpectatorNetworkService
	{
		private const float CapabilityStableDelaySeconds = 0.35f;

		private const int CapabilityStableDelayFrames = 3;

		private const float CompatiblePeerProbeTimeoutSeconds = 2.5f;

		private const float CapabilityProbeRetryIntervalSeconds = 2.5f;

		private const float SpectatorPoseHeartbeatIntervalSeconds = 0.5f;

		private readonly EnhancedSpectatorConfig _config;

		private readonly ISpectatorTargetStateProvider _spectatorTargetStateProvider;

		private readonly ISpectatorPoseStateProvider _spectatorPoseStateProvider;

		private readonly IPeerIdentityStateProvider _peerIdentityStateProvider;

		private readonly IVoiceActivityProvider _voiceActivityProvider;

		private readonly IModNetworkTransport _transport;

		private readonly INetworkRuntimeState _runtimeState;

		private readonly RemotePeerRegistry _peerRegistry;

		private readonly RemoteSpectatorTargetRegistry _remoteTargetRegistry;

		private readonly RemoteSpectatorPoseRegistry _remotePoseRegistry;

		private readonly RemotePeerIdentityRegistry _remoteIdentityRegistry;

		private readonly RemoteVoiceActivityRegistry _remoteVoiceActivityRegistry;

		private readonly VoiceActivityDebugLimiter _voiceDebugLimiter;

		private SpectatorTargetState? _lastObservedTargetState;

		private SpectatorTargetState? _lastSentTargetState;

		private SpectatorTargetState? _pendingTargetState;

		private SpectatorPoseState? _lastObservedPoseState;

		private SpectatorPoseState? _lastSentPoseState;

		private SpectatorPoseState? _pendingPoseState;

		private bool _pendingPoseRefresh;

		private VoiceActivityState? _lastObservedVoiceActivityState;

		private VoiceActivityState? _lastSentVoiceActivityState;

		private VoiceActivityState? _pendingVoiceActivityState;

		private PeerIdentityState? _lastSentIdentityState;

		private bool _pendingVoiceActivityRefresh;

		private bool _initialized;

		private bool _networkAvailable;

		private bool _targetSyncReady;

		private bool _hasCompatibleModPeer;

		private bool _noCompatiblePeerLocalOnly;

		private bool _capabilitySent;

		private float _capabilityProbeSentRealtime;

		private ulong? _lastLocalClientId;

		private float _nextTargetSyncTime;

		private float _nextPoseSyncTime;

		private float _nextVoiceActivitySyncTime;

		private float _nextTargetSampleTime;

		private float _nextPoseSampleTime;

		private float _nextPoseRefreshTime;

		private float _nextVoiceActivitySampleTime;

		private float _nextVoiceActivityRefreshTime;

		private float _nextPeerPruneTime;

		private float _transportRegisteredRealtime;

		private int _transportRegisteredFrame;

		private int _nextLifecycleDebugFrame;

		private int _nextCapabilityDelayDebugFrame;

		private string? _lastDegradationReason;

		private string? _lastTargetSyncWaitReason;

		private NetworkLifecycleState _lifecycleState;

		public bool IsNetworkAvailable => _networkAvailable;

		public bool IsTargetSyncEnabled => _targetSyncReady;

		public bool HasCompatibleModPeer => _hasCompatibleModPeer;

		public NetworkLifecycleState LifecycleState => _lifecycleState;

		public int RemotePeerIdentityRevision => _remoteIdentityRegistry.Revision;

		public int RemoteSpectatorTargetRevision => _remoteTargetRegistry.Revision;

		public EnhancedSpectatorNetworkService(EnhancedSpectatorConfig config, SpectatorModule spectatorModule)
		{
			EnhancedSpectatorConfig config2 = config;
			this..ctor(config2, spectatorModule, spectatorModule, spectatorModule, new LethalCompanyVoiceActivityProvider(), new UnityNetcodeMessagingTransport(() => config2.DebugNetworkMessages.Value), UnityNetworkRuntimeState.Instance);
		}

		public EnhancedSpectatorNetworkService(EnhancedSpectatorConfig config, ISpectatorTargetStateProvider spectatorTargetStateProvider, ISpectatorPoseStateProvider spectatorPoseStateProvider, IPeerIdentityStateProvider peerIdentityStateProvider, IVoiceActivityProvider voiceActivityProvider, IModNetworkTransport transport)
			: this(config, spectatorTargetStateProvider, spectatorPoseStateProvider, peerIdentityStateProvider, voiceActivityProvider, transport, UnityNetworkRuntimeState.Instance)
		{
		}

		public EnhancedSpectatorNetworkService(EnhancedSpectatorConfig config, ISpectatorTargetStateProvider spectatorTargetStateProvider, ISpectatorPoseStateProvider spectatorPoseStateProvider, IPeerIdentityStateProvider peerIdentityStateProvider, IVoiceActivityProvider voiceActivityProvider, IModNetworkTransport transport, INetworkRuntimeState runtimeState)
		{
			_peerRegistry = new RemotePeerRegistry();
			_remoteTargetRegistry = new RemoteSpectatorTargetRegistry();
			_remotePoseRegistry = new RemoteSpectatorPoseRegistry();
			_remoteIdentityRegistry = new RemotePeerIdentityRegistry();
			_remoteVoiceActivityRegistry = new RemoteVoiceActivityRegistry();
			_voiceDebugLimiter = new VoiceActivityDebugLimiter();
			_capabilityProbeSentRealtime = -1f;
			_transportRegisteredFrame = -1;
			base..ctor();
			_config = config ?? throw new ArgumentNullException("config");
			_spectatorTargetStateProvider = spectatorTargetStateProvider ?? throw new ArgumentNullException("spectatorTargetStateProvider");
			_spectatorPoseStateProvider = spectatorPoseStateProvider ?? throw new ArgumentNullException("spectatorPoseStateProvider");
			_peerIdentityStateProvider = peerIdentityStateProvider ?? throw new ArgumentNullException("peerIdentityStateProvider");
			_voiceActivityProvider = voiceActivityProvider ?? throw new ArgumentNullException("voiceActivityProvider");
			_transport = transport ?? throw new ArgumentNullException("transport");
			_runtimeState = runtimeState ?? throw new ArgumentNullException("runtimeState");
		}

		public void Initialize()
		{
			if (!_initialized)
			{
				_initialized = true;
				_lifecycleState = NetworkLifecycleState.LocalOnly;
				ModLog.Debug("Networking service initialized.");
			}
		}

		public void Tick()
		{
			if (!_initialized)
			{
				return;
			}
			try
			{
				string reason;
				if (!_config.EnableNetworking.Value)
				{
					Degrade("networking config disabled");
				}
				else if (!_runtimeState.CanUseModNetworking(out reason))
				{
					StopNetworkingForLifecycle(reason);
				}
				else if (EnsureTransportRegistered())
				{
					_networkAvailable = true;
					_lifecycleState = NetworkLifecycleState.TransportRegistered;
					RegisterLocalCapability();
					PruneDisconnectedPeers();
					SendLocalCapabilityIfNeeded();
					UpdateTargetSyncReadiness();
					UpdateCompatiblePeerProbeState();
					if (!NetworkCompatibilityPolicy.ShouldRunBusinessSync(_lifecycleState, _targetSyncReady))
					{
						ClearBusinessSyncState();
						return;
					}
					UpdateAndSendLocalPeerIdentity();
					UpdateLocalSpectatorTargetIfDue();
					UpdateLocalSpectatorPoseIfDue();
					UpdateLocalVoiceActivityIfDue();
					UpdateTargetSyncReadiness();
					TrySendPendingTargetState();
					TrySendPendingPoseState();
					TrySendPendingVoiceActivityState();
				}
			}
			catch (Exception ex)
			{
				Degrade("networking exception: " + ex.GetType().Name);
				ModLog.Error($"Enhanced Spectator networking failed and degraded to local-only mode: {ex}");
			}
		}

		public void Dispose()
		{
			_transport.Dispose();
			ClearNetworkState();
			_initialized = false;
			_lifecycleState = NetworkLifecycleState.Disposed;
			ModLog.Debug("Networking service disposed.");
		}

		public bool TryGetPeerCapability(ulong clientId, out ModPeerCapability capability)
		{
			return _peerRegistry.TryGetCapability(clientId, out capability);
		}

		public bool TryGetRemoteSpectatorTarget(ulong clientId, out SpectatorTargetState state)
		{
			return _remoteTargetRegistry.TryGet(clientId, out state);
		}

		public bool TryGetRemoteSpectatorPose(ulong clientId, out SpectatorPoseState state)
		{
			return _remotePoseRegistry.TryGet(clientId, out state);
		}

		public bool TryGetRemotePeerIdentity(ulong clientId, out PeerIdentityState state)
		{
			return _remoteIdentityRegistry.TryGet(clientId, out state);
		}

		public bool TryGetRemoteVoiceActivity(ulong clientId, out VoiceActivityState state)
		{
			if (!_remoteVoiceActivityRegistry.TryGet(clientId, out state, out var receivedAtTicks))
			{
				return false;
			}
			long ticks = TimeSpan.FromSeconds(Mathf.Max(0f, _config.VoiceActivityStaleSeconds.Value)).Ticks;
			if (VoiceActivitySyncRules.IsFresh(receivedAtTicks, _runtimeState.UtcNowTicks, ticks))
			{
				return true;
			}
			DebugVoice($"Remote voice activity expired: peer={clientId}.");
			_remoteVoiceActivityRegistry.Remove(clientId);
			state = VoiceActivityState.NoData;
			return false;
		}

		public IReadOnlyList<SpectatorTargetState> GetRemoteSpectatorTargets()
		{
			return _remoteTargetRegistry.GetSnapshot().AsReadOnly();
		}

		public void CopyRemoteSpectatorTargetsTo(List<SpectatorTargetState> destination)
		{
			if (destination == null)
			{
				throw new ArgumentNullException("destination");
			}
			_remoteTargetRegistry.CopySnapshotTo(destination);
		}

		public IReadOnlyList<SpectatorPoseState> GetRemoteSpectatorPoses()
		{
			return _remotePoseRegistry.GetSnapshot().AsReadOnly();
		}

		public IReadOnlyList<PeerIdentityState> GetRemotePeerIdentities()
		{
			return _remoteIdentityRegistry.GetSnapshot().AsReadOnly();
		}

		public void CopyRemotePeerIdentitiesTo(List<PeerIdentityState> destination)
		{
			if (destination == null)
			{
				throw new ArgumentNullException("destination");
			}
			_remoteIdentityRegistry.CopySnapshotTo(destination);
		}

		public IReadOnlyList<VoiceActivityState> GetRemoteVoiceActivities()
		{
			return _remoteVoiceActivityRegistry.GetSnapshot().AsReadOnly();
		}

		public IReadOnlyList<ModPeerCapability> GetKnownModdedPeers()
		{
			return _peerRegistry.GetCapabilitiesSnapshot().AsReadOnly();
		}

		private bool EnsureTransportRegistered()
		{
			if (_transport.IsRegistered)
			{
				if (!_transport.IsNetworkAvailable)
				{
					Degrade("custom messaging transport no longer available");
					return false;
				}
				return true;
			}
			if (!_transport.TryRegister(OnCapabilityReceived, OnSpectatorTargetReceived, OnSpectatorPoseReceived, OnPeerIdentityReceived, OnVoiceActivityReceived, out string reason))
			{
				Degrade(reason);
				return false;
			}
			_capabilitySent = false;
			_capabilityProbeSentRealtime = -1f;
			_noCompatiblePeerLocalOnly = false;
			_lastLocalClientId = _transport.LocalClientId;
			_transportRegisteredFrame = _runtimeState.FrameCount;
			_transportRegisteredRealtime = _runtimeState.RealtimeSinceStartup;
			_lastDegradationReason = null;
			Debug("Custom messaging transport registered.");
			return true;
		}

		private void RegisterLocalCapability()
		{
			ulong localClientId = _transport.LocalClientId;
			if (_lastLocalClientId.HasValue && _lastLocalClientId.Value != localClientId)
			{
				_peerRegistry.Clear();
				_remoteTargetRegistry.Clear();
				_remotePoseRegistry.Clear();
				_remoteIdentityRegistry.Clear();
				_remoteVoiceActivityRegistry.Clear();
				_lastObservedTargetState = null;
				_lastSentTargetState = null;
				_pendingTargetState = null;
				_lastObservedPoseState = null;
				_lastSentPoseState = null;
				_pendingPoseState = null;
				_pendingPoseRefresh = false;
				_lastObservedVoiceActivityState = null;
				_lastSentVoiceActivityState = null;
				_pendingVoiceActivityState = null;
				_pendingVoiceActivityRefresh = false;
				_lastSentIdentityState = null;
				_nextTargetSyncTime = 0f;
				_nextPoseSyncTime = 0f;
				_nextVoiceActivitySyncTime = 0f;
				_nextTargetSampleTime = 0f;
				_nextPoseSampleTime = 0f;
				_nextPoseRefreshTime = 0f;
				_nextVoiceActivitySampleTime = 0f;
				_nextVoiceActivityRefreshTime = 0f;
				_capabilitySent = false;
				_capabilityProbeSentRealtime = -1f;
				_noCompatiblePeerLocalOnly = false;
				_transportRegisteredFrame = _runtimeState.FrameCount;
				_transportRegisteredRealtime = _runtimeState.RealtimeSinceStartup;
				Debug($"Local Netcode client id changed from {_lastLocalClientId.Value} to {localClientId}; reset network state.");
			}
			_lastLocalClientId = localClientId;
			_peerRegistry.RegisterLocal(CreateLocalCapability(localClientId));
		}

		private void SendLocalCapabilityIfNeeded()
		{
			if (!_config.EnableCapabilityHandshake.Value)
			{
				_capabilitySent = false;
			}
			else
			{
				if (_capabilitySent && !NetworkCompatibilityPolicy.ShouldRetryCapabilityProbe(_targetSyncReady, _capabilitySent, _capabilityProbeSentRealtime, _runtimeState.RealtimeSinceStartup, 2.5f))
				{
					return;
				}
				if (!_runtimeState.CanUseModNetworking(out string reason))
				{
					StopNetworkingForLifecycle(reason);
					return;
				}
				if (!IsTransportStableForCapability(out string reason2))
				{
					DebugCapabilityDelay(reason2);
					return;
				}
				ModPeerCapability modPeerCapability = CreateLocalCapability(_transport.LocalClientId);
				_peerRegistry.RegisterLocal(modPeerCapability);
				if (_transport.SendCapability(modPeerCapability, null, out string reason3))
				{
					_capabilitySent = true;
					_capabilityProbeSentRealtime = _runtimeState.RealtimeSinceStartup;
					_noCompatiblePeerLocalOnly = false;
					_lifecycleState = NetworkLifecycleState.TransportRegistered;
					Debug($"Capability sent: client={modPeerCapability.ClientId}, targetSync={modPeerCapability.SupportsSpectatorTargetSync}, voiceSync={modPeerCapability.SupportsVoiceActivitySync}, voiceRoute={modPeerCapability.SupportsSpectatorVoiceToTarget}.");
				}
				else
				{
					Degrade("capability send failed: " + reason3);
				}
			}
		}

		private bool IsTransportStableForCapability(out string reason)
		{
			int num = _runtimeState.FrameCount - _transportRegisteredFrame;
			if (_transportRegisteredFrame < 0 || num < 3)
			{
				reason = $"transport registered {Mathf.Max(0, num)} frame(s) ago";
				return false;
			}
			float num2 = _runtimeState.RealtimeSinceStartup - _transportRegisteredRealtime;
			if (num2 < 0.35f)
			{
				reason = $"transport registered {num2:0.00}s ago";
				return false;
			}
			reason = string.Empty;
			return true;
		}

		private void UpdateAndSendLocalPeerIdentity()
		{
			if (!_capabilitySent || !_targetSyncReady || !_peerIdentityStateProvider.TryGetLocalPeerIdentity(out PeerIdentityState state) || string.IsNullOrWhiteSpace(state.DisplayName) || IdentityEquals(_lastSentIdentityState, state))
			{
				return;
			}
			List<ulong> spectatorTargetSyncPeerIds = _peerRegistry.GetSpectatorTargetSyncPeerIds(_transport.LocalClientId);
			if (spectatorTargetSyncPeerIds.Count != 0)
			{
				if (_transport.SendPeerIdentity(state, spectatorTargetSyncPeerIds, out string reason))
				{
					_lastSentIdentityState = state;
					Debug($"Peer identity sent: client={state.ClientId}, slot={state.PlayerSlotId}, name={state.DisplayName}, voiceName={FormatVoiceName(state.VoicePlayerName)}.");
				}
				else
				{
					Degrade("peer identity send failed: " + reason);
				}
			}
		}

		private void PruneDisconnectedPeers(bool force = false)
		{
			if (!force && _runtimeState.UnscaledTime < _nextPeerPruneTime)
			{
				return;
			}
			_nextPeerPruneTime = _runtimeState.UnscaledTime + 1f;
			if (!_lastLocalClientId.HasValue)
			{
				return;
			}
			foreach (ulong remotePeerId in _peerRegistry.GetRemotePeerIds(_lastLocalClientId.Value))
			{
				if ((_transport.IsHost || !_peerRegistry.IsRelayedPeer(remotePeerId)) && !_transport.IsPeerConnected(remotePeerId))
				{
					RelayDisconnectedPeerCleanup(remotePeerId);
					_peerRegistry.Remove(remotePeerId);
					_remoteTargetRegistry.Remove(remotePeerId);
					_remotePoseRegistry.Remove(remotePeerId);
					_remoteIdentityRegistry.Remove(remotePeerId);
					_remoteVoiceActivityRegistry.Remove(remotePeerId);
					Debug($"Removed disconnected network peer state: peer={remotePeerId}.");
				}
			}
		}

		private void UpdateTargetSyncReadiness()
		{
			string text = null;
			if (!_config.EnableCapabilityHandshake.Value)
			{
				text = "capability handshake disabled";
			}
			else if (!_config.EnableSpectatorTargetSync.Value)
			{
				text = "spectator target sync disabled";
			}
			else if (!_transport.IsRegistered)
			{
				text = "custom messaging transport not registered";
			}
			else if (!_peerRegistry.HasSpectatorTargetSyncPeer(_transport.LocalClientId))
			{
				text = "waiting for compatible remote peer capability";
			}
			_targetSyncReady = text == null;
			_hasCompatibleModPeer = _targetSyncReady;
			if (_config.DebugNetworkMessages.Value && _lastTargetSyncWaitReason != text)
			{
				_lastTargetSyncWaitReason = text;
				if (text == null)
				{
					ModLog.Debug("Spectator target sync is ready.");
				}
				else
				{
					ModLog.Debug("Spectator target sync is not ready: " + text + ".");
				}
			}
		}

		private void UpdateCompatiblePeerProbeState()
		{
			_lifecycleState = NetworkCompatibilityPolicy.ResolveLifecycleState(_targetSyncReady, _capabilitySent, _capabilityProbeSentRealtime, _runtimeState.RealtimeSinceStartup, 2.5f);
			_noCompatiblePeerLocalOnly = _lifecycleState == NetworkLifecycleState.NoCompatiblePeerLocalOnly;
			if (_noCompatiblePeerLocalOnly && _lastDegradationReason != "no compatible Enhanced Spectator peer capability received")
			{
				_lastDegradationReason = "no compatible Enhanced Spectator peer capability received";
				Debug("Networking remains local-only because no compatible Enhanced Spectator peer answered the capability probe.");
			}
		}

		private void ClearBusinessSyncState()
		{
			_lastObservedTargetState = null;
			_lastSentTargetState = null;
			_pendingTargetState = null;
			_lastObservedPoseState = null;
			_lastSentPoseState = null;
			_pendingPoseState = null;
			_pendingPoseRefresh = false;
			_lastObservedVoiceActivityState = null;
			_lastSentVoiceActivityState = null;
			_pendingVoiceActivityState = null;
			_pendingVoiceActivityRefresh = false;
			_lastSentIdentityState = null;
			_nextTargetSampleTime = 0f;
			_nextPoseSampleTime = 0f;
			_nextPoseRefreshTime = 0f;
			_nextVoiceActivitySampleTime = 0f;
		}

		private void UpdateLocalSpectatorTargetIfDue()
		{
			if (!_config.EnableSpectatorTargetSync.Value)
			{
				_lastObservedTargetState = null;
				_lastSentTargetState = null;
				_pendingTargetState = null;
			}
			else if (NetworkSyncSamplingRules.ShouldSample(_lastObservedTargetState != null, _runtimeState.UnscaledTime, _nextTargetSampleTime))
			{
				UpdateLocalSpectatorTarget();
				_nextTargetSampleTime = NetworkSyncSamplingRules.ResolveNextSampleTime(_runtimeState.UnscaledTime, 0.1f);
			}
		}

		private void UpdateLocalSpectatorTarget()
		{
			if (!_spectatorTargetStateProvider.TryGetCurrentSpectatorTarget(out SpectatorTargetState state))
			{
				state = new SpectatorTargetState(isSpectating: false, _transport.LocalClientId, 0uL, null, null, _runtimeState.UtcNowTicks);
			}
			if (_lastObservedTargetState == null || !_lastObservedTargetState.Equals(state))
			{
				_lastObservedTargetState = state;
				if (_lastSentTargetState != null && _lastSentTargetState.Equals(state))
				{
					_pendingTargetState = null;
				}
				else
				{
					_pendingTargetState = state;
				}
				Debug($"Observed spectator target change: spectating={state.IsSpectating}, localClient={state.LocalClientId}, localSlot={state.LocalPlayerSlotId}, targetClient={FormatNullable(state.TargetClientId)}, targetSlot={FormatNullable(state.TargetPlayerSlotId)}.");
			}
		}

		private void UpdateLocalSpectatorPoseIfDue()
		{
			if (!_config.EnableSpectatorPoseSync.Value)
			{
				_lastObservedPoseState = null;
				_lastSentPoseState = null;
				_pendingPoseState = null;
				_pendingPoseRefresh = false;
			}
			else if (NetworkSyncSamplingRules.ShouldSample(_lastObservedPoseState != null, _runtimeState.UnscaledTime, _nextPoseSampleTime))
			{
				UpdateLocalSpectatorPose();
				_nextPoseSampleTime = NetworkSyncSamplingRules.ResolveNextSampleTime(_runtimeState.UnscaledTime, GetPoseSyncInterval());
			}
		}

		private void TrySendPendingTargetState()
		{
			if (!_targetSyncReady || _pendingTargetState == null)
			{
				return;
			}
			string reason;
			if (!_transport.IsRegistered || !_transport.IsNetworkAvailable)
			{
				StopNetworkingForLifecycle("custom messaging transport unavailable before target send");
			}
			else if (!_runtimeState.CanUseModNetworking(out reason))
			{
				StopNetworkingForLifecycle(reason);
			}
			else if (_lastSentTargetState != null && _lastSentTargetState.Equals(_pendingTargetState))
			{
				_pendingTargetState = null;
			}
			else
			{
				if (_runtimeState.UnscaledTime < _nextTargetSyncTime)
				{
					return;
				}
				List<ulong> spectatorTargetSyncPeerIds = _peerRegistry.GetSpectatorTargetSyncPeerIds(_transport.LocalClientId);
				if (spectatorTargetSyncPeerIds.Count == 0)
				{
					UpdateTargetSyncReadiness();
					return;
				}
				SpectatorTargetSyncMessage spectatorTargetSyncMessage = new SpectatorTargetSyncMessage(1, _pendingTargetState, _runtimeState.UtcNowTicks);
				if (_transport.SendSpectatorTarget(spectatorTargetSyncMessage, spectatorTargetSyncPeerIds, out string reason2))
				{
					_lastSentTargetState = _pendingTargetState;
					_pendingTargetState = null;
					_nextTargetSyncTime = _runtimeState.UnscaledTime + 0.1f;
					Debug($"Spectator target sent to {spectatorTargetSyncPeerIds.Count} peer(s): spectating={spectatorTargetSyncMessage.State.IsSpectating}, targetClient={FormatNullable(spectatorTargetSyncMessage.State.TargetClientId)}, targetSlot={FormatNullable(spectatorTargetSyncMessage.State.TargetPlayerSlotId)}.");
				}
				else
				{
					Degrade("spectator target send failed: " + reason2);
				}
			}
		}

		private void UpdateLocalSpectatorPose()
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			if (!_config.EnableSpectatorPoseSync.Value)
			{
				_lastObservedPoseState = null;
				_lastSentPoseState = null;
				_pendingPoseState = null;
				_pendingPoseRefresh = false;
				return;
			}
			if (!_spectatorPoseStateProvider.TryGetCurrentSpectatorPose(out SpectatorPoseState state))
			{
				state = new SpectatorPoseState(isSpectating: false, _transport.LocalClientId, 0uL, null, null, Vector3.zero, Quaternion.identity, _runtimeState.UtcNowTicks);
			}
			if (_lastObservedPoseState != null && _lastObservedPoseState.ApproximatelyEquals(state))
			{
				if (NetworkSyncSamplingRules.ShouldRefreshUnchangedState(state.IsSpectating, _lastSentPoseState != null && _lastSentPoseState.IsSpectating, _runtimeState.UnscaledTime, _nextPoseRefreshTime))
				{
					_pendingPoseState = state;
					_pendingPoseRefresh = true;
				}
				return;
			}
			_lastObservedPoseState = state;
			if (_lastSentPoseState != null && _lastSentPoseState.ApproximatelyEquals(state))
			{
				_pendingPoseState = null;
				_pendingPoseRefresh = false;
			}
			else
			{
				_pendingPoseState = state;
				_pendingPoseRefresh = false;
			}
			if (IsPoseDebugEnabled())
			{
				ModLog.Debug($"Observed spectator pose change: spectating={state.IsSpectating}, localClient={state.LocalClientId}, targetClient={FormatNullable(state.TargetClientId)}, position={FormatVector(state.Position)}.");
			}
		}

		private void UpdateLocalVoiceActivityIfDue()
		{
			if (!_config.EnableVoiceActivitySync.Value)
			{
				_lastObservedVoiceActivityState = null;
				_lastSentVoiceActivityState = null;
				_pendingVoiceActivityState = null;
				_pendingVoiceActivityRefresh = false;
			}
			else if (NetworkSyncSamplingRules.ShouldSample(_lastObservedVoiceActivityState != null, _runtimeState.UnscaledTime, _nextVoiceActivitySampleTime))
			{
				UpdateLocalVoiceActivity();
				_nextVoiceActivitySampleTime = NetworkSyncSamplingRules.ResolveNextSampleTime(_runtimeState.UnscaledTime, GetVoiceActivitySyncInterval());
			}
		}

		private void TrySendPendingPoseState()
		{
			//IL_018a: Unknown result type (might be due to invalid IL or missing references)
			if (!_targetSyncReady || !_config.EnableSpectatorPoseSync.Value || _pendingPoseState == null)
			{
				return;
			}
			string reason;
			if (!_transport.IsRegistered || !_transport.IsNetworkAvailable)
			{
				StopNetworkingForLifecycle("custom messaging transport unavailable before pose send");
			}
			else if (!_runtimeState.CanUseModNetworking(out reason))
			{
				StopNetworkingForLifecycle(reason);
			}
			else if (!_pendingPoseRefresh && _lastSentPoseState != null && _lastSentPoseState.ApproximatelyEquals(_pendingPoseState))
			{
				_pendingPoseState = null;
			}
			else
			{
				if (_runtimeState.UnscaledTime < _nextPoseSyncTime)
				{
					return;
				}
				List<ulong> spectatorTargetSyncPeerIds = _peerRegistry.GetSpectatorTargetSyncPeerIds(_transport.LocalClientId);
				if (spectatorTargetSyncPeerIds.Count == 0)
				{
					UpdateTargetSyncReadiness();
					return;
				}
				SpectatorPoseSyncMessage spectatorPoseSyncMessage = new SpectatorPoseSyncMessage(1, _pendingPoseState, _runtimeState.UtcNowTicks);
				if (_transport.SendSpectatorPose(spectatorPoseSyncMessage, spectatorTargetSyncPeerIds, out string reason2))
				{
					_lastSentPoseState = _pendingPoseState;
					_pendingPoseState = null;
					_pendingPoseRefresh = false;
					_nextPoseSyncTime = _runtimeState.UnscaledTime + GetPoseSyncInterval();
					_nextPoseRefreshTime = _runtimeState.UnscaledTime + GetPoseHeartbeatInterval();
					if (IsPoseDebugEnabled())
					{
						ModLog.Debug($"Spectator pose sent to {spectatorTargetSyncPeerIds.Count} peer(s): spectating={spectatorPoseSyncMessage.State.IsSpectating}, targetClient={FormatNullable(spectatorPoseSyncMessage.State.TargetClientId)}, position={FormatVector(spectatorPoseSyncMessage.State.Position)}.");
					}
				}
				else
				{
					Degrade("spectator pose send failed: " + reason2);
				}
			}
		}

		private void UpdateLocalVoiceActivity()
		{
			if (!_config.EnableVoiceActivitySync.Value)
			{
				_lastObservedVoiceActivityState = null;
				_lastSentVoiceActivityState = null;
				_pendingVoiceActivityState = null;
				_pendingVoiceActivityRefresh = false;
			}
			else
			{
				if (!TryGetLocalVoiceIdentity(out var clientId, out var slotId))
				{
					return;
				}
				if (!_voiceActivityProvider.TryGetVoiceActivity(clientId, slotId, out VoiceActivityState state) || !state.HasData)
				{
					state = new VoiceActivityState(hasData: false, isSpeaking: false, 0f, 0f, clientId, slotId, _runtimeState.UtcNowTicks);
				}
				if (_lastObservedVoiceActivityState == null && !state.HasData)
				{
					_lastObservedVoiceActivityState = state;
					return;
				}
				if (VoiceActivitySyncRules.ApproximatelyEquals(_lastObservedVoiceActivityState, state))
				{
					if (ShouldRefreshVoiceActivity(state))
					{
						_pendingVoiceActivityState = state;
						_pendingVoiceActivityRefresh = true;
					}
					return;
				}
				_lastObservedVoiceActivityState = state;
				if (VoiceActivitySyncRules.ApproximatelyEquals(_lastSentVoiceActivityState, state))
				{
					_pendingVoiceActivityState = null;
					_pendingVoiceActivityRefresh = false;
				}
				else
				{
					_pendingVoiceActivityState = state;
					_pendingVoiceActivityRefresh = false;
				}
				DebugObservedVoiceActivity(state);
			}
		}

		private void TrySendPendingVoiceActivityState()
		{
			if (!_targetSyncReady || !_config.EnableVoiceActivitySync.Value || _pendingVoiceActivityState == null)
			{
				return;
			}
			string reason;
			if (!_transport.IsRegistered || !_transport.IsNetworkAvailable)
			{
				StopNetworkingForLifecycle("custom messaging transport unavailable before voice activity send");
			}
			else if (!_runtimeState.CanUseModNetworking(out reason))
			{
				StopNetworkingForLifecycle(reason);
			}
			else if (!_pendingVoiceActivityRefresh && VoiceActivitySyncRules.ApproximatelyEquals(_lastSentVoiceActivityState, _pendingVoiceActivityState))
			{
				_pendingVoiceActivityState = null;
			}
			else if (!_pendingVoiceActivityState.HasData && (_lastSentVoiceActivityState == null || !_lastSentVoiceActivityState.HasData))
			{
				_pendingVoiceActivityState = null;
			}
			else
			{
				if (_runtimeState.UnscaledTime < _nextVoiceActivitySyncTime)
				{
					return;
				}
				List<ulong> voiceActivitySyncPeerIds = _peerRegistry.GetVoiceActivitySyncPeerIds(_transport.LocalClientId);
				if (voiceActivitySyncPeerIds.Count == 0)
				{
					UpdateTargetSyncReadiness();
					return;
				}
				VoiceActivitySyncMessage voiceActivitySyncMessage = new VoiceActivitySyncMessage(1, _pendingVoiceActivityState, _runtimeState.UtcNowTicks);
				if (_transport.SendVoiceActivity(voiceActivitySyncMessage, voiceActivitySyncPeerIds, out string reason2))
				{
					_lastSentVoiceActivityState = _pendingVoiceActivityState;
					_pendingVoiceActivityState = null;
					_pendingVoiceActivityRefresh = false;
					_nextVoiceActivitySyncTime = _runtimeState.UnscaledTime + GetVoiceActivitySyncInterval();
					_nextVoiceActivityRefreshTime = _runtimeState.UnscaledTime + GetVoiceActivityRefreshInterval();
					DebugSentVoiceActivity(voiceActivitySyncMessage.State, voiceActivitySyncPeerIds.Count);
				}
				else
				{
					Degrade("voice activity send failed: " + reason2);
				}
			}
		}

		private void OnCapabilityReceived(ulong senderClientId, ModPeerCapability capability)
		{
			if (!_initialized || !_config.EnableNetworking.Value || !_config.EnableCapabilityHandshake.Value)
			{
				return;
			}
			if (!_runtimeState.CanUseModNetworking(out string reason))
			{
				Debug($"Dropped capability from sender={senderClientId}: {reason}.");
				return;
			}
			if (capability.ClientId == _transport.LocalClientId)
			{
				_peerRegistry.RegisterLocal(CreateLocalCapability(_transport.LocalClientId));
				return;
			}
			if (_transport.IsHost && capability.ClientId != senderClientId)
			{
				Debug($"Dropped capability with mismatched sender and peer id: sender={senderClientId}, peer={capability.ClientId}.");
				return;
			}
			if (!_transport.IsHost && senderClientId != capability.ClientId && senderClientId != _transport.ServerClientId)
			{
				Debug($"Dropped relayed capability from non-server sender={senderClientId}, peer={capability.ClientId}.");
				return;
			}
			bool flag = !_transport.IsHost && senderClientId != capability.ClientId;
			if (!ModPeerCapabilityRules.SupportsCurrentSpectatorTargetSync(capability))
			{
				RemoveRemotePeerState(capability.ClientId, $"capability no longer compatible: sender={senderClientId}, targetSync={capability.SupportsSpectatorTargetSync}, voiceSync={capability.SupportsVoiceActivitySync}, voiceRoute={capability.SupportsSpectatorVoiceToTarget}, relayed={flag}");
				return;
			}
			bool flag2 = _peerRegistry.RegisterRemote(capability, flag);
			if (flag2)
			{
				_noCompatiblePeerLocalOnly = false;
				_lifecycleState = NetworkLifecycleState.TransportRegistered;
			}
			Debug($"Capability received from sender={senderClientId}, peer={capability.ClientId}, protocol={capability.ProtocolVersion}, targetSync={capability.SupportsSpectatorTargetSync}, voiceSync={capability.SupportsVoiceActivitySync}, voiceRoute={capability.SupportsSpectatorVoiceToTarget}, compatible={flag2}, relayed={flag}.");
			if (flag2 && _transport.IsHost)
			{
				ModPeerCapability capability2 = CreateLocalCapability(_transport.LocalClientId);
				_peerRegistry.RegisterLocal(capability2);
				if (_transport.SendCapability(capability2, new ulong[1] { capability.ClientId }, out string reason2))
				{
					Debug($"Capability reply sent to peer={capability.ClientId}.");
				}
				else
				{
					Debug($"Capability reply to peer={capability.ClientId} failed: {reason2}.");
				}
				SendLocalPeerIdentityToRecipients(new ulong[1] { capability.ClientId });
				SendLocalSpectatorStateToPeer(capability.ClientId);
				PruneDisconnectedPeers(force: true);
				RelayCapabilityToCompatiblePeers(capability);
				RelayKnownCapabilitiesToPeer(capability.ClientId);
				RelayKnownSpectatorStatesToPeer(capability.ClientId);
				RelayKnownPeerIdentitiesToPeer(capability.ClientId);
				RelayKnownVoiceActivitiesToPeer(capability.ClientId);
			}
		}

		private void RemoveRemotePeerState(ulong peerId, string reason)
		{
			if (!_transport.IsRegistered || peerId != _transport.LocalClientId)
			{
				ModPeerCapability capability;
				SpectatorTargetState state;
				SpectatorPoseState state2;
				PeerIdentityState state3;
				VoiceActivityState state4;
				bool num = _peerRegistry.TryGetCapability(peerId, out capability) || _remoteTargetRegistry.TryGet(peerId, out state) || _remotePoseRegistry.TryGet(peerId, out state2) || _remoteIdentityRegistry.TryGet(peerId, out state3) || _remoteVoiceActivityRegistry.TryGet(peerId, out state4);
				_peerRegistry.Remove(peerId);
				_remoteTargetRegistry.Remove(peerId);
				_remotePoseRegistry.Remove(peerId);
				_remoteIdentityRegistry.Remove(peerId);
				_remoteVoiceActivityRegistry.Remove(peerId);
				if (num)
				{
					Debug($"Removed remote peer state for peer={peerId}: {reason}.");
				}
				else
				{
					Debug($"Ignored incompatible remote peer capability for peer={peerId}: {reason}.");
				}
			}
		}

		private void OnSpectatorTargetReceived(ulong senderClientId, SpectatorTargetState state)
		{
			if (!_initialized || !_config.EnableNetworking.Value || !_config.EnableSpectatorTargetSync.Value)
			{
				return;
			}
			if (!_runtimeState.CanUseModNetworking(out string reason))
			{
				Debug($"Dropped spectator target from sender={senderClientId}: {reason}.");
			}
			else if (state.LocalClientId != _transport.LocalClientId)
			{
				if (!HostRelayPlanner.CanAcceptSpectatorState(_transport.IsHost, _transport.LocalClientId, _transport.ServerClientId, senderClientId, state.LocalClientId, _peerRegistry, out string reason2))
				{
					Debug($"Dropped spectator target from sender={senderClientId}, peer={state.LocalClientId}: {reason2}.");
					return;
				}
				_remoteTargetRegistry.Update(state);
				Debug($"Spectator target received from peer={state.LocalClientId}: spectating={state.IsSpectating}, targetClient={FormatNullable(state.TargetClientId)}, targetSlot={FormatNullable(state.TargetPlayerSlotId)}.");
				RelaySpectatorTargetState(senderClientId, state);
			}
		}

		private void OnSpectatorPoseReceived(ulong senderClientId, SpectatorPoseState state)
		{
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			if (!_initialized || !_config.EnableNetworking.Value || !_config.EnableSpectatorPoseSync.Value)
			{
				return;
			}
			if (!_runtimeState.CanUseModNetworking(out string reason))
			{
				Debug($"Dropped spectator pose from sender={senderClientId}: {reason}.");
			}
			else
			{
				if (state.LocalClientId == _transport.LocalClientId)
				{
					return;
				}
				if (!HostRelayPlanner.CanAcceptSpectatorState(_transport.IsHost, _transport.LocalClientId, _transport.ServerClientId, senderClientId, state.LocalClientId, _peerRegistry, out string reason2))
				{
					Debug($"Dropped spectator pose from sender={senderClientId}, peer={state.LocalClientId}: {reason2}.");
					return;
				}
				_remotePoseRegistry.Update(state);
				if (IsPoseDebugEnabled())
				{
					ModLog.Debug($"Spectator pose received from peer={state.LocalClientId}: spectating={state.IsSpectating}, targetClient={FormatNullable(state.TargetClientId)}, position={FormatVector(state.Position)}.");
				}
				RelaySpectatorPoseState(senderClientId, state);
			}
		}

		private void OnPeerIdentityReceived(ulong senderClientId, PeerIdentityState state)
		{
			if (!_initialized || !_config.EnableNetworking.Value || !_config.EnableCapabilityHandshake.Value)
			{
				return;
			}
			if (!_runtimeState.CanUseModNetworking(out string reason))
			{
				Debug($"Dropped peer identity from sender={senderClientId}: {reason}.");
			}
			else if (state.ClientId != _transport.LocalClientId)
			{
				if (!HostRelayPlanner.CanAcceptSpectatorState(_transport.IsHost, _transport.LocalClientId, _transport.ServerClientId, senderClientId, state.ClientId, _peerRegistry, out string reason2))
				{
					Debug($"Dropped peer identity from sender={senderClientId}, peer={state.ClientId}: {reason2}.");
					return;
				}
				_remoteIdentityRegistry.Update(state);
				Debug($"Peer identity received from peer={state.ClientId}: slot={state.PlayerSlotId}, name={state.DisplayName}, voiceName={FormatVoiceName(state.VoicePlayerName)}.");
				RelayPeerIdentity(senderClientId, state);
			}
		}

		private void OnVoiceActivityReceived(ulong senderClientId, VoiceActivityState state)
		{
			if (!_initialized || !_config.EnableNetworking.Value || !_config.EnableVoiceActivitySync.Value)
			{
				return;
			}
			if (!_runtimeState.CanUseModNetworking(out string reason))
			{
				DebugVoice($"Dropped voice activity from sender={senderClientId}: {reason}.");
			}
			else if (state.ClientId != _transport.LocalClientId)
			{
				if (!HostRelayPlanner.CanAcceptSpectatorState(_transport.IsHost, _transport.LocalClientId, _transport.ServerClientId, senderClientId, state.ClientId, _peerRegistry, out string reason2))
				{
					DebugVoice($"Dropped voice activity from sender={senderClientId}, peer={state.ClientId}: {reason2}.");
					return;
				}
				_remoteVoiceActivityRegistry.Update(state, _runtimeState.UtcNowTicks);
				DebugReceivedVoiceActivity(senderClientId, state);
				RelayVoiceActivityState(senderClientId, state);
			}
		}

		private void RelayCapabilityToCompatiblePeers(ModPeerCapability capability)
		{
			if (!_transport.IsHost || !_config.EnableHostRelay.Value)
			{
				return;
			}
			IReadOnlyList<ulong> relayRecipients = HostRelayPlanner.GetRelayRecipients(_peerRegistry, _transport.LocalClientId, capability.ClientId);
			if (relayRecipients.Count != 0)
			{
				if (_transport.SendCapability(capability, relayRecipients, out string reason))
				{
					Debug($"Relayed capability for peer={capability.ClientId} to {relayRecipients.Count} peer(s).");
				}
				else
				{
					Debug($"Capability relay for peer={capability.ClientId} failed: {reason}.");
				}
			}
		}

		private void RelayKnownCapabilitiesToPeer(ulong recipientClientId)
		{
			if (!_transport.IsHost || !_config.EnableHostRelay.Value)
			{
				return;
			}
			foreach (ModPeerCapability item in _peerRegistry.GetCapabilitiesSnapshot())
			{
				if (item.ClientId != _transport.LocalClientId && item.ClientId != recipientClientId && item.HandshakeComplete && item.ProtocolVersion == 1 && item.SupportsCapabilityHandshake && item.SupportsSpectatorTargetSync)
				{
					if (_transport.SendCapability(item, new ulong[1] { recipientClientId }, out string reason))
					{
						Debug($"Relayed known capability for peer={item.ClientId} to peer={recipientClientId}.");
					}
					else
					{
						Debug($"Known capability relay for peer={item.ClientId} to peer={recipientClientId} failed: {reason}.");
					}
				}
			}
		}

		private void RelayKnownSpectatorStatesToPeer(ulong recipientClientId)
		{
			if (!_transport.IsHost || !_config.EnableHostRelay.Value)
			{
				return;
			}
			foreach (SpectatorTargetState item in _remoteTargetRegistry.GetSnapshot())
			{
				if (item.LocalClientId == _transport.LocalClientId || item.LocalClientId == recipientClientId)
				{
					continue;
				}
				if (!IsConnectedForKnownState(item.LocalClientId))
				{
					Debug($"Skipped known spectator target relay for peer={item.LocalClientId} to peer={recipientClientId}: origin is not connected.");
					continue;
				}
				if (item.TargetClientId.HasValue && !IsConnectedForKnownState(item.TargetClientId.Value))
				{
					Debug($"Skipped known spectator target relay for peer={item.LocalClientId} to peer={recipientClientId}: target={item.TargetClientId.Value} is not connected.");
					continue;
				}
				SpectatorTargetSyncMessage message = new SpectatorTargetSyncMessage(1, item, _runtimeState.UtcNowTicks);
				if (_transport.SendSpectatorTarget(message, new ulong[1] { recipientClientId }, out string reason))
				{
					Debug($"Relayed known spectator target for peer={item.LocalClientId} to peer={recipientClientId}.");
				}
				else
				{
					Debug($"Known spectator target relay for peer={item.LocalClientId} to peer={recipientClientId} failed: {reason}.");
				}
			}
			if (!_config.EnableSpectatorPoseSync.Value)
			{
				return;
			}
			foreach (SpectatorPoseState item2 in _remotePoseRegistry.GetSnapshot())
			{
				if (item2.LocalClientId == _transport.LocalClientId || item2.LocalClientId == recipientClientId)
				{
					continue;
				}
				if (!IsConnectedForKnownState(item2.LocalClientId))
				{
					DebugPose($"Skipped known spectator pose relay for peer={item2.LocalClientId} to peer={recipientClientId}: origin is not connected.");
					continue;
				}
				if (item2.TargetClientId.HasValue && !IsConnectedForKnownState(item2.TargetClientId.Value))
				{
					DebugPose($"Skipped known spectator pose relay for peer={item2.LocalClientId} to peer={recipientClientId}: target={item2.TargetClientId.Value} is not connected.");
					continue;
				}
				if (!HasMatchingStoredTarget(item2))
				{
					DebugPose($"Skipped known spectator pose relay for peer={item2.LocalClientId} to peer={recipientClientId}: no matching active target.");
					continue;
				}
				SpectatorPoseSyncMessage message2 = new SpectatorPoseSyncMessage(1, item2, _runtimeState.UtcNowTicks);
				if (_transport.SendSpectatorPose(message2, new ulong[1] { recipientClientId }, out string reason2))
				{
					DebugPose($"Relayed known spectator pose for peer={item2.LocalClientId} to peer={recipientClientId}.");
				}
				else
				{
					DebugPose($"Known spectator pose relay for peer={item2.LocalClientId} to peer={recipientClientId} failed: {reason2}.");
				}
			}
		}

		private bool HasMatchingStoredPose(SpectatorTargetState targetState)
		{
			if (_remotePoseRegistry.TryGet(targetState.LocalClientId, out SpectatorPoseState state) && state.IsSpectating && state.TargetClientId == targetState.TargetClientId)
			{
				return state.TargetPlayerSlotId == targetState.TargetPlayerSlotId;
			}
			return false;
		}

		private bool HasMatchingStoredTarget(SpectatorPoseState poseState)
		{
			if (_remoteTargetRegistry.TryGet(poseState.LocalClientId, out SpectatorTargetState state) && state.IsSpectating && state.TargetClientId == poseState.TargetClientId)
			{
				return state.TargetPlayerSlotId == poseState.TargetPlayerSlotId;
			}
			return false;
		}

		private bool IsConnectedForKnownState(ulong clientId)
		{
			if (clientId != _transport.LocalClientId)
			{
				return _transport.IsPeerConnected(clientId);
			}
			return true;
		}

		private bool IsConnectedForRelayState(ulong originClientId, bool isSpectating, ulong? targetClientId, out string reason)
		{
			if (!IsConnectedForKnownState(originClientId))
			{
				reason = "origin is not connected";
				return false;
			}
			if (isSpectating && targetClientId.HasValue && !IsConnectedForKnownState(targetClientId.Value))
			{
				reason = $"target={targetClientId.Value} is not connected";
				return false;
			}
			reason = string.Empty;
			return true;
		}

		private void RelaySpectatorTargetState(ulong senderClientId, SpectatorTargetState state)
		{
			if (!_transport.IsHost || !_config.EnableHostRelay.Value || senderClientId != state.LocalClientId)
			{
				return;
			}
			if (!IsConnectedForRelayState(state.LocalClientId, state.IsSpectating, state.TargetClientId, out string reason))
			{
				Debug($"Skipped spectator target relay from peer={state.LocalClientId}: {reason}.");
				return;
			}
			IReadOnlyList<ulong> relayRecipients = HostRelayPlanner.GetRelayRecipients(_peerRegistry, _transport.LocalClientId, state.LocalClientId);
			if (relayRecipients.Count != 0)
			{
				SpectatorTargetSyncMessage message = new SpectatorTargetSyncMessage(1, state, _runtimeState.UtcNowTicks);
				if (_transport.SendSpectatorTarget(message, relayRecipients, out string reason2))
				{
					Debug($"Relayed spectator target from peer={state.LocalClientId} to {relayRecipients.Count} peer(s).");
				}
				else
				{
					Debug($"Spectator target relay from peer={state.LocalClientId} failed: {reason2}.");
				}
			}
		}

		private void RelaySpectatorPoseState(ulong senderClientId, SpectatorPoseState state)
		{
			if (!_transport.IsHost || !_config.EnableHostRelay.Value || senderClientId != state.LocalClientId)
			{
				return;
			}
			if (!IsConnectedForRelayState(state.LocalClientId, state.IsSpectating, state.TargetClientId, out string reason))
			{
				DebugPose($"Skipped spectator pose relay from peer={state.LocalClientId}: {reason}.");
				return;
			}
			IReadOnlyList<ulong> relayRecipients = HostRelayPlanner.GetRelayRecipients(_peerRegistry, _transport.LocalClientId, state.LocalClientId);
			if (relayRecipients.Count != 0)
			{
				SpectatorPoseSyncMessage message = new SpectatorPoseSyncMessage(1, state, _runtimeState.UtcNowTicks);
				if (_transport.SendSpectatorPose(message, relayRecipients, out string reason2))
				{
					DebugPose($"Relayed spectator pose from peer={state.LocalClientId} to {relayRecipients.Count} peer(s).");
				}
				else
				{
					DebugPose($"Spectator pose relay from peer={state.LocalClientId} failed: {reason2}.");
				}
			}
		}

		private void RelayVoiceActivityState(ulong senderClientId, VoiceActivityState state)
		{
			if (!_transport.IsHost || !_config.EnableHostRelay.Value || senderClientId != state.ClientId)
			{
				return;
			}
			if (!IsConnectedForKnownState(state.ClientId))
			{
				DebugVoice($"Skipped voice activity relay from peer={state.ClientId}: origin is not connected.");
				return;
			}
			IReadOnlyList<ulong> voiceActivityRelayRecipients = HostRelayPlanner.GetVoiceActivityRelayRecipients(_peerRegistry, _transport.LocalClientId, state.ClientId);
			if (voiceActivityRelayRecipients.Count != 0)
			{
				VoiceActivitySyncMessage message = new VoiceActivitySyncMessage(1, state, _runtimeState.UtcNowTicks);
				if (_transport.SendVoiceActivity(message, voiceActivityRelayRecipients, out string reason))
				{
					DebugRelayedVoiceActivity(state, voiceActivityRelayRecipients.Count);
				}
				else
				{
					DebugVoice($"Voice activity relay from peer={state.ClientId} failed: {reason}.");
				}
			}
		}

		private void RelayDisconnectedPeerCleanup(ulong peerId)
		{
			//IL_0218: Unknown result type (might be due to invalid IL or missing references)
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			if (!_transport.IsHost || !_config.EnableHostRelay.Value)
			{
				return;
			}
			IReadOnlyList<ulong> relayRecipients = HostRelayPlanner.GetRelayRecipients(_peerRegistry, _transport.LocalClientId, peerId);
			if (relayRecipients.Count == 0)
			{
				return;
			}
			ModPeerCapability capability = new ModPeerCapability(peerId, 1, supportsCapabilityHandshake: false, supportsSpectatorTargetSync: false, handshakeComplete: false, _runtimeState.UtcNowTicks);
			if (_transport.SendCapability(capability, relayRecipients, out string reason))
			{
				Debug($"Relayed disconnected peer capability cleanup for peer={peerId} to {relayRecipients.Count} peer(s).");
			}
			else
			{
				Debug($"Disconnected peer capability cleanup relay for peer={peerId} failed: {reason}.");
			}
			SpectatorTargetState state = new SpectatorTargetState(isSpectating: false, peerId, 0uL, null, null, _runtimeState.UtcNowTicks);
			SpectatorTargetSyncMessage message = new SpectatorTargetSyncMessage(1, state, _runtimeState.UtcNowTicks);
			if (_transport.SendSpectatorTarget(message, relayRecipients, out string reason2))
			{
				Debug($"Relayed disconnected peer target cleanup for peer={peerId} to {relayRecipients.Count} peer(s).");
			}
			else
			{
				Debug($"Disconnected peer target cleanup relay for peer={peerId} failed: {reason2}.");
			}
			if (_config.EnableVoiceActivitySync.Value)
			{
				IReadOnlyList<ulong> voiceActivityRelayRecipients = HostRelayPlanner.GetVoiceActivityRelayRecipients(_peerRegistry, _transport.LocalClientId, peerId);
				VoiceActivityState state2 = new VoiceActivityState(hasData: false, isSpeaking: false, 0f, 0f, peerId, 0uL, _runtimeState.UtcNowTicks);
				VoiceActivitySyncMessage message2 = new VoiceActivitySyncMessage(1, state2, _runtimeState.UtcNowTicks);
				if (voiceActivityRelayRecipients.Count > 0)
				{
					if (_transport.SendVoiceActivity(message2, voiceActivityRelayRecipients, out string reason3))
					{
						DebugVoice($"Relayed disconnected peer voice activity cleanup for peer={peerId} to {voiceActivityRelayRecipients.Count} peer(s).");
					}
					else
					{
						DebugVoice($"Disconnected peer voice activity cleanup relay for peer={peerId} failed: {reason3}.");
					}
				}
			}
			if (_config.EnableSpectatorPoseSync.Value)
			{
				SpectatorPoseState state3 = new SpectatorPoseState(isSpectating: false, peerId, 0uL, null, null, Vector3.zero, Quaternion.identity, _runtimeState.UtcNowTicks);
				SpectatorPoseSyncMessage message3 = new SpectatorPoseSyncMessage(1, state3, _runtimeState.UtcNowTicks);
				if (_transport.SendSpectatorPose(message3, relayRecipients, out string reason4))
				{
					DebugPose($"Relayed disconnected peer pose cleanup for peer={peerId} to {relayRecipients.Count} peer(s).");
				}
				else
				{
					DebugPose($"Disconnected peer pose cleanup relay for peer={peerId} failed: {reason4}.");
				}
			}
		}

		private void SendLocalPeerIdentityToRecipients(IEnumerable<ulong> recipients)
		{
			if (_peerIdentityStateProvider.TryGetLocalPeerIdentity(out PeerIdentityState state) && !string.IsNullOrWhiteSpace(state.DisplayName))
			{
				if (_transport.SendPeerIdentity(state, recipients, out string reason))
				{
					_lastSentIdentityState = state;
					Debug($"Peer identity reply sent: client={state.ClientId}, slot={state.PlayerSlotId}, name={state.DisplayName}, voiceName={FormatVoiceName(state.VoicePlayerName)}.");
				}
				else
				{
					Debug("Peer identity reply failed: " + reason + ".");
				}
			}
		}

		private void SendLocalSpectatorStateToPeer(ulong recipientClientId)
		{
			if (!_config.EnableSpectatorTargetSync.Value || !_spectatorTargetStateProvider.TryGetCurrentSpectatorTarget(out SpectatorTargetState state) || !state.IsSpectating)
			{
				return;
			}
			if (!IsConnectedForRelayState(state.LocalClientId, state.IsSpectating, state.TargetClientId, out string reason))
			{
				Debug($"Skipped local spectator target replay to peer={recipientClientId}: {reason}.");
				return;
			}
			SpectatorTargetSyncMessage message = new SpectatorTargetSyncMessage(1, state, _runtimeState.UtcNowTicks);
			if (_transport.SendSpectatorTarget(message, new ulong[1] { recipientClientId }, out string reason2))
			{
				Debug($"Replayed local spectator target to peer={recipientClientId}.");
			}
			else
			{
				Debug($"Local spectator target replay to peer={recipientClientId} failed: {reason2}.");
			}
			if (_config.EnableSpectatorPoseSync.Value && _spectatorPoseStateProvider.TryGetCurrentSpectatorPose(out SpectatorPoseState state2) && state2.IsSpectating && HasMatchingLocalTarget(state, state2))
			{
				SpectatorPoseSyncMessage message2 = new SpectatorPoseSyncMessage(1, state2, _runtimeState.UtcNowTicks);
				if (_transport.SendSpectatorPose(message2, new ulong[1] { recipientClientId }, out string reason3))
				{
					DebugPose($"Replayed local spectator pose to peer={recipientClientId}.");
				}
				else
				{
					DebugPose($"Local spectator pose replay to peer={recipientClientId} failed: {reason3}.");
				}
			}
		}

		private static bool HasMatchingLocalTarget(SpectatorTargetState targetState, SpectatorPoseState poseState)
		{
			if (targetState.IsSpectating && poseState.TargetClientId == targetState.TargetClientId)
			{
				return poseState.TargetPlayerSlotId == targetState.TargetPlayerSlotId;
			}
			return false;
		}

		private void RelayPeerIdentity(ulong senderClientId, PeerIdentityState state)
		{
			if (!_transport.IsHost || !_config.EnableHostRelay.Value || senderClientId != state.ClientId)
			{
				return;
			}
			IReadOnlyList<ulong> relayRecipients = HostRelayPlanner.GetRelayRecipients(_peerRegistry, _transport.LocalClientId, state.ClientId);
			if (relayRecipients.Count != 0)
			{
				if (_transport.SendPeerIdentity(state, relayRecipients, out string reason))
				{
					Debug($"Relayed peer identity from peer={state.ClientId} to {relayRecipients.Count} peer(s).");
				}
				else
				{
					Debug($"Peer identity relay from peer={state.ClientId} failed: {reason}.");
				}
			}
		}

		private void RelayKnownPeerIdentitiesToPeer(ulong recipientClientId)
		{
			if (!_transport.IsHost || !_config.EnableHostRelay.Value)
			{
				return;
			}
			foreach (PeerIdentityState item in _remoteIdentityRegistry.GetSnapshot())
			{
				if (item.ClientId != _transport.LocalClientId && item.ClientId != recipientClientId)
				{
					if (_transport.SendPeerIdentity(item, new ulong[1] { recipientClientId }, out string reason))
					{
						Debug($"Relayed known peer identity for peer={item.ClientId} to peer={recipientClientId}.");
					}
					else
					{
						Debug($"Known peer identity relay for peer={item.ClientId} to peer={recipientClientId} failed: {reason}.");
					}
				}
			}
		}

		private void RelayKnownVoiceActivitiesToPeer(ulong recipientClientId)
		{
			if (!_transport.IsHost || !_config.EnableHostRelay.Value || !_config.EnableVoiceActivitySync.Value || !_peerRegistry.TryGetCapability(recipientClientId, out ModPeerCapability capability) || !ModPeerCapabilityRules.SupportsCurrentVoiceActivitySync(capability))
			{
				return;
			}
			foreach (VoiceActivityState item in _remoteVoiceActivityRegistry.GetSnapshot())
			{
				if (item.ClientId == _transport.LocalClientId || item.ClientId == recipientClientId)
				{
					continue;
				}
				if (!IsConnectedForKnownState(item.ClientId))
				{
					DebugVoice($"Skipped known voice activity relay for peer={item.ClientId} to peer={recipientClientId}: origin is not connected.");
					continue;
				}
				VoiceActivitySyncMessage message = new VoiceActivitySyncMessage(1, item, _runtimeState.UtcNowTicks);
				if (_transport.SendVoiceActivity(message, new ulong[1] { recipientClientId }, out string reason))
				{
					DebugVoice($"Relayed known voice activity for peer={item.ClientId} to peer={recipientClientId}.");
				}
				else
				{
					DebugVoice($"Known voice activity relay for peer={item.ClientId} to peer={recipientClientId} failed: {reason}.");
				}
			}
		}

		private void Degrade(string reason)
		{
			_transport.Unregister();
			ClearNetworkState();
			_lifecycleState = NetworkLifecycleState.LocalOnly;
			if (!(_lastDegradationReason == reason))
			{
				_lastDegradationReason = reason;
				Debug("Networking degraded to local-only mode: " + reason + ".");
			}
		}

		private void StopNetworkingForLifecycle(string reason)
		{
			if (_transport.IsRegistered)
			{
				_transport.Unregister();
			}
			ClearNetworkState();
			_lifecycleState = NetworkLifecycleState.LocalOnly;
			if (!(_lastDegradationReason == reason))
			{
				_lastDegradationReason = reason;
				if (_runtimeState.FrameCount >= _nextLifecycleDebugFrame)
				{
					_nextLifecycleDebugFrame = _runtimeState.FrameCount + 120;
					Debug("Networking stopped because shutdown/disconnect was detected: " + reason + ".");
				}
			}
		}

		private void ClearNetworkState()
		{
			_peerRegistry.Clear();
			_remoteTargetRegistry.Clear();
			_remotePoseRegistry.Clear();
			_remoteIdentityRegistry.Clear();
			_remoteVoiceActivityRegistry.Clear();
			_lastObservedTargetState = null;
			_lastSentTargetState = null;
			_pendingTargetState = null;
			_lastObservedPoseState = null;
			_lastSentPoseState = null;
			_pendingPoseState = null;
			_pendingPoseRefresh = false;
			_lastObservedVoiceActivityState = null;
			_lastSentVoiceActivityState = null;
			_pendingVoiceActivityState = null;
			_pendingVoiceActivityRefresh = false;
			_lastSentIdentityState = null;
			_networkAvailable = false;
			_targetSyncReady = false;
			_hasCompatibleModPeer = false;
			_noCompatiblePeerLocalOnly = false;
			_capabilitySent = false;
			_capabilityProbeSentRealtime = -1f;
			_lastLocalClientId = null;
			_nextPeerPruneTime = 0f;
			_transportRegisteredRealtime = 0f;
			_transportRegisteredFrame = -1;
			_lastTargetSyncWaitReason = null;
			_nextTargetSyncTime = 0f;
			_nextPoseSyncTime = 0f;
			_nextVoiceActivitySyncTime = 0f;
			_nextTargetSampleTime = 0f;
			_nextPoseSampleTime = 0f;
			_nextPoseRefreshTime = 0f;
			_nextVoiceActivitySampleTime = 0f;
			_nextVoiceActivityRefreshTime = 0f;
			_voiceDebugLimiter.Clear();
		}

		private ModPeerCapability CreateLocalCapability(ulong localClientId)
		{
			return new ModPeerCapability(localClientId, 1, _config.EnableCapabilityHandshake.Value, _config.EnableSpectatorTargetSync.Value, _config.EnableCapabilityHandshake.Value, _runtimeState.UtcNowTicks, _config.EnableVoiceActivitySync.Value, _config.EnableSpectatorVoiceToTarget.Value);
		}

		private void Debug(string message)
		{
			if (_config.DebugNetworkMessages.Value)
			{
				ModLog.Debug(message);
			}
		}

		private void DebugPose(string message)
		{
			if (IsPoseDebugEnabled())
			{
				ModLog.Debug(message);
			}
		}

		private bool IsPoseDebugEnabled()
		{
			if (_config.DebugNetworkMessages.Value)
			{
				return _config.DebugPoseMessages.Value;
			}
			return false;
		}

		private float GetPoseSyncInterval()
		{
			return Mathf.Max(0.02f, _config.SpectatorPoseSyncInterval.Value);
		}

		private float GetPoseHeartbeatInterval()
		{
			return Mathf.Max(0.5f, GetPoseSyncInterval() * 5f);
		}

		private float GetVoiceActivitySyncInterval()
		{
			return NetworkSyncSamplingRules.ResolveVoiceActivitySyncInterval(_config.VoiceActivitySyncInterval.Value);
		}

		private float GetVoiceActivityRefreshInterval()
		{
			return Mathf.Clamp(Mathf.Max(0.1f, _config.VoiceActivityStaleSeconds.Value) * 0.5f, 0.1f, 0.25f);
		}

		private bool ShouldRefreshVoiceActivity(VoiceActivityState state)
		{
			if (!state.HasData || (!state.IsSpeaking && state.Amplitude <= 0f))
			{
				return false;
			}
			return _runtimeState.UnscaledTime >= _nextVoiceActivityRefreshTime;
		}

		private bool TryGetLocalVoiceIdentity(out ulong clientId, out ulong slotId)
		{
			if (_peerIdentityStateProvider.TryGetLocalPeerIdentity(out PeerIdentityState state))
			{
				clientId = state.ClientId;
				slotId = state.PlayerSlotId;
				return true;
			}
			if (_transport.IsRegistered)
			{
				clientId = _transport.LocalClientId;
				slotId = 0uL;
				return true;
			}
			clientId = 0uL;
			slotId = 0uL;
			return false;
		}

		private void DebugVoice(string message)
		{
			if (IsVoiceDebugEnabled())
			{
				ModLog.Debug(message);
			}
		}

		private void DebugObservedVoiceActivity(VoiceActivityState state)
		{
			if (ShouldLogVoiceActivity("observed", state.ClientId, state, isRelayed: false))
			{
				ModLog.Debug($"Observed local voice activity change: hasData={state.HasData}, speaking={state.IsSpeaking}, amplitude={state.Amplitude:0.00}, client={state.ClientId}, slot={state.SlotId}.");
			}
		}

		private void DebugSentVoiceActivity(VoiceActivityState state, int recipientCount)
		{
			if (ShouldLogVoiceActivity("sent", state.ClientId, state, isRelayed: false))
			{
				ModLog.Debug($"Voice activity sent to {recipientCount} peer(s): hasData={state.HasData}, speaking={state.IsSpeaking}, amplitude={state.Amplitude:0.00}.");
			}
		}

		private void DebugReceivedVoiceActivity(ulong senderClientId, VoiceActivityState state)
		{
			bool flag = senderClientId != state.ClientId;
			if (ShouldLogVoiceActivity("received", state.ClientId, state, flag))
			{
				ModLog.Debug($"Voice activity received from peer={state.ClientId}: hasData={state.HasData}, speaking={state.IsSpeaking}, amplitude={state.Amplitude:0.00}, relayed={flag}.");
			}
		}

		private void DebugRelayedVoiceActivity(VoiceActivityState state, int recipientCount)
		{
			if (ShouldLogVoiceActivity("relayed", state.ClientId, state, isRelayed: true))
			{
				ModLog.Debug($"Relayed voice activity from peer={state.ClientId} to {recipientCount} peer(s).");
			}
		}

		private bool ShouldLogVoiceActivity(string category, ulong peerId, VoiceActivityState state, bool isRelayed)
		{
			if (IsVoiceDebugEnabled())
			{
				return _voiceDebugLimiter.ShouldLog(category, peerId, _runtimeState.FrameCount, state, isRelayed);
			}
			return false;
		}

		private bool IsVoiceDebugEnabled()
		{
			if (_config.DebugNetworkMessages.Value)
			{
				return _config.DebugVoiceActivitySync.Value;
			}
			return false;
		}

		private void DebugCapabilityDelay(string reason)
		{
			if (_config.DebugNetworkMessages.Value && _runtimeState.FrameCount >= _nextCapabilityDelayDebugFrame)
			{
				_nextCapabilityDelayDebugFrame = _runtimeState.FrameCount + 120;
				ModLog.Debug("Capability delayed because network is not stable yet: " + reason + ".");
			}
		}

		private static string FormatNullable(ulong? value)
		{
			if (!value.HasValue)
			{
				return "none";
			}
			return value.Value.ToString();
		}

		private static string FormatVector(Vector3 value)
		{
			//IL_0005: 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_001b: Unknown result type (might be due to invalid IL or missing references)
			return $"({value.x:0.00}, {value.y:0.00}, {value.z:0.00})";
		}

		private static string FormatVoiceName(string value)
		{
			if (!string.IsNullOrWhiteSpace(value))
			{
				return "present";
			}
			return "none";
		}

		private static bool IdentityEquals(PeerIdentityState? left, PeerIdentityState right)
		{
			if (left != null && left.ClientId == right.ClientId && left.PlayerSlotId == right.PlayerSlotId && string.Equals(left.DisplayName, right.DisplayName, StringComparison.Ordinal))
			{
				return string.Equals(left.VoicePlayerName, right.VoicePlayerName, StringComparison.Ordinal);
			}
			return false;
		}
	}
	public static class HostRelayPlanner
	{
		public static IReadOnlyList<ulong> GetRelayRecipients(RemotePeerRegistry registry, ulong hostClientId, ulong originClientId)
		{
			List<ulong> spectatorTargetSyncPeerIds = registry.GetSpectatorTargetSyncPeerIds(hostClientId);
			for (int num = spectatorTargetSyncPeerIds.Count - 1; num >= 0; num--)
			{
				ulong num2 = spectatorTargetSyncPeerIds[num];
				if (num2 == hostClientId || num2 == originClientId)
				{
					spectatorTargetSyncPeerIds.RemoveAt(num);
				}
			}
			return spectatorTargetSyncPeerIds;
		}

		public static IReadOnlyList<ulong> GetVoiceActivityRelayRecipients(RemotePeerRegistry registry, ulong hostClientId, ulong originClientId)
		{
			List<ulong> voiceActivitySyncPeerIds = registry.GetVoiceActivitySyncPeerIds(hostClientId);
			for (int num = voiceActivitySyncPeerIds.Count - 1; num >= 0; num--)
			{
				ulong num2 = voiceActivitySyncPeerIds[num];
				if (num2 == hostClientId || num2 == originClientId)
				{
					voiceActivitySyncPeerIds.RemoveAt(num);
				}
			}
			return voiceActivitySyncPeerIds;
		}

		public static bool CanAcceptSpectatorState(bool isHost, ulong localClientId, ulong serverClientId, ulong senderClientId, ulong originClientId, RemotePeerRegistry registry, out string reason)
		{
			if (originClientId == localClientId)
			{
				reason = "origin is local client";
				return false;
			}
			if (!registry.IsSpectatorTargetSyncPeer(originClientId))
			{
				reason = "origin is not a compatible spectator sync peer";
				return false;
			}
			if (isHost)
			{
				if (senderClientId == originClientId)
				{
					reason = string.Empty;
					return true;
				}
				reason = "host rejected state where sender does not match origin";
				return false;
			}
			if (senderClientId == originClientId && !registry.IsRelayedPeer(originClientId))
			{
				reason = string.Empty;
				return true;
			}
			if (senderClientId == serverClientId && registry.IsRelayedPeer(originClientId))
			{
				reason = string.Empty;
				return true;
			}
			reason = "client rejected non-host relayed foreign origin";
			return false;
		}
	}
	public interface IEnhancedSpectatorNetworkService
	{
		bool IsNetworkAvailable { get; }

		bool IsTargetSyncEnabled { get; }

		bool HasCompatibleModPeer { get; }

		NetworkLifecycleState LifecycleState { get; }

		int RemotePeerIdentityRevision { get; }

		int RemoteSpectatorTargetRevision { get; }

		void Initialize();

		void Tick();

		void Dispose();

		bool TryGetPeerCapability(ulong clientId, out ModPeerCapability capability);

		bool TryGetRemoteSpectatorTarget(ulong clientId, out SpectatorTargetState state);

		bool TryGetRemoteSpectatorPose(ulong clientId, out SpectatorPoseState state);

		bool TryGetRemotePeerIdentity(ulong clientId, out PeerIdentityState state);

		bool TryGetRemoteVoiceActivity(ulong clientId, out VoiceActivityState state);

		IReadOnlyList<SpectatorTargetState> GetRemoteSpectatorTargets();

		void CopyRemoteSpectatorTargetsTo(List<SpectatorTargetState> destination);

		IReadOnlyList<SpectatorPoseState> GetRemoteSpectatorPoses();

		IReadOnlyList<PeerIdentityState> GetRemotePeerIdentities();

		void CopyRemotePeerIdentitiesTo(List<PeerIdentityState> destination);

		IReadOnlyList<VoiceActivityState> GetRemoteVoiceActivities();

		IReadOnlyList<ModPeerCapability> GetKnownModdedPeers();
	}
	public interface IModNetworkTransport : IDisposable
	{
		bool IsRegistered { get; }

		bool IsNetworkAvailable { get; }

		bool IsHost { get; }

		ulong LocalClientId { get; }

		ulong ServerClientId { get; }

		bool IsPeerConnected(ulong clientId);

		bool TryRegister(Action<ulong, ModPeerCapability> capabilityReceived, Action<ulong, SpectatorTargetState> spectatorTargetReceived, Action<ulong, SpectatorPoseState> spectatorPoseReceived, Action<ulong, PeerIdentityState> peerIdentityReceived, Action<ulong, VoiceActivityState> voiceActivityReceived, out string reason);

		void Unregister();

		bool SendCapability(ModPeerCapability capability, IEnumerable<ulong>? recipients, out string reason);

		bool SendSpectatorTarget(SpectatorTargetSyncMessage message, IEnumerable<ulong> recipients, out string reason);

		bool SendSpectatorPose(SpectatorPoseSyncMessage message, IEnumerable<ulong> recipients, out string reason);

		bool SendPeerIdentity(PeerIdentityState state, IEnumerable<ulong>? recipients, out string reason);

		bool SendVoiceActivity(VoiceActivitySyncMessage message, IEnumerable<ulong> recipients, out string reason);
	}
	public interface INetworkRuntimeState
	{
		int FrameCount { get; }

		float RealtimeSinceStartup { get; }

		float UnscaledTime { get; }

		long UtcNowTicks { get; }

		bool CanUseModNetworking(out string reason);
	}
	public sealed class UnityNetworkRuntimeState : INetworkRuntimeState
	{
		public static UnityNetworkRuntimeState Instance { get; } = new UnityNetworkRuntimeState();


		public int FrameCount => Time.frameCount;

		public float RealtimeSinceStartup => Time.realtimeSinceStartup;

		public float UnscaledTime => Time.unscaledTime;

		public long UtcNowTicks => DateTime.UtcNow.Ticks;

		private UnityNetworkRuntimeState()
		{
		}

		public bool CanUseModNetworking(out string reason)
		{
			return RuntimeConnectionState.CanUseModNetworking(out reason);
		}
	}
	public interface IPeerIdentityStateProvider
	{
		bool TryGetLocalPeerIdentity(out PeerIdentityState state);
	}
	public static class ModNetworkConstants
	{
		public const int ProtocolVersion = 1;

		public const string CapabilityMessageName = "EnhancedSpectator.Capability.V1";

		public const string SpectatorTargetMessageName = "EnhancedSpectator.SpectatorTarget.V1";

		public const string SpectatorPoseMessageName = "EnhancedSpectator.SpectatorPose.V1";

		public const string PeerIdentityMessageName = "EnhancedSpectator.PeerIdentity.V1";

		public const string VoiceActivityMessageName = "EnhancedSpectator.VoiceActivity.V1";

		public const double TargetSyncMinIntervalSeconds = 0.1;

		public const double VoiceActivitySyncMinIntervalSeconds = 0.066;
	}
	public static class ModNetworkSerializer
	{
		public static int CapabilityMessageSize => FastBufferWriter.GetWriteSize<int>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<long>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<bool>();

		public static int SpectatorTargetMessageSize => FastBufferWriter.GetWriteSize<int>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<long>();

		public static int SpectatorPoseMessageSize => FastBufferWriter.GetWriteSize<int>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<float>() * 7 + FastBufferWriter.GetWriteSize<long>();

		public static int PeerIdentityMessageSize => FastBufferWriter.GetWriteSize<int>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<FixedString64Bytes>() + FastBufferWriter.GetWriteSize<long>() + FastBufferWriter.GetWriteSize<FixedString64Bytes>();

		public static int VoiceActivityMessageSize => FastBufferWriter.GetWriteSize<int>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<float>() + FastBufferWriter.GetWriteSize<float>() + FastBufferWriter.GetWriteSize<long>();

		public static void WriteCapability(ref FastBufferWriter writer, ModPeerCapability capability)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: 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)
			int protocolVersion = capability.ProtocolVersion;
			((FastBufferWriter)(ref writer)).WriteValueSafe<int>(ref protocolVersion, default(ForPrimitives));
			ulong clientId = capability.ClientId;
			((FastBufferWriter)(ref writer)).WriteValueSafe<ulong>(ref clientId, default(ForPrimitives));
			bool supportsCapabilityHandshake = capability.SupportsCapabilityHandshake;
			((FastBufferWriter)(ref writer)).WriteValueSafe<bool>(ref supportsCapabilityHandshake, default(ForPrimitives));
			supportsCapabilityHandshake = capability.SupportsSpectatorTargetSync;
			((FastBufferWriter)(ref writer)).WriteValueSafe<bool>(ref supportsCapabilityHandshake, default(ForPrimitives));
			long lastSeenTicks = capability.LastSeenTicks;
			((FastBufferWriter)(ref writer)).WriteValueSafe<long>(ref lastSeenTicks, default(ForPrimitives));
			supportsCapabilityHandshake = capability.SupportsVoiceActivitySync;
			((FastBufferWriter)(ref writer)).WriteValueSafe<bool>(ref supportsCapabilityHandshake, default(ForPrimitives));
			supportsCapabilityHandshake = capability.SupportsSpectatorVoiceToTarget;
			((FastBufferWriter)(ref writer)).WriteValueSafe<bool>(ref supportsCapabilityHandshake, default(ForPrimitives));
		}

		public static bool TryReadCapability(ref FastBufferReader reader, out ModPeerCapability capability, out string reason)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: 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_0063: 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_0075: 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_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			capability = null;
			reason = string.Empty;
			try
			{
				int num = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives));
				if (num != 1)
				{
					reason = $"unsupported protocol version {num}";
					return false;
				}
				ulong clientId = default(ulong);
				((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref clientId, default(ForPrimitives));
				bool flag = default(bool);
				((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref flag, default(ForPrimitives));
				bool supportsSpectat