Decompiled source of DarkwaterRejoinFix v1.0.1

plugins/DarkwaterRejoinFix.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Logging;
using Fusion;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Voice;
using Photon.Voice.Fusion;
using Photon.Voice.Unity;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("DarkwaterRejoinFix")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyInformationalVersion("1.0.1+859e4696212cace146228777f6b82e506522ba9a")]
[assembly: AssemblyProduct("DarkwaterRejoinFix")]
[assembly: AssemblyTitle("DarkwaterRejoinFix")]
[assembly: AssemblyVersion("1.0.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace DarkwaterRejoinFix
{
	[BepInPlugin("DarkwaterRejoinFix", "DarkwaterRejoinFix", "1.0.1")]
	public class Plugin : BaseUnityPlugin
	{
		[CompilerGenerated]
		private sealed class <RecoverVoice>d__17 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public int session;

			private FusionVoiceClient <voiceClient>5__2;

			private int <attempt>5__3;

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

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

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

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

			private bool MoveNext()
			{
				//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
				//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
				//IL_01d2: Expected O, but got Unknown
				//IL_014a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0154: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<voiceClient>5__2 = Object.FindFirstObjectByType<FusionVoiceClient>();
					if (!Object.op_Implicit((Object)(object)<voiceClient>5__2))
					{
						Logger.LogWarning((object)$"[HotJoinRecovery {session}] FusionVoiceClient not found.");
						return false;
					}
					if (CountRemotePlayers() == 0)
					{
						Logger.LogInfo((object)$"[HotJoinRecovery {session}] No remote players present, skipping voice recovery.");
						return false;
					}
					LogVoiceState(session, <voiceClient>5__2, "before voice recovery");
					if (CountLinkedRemoteSpeakers() > 0)
					{
						Logger.LogInfo((object)$"[HotJoinRecovery {session}] Voice already linked, skipping reconnect.");
						return false;
					}
					<attempt>5__3 = 1;
					goto IL_024c;
				case 1:
					<>1__state = -1;
					goto IL_0144;
				case 2:
				{
					<>1__state = -1;
					bool flag = ((VoiceFollowClient)<voiceClient>5__2).ConnectAndJoinRoom();
					Logger.LogInfo((object)$"[HotJoinRecovery {session}] Voice attempt {<attempt>5__3}: ConnectAndJoinRoom returned {flag}. state={((VoiceConnection)<voiceClient>5__2).ClientState}.");
					<>2__current = (object)new WaitForSeconds(4f);
					<>1__state = 3;
					return true;
				}
				case 3:
					{
						<>1__state = -1;
						LogVoiceState(session, <voiceClient>5__2, $"after voice attempt {<attempt>5__3}");
						if (CountLinkedRemoteSpeakers() > 0)
						{
							Logger.LogInfo((object)$"[HotJoinRecovery {session}] Voice linked successfully on attempt {<attempt>5__3}.");
							return false;
						}
						<attempt>5__3++;
						goto IL_024c;
					}
					IL_0144:
					<>2__current = (object)new WaitForSeconds(0.5f);
					<>1__state = 2;
					return true;
					IL_024c:
					if (<attempt>5__3 <= 2)
					{
						if (GetClientBool(((VoiceConnection)<voiceClient>5__2).Client, "IsConnected"))
						{
							Logger.LogInfo((object)$"[HotJoinRecovery {session}] Voice attempt {<attempt>5__3}: disconnecting busy voice client.");
							((VoiceFollowClient)<voiceClient>5__2).Disconnect();
							<>2__current = WaitForVoiceDisconnect(<voiceClient>5__2, 4f);
							<>1__state = 1;
							return true;
						}
						goto IL_0144;
					}
					Logger.LogWarning((object)$"[HotJoinRecovery {session}] Voice recovery finished without any linked remote speakers.");
					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();
			}
		}

		[CompilerGenerated]
		private sealed class <RunClientRecovery>d__11 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			private int <session>5__2;

			private int <observedRequestVersion>5__3;

			private int <attempt>5__4;

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

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

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

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

			private bool MoveNext()
			{
				//IL_013a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0144: Expected O, but got Unknown
				//IL_0082: Unknown result type (might be due to invalid IL or missing references)
				//IL_008c: Expected O, but got Unknown
				//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e6: Expected O, but got Unknown
				//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
				//IL_0205: Expected O, but got Unknown
				//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
				//IL_01bc: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					_clientRecoveryRunning = true;
					<session>5__2 = ++_clientRecoverySession;
					<observedRequestVersion>5__3 = _clientRecoveryRequestVersion;
					Logger.LogInfo((object)$"[HotJoinRecovery {<session>5__2}] Started from {_clientRecoveryLastReason}.");
					goto IL_009c;
				case 1:
					<>1__state = -1;
					goto IL_009c;
				case 2:
					<>1__state = -1;
					if (<observedRequestVersion>5__3 != _clientRecoveryRequestVersion)
					{
						<observedRequestVersion>5__3 = _clientRecoveryRequestVersion;
						Logger.LogInfo((object)$"[HotJoinRecovery {<session>5__2}] Waiting for latest targeted sync ({_clientRecoveryLastReason}).");
						goto IL_00d6;
					}
					<>2__current = (object)new WaitForSeconds(2.5f);
					<>1__state = 3;
					return true;
				case 3:
					<>1__state = -1;
					LogClientState(<session>5__2, "before gameplay recovery");
					<attempt>5__4 = 1;
					goto IL_01dc;
				case 4:
					<>1__state = -1;
					<attempt>5__4++;
					goto IL_01dc;
				case 5:
					<>1__state = -1;
					<>2__current = RecoverVoice(<session>5__2);
					<>1__state = 6;
					return true;
				case 6:
					{
						<>1__state = -1;
						LogClientState(<session>5__2, "after voice recovery");
						CMD.Instance.hotJoined = false;
						_clientRecoveryRunning = false;
						Logger.LogInfo((object)$"[HotJoinRecovery {<session>5__2}] Finished.");
						return false;
					}
					IL_01dc:
					if (<attempt>5__4 <= 8)
					{
						if (!StabilizeLocalHotJoinState(<session>5__2, <attempt>5__4))
						{
							<>2__current = (object)new WaitForSeconds(0.5f);
							<>1__state = 4;
							return true;
						}
						Logger.LogInfo((object)$"[HotJoinRecovery {<session>5__2}] Local gameplay state stabilized on attempt {<attempt>5__4}.");
					}
					LogClientState(<session>5__2, "after gameplay recovery");
					<>2__current = (object)new WaitForSeconds(1.5f);
					<>1__state = 5;
					return true;
					IL_009c:
					if (!Object.op_Implicit((Object)(object)CMD_PlayerManager.Instance) || !Object.op_Implicit((Object)(object)CMD_PlayerManager.Instance.localPlayer) || !Object.op_Implicit((Object)(object)CMD_ShipManager.Instance) || !Object.op_Implicit((Object)(object)CMD_ShipManager.Instance.playerShip))
					{
						<>2__current = (object)new WaitForSeconds(0.25f);
						<>1__state = 1;
						return true;
					}
					goto IL_00d6;
					IL_00d6:
					<>2__current = (object)new WaitForSeconds(1.5f);
					<>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();
			}
		}

		[CompilerGenerated]
		private sealed class <WaitForVoiceDisconnect>d__18 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public float timeoutSeconds;

			public FusionVoiceClient voiceClient;

			private float <endTime>5__2;

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

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

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

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

			private bool MoveNext()
			{
				//IL_0031: Unknown result type (might be due to invalid IL or missing references)
				//IL_003b: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<endTime>5__2 = Time.time + timeoutSeconds;
					break;
				case 1:
					<>1__state = -1;
					break;
				}
				if (Time.time < <endTime>5__2 && GetClientBool(((VoiceConnection)voiceClient).Client, "IsConnected"))
				{
					<>2__current = (object)new WaitForSeconds(0.1f);
					<>1__state = 1;
					return true;
				}
				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();
			}
		}

		internal const string StoreReplaySeed = "__dw_store__";

		internal static ManualLogSource Logger;

		private Harmony _harmony;

		private static bool _clientRecoveryRunning;

		private static int _clientRecoverySession;

		private static int _clientRecoveryRequestVersion;

		private static string _clientRecoveryLastReason = "unknown";

		private static int _suppressDockOutpost;

		private void Awake()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			Logger = ((BaseUnityPlugin)this).Logger;
			_harmony = new Harmony("DarkwaterRejoinFix");
			_harmony.PatchAll();
			Logger.LogInfo((object)"Plugin DarkwaterRejoinFix is loaded!");
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		internal static void StartClientRecovery(MonoBehaviour host, string reason)
		{
			_clientRecoveryLastReason = reason;
			_clientRecoveryRequestVersion++;
			if (_clientRecoveryRunning)
			{
				Logger.LogInfo((object)("[HotJoinRecovery] Client recovery refreshed by " + reason + "."));
			}
			else
			{
				host.StartCoroutine(RunClientRecovery());
			}
		}

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

		private static bool StabilizeLocalHotJoinState(int session, int attempt)
		{
			//IL_000c: 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_0017: 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_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Invalid comparison between Unknown and I4
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			C_Controller_Player localPlayer = CMD_PlayerManager.Instance.localPlayer;
			ShipNetworkID val = DetermineDesiredLocalSubmarine(localPlayer);
			E_Submarine submarine = CMD_ShipManager.Instance.GetSubmarine(val);
			Transform val2 = (Object.op_Implicit((Object)(object)submarine) ? submarine.spawnLocation : null);
			float num = (Object.op_Implicit((Object)(object)val2) ? Vector3.Distance(((Component)localPlayer).transform.position, val2.position) : (-1f));
			((C_Controller)localPlayer).newClient = false;
			ApplyDesiredLocalScene(localPlayer, val);
			if (((C_Controller)localPlayer).MouseModeEnabled() || localPlayer.requiresRevive)
			{
				((C_Controller)localPlayer).RPC_MouseMode(false, default(RpcInfo));
				localPlayer.requiresRevive = false;
			}
			if (Object.op_Implicit((Object)(object)CMD_Effects.Instance) && CMD_Effects.Instance.localPlayerUnderwater)
			{
				CMD_Effects.Instance.LocalPlayerSurface();
			}
			CMD_PlayerManager.Instance.UpdateAllPlayersMics();
			bool flag = !((C_Controller)localPlayer).MouseModeEnabled() && !localPlayer.requiresRevive && ((C_Controller)localPlayer).inOutpost == ((int)val == 2) && ((C_Controller)localPlayer).submarineLocation == val && CMD_ShipManager.Instance.currentSubmarineID == val;
			if (flag)
			{
				bool flag2 = ((num < 0f || num < 4f) ? true : false);
				flag = flag2;
			}
			bool flag3 = flag;
			if (attempt == 1 || flag3 || attempt == 8)
			{
				Logger.LogInfo((object)($"[HotJoinRecovery {session}] Gameplay attempt={attempt} stable={flag3} pos={FormatVector(((Component)localPlayer).transform.position)} " + $"desiredSub={val} sub={((C_Controller)localPlayer).submarineLocation} currentSub={CMD_ShipManager.Instance.currentSubmarineID} inOutpost={((C_Controller)localPlayer).inOutpost} " + string.Format("mouse={0} requiresRevive={1} distToSpawn={2}", ((C_Controller)localPlayer).MouseModeEnabled(), localPlayer.requiresRevive, (num < 0f) ? "n/a" : num.ToString("F2"))));
			}
			return flag3;
		}

		private static ShipNetworkID DetermineDesiredLocalSubmarine(C_Controller_Player localPlayer)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Invalid comparison between Unknown and I4
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Invalid comparison between Unknown and I4
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			if (Object.op_Implicit((Object)(object)CMD_ShipManager.Instance) && Object.op_Implicit((Object)(object)CMD_ShipManager.Instance.outpost))
			{
				if (!((C_Controller)localPlayer).inOutpost && (int)((C_Controller)localPlayer).submarineLocation != 2 && (int)CMD_ShipManager.Instance.currentSubmarineID != 2 && !(((Component)localPlayer).transform.position.y < -2.2f))
				{
					return (ShipNetworkID)0;
				}
				return (ShipNetworkID)2;
			}
			return (ShipNetworkID)0;
		}

		private static void ApplyDesiredLocalScene(C_Controller_Player localPlayer, ShipNetworkID desiredSubmarine)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			if ((int)desiredSubmarine == 2 && Object.op_Implicit((Object)(object)CMD_ShipManager.Instance.outpost))
			{
				((C_Controller)localPlayer).inOutpost = true;
				CMD_ShipManager.Instance.SetCurrentSubmarine((ShipNetworkID)2);
				if (Object.op_Implicit((Object)(object)CMD_OutpostManager.Instance))
				{
					CMD_OutpostManager.Instance.EnterOutpost(CMD_ShipManager.Instance.outpost.shipTheme);
				}
			}
			else
			{
				if (Object.op_Implicit((Object)(object)CMD_OutpostManager.Instance))
				{
					CMD_OutpostManager.Instance.ExitOutpost();
				}
				((C_Controller)localPlayer).inOutpost = false;
				CMD_ShipManager.Instance.SetCurrentSubmarine((ShipNetworkID)0);
			}
		}

		internal static bool IsStoreReplay(string seed)
		{
			return string.Equals(seed, "__dw_store__", StringComparison.Ordinal);
		}

		internal static void ReplayStoreDock()
		{
			if (Object.op_Implicit((Object)(object)CMD_OutpostManager.Instance) && Object.op_Implicit((Object)(object)CMD_Store.Instance) && !CMD_OutpostManager.Instance.DockedAtStore())
			{
				Logger.LogInfo((object)"Replaying local store dock for hot-join client.");
				CMD_OutpostManager.Instance.DockWithStore();
				CMD_Store.Instance.ModifySubmarine(false);
			}
		}

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

		[IteratorStateMachine(typeof(<WaitForVoiceDisconnect>d__18))]
		private static IEnumerator WaitForVoiceDisconnect(FusionVoiceClient voiceClient, float timeoutSeconds)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <WaitForVoiceDisconnect>d__18(0)
			{
				voiceClient = voiceClient,
				timeoutSeconds = timeoutSeconds
			};
		}

		private static void LogClientState(int session, string label)
		{
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)CMD_PlayerManager.Instance) || !Object.op_Implicit((Object)(object)CMD_PlayerManager.Instance.localPlayer))
			{
				Logger.LogInfo((object)$"[HotJoinRecovery {session}] {label}: local player missing.");
				return;
			}
			C_Controller_Player localPlayer = CMD_PlayerManager.Instance.localPlayer;
			Transform val = (Object.op_Implicit((Object)(object)CMD_AudioManager.Instance) ? CMD_AudioManager.Instance.localListener : null);
			E_Submarine val2 = (Object.op_Implicit((Object)(object)CMD_ShipManager.Instance) ? CMD_ShipManager.Instance.currentSubmarine : null);
			Logger.LogInfo((object)($"[HotJoinRecovery {session}] {label}: pos={FormatVector(((Component)localPlayer).transform.position)} head={FormatVector(Object.op_Implicit((Object)(object)((C_Controller)localPlayer).headPosition) ? ((C_Controller)localPlayer).headPosition.position : Vector3.zero)} " + string.Format("listener={0} sub={1} currentSub={2} ", FormatVector(Object.op_Implicit((Object)(object)val) ? val.position : Vector3.zero), ((C_Controller)localPlayer).submarineLocation, Object.op_Implicit((Object)(object)val2) ? ((object)(ShipNetworkID)(ref val2.networkShipType)).ToString() : "null") + $"inOutpost={((C_Controller)localPlayer).inOutpost} mouse={((C_Controller)localPlayer).MouseModeEnabled()} requiresRevive={localPlayer.requiresRevive} linkedSpeakers={CountLinkedRemoteSpeakers()}/{CountRemotePlayers()}."));
		}

		private static void LogVoiceState(int session, FusionVoiceClient voiceClient, string label)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			object client = ((VoiceConnection)voiceClient).Client;
			string currentRoomName = GetCurrentRoomName(client);
			Logger.LogInfo((object)(string.Format("[HotJoinRecovery {0}] {1}: voiceState={2} connected={3} ", session, label, ((VoiceConnection)voiceClient).ClientState, GetClientBool(client, "IsConnected")) + string.Format("inRoom={0} room={1} linkedSpeakers={2}/{3}.", GetClientBool(client, "InRoom"), currentRoomName, CountLinkedRemoteSpeakers(), CountRemotePlayers())));
			foreach (C_Controller_Player player in CMD_PlayerManager.Instance.players)
			{
				if (Object.op_Implicit((Object)(object)player) && !((Object)(object)player == (Object)(object)CMD_PlayerManager.Instance.localPlayer))
				{
					Speaker val = (Object.op_Implicit((Object)(object)player.source_VoiceSpeaker) ? player.source_VoiceSpeaker.SpeakerInUse : null);
					AudioSource val2 = (Object.op_Implicit((Object)(object)val) ? ((Component)val).GetComponent<AudioSource>() : null);
					Logger.LogInfo((object)($"[HotJoinRecovery {session}] remote={player.playerName} linked={Object.op_Implicit((Object)(object)val) && val.IsLinked} playing={Object.op_Implicit((Object)(object)val) && val.IsPlaying} " + string.Format("audioEnabled={0} volume={1} room={2}.", Object.op_Implicit((Object)(object)val2) && ((Behaviour)val2).enabled, Object.op_Implicit((Object)(object)val2) ? val2.volume.ToString("F2") : "n/a", currentRoomName)));
				}
			}
		}

		private static int CountRemotePlayers()
		{
			if (!Object.op_Implicit((Object)(object)CMD_PlayerManager.Instance) || CMD_PlayerManager.Instance.players == null)
			{
				return 0;
			}
			int num = 0;
			C_Controller_Player localPlayer = CMD_PlayerManager.Instance.localPlayer;
			foreach (C_Controller_Player player in CMD_PlayerManager.Instance.players)
			{
				if (Object.op_Implicit((Object)(object)player) && (Object)(object)player != (Object)(object)localPlayer)
				{
					num++;
				}
			}
			return num;
		}

		private static int CountLinkedRemoteSpeakers()
		{
			if (!Object.op_Implicit((Object)(object)CMD_PlayerManager.Instance) || CMD_PlayerManager.Instance.players == null)
			{
				return 0;
			}
			int num = 0;
			C_Controller_Player localPlayer = CMD_PlayerManager.Instance.localPlayer;
			foreach (C_Controller_Player player in CMD_PlayerManager.Instance.players)
			{
				if (Object.op_Implicit((Object)(object)player) && !((Object)(object)player == (Object)(object)localPlayer) && Object.op_Implicit((Object)(object)player.source_VoiceSpeaker))
				{
					Speaker speakerInUse = player.source_VoiceSpeaker.SpeakerInUse;
					if (Object.op_Implicit((Object)(object)speakerInUse) && speakerInUse.IsLinked)
					{
						num++;
					}
				}
			}
			return num;
		}

		private static bool GetClientBool(object client, string propertyName)
		{
			object obj = client.GetType().GetProperty(propertyName)?.GetValue(client);
			bool flag = default(bool);
			int num;
			if (obj is bool)
			{
				flag = (bool)obj;
				num = 1;
			}
			else
			{
				num = 0;
			}
			return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
		}

		private static string GetCurrentRoomName(object client)
		{
			object obj = client.GetType().GetProperty("CurrentRoom")?.GetValue(client);
			return obj?.GetType().GetProperty("Name")?.GetValue(obj)?.ToString() ?? "null";
		}

		private static string FormatVector(Vector3 vector)
		{
			//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 $"({vector.x:F2}, {vector.y:F2}, {vector.z:F2})";
		}

		internal static bool IsDockOutpostSuppressed()
		{
			return _suppressDockOutpost > 0;
		}

		internal static void PushDockOutpostSuppress()
		{
			_suppressDockOutpost++;
		}

		internal static void PopDockOutpostSuppress()
		{
			if (_suppressDockOutpost > 0)
			{
				_suppressDockOutpost--;
			}
		}
	}
	[HarmonyPatch(typeof(CMD_NetworkEssentials), "OnPlayerJoined")]
	public static class LateJoinHostPatch
	{
		[CompilerGenerated]
		private sealed class <PlaceLateJoinPlayer>d__1 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public PlayerRef player;

			private int <attempt>5__2;

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

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

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

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

			private bool MoveNext()
			{
				//IL_002c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0036: Expected O, but got Unknown
				//IL_0058: Unknown result type (might be due to invalid IL or missing references)
				//IL_0066: Unknown result type (might be due to invalid IL or missing references)
				//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
				//IL_019e: Unknown result type (might be due to invalid IL or missing references)
				//IL_01a8: Expected O, but got Unknown
				//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
				//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
				//IL_0102: Unknown result type (might be due to invalid IL or missing references)
				//IL_0137: 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_0144: Unknown result type (might be due to invalid IL or missing references)
				//IL_014a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0121: Unknown result type (might be due to invalid IL or missing references)
				//IL_0127: Unknown result type (might be due to invalid IL or missing references)
				//IL_0159: Unknown result type (might be due to invalid IL or missing references)
				//IL_015b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0177: Unknown result type (might be due to invalid IL or missing references)
				//IL_0182: Unknown result type (might be due to invalid IL or missing references)
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<>2__current = (object)new WaitForSeconds(2.5f);
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					if (Object.op_Implicit((Object)(object)CMD_NetworkEssentials.Instance) && CMD_NetworkEssentials.Instance.PlayerExists(player))
					{
						<>2__current = ReplayDockedScene(player);
						<>1__state = 2;
						return true;
					}
					goto IL_0085;
				case 2:
					<>1__state = -1;
					goto IL_0085;
				case 3:
					{
						<>1__state = -1;
						<attempt>5__2++;
						break;
					}
					IL_0085:
					<attempt>5__2 = 1;
					break;
				}
				if (<attempt>5__2 <= 4)
				{
					Transform val = CMD_ShipManager.Instance.GetSubmarine((ShipNetworkID)0)?.spawnLocation;
					if (!Object.op_Implicit((Object)(object)val))
					{
						Plugin.Logger.LogWarning((object)"Late-join placement could not find the player submarine spawn.");
						return false;
					}
					if (Object.op_Implicit((Object)(object)CMD_NetworkEssentials.Instance) && CMD_NetworkEssentials.Instance.PlayerExists(player))
					{
						C_Controller_Player val2 = CMD_NetworkEssentials.Instance.GetPlayer(player);
						Vector3 position = ((Component)val2).transform.position;
						if (Object.op_Implicit((Object)(object)val2))
						{
							val2.RPC_SetCurrentSubmarine((ShipNetworkID)0);
							if (((C_Controller)val2).MouseModeEnabled())
							{
								((C_Controller)val2).RPC_MouseMode(false, default(RpcInfo));
								val2.requiresRevive = false;
							}
							((C_Controller)val2).RPC_SetPosition(val.position, val.eulerAngles, default(RpcInfo));
							if (!((C_Controller)val2).MouseModeEnabled() && Vector3.Distance(position, val.position) < 3f)
							{
								Plugin.Logger.LogInfo((object)$"Placed late-join player {player} at submarine spawn {val.position}.");
								return false;
							}
						}
					}
					<>2__current = (object)new WaitForSeconds(0.75f);
					<>1__state = 3;
					return true;
				}
				Plugin.Logger.LogWarning((object)$"Late-join placement timed out for {player} after {4} attempts.");
				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();
			}
		}

		[CompilerGenerated]
		private sealed class <ReplayDockedScene>d__2 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public PlayerRef player;

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

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

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

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

			private bool MoveNext()
			{
				//IL_006a: Unknown result type (might be due to invalid IL or missing references)
				//IL_0084: 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)
				//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ce: Expected O, but got Unknown
				//IL_010f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0129: Unknown result type (might be due to invalid IL or missing references)
				//IL_0141: 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)
				//IL_0168: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
				{
					<>1__state = -1;
					if (!Object.op_Implicit((Object)(object)CMD_ShipManager.Instance) || !Object.op_Implicit((Object)(object)CMD_ShipManager.Instance.outpost))
					{
						return false;
					}
					if (Object.op_Implicit((Object)(object)CMD_OutpostManager.Instance) && CMD_OutpostManager.Instance.DockedAtStore())
					{
						Plugin.Logger.LogInfo((object)$"Replaying docked store for late-join player {player}.");
						CMD_Network.Instance.RPC_GenerateShip_LoadByChunk_TargetedClient(player, (ShipNetworkID)2, "__dw_store__", string.Empty, ((Component)CMD_OutpostManager.Instance.store).transform.position, true, 1f, 0, CMD_Map.Instance.GetCurrentNodeID());
						<>2__current = (object)new WaitForSeconds(0.75f);
						<>1__state = 1;
						return true;
					}
					E_Submarine outpost = CMD_ShipManager.Instance.outpost;
					float num = ((CMD_DistrictManager.currentDistrict != null) ? CMD_DistrictManager.currentDistrict.GetLightingChance() : 1f);
					Plugin.Logger.LogInfo((object)$"Replaying docked outpost for late-join player {player}.");
					CMD_Network.Instance.RPC_GenerateShip_LoadByChunk_TargetedClient(player, (ShipNetworkID)2, outpost.seed, outpost.shipInstructions, ((Component)outpost).transform.position, true, num, 0, CMD_Map.Instance.GetCurrentNodeID());
					<>2__current = (object)new WaitForSeconds(1.25f);
					<>1__state = 2;
					return true;
				}
				case 1:
					<>1__state = -1;
					return false;
				case 2:
					<>1__state = -1;
					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 void Postfix(CMD_NetworkEssentials __instance, NetworkRunner runner, PlayerRef player)
		{
			//IL_001f: 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)
			if (runner.IsServer && CMD_Network.Instance.gameStarted)
			{
				Plugin.Logger.LogInfo((object)$"Scheduling late-join placement for {player}.");
				((MonoBehaviour)__instance).StartCoroutine(PlaceLateJoinPlayer(player));
			}
		}

		[IteratorStateMachine(typeof(<PlaceLateJoinPlayer>d__1))]
		private static IEnumerator PlaceLateJoinPlayer(PlayerRef player)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <PlaceLateJoinPlayer>d__1(0)
			{
				player = player
			};
		}

		[IteratorStateMachine(typeof(<ReplayDockedScene>d__2))]
		private static IEnumerator ReplayDockedScene(PlayerRef player)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <ReplayDockedScene>d__2(0)
			{
				player = player
			};
		}
	}
	[HarmonyPatch(typeof(CMD_Network), "RPC_LoadByChunk_GenerateSubmarineComplete_Targeted")]
	public static class ClientHotJoinRecoveryPatch
	{
		private static void Postfix(CMD_Network __instance)
		{
			if (!CMD_Network.Instance.IsHost())
			{
				Plugin.StartClientRecovery((MonoBehaviour)(object)__instance, "targeted RPC");
			}
		}
	}
	[HarmonyPatch(typeof(CMD_Network), "RPC_LoadByChunk_GenerateOutpostComplete_Targeted")]
	public static class ClientOutpostRecoveryPatch
	{
		private static bool Prefix(CMD_Network __instance, string P_seed, ref bool __state)
		{
			if (CMD_Network.Instance.IsHost())
			{
				return true;
			}
			if (Plugin.IsStoreReplay(P_seed))
			{
				__state = false;
				Plugin.ReplayStoreDock();
				return false;
			}
			__state = true;
			Plugin.PushDockOutpostSuppress();
			return true;
		}

		private static void Postfix(CMD_Network __instance, string P_seed, bool __state)
		{
			if (!CMD_Network.Instance.IsHost())
			{
				if (__state)
				{
					Plugin.PopDockOutpostSuppress();
				}
				Plugin.StartClientRecovery((MonoBehaviour)(object)__instance, Plugin.IsStoreReplay(P_seed) ? "targeted store RPC" : "targeted outpost RPC");
			}
		}

		private static void Finalizer(CMD_Network __instance, bool __state)
		{
			if (!CMD_Network.Instance.IsHost() && __state)
			{
				Plugin.PopDockOutpostSuppress();
			}
		}
	}
	[HarmonyPatch(typeof(CMD_Network), "DockWithOutpost")]
	public static class DockWithOutpostGuardPatch
	{
		private static bool Prefix()
		{
			if (!Plugin.IsDockOutpostSuppressed())
			{
				return true;
			}
			Plugin.Logger.LogInfo((object)"Suppressing DockWithOutpost during targeted outpost hot-join replay.");
			return false;
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "DarkwaterRejoinFix";

		public const string PLUGIN_NAME = "DarkwaterRejoinFix";

		public const string PLUGIN_VERSION = "1.0.1";
	}
}