Decompiled source of Reconnect v0.0.5

AndrewLin.Reconnect.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using Alpha;
using Alpha.Core.Command;
using Alpha.Core.Util;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using PurrNet;
using PurrNet.Transports;
using Reconnect.Core;
using Reconnect.Core.Commands;
using Reconnect.Patches;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("AndrewLin")]
[assembly: AssemblyConfiguration("Publish")]
[assembly: AssemblyDescription("Reconnect: Auto-reconnect to lobby after unexpected disconnection.")]
[assembly: AssemblyFileVersion("0.0.5.0")]
[assembly: AssemblyInformationalVersion("0.0.5+04a0d07c90b171cd035609be9cc718ae62140a14")]
[assembly: AssemblyProduct("AndrewLin.Reconnect")]
[assembly: AssemblyTitle("AndrewLin.Reconnect")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/andrewlimforfun/ot-mods")]
[assembly: AssemblyVersion("0.0.5.0")]
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;
		}
	}
}
namespace Reconnect
{
	public static class BuildInfo
	{
		public const string Version = "0.0.5";
	}
	[BepInPlugin("com.andrewlin.ontogether.reconnect", "Reconnect", "0.0.5")]
	public class ReconnectPlugin : BaseUnityPlugin
	{
		[CompilerGenerated]
		private sealed class <ReconnectCoroutine>d__45 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			private ConnectionState <state>5__1;

			private float <waited>5__2;

			private float <timeout>5__3;

			private Exception <ex>5__4;

			private ConnectionState <current>5__5;

			object? IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object? IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <ReconnectCoroutine>d__45(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<ex>5__4 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
				//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
				//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
				//IL_01d9: Invalid comparison between Unknown and I4
				//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
				//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
				//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
				//IL_00b6: Invalid comparison between Unknown and I4
				//IL_0251: Unknown result type (might be due to invalid IL or missing references)
				//IL_0257: Invalid comparison between Unknown and I4
				int num = <>1__state;
				if (num != 0)
				{
					if (num != 1)
					{
						return false;
					}
					<>1__state = -1;
					<waited>5__2 += Time.unscaledDeltaTime;
					<current>5__5 = NetworkManager.main.clientState;
					if ((int)<current>5__5 == 1)
					{
						ChatUtils.AddGlobalNotification($"Reconnected successfully on attempt {ReconnectManager.CurrentAttempt}/{ReconnectManager.MaxAttempts}!");
						Log.LogInfo((object)$"Reconnected successfully on attempt {ReconnectManager.CurrentAttempt}/{ReconnectManager.MaxAttempts}!");
						ReconnectManager.OnConnected();
						return false;
					}
					if ((int)<current>5__5 != 2 || !(<waited>5__2 > 2f))
					{
						goto IL_0272;
					}
				}
				else
				{
					<>1__state = -1;
					if (!ReconnectManager.TryBeginSequence(Time.unscaledTime))
					{
						Log.LogWarning((object)$"Reconnect cooldown active ({ReconnectManager.CooldownSec}s). Allowing normal disconnect flow.");
						FallbackToMenu();
						return false;
					}
					Log.LogInfo((object)$"Starting reconnect sequence. Max attempts: {ReconnectManager.MaxAttempts}, interval: {ReconnectManager.AttemptIntervalSec}s");
				}
				goto IL_028a;
				IL_0272:
				if (<waited>5__2 < <timeout>5__3)
				{
					<>2__current = null;
					<>1__state = 1;
					return true;
				}
				goto IL_028a;
				IL_028a:
				if (ReconnectManager.TryNextAttempt())
				{
					<state>5__1 = NetworkManager.main.clientState;
					if ((int)<state>5__1 == 1)
					{
						Log.LogInfo((object)"Already connected - reconnect succeeded (or wasn't needed).");
						ReconnectManager.OnConnected();
						return false;
					}
					ChatUtils.AddGlobalNotification($"Reconnect attempt {ReconnectManager.CurrentAttempt}/{ReconnectManager.MaxAttempts}...");
					Log.LogInfo((object)$"Reconnect attempt {ReconnectManager.CurrentAttempt}/{ReconnectManager.MaxAttempts}...");
					try
					{
						NetworkManager.main.StartClient();
					}
					catch (Exception ex)
					{
						<ex>5__4 = ex;
						Log.LogError((object)("StartClient() threw: " + <ex>5__4.Message));
						goto IL_029d;
					}
					<waited>5__2 = 0f;
					<timeout>5__3 = ReconnectManager.AttemptIntervalSec;
					goto IL_0272;
				}
				goto IL_029d;
				IL_029d:
				Log.LogWarning((object)"Reconnect failed after all attempts. Returning to menu.");
				ReconnectManager.OnFailed();
				FallbackToMenu();
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		public const string ModGUID = "com.andrewlin.ontogether.reconnect";

		public const string ModName = "Reconnect";

		public const string ModVersion = "0.0.5";

		internal static ManualLogSource Log = null;

		private static ReconnectPlugin? _instance;

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

		public static ConfigEntry<int>? MaxAttempts { get; private set; }

		public static ConfigEntry<float>? AttemptIntervalSec { get; private set; }

		public static ConfigEntry<float>? CooldownSec { get; private set; }

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

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

		internal static ReconnectManager ReconnectManager { get; private set; } = new ReconnectManager(() => MaxAttempts?.Value ?? 10, () => AttemptIntervalSec?.Value ?? 5f, () => CooldownSec?.Value ?? 30f);


		public static bool IsIntentionalLeave
		{
			get
			{
				return ReconnectManager.IsIntentionalLeave;
			}
			set
			{
				ReconnectManager.IsIntentionalLeave = value;
			}
		}

		public static bool IsReconnecting => ReconnectManager.IsReconnecting;

		public static string? SavedLobbyId
		{
			get
			{
				return ReconnectManager.SavedLobbyId;
			}
			set
			{
				ReconnectManager.SavedLobbyId = value;
			}
		}

		private void Awake()
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			_instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Reconnect v0.0.5 is loaded!");
			InitConfig();
			Harmony val = new Harmony("com.andrewlin.ontogether.reconnect");
			val.PatchAll(typeof(MainSceneManagerPatch));
			val.PatchAll(typeof(MultiplayerManagerPatch));
			val.PatchAll(typeof(PlayerControllerPatch));
			ChatCommandManager commandManager = AlphaPlugin.CommandManager;
			if (commandManager != null)
			{
				commandManager.Register((IChatCommand)(object)new ReconnectToggleCommand());
			}
			ChatCommandManager commandManager2 = AlphaPlugin.CommandManager;
			if (commandManager2 != null)
			{
				commandManager2.Register((IChatCommand)(object)new ReconnectMaxAttemptsCommand());
			}
			ChatCommandManager commandManager3 = AlphaPlugin.CommandManager;
			if (commandManager3 != null)
			{
				commandManager3.Register((IChatCommand)(object)new ReconnectIntervalCommand());
			}
			ChatCommandManager commandManager4 = AlphaPlugin.CommandManager;
			if (commandManager4 != null)
			{
				commandManager4.Register((IChatCommand)(object)new ReconnectCooldownCommand());
			}
			ChatCommandManager commandManager5 = AlphaPlugin.CommandManager;
			if (commandManager5 != null)
			{
				commandManager5.Register((IChatCommand)(object)new ReconnectSimulateCommand());
			}
			ChatCommandManager commandManager6 = AlphaPlugin.CommandManager;
			if (commandManager6 != null)
			{
				commandManager6.Register((IChatCommand)(object)new ReconnectFocusCommand());
			}
		}

		private void InitConfig()
		{
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable auto-reconnect on unexpected disconnection.");
			MaxAttempts = ((BaseUnityPlugin)this).Config.Bind<int>("General", "MaxAttempts", 100, "Maximum reconnect attempts before giving up (1-100).");
			AttemptIntervalSec = ((BaseUnityPlugin)this).Config.Bind<float>("General", "AttemptIntervalSec", 6f, "Seconds between reconnect attempts.");
			CooldownSec = ((BaseUnityPlugin)this).Config.Bind<float>("General", "CooldownSec", 30f, "Minimum seconds between reconnect sequences to prevent rapid-fire loops.");
			FixFocusArea = ((BaseUnityPlugin)this).Config.Bind<bool>("Fix", "FocusArea", true, "Re-request focus area state after spawn if Init RPC was missed.");
			RestoreFocus = ((BaseUnityPlugin)this).Config.Bind<bool>("Fix", "RestoreFocus", true, "Automatically restore focus activity after reconnect.");
		}

		internal static Coroutine? StartReconnectCoroutine()
		{
			if ((Object)(object)_instance == (Object)null)
			{
				return null;
			}
			return ((MonoBehaviour)_instance).StartCoroutine(ReconnectCoroutine());
		}

		internal static Coroutine? StartPluginCoroutine(IEnumerator routine)
		{
			if ((Object)(object)_instance == (Object)null)
			{
				return null;
			}
			return ((MonoBehaviour)_instance).StartCoroutine(routine);
		}

		[IteratorStateMachine(typeof(<ReconnectCoroutine>d__45))]
		private static IEnumerator ReconnectCoroutine()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <ReconnectCoroutine>d__45(0);
		}

		private static void FallbackToMenu()
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			ReconnectManager.SavedState = null;
			try
			{
				MainSceneManager i = MonoSingleton<MainSceneManager>.I;
				if (!((Object)(object)i == (Object)null))
				{
					AccessTools.Field(typeof(MainSceneManager), "_returnMenuStarted")?.SetValue(i, false);
					MultiplayerManager i2 = MonoSingleton<MultiplayerManager>.I;
					if ((Object)(object)i2 != (Object)null)
					{
						i2._notificationState = (NotificationStatus)3;
					}
					i.ReturnMenu(false);
				}
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"FallbackToMenu failed: {arg}");
			}
		}

		private void OnDestroy()
		{
			_instance = null;
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "AndrewLin.Reconnect";

		public const string PLUGIN_NAME = "AndrewLin.Reconnect";

		public const string PLUGIN_VERSION = "0.0.5";
	}
}
namespace Reconnect.Patches
{
	[HarmonyPatch(typeof(MainSceneManager))]
	public static class MainSceneManagerPatch
	{
		private static ManualLogSource _log = Logger.CreateLogSource("Reconnect.MainSceneManagerPatch");

		[HarmonyPatch("ConnectionLost")]
		[HarmonyPrefix]
		private static bool ConnectionLost_Prefix(NotificationStatus notificationStatus)
		{
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			ConfigEntry<bool>? enabled = ReconnectPlugin.Enabled;
			if (enabled == null || !enabled.Value)
			{
				return true;
			}
			if (ReconnectPlugin.IsIntentionalLeave)
			{
				_log.LogInfo((object)"Intentional leave - allowing normal disconnect flow.");
				ReconnectPlugin.IsIntentionalLeave = false;
				return true;
			}
			if (ReconnectPlugin.IsReconnecting)
			{
				_log.LogInfo((object)"Already reconnecting - suppressing duplicate ConnectionLost.");
				return false;
			}
			if (NetworkManager.main.isHost)
			{
				_log.LogInfo((object)"Host disconnected - cannot self-reconnect, allowing normal flow.");
				return true;
			}
			try
			{
				MultiplayerManager i = MonoSingleton<MultiplayerManager>.I;
				if ((Object)(object)i != (Object)null && i.LobbyStatus)
				{
					ReconnectPlugin.SavedLobbyId = i.LobbyCode;
				}
			}
			catch
			{
			}
			_log.LogInfo((object)$"Unexpected disconnect (status={notificationStatus}). Attempting reconnect...");
			ChatUtils.AddGlobalNotification("Connection lost - attempting to reconnect...");
			ReconnectPlugin.ReconnectManager.SavedState = PlayerStateSnapshot.Capture();
			if (ReconnectPlugin.ReconnectManager.SavedState != null)
			{
				_log.LogInfo((object)$"Saved player state: pos={ReconnectPlugin.ReconnectManager.SavedState.Position}, focused={ReconnectPlugin.ReconnectManager.SavedState.WasFocused}");
			}
			ReconnectPlugin.StartReconnectCoroutine();
			return false;
		}
	}
	[HarmonyPatch(typeof(MultiplayerManager))]
	public static class MultiplayerManagerPatch
	{
		private static ManualLogSource Logger = Logger.CreateLogSource("Reconnect.MultiplayerManagerPatch");

		[HarmonyPatch("QuitSession")]
		[HarmonyPrefix]
		private static void QuitSession_Prefix()
		{
			Logger.LogDebug((object)"QuitSession called - marking as intentional leave.");
			ReconnectPlugin.IsIntentionalLeave = true;
		}
	}
	[HarmonyPatch(typeof(MainSceneManager))]
	public static class MainSceneManagerQuitPatch
	{
		private static ManualLogSource _log = Logger.CreateLogSource("Reconnect.MainSceneManagerQuitPatch");

		[HarmonyPatch("ButtonQuit")]
		[HarmonyPrefix]
		private static void ButtonQuit_Prefix()
		{
			_log.LogInfo((object)"ButtonQuit called - marking as intentional leave.");
			ReconnectPlugin.IsIntentionalLeave = true;
		}
	}
	[HarmonyPatch(typeof(PlayerController))]
	public static class PlayerControllerPatch
	{
		private static ManualLogSource _log = Logger.CreateLogSource("Reconnect.PlayerControllerPatch");

		[HarmonyPatch("OnSpawned")]
		[HarmonyPostfix]
		private static void OnSpawned_Postfix(PlayerController __instance)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			if (!((NetworkIdentity)__instance).isOwner)
			{
				return;
			}
			ConfigEntry<bool>? fixFocusArea = ReconnectPlugin.FixFocusArea;
			if (fixFocusArea != null && fixFocusArea.Value)
			{
				ReconnectPlugin.StartPluginCoroutine(FocusAreaFix.TryRepairAfterSpawn());
			}
			PlayerStateSnapshot savedState = ReconnectPlugin.ReconnectManager.SavedState;
			if (savedState == null)
			{
				return;
			}
			ReconnectPlugin.ReconnectManager.SavedState = null;
			((Component)__instance).transform.position = savedState.Position;
			((Component)__instance).transform.rotation = savedState.Rotation;
			_log.LogInfo((object)$"Restored position: {savedState.Position}");
			if (savedState.WasFocused)
			{
				ConfigEntry<bool>? restoreFocus = ReconnectPlugin.RestoreFocus;
				if (restoreFocus != null && restoreFocus.Value)
				{
					_log.LogInfo((object)$"Player was in focus mode ({savedState.FocusType}) before disconnect. Attempting auto-restore...");
					ReconnectPlugin.StartPluginCoroutine(FocusRestorer.TryRestoreFocus(savedState));
					return;
				}
			}
			if (savedState.WasFocused)
			{
				ChatUtils.AddGlobalNotification("Reconnected - position restored. Press F to re-enter focus.");
			}
			else
			{
				ChatUtils.AddGlobalNotification("Reconnected - position restored.");
			}
		}
	}
}
namespace Reconnect.Core
{
	public static class FocusAreaFix
	{
		[CompilerGenerated]
		private sealed class <TryRepairAfterSpawn>d__2 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <TryRepairAfterSpawn>d__2(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0026: Unknown result type (might be due to invalid IL or missing references)
				//IL_0030: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = (object)new WaitForSeconds(5f);
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					if (!ShouldRepair())
					{
						_log.LogDebug((object)"FocusAreaFix: AreaIndexes already populated - no repair needed.");
						return false;
					}
					_log.LogWarning((object)"FocusAreaFix: AreaIndexes is null after spawn - requesting re-sync from server.");
					Apply();
					return false;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private static readonly ManualLogSource _log = Logger.CreateLogSource("Reconnect.FocusAreaFix");

		private const float RetryDelaySec = 5f;

		[IteratorStateMachine(typeof(<TryRepairAfterSpawn>d__2))]
		public static IEnumerator TryRepairAfterSpawn()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <TryRepairAfterSpawn>d__2(0);
		}

		public static bool Apply()
		{
			FocusAreaManager i = NetworkSingleton<FocusAreaManager>.I;
			if ((Object)(object)i == (Object)null)
			{
				_log.LogWarning((object)"FocusAreaFix: FocusAreaManager singleton not available.");
				return false;
			}
			NetworkTransform val = NetworkSingleton<TextChannelManager>.I?.MainNetTransform;
			if ((Object)(object)val == (Object)null)
			{
				_log.LogWarning((object)"FocusAreaFix: MainNetTransform not available - cannot request re-sync.");
				return false;
			}
			i.UpdateFocusAreasOnNewPeople(val);
			_log.LogInfo((object)"FocusAreaFix: Sent UpdateFocusAreasOnNewPeople ServerRpc to re-sync focus areas.");
			return true;
		}

		public static bool ShouldRepair()
		{
			FocusAreaManager i = NetworkSingleton<FocusAreaManager>.I;
			return (Object)(object)i != (Object)null && i.AreaIndexes == null;
		}
	}
	public static class FocusRestorer
	{
		[CompilerGenerated]
		private sealed class <TryRestoreFocus>d__7 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public PlayerStateSnapshot state;

			private float <elapsed>5__1;

			private FocusAreaManager <focusAreaManager>5__2;

			private PlayerFocusController <focusCtrl>5__3;

			private bool <success>5__4;

			private FocusAreaManager <fam>5__5;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <TryRestoreFocus>d__7(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<focusAreaManager>5__2 = null;
				<focusCtrl>5__3 = null;
				<fam>5__5 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0086: Unknown result type (might be due to invalid IL or missing references)
				//IL_0090: Expected O, but got Unknown
				//IL_0114: Unknown result type (might be due to invalid IL or missing references)
				//IL_011e: Expected O, but got Unknown
				//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					if (!state.WasFocused)
					{
						return false;
					}
					<elapsed>5__1 = 0f;
					goto IL_00ba;
				case 1:
					<>1__state = -1;
					<elapsed>5__1 += 0.5f;
					<fam>5__5 = null;
					goto IL_00ba;
				case 2:
					{
						<>1__state = -1;
						<focusCtrl>5__3 = NetworkSingleton<TextChannelManager>.I?.MainFocusController;
						if ((Object)(object)<focusCtrl>5__3 == (Object)null)
						{
							_log.LogWarning((object)"FocusRestorer: MainFocusController not available.");
							return false;
						}
						if (<focusCtrl>5__3.IsFocus)
						{
							_log.LogDebug((object)"FocusRestorer: Player is already in focus - skipping.");
							return false;
						}
						<success>5__4 = RestoreFocusState(<focusCtrl>5__3, state, <focusAreaManager>5__2);
						if (<success>5__4)
						{
							_log.LogInfo((object)$"FocusRestorer: Restored focus ({state.FocusType}) at area {state.FocusAreaId}.");
							ChatUtils.AddGlobalNotification("Reconnected - focus restored.");
						}
						else
						{
							_log.LogWarning((object)"FocusRestorer: Could not restore focus automatically.");
							ChatUtils.AddGlobalNotification("Reconnected - position restored. Press F to re-enter focus.");
						}
						return false;
					}
					IL_00ba:
					if (<elapsed>5__1 < 5f)
					{
						<fam>5__5 = NetworkSingleton<FocusAreaManager>.I;
						if (!((Object)(object)<fam>5__5 != (Object)null) || <fam>5__5.AreaIndexes == null)
						{
							<>2__current = (object)new WaitForSeconds(0.5f);
							<>1__state = 1;
							return true;
						}
					}
					<focusAreaManager>5__2 = NetworkSingleton<FocusAreaManager>.I;
					if ((Object)(object)<focusAreaManager>5__2 == (Object)null || <focusAreaManager>5__2.AreaIndexes == null)
					{
						_log.LogWarning((object)"FocusRestorer: AreaIndexes still null after wait - cannot restore focus.");
						return false;
					}
					<>2__current = (object)new WaitForSeconds(1f);
					<>1__state = 2;
					return true;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private static readonly ManualLogSource _log = Logger.CreateLogSource("Reconnect.FocusRestorer");

		private const float WaitForAreaFixSec = 5f;

		private static readonly FieldInfo _simpledFocusTypesField = AccessTools.Field(typeof(PlayerFocusController), "_simpledFocusTypes");

		private static readonly FieldInfo _currentFocusMainIndexField = AccessTools.Field(typeof(PlayerFocusController), "_currentFocusMainIndex");

		private static readonly FieldInfo _preFocusPositionField = AccessTools.Field(typeof(PlayerFocusController), "_preFocusPosition");

		private static readonly FieldInfo _preFocusRotationField = AccessTools.Field(typeof(PlayerFocusController), "_preFocusRotation");

		private static readonly PropertyInfo _currentFocusAreaControllerProp = AccessTools.Property(typeof(PlayerFocusController), "CurrentFocusAreaController");

		[IteratorStateMachine(typeof(<TryRestoreFocus>d__7))]
		public static IEnumerator TryRestoreFocus(PlayerStateSnapshot state)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <TryRestoreFocus>d__7(0)
			{
				state = state
			};
		}

		private static bool RestoreFocusState(PlayerFocusController focusCtrl, PlayerStateSnapshot state, FocusAreaManager focusAreaManager)
		{
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			FocusAreaController val = null;
			if (state.FocusAreaId >= 0)
			{
				List<FocusAreaController> areaControllers = focusAreaManager.AreaControllers;
				if (state.FocusAreaId < areaControllers.Count)
				{
					val = areaControllers[state.FocusAreaId];
				}
				if ((Object)(object)val == (Object)null)
				{
					_log.LogWarning((object)$"FocusRestorer: Area controller {state.FocusAreaId} not found.");
					return false;
				}
				if (!val.IsYogaOrIsland && !focusAreaManager.IsAreaEmpty(state.FocusAreaId))
				{
					_log.LogWarning((object)$"FocusRestorer: Seat {state.FocusAreaId} is occupied.");
					return false;
				}
			}
			_currentFocusAreaControllerProp.SetValue(focusCtrl, val);
			List<FocusType> list = (((Object)(object)val == (Object)null) ? ScriptableSingleton<GameSettings>.I.FocusTypes : val.FocusAreaSettings.FocusTypes);
			List<FocusType> list2 = new List<FocusType>();
			for (int i = 0; i < list.Count; i++)
			{
				if (!list2.Contains(list[i]))
				{
					list2.Add(list[i]);
				}
			}
			int num = list2.IndexOf(state.FocusType);
			if (num == -1)
			{
				_log.LogWarning((object)$"FocusRestorer: FocusType {state.FocusType} not available at this area.");
				_currentFocusAreaControllerProp.SetValue(focusCtrl, null);
				return false;
			}
			_simpledFocusTypesField.SetValue(focusCtrl, list2);
			_currentFocusMainIndexField.SetValue(focusCtrl, num);
			_preFocusPositionField.SetValue(focusCtrl, state.PreFocusPosition);
			_preFocusRotationField.SetValue(focusCtrl, state.PreFocusRotation);
			focusCtrl.SetFocus(0);
			_preFocusPositionField.SetValue(focusCtrl, state.PreFocusPosition);
			_preFocusRotationField.SetValue(focusCtrl, state.PreFocusRotation);
			return true;
		}
	}
	public class PlayerStateSnapshot
	{
		public Vector3 Position { get; }

		public Quaternion Rotation { get; }

		public bool WasFocused { get; }

		public FocusType FocusType { get; }

		public int FocusAreaId { get; }

		public Vector3 PreFocusPosition { get; }

		public Quaternion PreFocusRotation { get; }

		public PlayerStateSnapshot(Vector3 position, Quaternion rotation, bool wasFocused, FocusType focusType, int focusAreaId, Vector3 preFocusPosition, Quaternion preFocusRotation)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			Position = position;
			Rotation = rotation;
			WasFocused = wasFocused;
			FocusType = focusType;
			FocusAreaId = focusAreaId;
			PreFocusPosition = preFocusPosition;
			PreFocusRotation = preFocusRotation;
		}

		public static PlayerStateSnapshot? Capture()
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			TextChannelManager i = NetworkSingleton<TextChannelManager>.I;
			if ((Object)(object)i == (Object)null || (Object)(object)i.MainPlayer == (Object)null)
			{
				return null;
			}
			Transform mainPlayer = i.MainPlayer;
			Vector3 position = mainPlayer.position;
			Quaternion rotation = mainPlayer.rotation;
			bool wasFocused = false;
			FocusType focusType = (FocusType)0;
			int focusAreaId = -1;
			Vector3 preFocusPosition = position;
			Quaternion preFocusRotation = rotation;
			PlayerFocusController mainFocusController = i.MainFocusController;
			if ((Object)(object)mainFocusController != (Object)null && mainFocusController.IsFocus)
			{
				wasFocused = true;
				focusType = mainFocusController.FocusType;
				FocusAreaController currentFocusAreaController = mainFocusController.CurrentFocusAreaController;
				if ((Object)(object)currentFocusAreaController != (Object)null)
				{
					focusAreaId = currentFocusAreaController.ID;
				}
				FieldInfo fieldInfo = AccessTools.Field(typeof(PlayerFocusController), "_preFocusPosition");
				FieldInfo fieldInfo2 = AccessTools.Field(typeof(PlayerFocusController), "_preFocusRotation");
				if (fieldInfo != null)
				{
					preFocusPosition = (Vector3)fieldInfo.GetValue(mainFocusController);
				}
				if (fieldInfo2 != null)
				{
					preFocusRotation = (Quaternion)fieldInfo2.GetValue(mainFocusController);
				}
			}
			return new PlayerStateSnapshot(position, rotation, wasFocused, focusType, focusAreaId, preFocusPosition, preFocusRotation);
		}
	}
	public class ReconnectManager
	{
		private float _lastSequenceTime;

		private int _currentAttempt;

		private readonly Func<int> _maxAttempts;

		private readonly Func<float> _attemptIntervalSec;

		private readonly Func<float> _cooldownSec;

		public int MaxAttempts => _maxAttempts();

		public float AttemptIntervalSec => _attemptIntervalSec();

		public float CooldownSec => _cooldownSec();

		public bool IsIntentionalLeave { get; set; }

		public bool IsReconnecting { get; private set; }

		public string? SavedLobbyId { get; set; }

		public PlayerStateSnapshot? SavedState { get; set; }

		public int CurrentAttempt => _currentAttempt;

		public ReconnectManager(Func<int> maxAttempts, Func<float> attemptIntervalSec, Func<float> cooldownSec)
		{
			_maxAttempts = maxAttempts;
			_attemptIntervalSec = attemptIntervalSec;
			_cooldownSec = cooldownSec;
		}

		public bool TryBeginSequence(float currentTime)
		{
			if (_lastSequenceTime > 0f && currentTime - _lastSequenceTime < CooldownSec)
			{
				return false;
			}
			_lastSequenceTime = currentTime;
			_currentAttempt = 0;
			IsReconnecting = true;
			return true;
		}

		public bool TryNextAttempt()
		{
			if (IsIntentionalLeave)
			{
				IsReconnecting = false;
				return false;
			}
			if (_currentAttempt >= MaxAttempts)
			{
				IsReconnecting = false;
				return false;
			}
			_currentAttempt++;
			return true;
		}

		public void OnConnected()
		{
			IsReconnecting = false;
			IsIntentionalLeave = false;
		}

		public void OnFailed()
		{
			IsReconnecting = false;
		}
	}
}
namespace Reconnect.Core.Commands
{
	public class ReconnectCooldownCommand : IChatCommand, IComparable<IChatCommand>
	{
		public string Name => "reconnectcooldown";

		public string ShortName => "rccd";

		public string Description => "Get or set cooldown between sequences in seconds (10-120). Usage: /rccd [value]";

		public string Namespace => "reconnect";

		public void Execute(string[] args)
		{
			if (ReconnectPlugin.CooldownSec == null)
			{
				return;
			}
			if (args.Length >= 1)
			{
				if (!float.TryParse(args[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || result < 10f || result > 120f)
				{
					ChatUtils.AddGlobalNotification("Usage: /rccd [10-120]");
					return;
				}
				ReconnectPlugin.CooldownSec.Value = result;
			}
			ChatUtils.AddGlobalNotification($"CooldownSec = {ReconnectPlugin.CooldownSec.Value}");
		}
	}
	public class ReconnectFocusCommand : IChatCommand, IComparable<IChatCommand>
	{
		public string Name => "reconnectfocus";

		public string ShortName => "rcf";

		public string Description => "Repair broken focus areas after reconnect.";

		public string Namespace => "reconnect";

		public void Execute(string[] args)
		{
			if (!FocusAreaFix.ShouldRepair())
			{
				ChatUtils.AddGlobalNotification("Focus areas are working normally (AreaIndexes is populated).");
			}
			else if (FocusAreaFix.Apply())
			{
				ChatUtils.AddGlobalNotification("Sent focus area re-sync request to server.");
			}
			else
			{
				ChatUtils.AddGlobalNotification("Failed to repair - FocusAreaManager or MainNetTransform not available.");
			}
		}
	}
	public class ReconnectIntervalCommand : IChatCommand, IComparable<IChatCommand>
	{
		public string Name => "reconnectinterval";

		public string ShortName => "rciv";

		public string Description => "Get or set seconds between attempts (2-30). Usage: /rciv [value]";

		public string Namespace => "reconnect";

		public void Execute(string[] args)
		{
			if (ReconnectPlugin.AttemptIntervalSec == null)
			{
				return;
			}
			if (args.Length >= 1)
			{
				if (!float.TryParse(args[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || result < 2f || result > 30f)
				{
					ChatUtils.AddGlobalNotification("Usage: /rciv [2-30]");
					return;
				}
				ReconnectPlugin.AttemptIntervalSec.Value = result;
			}
			ChatUtils.AddGlobalNotification($"AttemptIntervalSec = {ReconnectPlugin.AttemptIntervalSec.Value}");
		}
	}
	public class ReconnectMaxAttemptsCommand : IChatCommand, IComparable<IChatCommand>
	{
		public string Name => "reconnectmaxattempts";

		public string ShortName => "rcma";

		public string Description => "Get or set max reconnect attempts (1-10). Usage: /rcma [value]";

		public string Namespace => "reconnect";

		public void Execute(string[] args)
		{
			if (ReconnectPlugin.MaxAttempts == null)
			{
				return;
			}
			if (args.Length >= 1)
			{
				if (!int.TryParse(args[0], out var result) || result < 1 || result > 100)
				{
					ChatUtils.AddGlobalNotification("Usage: /rcma [1-100]");
					return;
				}
				ReconnectPlugin.MaxAttempts.Value = result;
			}
			ChatUtils.AddGlobalNotification($"MaxAttempts = {ReconnectPlugin.MaxAttempts.Value}");
		}
	}
	public class ReconnectSimulateCommand : IChatCommand, IComparable<IChatCommand>
	{
		public string Name => "reconnectsimulate";

		public string ShortName => "rcs";

		public string Description => "Simulate an unintentional disconnect to test the reconnect sequence.";

		public string Namespace => "reconnect";

		public void Execute(string[] args)
		{
			ConfigEntry<bool>? enabled = ReconnectPlugin.Enabled;
			if (enabled == null || !enabled.Value)
			{
				ChatUtils.AddGlobalNotification("Reconnect is disabled. Enable it first with /rct on");
				return;
			}
			if (ReconnectPlugin.IsReconnecting)
			{
				ChatUtils.AddGlobalNotification("Already reconnecting.");
				return;
			}
			if (NetworkManager.main.isHost)
			{
				ChatUtils.AddGlobalNotification("Cannot test reconnect as host.");
				return;
			}
			ChatUtils.AddGlobalNotification("Simulating unintentional disconnect...");
			ReconnectPlugin.IsIntentionalLeave = false;
			NetworkManager.main.StopClient();
		}
	}
	public class ReconnectToggleCommand : IChatCommand, IComparable<IChatCommand>
	{
		public string Name => "reconnecttoggle";

		public string ShortName => "rct";

		public string Description => "Get or set auto-reconnect on/off. Usage: /rct [on|off]";

		public string Namespace => "reconnect";

		public void Execute(string[] args)
		{
			if (ReconnectPlugin.Enabled == null)
			{
				return;
			}
			if (args.Length >= 1)
			{
				string text = args[0].ToLowerInvariant();
				if (text == "on" || text == "true" || text == "1")
				{
					ReconnectPlugin.Enabled.Value = true;
				}
				else
				{
					if (!(text == "off") && !(text == "false") && !(text == "0"))
					{
						ChatUtils.AddGlobalNotification("Usage: /rct [on|off]");
						return;
					}
					ReconnectPlugin.Enabled.Value = false;
				}
			}
			else
			{
				ReconnectPlugin.Enabled.Value = !ReconnectPlugin.Enabled.Value;
			}
			ChatUtils.AddGlobalNotification("Reconnect is now " + (ReconnectPlugin.Enabled.Value ? "enabled" : "disabled") + ".");
		}
	}
}