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 d__45 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; private ConnectionState 5__1; private float 5__2; private float 5__3; private Exception 5__4; private ConnectionState 5__5; object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__45(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 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; 5__2 += Time.unscaledDeltaTime; 5__5 = NetworkManager.main.clientState; if ((int)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)5__5 != 2 || !(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 (5__2 < 5__3) { <>2__current = null; <>1__state = 1; return true; } goto IL_028a; IL_028a: if (ReconnectManager.TryNextAttempt()) { 5__1 = NetworkManager.main.clientState; if ((int)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) { 5__4 = ex; Log.LogError((object)("StartClient() threw: " + 5__4.Message)); goto IL_029d; } 5__2 = 0f; 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? Enabled { get; private set; } public static ConfigEntry? MaxAttempts { get; private set; } public static ConfigEntry? AttemptIntervalSec { get; private set; } public static ConfigEntry? CooldownSec { get; private set; } public static ConfigEntry? FixFocusArea { get; private set; } public static ConfigEntry? 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("General", "Enabled", true, "Enable auto-reconnect on unexpected disconnection."); MaxAttempts = ((BaseUnityPlugin)this).Config.Bind("General", "MaxAttempts", 100, "Maximum reconnect attempts before giving up (1-100)."); AttemptIntervalSec = ((BaseUnityPlugin)this).Config.Bind("General", "AttemptIntervalSec", 6f, "Seconds between reconnect attempts."); CooldownSec = ((BaseUnityPlugin)this).Config.Bind("General", "CooldownSec", 30f, "Minimum seconds between reconnect sequences to prevent rapid-fire loops."); FixFocusArea = ((BaseUnityPlugin)this).Config.Bind("Fix", "FocusArea", true, "Re-request focus area state after spawn if Init RPC was missed."); RestoreFocus = ((BaseUnityPlugin)this).Config.Bind("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(d__45))] private static IEnumerator ReconnectCoroutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new 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.I; if (!((Object)(object)i == (Object)null)) { AccessTools.Field(typeof(MainSceneManager), "_returnMenuStarted")?.SetValue(i, false); MultiplayerManager i2 = MonoSingleton.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? 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.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? 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? 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 d__2 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public 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(d__2))] public static IEnumerator TryRepairAfterSpawn() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__2(0); } public static bool Apply() { FocusAreaManager i = NetworkSingleton.I; if ((Object)(object)i == (Object)null) { _log.LogWarning((object)"FocusAreaFix: FocusAreaManager singleton not available."); return false; } NetworkTransform val = NetworkSingleton.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.I; return (Object)(object)i != (Object)null && i.AreaIndexes == null; } } public static class FocusRestorer { [CompilerGenerated] private sealed class d__7 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public PlayerStateSnapshot state; private float 5__1; private FocusAreaManager 5__2; private PlayerFocusController 5__3; private bool 5__4; private FocusAreaManager 5__5; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__7(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__2 = null; 5__3 = null; 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; } 5__1 = 0f; goto IL_00ba; case 1: <>1__state = -1; 5__1 += 0.5f; 5__5 = null; goto IL_00ba; case 2: { <>1__state = -1; 5__3 = NetworkSingleton.I?.MainFocusController; if ((Object)(object)5__3 == (Object)null) { _log.LogWarning((object)"FocusRestorer: MainFocusController not available."); return false; } if (5__3.IsFocus) { _log.LogDebug((object)"FocusRestorer: Player is already in focus - skipping."); return false; } 5__4 = RestoreFocusState(5__3, state, 5__2); if (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 (5__1 < 5f) { 5__5 = NetworkSingleton.I; if (!((Object)(object)5__5 != (Object)null) || 5__5.AreaIndexes == null) { <>2__current = (object)new WaitForSeconds(0.5f); <>1__state = 1; return true; } } 5__2 = NetworkSingleton.I; if ((Object)(object)5__2 == (Object)null || 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(d__7))] public static IEnumerator TryRestoreFocus(PlayerStateSnapshot state) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new 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 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 list = (((Object)(object)val == (Object)null) ? ScriptableSingleton.I.FocusTypes : val.FocusAreaSettings.FocusTypes); List list2 = new List(); 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.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 _maxAttempts; private readonly Func _attemptIntervalSec; private readonly Func _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 maxAttempts, Func attemptIntervalSec, Func 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 { 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 { 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 { 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 { 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 { 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? 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 { 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") + "."); } } }