Decompiled source of NoDisconnectRestart v2.5.0

NoDisconnectRestart.dll

Decompiled 3 months 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 System.Text;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using UnityEngine.SceneManagement;

[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.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("NoDisconnectRestart")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("NoDisconnectRestart")]
[assembly: AssemblyTitle("NoDisconnectRestart")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace NoDisconnectRestart;

[BepInPlugin("com.sparky777.rounds.nodisconnectrestart", "NoDisconnectRestart", "2.5.0")]
public class NoDisconnectRestartPlugin : BaseUnityPlugin
{
	public const string ModId = "com.sparky777.rounds.nodisconnectrestart";

	public const string ModName = "NoDisconnectRestart";

	public const string ModVersion = "2.5.0";

	internal static ManualLogSource Log;

	private Harmony _harmony;

	private void Awake()
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		_harmony = new Harmony("com.sparky777.rounds.nodisconnectrestart");
		_harmony.PatchAll(typeof(NoDisconnectRestartPlugin).Assembly);
		Log.LogInfo((object)"NoDisconnectRestart 2.5.0 loaded.");
	}

	private void Start()
	{
		ApplyRWFPatches();
	}

	private void ApplyRWFPatches()
	{
		//IL_0107: Unknown result type (might be due to invalid IL or missing references)
		//IL_0115: Expected O, but got Unknown
		//IL_0097: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a5: Expected O, but got Unknown
		//IL_0177: Unknown result type (might be due to invalid IL or missing references)
		//IL_0185: Expected O, but got Unknown
		//IL_020e: Unknown result type (might be due to invalid IL or missing references)
		//IL_021c: Expected O, but got Unknown
		//IL_0299: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a7: Expected O, but got Unknown
		//IL_0330: Unknown result type (might be due to invalid IL or missing references)
		//IL_033e: Expected O, but got Unknown
		//IL_0387: Unknown result type (might be due to invalid IL or missing references)
		//IL_0395: Expected O, but got Unknown
		//IL_03fa: Unknown result type (might be due to invalid IL or missing references)
		//IL_0408: Expected O, but got Unknown
		Type type = AccessTools.TypeByName("Photon.Pun.PhotonNetwork") ?? AccessTools.TypeByName("PhotonNetwork");
		if (type != null)
		{
			MethodInfo methodInfo = AccessTools.Method(type, "LoadLevel", new Type[1] { typeof(string) }, (Type[])null) ?? AccessTools.Method(type, "LoadLevel", new Type[1] { typeof(int) }, (Type[])null);
			if (methodInfo != null)
			{
				_harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(Patch_PhotonNetwork_LoadLevel), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Log.LogInfo((object)"[NDR] Patched PhotonNetwork.LoadLevel");
			}
		}
		Type type2 = AccessTools.TypeByName("NetworkConnectionHandler");
		if (type2 != null)
		{
			MethodInfo methodInfo2 = AccessTools.Method(type2, "OnDisconnected", (Type[])null, (Type[])null);
			if (methodInfo2 != null)
			{
				_harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(Patch_OnDisconnected), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Log.LogInfo((object)"[NDR] Patched NetworkConnectionHandler.OnDisconnected");
			}
		}
		Type type3 = AccessTools.TypeByName("PlayerManager");
		if (type3 != null)
		{
			MethodInfo methodInfo3 = AccessTools.Method(type3, "ResetCharacters", (Type[])null, (Type[])null);
			if (methodInfo3 != null)
			{
				_harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(Patch_PlayerManager_ResetCharacters), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Log.LogInfo((object)"[NDR] Patched PlayerManager.ResetCharacters");
			}
		}
		Type typeFromHandle = typeof(SceneManager);
		MethodInfo methodInfo4 = AccessTools.Method(typeFromHandle, "LoadScene", new Type[1] { typeof(string) }, (Type[])null) ?? AccessTools.Method(typeFromHandle, "LoadScene", new Type[1] { typeof(int) }, (Type[])null);
		if (methodInfo4 != null)
		{
			_harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(typeof(Patch_SceneManager_LoadScene), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Log.LogInfo((object)"[NDR] Patched SceneManager.LoadScene");
		}
		MethodInfo methodInfo5 = AccessTools.Method(typeFromHandle, "LoadSceneAsync", new Type[1] { typeof(string) }, (Type[])null) ?? AccessTools.Method(typeFromHandle, "LoadSceneAsync", new Type[1] { typeof(int) }, (Type[])null);
		if (methodInfo5 != null)
		{
			_harmony.Patch((MethodBase)methodInfo5, new HarmonyMethod(typeof(Patch_SceneManager_LoadSceneAsync), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Log.LogInfo((object)"[NDR] Patched SceneManager.LoadSceneAsync");
		}
		Type type4 = AccessTools.TypeByName("RWF.GameModes.RWFGameMode") ?? AccessTools.TypeByName("RWFGameMode");
		if (type4 == null)
		{
			Log.LogWarning((object)"[NDR] RWFGameMode not found.");
		}
		else
		{
			MethodInfo methodInfo6 = AccessTools.Method(type4, "ResetMatch", (Type[])null, (Type[])null);
			if (methodInfo6 != null)
			{
				_harmony.Patch((MethodBase)methodInfo6, new HarmonyMethod(typeof(Patch_RWFGameMode_ResetMatch), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Log.LogInfo((object)"[NDR] Patched RWFGameMode.ResetMatch");
			}
			MethodInfo methodInfo7 = AccessTools.Method(type4, "OnDisable", (Type[])null, (Type[])null);
			if (methodInfo7 != null)
			{
				_harmony.Patch((MethodBase)methodInfo7, new HarmonyMethod(typeof(Patch_RWFGameMode_OnDisable), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Log.LogInfo((object)"[NDR] Patched RWFGameMode.OnDisable");
			}
		}
		Type type5 = AccessTools.TypeByName("RWF.PointVisualizerExtensions");
		if (type5 != null)
		{
			MethodInfo methodInfo8 = AccessTools.Method(type5, "ResetBalls", (Type[])null, (Type[])null);
			if (methodInfo8 != null)
			{
				_harmony.Patch((MethodBase)methodInfo8, new HarmonyMethod(typeof(Patch_PointVisualizer_ResetBalls), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Log.LogInfo((object)"[NDR] Patched PointVisualizerExtensions.ResetBalls");
			}
		}
		TryPatch(new string[2] { "CardChoiceVisuals", "CardChoice" }, new string[4] { "PlayerLeftInPickPhase", "CheckPlayerCount", "OnPlayerLeft", "PlayerLeft" }, "Patch_CardPickDisconnect", "Prefix");
		TryPatch(new string[3] { "GameManager", "GM", "RoundHandler" }, new string[4] { "RoundOver", "OnRoundEnd", "RoundEnd", "PointOver" }, "Patch_RoundEnd", "Postfix", isPrefix: false, isPostfix: true);
		TryPatch(new string[2] { "NetworkConnectionHandler", "RoundsNetworkConnectionHandler" }, new string[3] { "OnPlayerEnteredRoom", "PlayerEnteredRoom", "OnPlayerJoined" }, "Patch_OnPlayerEnteredRoom", "Postfix", isPrefix: false, isPostfix: true);
		TryPatch(new string[2] { "CardChoice", "CardChoiceVisuals" }, new string[5] { "StartPick", "DoPick", "StartCardPick", "StartPickPhase", "Pick" }, "Patch_CardPickPhaseStart", "Postfix", isPrefix: false, isPostfix: true);
		void TryPatch(string[] typeNames, string[] methodNames, string patchClass, string patchMethod, bool isPrefix = true, bool isPostfix = false)
		{
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			foreach (string text in typeNames)
			{
				Type type6 = AccessTools.TypeByName(text);
				if (!(type6 == null))
				{
					foreach (string text2 in methodNames)
					{
						MethodInfo methodInfo9 = AccessTools.Method(type6, text2, (Type[])null, (Type[])null);
						if (!(methodInfo9 == null))
						{
							try
							{
								Type type7 = Type.GetType("NoDisconnectRestart." + patchClass + ", NoDisconnectRestart");
								if (!(type7 == null))
								{
									HarmonyMethod val = ((!isPrefix) ? ((HarmonyMethod)null) : new HarmonyMethod(type7, patchMethod, (Type[])null));
									HarmonyMethod val2 = ((!isPostfix) ? ((HarmonyMethod)null) : new HarmonyMethod(type7, patchMethod, (Type[])null));
									_harmony.Patch((MethodBase)methodInfo9, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
									Log.LogInfo((object)("[NDR] Patched " + text + "." + text2 + " -> " + patchClass + "." + patchMethod));
								}
								return;
							}
							catch (Exception ex)
							{
								Log.LogWarning((object)("[NDR] Failed to patch " + text + "." + text2 + ": " + ex.Message));
							}
						}
					}
				}
			}
		}
	}

	private void OnDestroy()
	{
		Harmony harmony = _harmony;
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
	}
}
public static class PlayerStateTracker
{
	public static Dictionary<int, PlayerSnapshot> Snapshots = new Dictionary<int, PlayerSnapshot>();

	public static Dictionary<int, int> MissedRounds = new Dictionary<int, int>();

	public static int CurrentRound = 0;

	public static void SaveSnapshot(int actorNumber, PlayerSnapshot snap)
	{
		Snapshots[actorNumber] = snap;
		MissedRounds[actorNumber] = 0;
		NoDisconnectRestartPlugin.Log.LogInfo((object)$"[NDR] Saved snapshot for actor {actorNumber} — {snap.CardNames.Count} cards.");
	}

	public static void IncrementMissedRounds()
	{
		CurrentRound++;
		List<int> list = new List<int>(MissedRounds.Keys);
		foreach (int item in list)
		{
			MissedRounds[item]++;
		}
		NoDisconnectRestartPlugin.Log.LogInfo((object)$"[NDR] Round {CurrentRound} ended. Tracking {MissedRounds.Count} absent player(s).");
	}

	public static bool TryGetSnapshot(int actorNumber, out PlayerSnapshot snap)
	{
		return Snapshots.TryGetValue(actorNumber, out snap);
	}

	public static int GetMissedRounds(int actorNumber)
	{
		int value;
		return MissedRounds.TryGetValue(actorNumber, out value) ? value : 0;
	}

	public static void ClearPlayer(int actorNumber)
	{
		Snapshots.Remove(actorNumber);
		MissedRounds.Remove(actorNumber);
	}

	public static void Reset()
	{
		Snapshots.Clear();
		MissedRounds.Clear();
		CurrentRound = 0;
	}
}
public class PlayerSnapshot
{
	public List<string> CardNames = new List<string>();

	public int TeamIndex;

	public int ActorNumber;
}
[HarmonyPatch]
public static class Patch_OnPlayerLeftRoom
{
	private static MethodBase TargetMethod()
	{
		string[] array = new string[2] { "NetworkConnectionHandler", "RoundsNetworkConnectionHandler" };
		string[] array2 = new string[3] { "OnPlayerLeftRoom", "PlayerLeftRoom", "OnPlayerDisconnected" };
		string[] array3 = array;
		foreach (string text in array3)
		{
			Type type = AccessTools.TypeByName(text);
			if (type == null)
			{
				continue;
			}
			string[] array4 = array2;
			foreach (string text2 in array4)
			{
				MethodInfo methodInfo = AccessTools.Method(type, text2, (Type[])null, (Type[])null);
				if (methodInfo != null)
				{
					NoDisconnectRestartPlugin.Log.LogInfo((object)("[NDR] Patching " + text + "." + text2));
					return methodInfo;
				}
			}
		}
		NoDisconnectRestartPlugin.Log.LogWarning((object)"[NDR] OnPlayerLeftRoom not found.");
		return null;
	}

	private static bool Prefix(object __instance, object otherPlayer)
	{
		//IL_0097: Unknown result type (might be due to invalid IL or missing references)
		//IL_009d: Expected O, but got Unknown
		if (otherPlayer == null)
		{
			return false;
		}
		Type type = otherPlayer.GetType();
		string arg = type.GetProperty("NickName")?.GetValue(otherPlayer)?.ToString() ?? "unknown";
		int num = (int)(type.GetProperty("ActorNumber")?.GetValue(otherPlayer) ?? ((object)0));
		NoDisconnectRestartPlugin.Log.LogInfo((object)$"[NDR] Player left: {arg} (actor {num}) — suppressing restart.");
		DisconnectGuard.DisconnectInProgress = true;
		GameObject val = new GameObject("NDR_DisconnectGuardClearer");
		val.AddComponent<DisconnectGuardClearer>();
		Object.DontDestroyOnLoad((Object)(object)val);
		if (num > 0)
		{
			CardSaver.TrySavePlayerCards(num);
		}
		return false;
	}
}
public class DisconnectGuardClearer : MonoBehaviour
{
	private void Start()
	{
		((MonoBehaviour)this).StartCoroutine(Clear());
	}

	private IEnumerator Clear()
	{
		yield return (object)new WaitForSeconds(30f);
		DisconnectGuard.DisconnectInProgress = false;
		NoDisconnectRestartPlugin.Log.LogInfo((object)"[NDR] Disconnect guard cleared.");
		Object.Destroy((Object)(object)((Component)this).gameObject);
	}
}
public static class DisconnectGuard
{
	public static bool DisconnectInProgress;
}
public static class Patch_PhotonNetwork_LoadLevel
{
	public static bool Prefix()
	{
		if (DisconnectGuard.DisconnectInProgress)
		{
			NoDisconnectRestartPlugin.Log.LogInfo((object)"[NDR] Blocked PhotonNetwork.LoadLevel from disconnect.");
			return false;
		}
		return true;
	}
}
public static class Patch_OnDisconnected
{
	public static bool Prefix()
	{
		if (DisconnectGuard.DisconnectInProgress)
		{
			NoDisconnectRestartPlugin.Log.LogInfo((object)"[NDR] Blocked OnDisconnected callback from disconnect.");
			return false;
		}
		return true;
	}
}
public static class Patch_PlayerManager_ResetCharacters
{
	public static bool Prefix()
	{
		if (DisconnectGuard.DisconnectInProgress)
		{
			NoDisconnectRestartPlugin.Log.LogInfo((object)"[NDR] Blocked PlayerManager.ResetCharacters from disconnect.");
			return false;
		}
		return true;
	}
}
public static class Patch_SceneManager_LoadScene
{
	public static bool Prefix()
	{
		StackTrace stackTrace = new StackTrace();
		StringBuilder stringBuilder = new StringBuilder("[NDR] SceneManager.LoadScene called from:\n");
		for (int i = 0; i < Math.Min(stackTrace.FrameCount, 12); i++)
		{
			MethodBase methodBase = stackTrace.GetFrame(i)?.GetMethod();
			if (methodBase != null)
			{
				stringBuilder.AppendLine($"  [{i}] {methodBase.DeclaringType?.Name}.{methodBase.Name}");
			}
		}
		NoDisconnectRestartPlugin.Log.LogInfo((object)stringBuilder.ToString());
		if (DisconnectGuard.DisconnectInProgress)
		{
			NoDisconnectRestartPlugin.Log.LogInfo((object)"[NDR] Blocked scene reload from disconnect.");
			return false;
		}
		return true;
	}
}
public static class Patch_SceneManager_LoadSceneAsync
{
	public static bool Prefix()
	{
		StackTrace stackTrace = new StackTrace();
		StringBuilder stringBuilder = new StringBuilder("[NDR] SceneManager.LoadSceneAsync called from:\n");
		for (int i = 0; i < Math.Min(stackTrace.FrameCount, 12); i++)
		{
			MethodBase methodBase = stackTrace.GetFrame(i)?.GetMethod();
			if (methodBase != null)
			{
				stringBuilder.AppendLine($"  [{i}] {methodBase.DeclaringType?.Name}.{methodBase.Name}");
			}
		}
		NoDisconnectRestartPlugin.Log.LogInfo((object)stringBuilder.ToString());
		if (DisconnectGuard.DisconnectInProgress)
		{
			NoDisconnectRestartPlugin.Log.LogInfo((object)"[NDR] Blocked async scene reload from disconnect.");
			return false;
		}
		return true;
	}
}
public static class Patch_RWFGameMode_ResetMatch
{
	public static bool Prefix()
	{
		if (DisconnectGuard.DisconnectInProgress)
		{
			NoDisconnectRestartPlugin.Log.LogInfo((object)"[NDR] Blocked RWFGameMode.ResetMatch from disconnect.");
			return false;
		}
		return true;
	}
}
public static class Patch_RWFGameMode_OnDisable
{
	public static bool Prefix()
	{
		if (DisconnectGuard.DisconnectInProgress)
		{
			NoDisconnectRestartPlugin.Log.LogInfo((object)"[NDR] Blocked RWFGameMode.OnDisable from disconnect.");
			return false;
		}
		return true;
	}
}
public static class Patch_PointVisualizer_ResetBalls
{
	public static bool Prefix()
	{
		if (DisconnectGuard.DisconnectInProgress)
		{
			return false;
		}
		return true;
	}
}
public static class Patch_CardPickDisconnect
{
	public static bool Prefix()
	{
		return false;
	}
}
public static class Patch_RoundEnd
{
	public static void Postfix()
	{
		PlayerStateTracker.IncrementMissedRounds();
	}
}
public static class Patch_OnPlayerEnteredRoom
{
	public static void Postfix(object newPlayer)
	{
		//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e9: Expected O, but got Unknown
		if (newPlayer == null)
		{
			return;
		}
		Type type = newPlayer.GetType();
		int num = (int)(type.GetProperty("ActorNumber")?.GetValue(newPlayer) ?? ((object)0));
		if (num == 0)
		{
			return;
		}
		if (PlayerStateTracker.TryGetSnapshot(num, out var snap))
		{
			int missedRounds = PlayerStateTracker.GetMissedRounds(num);
			string arg = newPlayer.GetType().GetProperty("NickName")?.GetValue(newPlayer)?.ToString() ?? "unknown";
			NoDisconnectRestartPlugin.Log.LogInfo((object)($"[NDR] {arg} rejoined (actor {num}). " + $"Restoring {snap.CardNames.Count} cards, queuing {missedRounds} catch-up picks."));
			GameObject val = new GameObject("NDR_RejoinHandler");
			RejoinHandler rejoinHandler = val.AddComponent<RejoinHandler>();
			rejoinHandler.ActorNumber = num;
			rejoinHandler.Snapshot = snap;
			rejoinHandler.MissedRounds = missedRounds;
			Object.DontDestroyOnLoad((Object)(object)val);
		}
		else
		{
			int currentRound = PlayerStateTracker.CurrentRound;
			if (currentRound > 0)
			{
				string arg2 = newPlayer.GetType().GetProperty("NickName")?.GetValue(newPlayer)?.ToString() ?? "unknown";
				NoDisconnectRestartPlugin.Log.LogInfo((object)$"[NDR] Late joiner {arg2} — queuing {currentRound} catch-up picks.");
				CatchupPickQueue.Enqueue(num, currentRound);
			}
		}
	}
}
public class RejoinHandler : MonoBehaviour
{
	public int ActorNumber;

	public PlayerSnapshot Snapshot;

	public int MissedRounds;

	private void Start()
	{
		((MonoBehaviour)this).StartCoroutine(RestoreAfterDelay());
	}

	private IEnumerator RestoreAfterDelay()
	{
		yield return (object)new WaitForSeconds(3f);
		CardRestorer.RestoreCards(ActorNumber, Snapshot);
		CatchupPickQueue.Enqueue(ActorNumber, MissedRounds);
		PlayerStateTracker.ClearPlayer(ActorNumber);
		Object.Destroy((Object)(object)((Component)this).gameObject);
	}
}
public static class CardSaver
{
	public static void TrySavePlayerCards(int actorNumber)
	{
		try
		{
			Type type = AccessTools.TypeByName("PlayerManager");
			if (type == null)
			{
				NoDisconnectRestartPlugin.Log.LogWarning((object)"[NDR] PlayerManager not found.");
				return;
			}
			object obj = AccessTools.Field(type, "instance")?.GetValue(null);
			if (obj == null)
			{
				NoDisconnectRestartPlugin.Log.LogWarning((object)"[NDR] PlayerManager.instance null.");
				return;
			}
			if (!(AccessTools.Field(obj.GetType(), "players")?.GetValue(obj) is IList list))
			{
				NoDisconnectRestartPlugin.Log.LogWarning((object)"[NDR] PlayerManager.players null.");
				return;
			}
			PlayerSnapshot playerSnapshot = new PlayerSnapshot
			{
				ActorNumber = actorNumber
			};
			foreach (object item in list)
			{
				if (item == null)
				{
					continue;
				}
				object obj2 = null;
				MethodInfo method = item.GetType().GetMethod("GetComponent", new Type[1] { typeof(Type) });
				if (method != null)
				{
					Type type2 = AccessTools.TypeByName("Photon.Pun.PhotonView") ?? AccessTools.TypeByName("PhotonView");
					if (type2 != null)
					{
						obj2 = method.Invoke(item, new object[1] { type2 });
					}
				}
				if (obj2 == null)
				{
					obj2 = AccessTools.Field(item.GetType(), "photonView")?.GetValue(item);
				}
				if (obj2 == null)
				{
					continue;
				}
				int num = (int)(obj2.GetType().GetProperty("OwnerActorNr")?.GetValue(obj2) ?? ((object)(-1)));
				if (num != actorNumber)
				{
					continue;
				}
				object obj3 = AccessTools.Field(item.GetType(), "data")?.GetValue(item);
				if (obj3 == null || !(AccessTools.Field(obj3.GetType(), "currentCards")?.GetValue(obj3) is IList list2))
				{
					continue;
				}
				foreach (object item2 in list2)
				{
					string text = item2?.GetType().GetProperty("cardName")?.GetValue(item2)?.ToString() ?? item2?.GetType().GetField("cardName")?.GetValue(item2)?.ToString();
					if (text != null)
					{
						playerSnapshot.CardNames.Add(text);
					}
				}
				MemberInfo memberInfo = (MemberInfo)(((object)AccessTools.Property(item.GetType(), "teamID")) ?? ((object)AccessTools.Field(item.GetType(), "teamID")));
				if (memberInfo is PropertyInfo propertyInfo)
				{
					playerSnapshot.TeamIndex = (int)propertyInfo.GetValue(item);
				}
				else if (memberInfo is FieldInfo fieldInfo)
				{
					playerSnapshot.TeamIndex = (int)fieldInfo.GetValue(item);
				}
				break;
			}
			if (playerSnapshot.CardNames.Count > 0)
			{
				PlayerStateTracker.SaveSnapshot(actorNumber, playerSnapshot);
			}
			else
			{
				NoDisconnectRestartPlugin.Log.LogWarning((object)$"[NDR] No cards found for actor {actorNumber} — snapshot skipped.");
			}
		}
		catch (Exception ex)
		{
			NoDisconnectRestartPlugin.Log.LogWarning((object)$"[NDR] Failed to save cards for actor {actorNumber}: {ex.Message}");
		}
	}
}
public static class CardRestorer
{
	public static void RestoreCards(int actorNumber, PlayerSnapshot snap)
	{
		try
		{
			Type type = AccessTools.TypeByName("PlayerManager");
			if (type == null)
			{
				return;
			}
			object obj = AccessTools.Field(type, "instance")?.GetValue(null);
			if (obj == null || !(AccessTools.Field(obj.GetType(), "players")?.GetValue(obj) is IList list))
			{
				return;
			}
			object obj2 = null;
			foreach (object item in list)
			{
				if (item == null)
				{
					continue;
				}
				object obj3 = null;
				MethodInfo method = item.GetType().GetMethod("GetComponent", new Type[1] { typeof(Type) });
				if (method != null)
				{
					Type type2 = AccessTools.TypeByName("Photon.Pun.PhotonView") ?? AccessTools.TypeByName("PhotonView");
					if (type2 != null)
					{
						obj3 = method.Invoke(item, new object[1] { type2 });
					}
				}
				if (obj3 == null)
				{
					obj3 = AccessTools.Field(item.GetType(), "photonView")?.GetValue(item);
				}
				if (obj3 != null)
				{
					int num = (int)(obj3.GetType().GetProperty("OwnerActorNr")?.GetValue(obj3) ?? ((object)(-1)));
					if (num == actorNumber)
					{
						obj2 = item;
						break;
					}
				}
			}
			if (obj2 == null)
			{
				NoDisconnectRestartPlugin.Log.LogWarning((object)$"[NDR] Could not find player object for actor {actorNumber}.");
				return;
			}
			Type type3 = AccessTools.TypeByName("CardManager");
			IList list2 = ((type3 == null) ? null : ((AccessTools.Property(type3, "cards")?.GetValue(null) as IList) ?? (AccessTools.Field(type3, "cards")?.GetValue(null) as IList)));
			if (list2 == null)
			{
				NoDisconnectRestartPlugin.Log.LogWarning((object)"[NDR] Could not access CardManager.cards.");
				return;
			}
			Dictionary<string, object> dictionary = new Dictionary<string, object>();
			foreach (object item2 in list2)
			{
				string text = item2?.GetType().GetProperty("cardName")?.GetValue(item2)?.ToString() ?? item2?.GetType().GetField("cardName")?.GetValue(item2)?.ToString();
				if (text != null && !dictionary.ContainsKey(text))
				{
					dictionary[text] = item2;
				}
			}
			MethodInfo methodInfo = AccessTools.Method(obj2.GetType(), "AddCard", (Type[])null, (Type[])null) ?? AccessTools.Method(obj2.GetType(), "PickCard", (Type[])null, (Type[])null) ?? AccessTools.Method(obj2.GetType(), "AddCardToPlayer", (Type[])null, (Type[])null);
			foreach (string cardName in snap.CardNames)
			{
				if (dictionary.TryGetValue(cardName, out var value))
				{
					try
					{
						methodInfo?.Invoke(obj2, new object[1] { value });
					}
					catch (Exception ex)
					{
						NoDisconnectRestartPlugin.Log.LogWarning((object)("[NDR] Could not apply card '" + cardName + "': " + ex.Message));
					}
				}
			}
			NoDisconnectRestartPlugin.Log.LogInfo((object)$"[NDR] Restored {snap.CardNames.Count} cards to actor {actorNumber}.");
		}
		catch (Exception ex2)
		{
			NoDisconnectRestartPlugin.Log.LogWarning((object)$"[NDR] RestoreCards failed for actor {actorNumber}: {ex2.Message}");
		}
	}
}
public static class CatchupPickQueue
{
	public static Dictionary<int, int> PendingPicks = new Dictionary<int, int>();

	public static void Enqueue(int actorNumber, int picks)
	{
		if (picks > 0)
		{
			if (PendingPicks.ContainsKey(actorNumber))
			{
				PendingPicks[actorNumber] += picks;
			}
			else
			{
				PendingPicks[actorNumber] = picks;
			}
			NoDisconnectRestartPlugin.Log.LogInfo((object)$"[NDR] Queued {picks} catch-up pick(s) for actor {actorNumber}.");
		}
	}

	public static bool TryConsume(int actorNumber, out int picks)
	{
		if (PendingPicks.TryGetValue(actorNumber, out picks) && picks > 0)
		{
			PendingPicks.Remove(actorNumber);
			return true;
		}
		picks = 0;
		return false;
	}
}
public static class Patch_CardPickPhaseStart
{
	public static void Postfix(object __instance)
	{
		//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fc: Expected O, but got Unknown
		if (CatchupPickQueue.PendingPicks.Count == 0)
		{
			return;
		}
		foreach (KeyValuePair<int, int> item in new Dictionary<int, int>(CatchupPickQueue.PendingPicks))
		{
			int key = item.Key;
			int value = item.Value;
			NoDisconnectRestartPlugin.Log.LogInfo((object)$"[NDR] Granting {value} extra pick(s) to actor {key} in this pick phase.");
			MethodInfo methodInfo = AccessTools.Method(__instance.GetType(), "AddExtraPick", (Type[])null, (Type[])null) ?? AccessTools.Method(__instance.GetType(), "GiveExtraPick", (Type[])null, (Type[])null) ?? AccessTools.Method(__instance.GetType(), "AddPicks", (Type[])null, (Type[])null);
			if (methodInfo != null)
			{
				try
				{
					methodInfo.Invoke(__instance, new object[2] { key, value });
				}
				catch
				{
				}
			}
			CatchupPickQueue.PendingPicks.Remove(key);
			GameObject val = new GameObject("NDR_CatchupPick");
			CatchupPickRunner catchupPickRunner = val.AddComponent<CatchupPickRunner>();
			catchupPickRunner.ActorNumber = key;
			catchupPickRunner.RemainingPicks = value;
			catchupPickRunner.PickInstance = __instance;
			Object.DontDestroyOnLoad((Object)(object)val);
		}
	}
}
public class CatchupPickRunner : MonoBehaviour
{
	public int ActorNumber;

	public int RemainingPicks;

	public object PickInstance;

	private void Start()
	{
		((MonoBehaviour)this).StartCoroutine(RunPicks());
	}

	private IEnumerator RunPicks()
	{
		for (int i = 0; i < RemainingPicks; i++)
		{
			yield return (object)new WaitForSeconds(1f);
			NoDisconnectRestartPlugin.Log.LogInfo((object)$"[NDR] Triggering catch-up pick {i + 1}/{RemainingPicks} for actor {ActorNumber}.");
			MethodInfo startPickForPlayer = AccessTools.Method(PickInstance?.GetType(), "StartPickForPlayer", (Type[])null, (Type[])null) ?? AccessTools.Method(PickInstance?.GetType(), "PickForPlayer", (Type[])null, (Type[])null) ?? AccessTools.Method(PickInstance?.GetType(), "DoPickForActor", (Type[])null, (Type[])null);
			if (startPickForPlayer != null)
			{
				try
				{
					startPickForPlayer.Invoke(PickInstance, new object[1] { ActorNumber });
				}
				catch (Exception ex)
				{
					Exception ex2 = ex;
					NoDisconnectRestartPlugin.Log.LogWarning((object)("[NDR] Catch-up pick invoke failed: " + ex2.Message));
				}
			}
		}
		Object.Destroy((Object)(object)((Component)this).gameObject);
	}
}