Decompiled source of SimulPicks v0.2.1

SimulPicks.dll

Decompiled 3 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using ExitGames.Client.Photon;
using HarmonyLib;
using Photon.Pun;
using Photon.Realtime;
using UnboundLib;
using UnboundLib.GameModes;
using UnboundLib.Networking;
using UnboundLib.Utils;
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: AssemblyVersion("0.0.0.0")]
namespace SimulPicks;

[BepInPlugin("com.redredrain.rounds.simulpicks", "SimulPicks", "0.2.1")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class SimulPicksPlugin : BaseUnityPlugin, IInRoomCallbacks, IMatchmakingCallbacks
{
	public const string ModId = "com.redredrain.rounds.simulpicks";

	public const string ModName = "SimulPicks";

	public const string Version = "0.2.1";

	public static SimulPicksPlugin Instance;

	public static ConfigEntry<int> PickTimeSeconds;

	private void Awake()
	{
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		Instance = this;
		PickTimeSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("SimulPicks", "PickTimeSeconds", 45, "Seconds each player has per pick session before auto-completion.");
		new Harmony("com.redredrain.rounds.simulpicks").PatchAll();
		PhotonNetwork.AddCallbackTarget((object)this);
	}

	private void Start()
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000f: Expected O, but got Unknown
		SoftPatches.Apply(new Harmony("com.redredrain.rounds.simulpicks.soft"));
		GameModeManager.AddHook("GameStart", (Func<IGameModeHandler, IEnumerator>)((IGameModeHandler gm) => State.OnGameStart()));
		GameModeManager.AddHook("PickStart", (Func<IGameModeHandler, IEnumerator>)((IGameModeHandler gm) => State.OnPickStart()));
		GameModeManager.AddHook("PickEnd", (Func<IGameModeHandler, IEnumerator>)((IGameModeHandler gm) => State.Barrier()), 600);
		GameModeManager.AddHook("GameEnd", (Func<IGameModeHandler, IEnumerator>)((IGameModeHandler gm) => State.HardResetHook()));
	}

	private void OnDestroy()
	{
		PhotonNetwork.RemoveCallbackTarget((object)this);
	}

	private void Update()
	{
		if (Input.GetKeyDown((KeyCode)290) && (PhotonNetwork.OfflineMode || PhotonNetwork.CurrentRoom == null || PhotonNetwork.CurrentRoom.PlayerCount <= 1) && !State.phaseActive && !State.localRunnerBusy && !((Object)(object)CardChoice.instance == (Object)null) && !CardChoice.instance.IsPicking && !((Object)(object)PlayerManager.instance == (Object)null))
		{
			Player val = ((IEnumerable<Player>)PlayerManager.instance.players).FirstOrDefault((Func<Player, bool>)((Player p) => (Object)(object)p != (Object)null && State.IsLocalPlayer(p)));
			if (!((Object)(object)val == (Object)null))
			{
				Log("F9 debug pick starting");
				((MonoBehaviour)this).StartCoroutine(DebugRun(val.playerID));
			}
		}
	}

	private IEnumerator DebugRun(int pid)
	{
		yield return State.OnPickStart();
		State.RecordEntitlement(pid, (PickerType)1);
		State.EnqueueLocalPick(pid, (PickerType)1);
		yield return State.Barrier();
	}

	internal static void Log(string m)
	{
		((BaseUnityPlugin)Instance).Logger.LogInfo((object)("[SimulPicks] " + m));
	}

	internal static void Warn(string m)
	{
		((BaseUnityPlugin)Instance).Logger.LogWarning((object)("[SimulPicks] " + m));
	}

	[UnboundRPC]
	public static void RPCA_SimulResult(int token, int playerID, int pickIdx, string cardName, bool cursed)
	{
		State.StoreResult(token, playerID, pickIdx, cardName, cursed);
	}

	[UnboundRPC]
	public static void RPCA_SimulSessionDone(int token, int playerID, int count)
	{
		State.StoreSessionDone(token, playerID, count);
	}

	[UnboundRPC]
	public static void RPCA_SimulApplySet(int token, string[] packed)
	{
		State.StoreApplySet(token, packed);
	}

	public void OnLeftRoom()
	{
		State.HardReset("left room");
	}

	public void OnJoinedRoom()
	{
		State.HardReset("joined room");
	}

	public void OnPlayerLeftRoom(Player otherPlayer)
	{
	}

	public void OnPlayerEnteredRoom(Player newPlayer)
	{
	}

	public void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
	{
	}

	public void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
	{
	}

	public void OnMasterClientSwitched(Player newMasterClient)
	{
	}

	public void OnFriendListUpdate(List<FriendInfo> friendList)
	{
	}

	public void OnCreatedRoom()
	{
	}

	public void OnCreateRoomFailed(short returnCode, string message)
	{
	}

	public void OnJoinRoomFailed(short returnCode, string message)
	{
	}

	public void OnJoinRandomFailed(short returnCode, string message)
	{
	}
}
internal class ResultEntry
{
	public string card;

	public bool cursed;
}
internal static class State
{
	internal static int phaseToken;

	internal static float phaseStartTime;

	internal static bool phaseActive;

	internal static bool passThroughActive;

	internal static readonly Dictionary<int, int> entitlements = new Dictionary<int, int>();

	internal static readonly Dictionary<int, PickerType> entitledType = new Dictionary<int, PickerType>();

	internal static readonly Dictionary<int, int> sessionsDone = new Dictionary<int, int>();

	internal static readonly Dictionary<int, List<ResultEntry>> results = new Dictionary<int, List<ResultEntry>>();

	internal static readonly Dictionary<int, int> producedCount = new Dictionary<int, int>();

	internal static readonly HashSet<string> locallyProduced = new HashSet<string>();

	internal static string[] pendingApplySet;

	internal static bool localPickActive;

	internal static bool allowNativeVisualCall;

	internal static int currentSessionPlayer = -1;

	internal static readonly List<GameObject> localCards = new List<GameObject>();

	internal static readonly List<ResultEntry> currentSessionPicks = new List<ResultEntry>();

	internal static readonly Queue<KeyValuePair<int, PickerType>> localQueue = new Queue<KeyValuePair<int, PickerType>>();

	internal static bool localRunnerBusy;

	private static Coroutine runner;

	internal static readonly HashSet<string> RedirectLocalRpcs = new HashSet<string> { "RPCA_DoEndPick", "RPCA_ChangeSelected", "RPCA_SetCurrentSelected", "RPCA_DonePicking", "RPCA_SetFace", "RPCA_Pick" };

	internal static readonly HashSet<string> LocalizedManagerRpcs = new HashSet<string> { "RPCO_AddRemotelySpawnedCard", "SpawnCurseDraw", "FixHandSize" };

	internal static IEnumerator OnGameStart()
	{
		phaseToken = 0;
		yield break;
	}

	internal static IEnumerator OnPickStart()
	{
		phaseToken++;
		phaseStartTime = Time.realtimeSinceStartup;
		phaseActive = true;
		entitlements.Clear();
		entitledType.Clear();
		sessionsDone.Clear();
		results.Clear();
		producedCount.Clear();
		locallyProduced.Clear();
		localQueue.Clear();
		pendingApplySet = null;
		yield break;
	}

	internal static bool IsLocalPlayer(Player p)
	{
		try
		{
			if (PhotonNetwork.OfflineMode || (Object)(object)p.data.view == (Object)null)
			{
				return true;
			}
			return p.data.view.Owner != null && p.data.view.Owner.IsLocal;
		}
		catch
		{
			return false;
		}
	}

	internal static bool HasLeftRoom(Player p)
	{
		if (PhotonNetwork.OfflineMode || !PhotonNetwork.InRoom)
		{
			return false;
		}
		try
		{
			return (Object)(object)p == (Object)null || (Object)(object)p.data == (Object)null || (Object)(object)p.data.view == (Object)null || p.data.view.Owner == null;
		}
		catch
		{
			return true;
		}
	}

	internal static void RecordEntitlement(int playerID, PickerType pType)
	{
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		entitlements.TryGetValue(playerID, out var value);
		entitlements[playerID] = value + 1;
		entitledType[playerID] = pType;
	}

	internal static void EnqueueLocalPick(int playerID, PickerType pType)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		localQueue.Enqueue(new KeyValuePair<int, PickerType>(playerID, pType));
		if (runner == null)
		{
			runner = ((MonoBehaviour)SimulPicksPlugin.Instance).StartCoroutine(RunnerLoop());
		}
	}

	internal static void StoreResult(int token, int playerID, int pickIdx, string cardName, bool cursed)
	{
		if (token == phaseToken)
		{
			if (!results.TryGetValue(playerID, out var value))
			{
				value = new List<ResultEntry>();
				results[playerID] = value;
			}
			while (value.Count <= pickIdx)
			{
				value.Add(null);
			}
			if (value[pickIdx] == null)
			{
				value[pickIdx] = new ResultEntry
				{
					card = (cardName ?? ""),
					cursed = cursed
				};
			}
		}
	}

	internal static void StoreSessionDone(int token, int playerID, int count)
	{
		if (token == phaseToken)
		{
			sessionsDone.TryGetValue(playerID, out var value);
			sessionsDone[playerID] = value + 1;
		}
	}

	internal static void StoreApplySet(int token, string[] packed)
	{
		if (token == phaseToken)
		{
			pendingApplySet = packed ?? Array.Empty<string>();
		}
	}

	private static void Broadcast(string method, params object[] args)
	{
		try
		{
			AccessTools.Method(typeof(SimulPicksPlugin), method, (Type[])null, (Type[])null).Invoke(null, args);
		}
		catch (Exception ex)
		{
			SimulPicksPlugin.Warn("Local " + method + " store failed: " + ex.Message);
		}
		if (!PhotonNetwork.OfflineMode && PhotonNetwork.InRoom)
		{
			NetworkingManager.RPC_Others(typeof(SimulPicksPlugin), method, args);
		}
	}

	internal static float ClusterBudget()
	{
		float num = Mathf.Clamp(SimulPicksPlugin.PickTimeSeconds.Value, 10, 180);
		int num2 = 1;
		try
		{
			if (entitlements.Count > 0)
			{
				Dictionary<int, int> dictionary = new Dictionary<int, int>();
				foreach (KeyValuePair<int, int> kv in entitlements)
				{
					Player val = ((IEnumerable<Player>)PlayerManager.instance?.players).FirstOrDefault((Func<Player, bool>)((Player p) => (Object)(object)p != (Object)null && p.playerID == kv.Key));
					int key = 0;
					try
					{
						int? obj;
						if (val == null)
						{
							obj = null;
						}
						else
						{
							CharacterData data = val.data;
							if (data == null)
							{
								obj = null;
							}
							else
							{
								PhotonView view = data.view;
								if (view == null)
								{
									obj = null;
								}
								else
								{
									Player owner = view.Owner;
									obj = ((owner != null) ? new int?(owner.ActorNumber) : ((int?)null));
								}
							}
						}
						int? num3 = obj;
						key = num3.GetValueOrDefault();
					}
					catch
					{
					}
					dictionary.TryGetValue(key, out var value);
					dictionary[key] = value + kv.Value;
				}
				num2 = Mathf.Max(1, dictionary.Values.Max());
			}
		}
		catch
		{
		}
		return num * (float)num2 + 25f;
	}

	private static IEnumerator RunnerLoop()
	{
		localRunnerBusy = true;
		try
		{
			while (localQueue.Count > 0)
			{
				KeyValuePair<int, PickerType> keyValuePair = localQueue.Dequeue();
				yield return RunLocalNativePick(keyValuePair.Key, keyValuePair.Value);
			}
		}
		finally
		{
			localRunnerBusy = false;
			runner = null;
		}
	}

	private static IEnumerator RunLocalNativePick(int playerID, PickerType pType)
	{
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		int myToken = phaseToken;
		CardChoice choice = CardChoice.instance;
		if ((Object)(object)choice != (Object)null)
		{
			producedCount.TryGetValue(playerID, out var baseIdx);
			currentSessionPicks.Clear();
			localCards.Clear();
			currentSessionPlayer = playerID;
			localPickActive = true;
			try
			{
				bool flag = false;
				try
				{
					WithNativeVisuals(delegate
					{
						CardChoiceVisuals.instance.Show(playerID, true);
					});
					AccessTools.Field(typeof(CardChoice), "pickerType").SetValue(choice, pType);
					Traverse.Create((object)choice).Method("StartPick", new object[2] { 1, playerID }).GetValue();
					flag = true;
				}
				catch (Exception ex)
				{
					SimulPicksPlugin.Warn("Session start failed: " + ex.Message);
				}
				if (flag)
				{
					float deadline = Time.realtimeSinceStartup + (float)Mathf.Clamp(SimulPicksPlugin.PickTimeSeconds.Value, 10, 180);
					bool forced = false;
					while (SafeIsPicking(choice) && myToken == phaseToken)
					{
						if (Time.realtimeSinceStartup > deadline)
						{
							if (forced)
							{
								SimulPicksPlugin.Warn($"Session for {playerID} stuck; abandoning.");
								break;
							}
							forced = true;
							ForceCompletePick(choice);
							deadline += 10f;
						}
						yield return null;
					}
					float settle = Time.realtimeSinceStartup + 5f;
					FieldInfo isPlayingField = AccessTools.Field(typeof(CardChoice), "isPlaying");
					while (SafeBool(isPlayingField, choice) && Time.realtimeSinceStartup < settle)
					{
						yield return null;
					}
					yield return (object)new WaitForSecondsRealtime(0.2f);
				}
				ScrubCardChoice(choice);
			}
			finally
			{
				CleanupLocalCards();
				currentSessionPlayer = -1;
				localPickActive = false;
				try
				{
					WithNativeVisuals(delegate
					{
						UIHandler.instance.StopShowPicker();
						CardChoiceVisuals.instance.Hide();
					});
				}
				catch
				{
				}
			}
			ReportSession(myToken, playerID, baseIdx, currentSessionPicks);
		}
		else
		{
			producedCount.TryGetValue(playerID, out var value);
			ReportSession(myToken, playerID, value, new List<ResultEntry>());
		}
	}

	internal static void ReportSession(int token, int playerID, int baseIdx, List<ResultEntry> picks)
	{
		int num = 0;
		for (int i = 0; i < picks.Count; i++)
		{
			ResultEntry resultEntry = picks[i];
			try
			{
				locallyProduced.Add($"{playerID}|{baseIdx + i}");
				Broadcast("RPCA_SimulResult", token, playerID, baseIdx + i, resultEntry.card, resultEntry.cursed);
				num++;
			}
			catch (Exception ex)
			{
				SimulPicksPlugin.Warn("Result broadcast failed: " + ex.Message);
			}
		}
		if (num == 0)
		{
			try
			{
				locallyProduced.Add($"{playerID}|{baseIdx}");
				Broadcast("RPCA_SimulResult", token, playerID, baseIdx, "", false);
				num = 1;
			}
			catch (Exception ex2)
			{
				SimulPicksPlugin.Warn("Skip broadcast failed: " + ex2.Message);
			}
		}
		producedCount[playerID] = baseIdx + num;
		try
		{
			Broadcast("RPCA_SimulSessionDone", token, playerID, num);
		}
		catch (Exception ex3)
		{
			SimulPicksPlugin.Warn("Session marker failed: " + ex3.Message);
		}
	}

	private static bool SafeIsPicking(CardChoice c)
	{
		try
		{
			return (Object)(object)c != (Object)null && c.IsPicking;
		}
		catch
		{
			return false;
		}
	}

	private static bool SafeBool(FieldInfo f, object o)
	{
		try
		{
			return (bool)f.GetValue(o);
		}
		catch
		{
			return false;
		}
	}

	internal static void ScrubCardChoice(CardChoice choice)
	{
		try
		{
			((MonoBehaviour)choice).StopAllCoroutines();
			choice.IsPicking = false;
			AccessTools.Field(typeof(CardChoice), "picks").SetValue(choice, 0);
			AccessTools.Field(typeof(CardChoice), "isPlaying").SetValue(choice, false);
			AccessTools.Field(typeof(CardChoice), "pickrID").SetValue(choice, -1);
			List<GameObject> list = (List<GameObject>)AccessTools.Field(typeof(CardChoice), "spawnedCards").GetValue(choice);
			if (list == null)
			{
				return;
			}
			foreach (GameObject item in list)
			{
				if ((Object)(object)item != (Object)null && !localCards.Contains(item))
				{
					localCards.Add(item);
				}
			}
			list.Clear();
		}
		catch (Exception ex)
		{
			SimulPicksPlugin.Warn("Scrub failed: " + ex.Message);
		}
	}

	internal static void ForceCompletePick(CardChoice choice)
	{
		try
		{
			List<GameObject> list = ((List<GameObject>)AccessTools.Field(typeof(CardChoice), "spawnedCards").GetValue(choice))?.Where((GameObject g) => (Object)(object)g != (Object)null).ToList();
			if (list == null || list.Count == 0)
			{
				choice.IsPicking = false;
				return;
			}
			int index = Mathf.Clamp((int)AccessTools.Field(typeof(CardChoice), "currentlySelectedCard").GetValue(choice), 0, list.Count - 1);
			SimulPicksPlugin.Warn("Pick time expired - auto-picking selected card.");
			choice.Pick(list[index], false);
		}
		catch (Exception ex)
		{
			SimulPicksPlugin.Warn("Force-complete failed: " + ex.Message);
			try
			{
				choice.IsPicking = false;
			}
			catch
			{
			}
		}
	}

	internal static void WithNativeVisuals(Action a)
	{
		allowNativeVisualCall = true;
		try
		{
			a();
		}
		finally
		{
			allowNativeVisualCall = false;
		}
	}

	private static void CleanupLocalCards()
	{
		foreach (GameObject localCard in localCards)
		{
			if ((Object)(object)localCard == (Object)null)
			{
				continue;
			}
			PhotonView[] componentsInChildren = localCard.GetComponentsInChildren<PhotonView>(true);
			foreach (PhotonView val in componentsInChildren)
			{
				try
				{
					PhotonNetwork.LocalCleanPhotonView(val);
				}
				catch
				{
				}
			}
			Object.Destroy((Object)(object)localCard);
		}
		localCards.Clear();
	}

	private static void FlushRunner(int token)
	{
		if (runner != null)
		{
			try
			{
				((MonoBehaviour)SimulPicksPlugin.Instance).StopCoroutine(runner);
			}
			catch
			{
			}
			runner = null;
		}
		bool flag = localRunnerBusy;
		localRunnerBusy = false;
		CardChoice instance = CardChoice.instance;
		if ((Object)(object)instance != (Object)null && (localPickActive || flag))
		{
			bool flag2 = localPickActive;
			localPickActive = true;
			try
			{
				ScrubCardChoice(instance);
			}
			finally
			{
				localPickActive = flag2;
			}
		}
		if (localPickActive)
		{
			int num = currentSessionPlayer;
			if (num >= 0)
			{
				producedCount.TryGetValue(num, out var value);
				ReportSession(token, num, value, new List<ResultEntry>(currentSessionPicks));
			}
			localPickActive = false;
			currentSessionPlayer = -1;
		}
		while (localQueue.Count > 0)
		{
			KeyValuePair<int, PickerType> keyValuePair = localQueue.Dequeue();
			producedCount.TryGetValue(keyValuePair.Key, out var value2);
			ReportSession(token, keyValuePair.Key, value2, new List<ResultEntry>());
		}
		CleanupLocalCards();
		try
		{
			WithNativeVisuals(delegate
			{
				UIHandler.instance.StopShowPicker();
				CardChoiceVisuals.instance.Hide();
			});
		}
		catch
		{
		}
	}

	internal static IEnumerator Barrier()
	{
		int myToken = phaseToken;
		if (entitlements.Count == 0 && localQueue.Count == 0 && !localRunnerBusy)
		{
			phaseActive = false;
			yield break;
		}
		float num = ClusterBudget();
		float deadline = phaseStartTime + num;
		bool flag = PhotonNetwork.OfflineMode || !PhotonNetwork.InRoom || PhotonNetwork.IsMasterClient;
		SimulPicksPlugin.Log($"Barrier(token {myToken}): {entitlements.Count} player(s), {entitlements.Values.Sum()} session(s), authority={flag}, budget={num:F0}s.");
		if (flag)
		{
			while (Time.realtimeSinceStartup < deadline && myToken == phaseToken && (localQueue.Count != 0 || localRunnerBusy || !AllSessionsIn()))
			{
				yield return null;
			}
			if (myToken != phaseToken)
			{
				yield break;
			}
			FlushRunner(myToken);
			Broadcast("RPCA_SimulApplySet", myToken, BuildApplySet());
		}
		else
		{
			float fallbackDeadline = phaseStartTime + 180f * (float)Mathf.Max(1, (entitlements.Count <= 0) ? 1 : entitlements.Values.Max()) + 45f;
			while (pendingApplySet == null && Time.realtimeSinceStartup < fallbackDeadline && myToken == phaseToken)
			{
				if (PhotonNetwork.IsMasterClient && localQueue.Count == 0 && !localRunnerBusy && (AllSessionsIn() || Time.realtimeSinceStartup >= deadline))
				{
					FlushRunner(myToken);
					Broadcast("RPCA_SimulApplySet", myToken, BuildApplySet());
					break;
				}
				yield return null;
			}
			if (myToken != phaseToken)
			{
				yield break;
			}
			FlushRunner(myToken);
			if (pendingApplySet == null)
			{
				SimulPicksPlugin.Warn("Apply-set never arrived - degrading to local results.");
				pendingApplySet = BuildApplySet();
			}
		}
		ApplySet(pendingApplySet ?? BuildApplySet());
		entitlements.Clear();
		entitledType.Clear();
		sessionsDone.Clear();
		results.Clear();
		pendingApplySet = null;
		phaseActive = false;
		try
		{
			WithNativeVisuals(delegate
			{
				UIHandler.instance.StopShowPicker();
				CardChoiceVisuals.instance.Hide();
			});
		}
		catch
		{
		}
		SimulPicksPlugin.Log("Barrier complete.");
	}

	internal static bool AllSessionsIn()
	{
		if ((Object)(object)PlayerManager.instance == (Object)null)
		{
			return true;
		}
		foreach (KeyValuePair<int, int> kv in entitlements)
		{
			sessionsDone.TryGetValue(kv.Key, out var value);
			if (value >= kv.Value)
			{
				continue;
			}
			Player val = null;
			try
			{
				val = ((IEnumerable<Player>)PlayerManager.instance.players).FirstOrDefault((Func<Player, bool>)((Player p) => (Object)(object)p != (Object)null && p.playerID == kv.Key));
			}
			catch
			{
			}
			if (!((Object)(object)val == (Object)null) && !HasLeftRoom(val))
			{
				return false;
			}
		}
		return true;
	}

	private static string[] BuildApplySet()
	{
		List<string> list = new List<string>();
		foreach (int item in results.Keys.OrderBy((int k) => k))
		{
			List<ResultEntry> list2 = results[item];
			for (int num = 0; num < list2.Count; num++)
			{
				ResultEntry resultEntry = list2[num];
				if (resultEntry != null && !string.IsNullOrEmpty(resultEntry.card))
				{
					list.Add($"{item}|{num}|{(resultEntry.cursed ? 1 : 0)}|{resultEntry.card}");
				}
			}
		}
		return list.ToArray();
	}

	private static void ApplySet(string[] set)
	{
		foreach (string text in set)
		{
			try
			{
				ApplyRow(text);
			}
			catch (Exception ex)
			{
				SimulPicksPlugin.Warn("Apply row '" + text + "' failed: " + ex.Message);
			}
		}
	}

	private static void ApplyRow(string row)
	{
		string[] array = row.Split(new char[1] { '|' }, 4);
		if (array.Length != 4)
		{
			return;
		}
		int pid = int.Parse(array[0]);
		int num = int.Parse(array[1]);
		bool flag = array[2] == "1";
		string text = array[3];
		Player val = ((IEnumerable<Player>)PlayerManager.instance?.players).FirstOrDefault((Func<Player, bool>)((Player p) => (Object)(object)p != (Object)null && p.playerID == pid));
		if ((Object)(object)val == (Object)null)
		{
			return;
		}
		CardInfo val2 = FindCard(text);
		bool flag2 = PhotonNetwork.OfflineMode || locallyProduced.Contains($"{pid}|{num}");
		if (!flag2)
		{
			AccessTools.Method(AccessTools.TypeByName("ModdingUtils.Utils.Cards"), "RPCA_AssignCard", new Type[7]
			{
				typeof(string),
				typeof(int),
				typeof(bool),
				typeof(string),
				typeof(float),
				typeof(float),
				typeof(bool)
			}, (Type[])null).Invoke(null, new object[7] { text, pid, false, "", 0f, 0f, true });
			if ((Object)(object)val2 != (Object)null)
			{
				SoftPatches.QueueReveal(val, val2);
			}
		}
		SoftPatches.FeedPickTracker(val2);
		if (flag && !flag2 && PhotonNetwork.IsMasterClient && !PhotonNetwork.OfflineMode)
		{
			SoftPatches.CursePlayer(val);
		}
	}

	internal static CardInfo FindCard(string objectName)
	{
		CardChoice instance = CardChoice.instance;
		if (instance?.cards != null)
		{
			CardInfo[] cards = instance.cards;
			foreach (CardInfo val in cards)
			{
				if ((Object)(object)val != (Object)null && ((Object)val).name == objectName)
				{
					return val;
				}
			}
		}
		try
		{
			return CardManager.GetCardInfoWithName(objectName);
		}
		catch
		{
			return null;
		}
	}

	internal static IEnumerator HardResetHook()
	{
		HardReset("GameEnd hook");
		yield break;
	}

	internal static void HardReset(string why)
	{
		SimulPicksPlugin.Log("Hard reset: " + why);
		phaseToken = 0;
		if (runner != null)
		{
			try
			{
				((MonoBehaviour)SimulPicksPlugin.Instance).StopCoroutine(runner);
			}
			catch
			{
			}
			runner = null;
		}
		localRunnerBusy = false;
		localQueue.Clear();
		CardChoice instance = CardChoice.instance;
		if ((Object)(object)instance != (Object)null)
		{
			bool flag = localPickActive;
			localPickActive = true;
			try
			{
				ScrubCardChoice(instance);
			}
			finally
			{
				localPickActive = flag;
			}
		}
		CleanupLocalCards();
		localPickActive = false;
		passThroughActive = false;
		currentSessionPlayer = -1;
		phaseActive = false;
		entitlements.Clear();
		entitledType.Clear();
		sessionsDone.Clear();
		results.Clear();
		producedCount.Clear();
		locallyProduced.Clear();
		pendingApplySet = null;
		currentSessionPicks.Clear();
		try
		{
			WithNativeVisuals(delegate
			{
				UIHandler.instance.StopShowPicker();
				CardChoiceVisuals.instance.Hide();
			});
		}
		catch
		{
		}
	}
}
[HarmonyPatch(typeof(CardChoice), "DoPick")]
internal static class Patch_DoPick
{
	private static bool Prefix(CardChoice __instance, int picksToSet, int picketIDToSet, PickerType pType, ref IEnumerator __result)
	{
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		if (!State.phaseActive)
		{
			return true;
		}
		if (!CallerIsOrchestrator())
		{
			return true;
		}
		for (int i = 0; i < Mathf.Max(1, picksToSet); i++)
		{
			State.RecordEntitlement(picketIDToSet, pType);
		}
		Player val = ((IEnumerable<Player>)PlayerManager.instance?.players).FirstOrDefault((Func<Player, bool>)((Player p) => (Object)(object)p != (Object)null && p.playerID == picketIDToSet));
		if ((Object)(object)val != (Object)null && State.IsLocalPlayer(val))
		{
			for (int num = 0; num < Mathf.Max(1, picksToSet); num++)
			{
				State.EnqueueLocalPick(picketIDToSet, pType);
			}
		}
		__result = Noop();
		return false;
	}

	private static void Postfix(int picketIDToSet, ref IEnumerator __result)
	{
		if (State.phaseActive && __result != null && !IsOurs(__result))
		{
			__result = DeferredPassThrough(__result, picketIDToSet);
		}
	}

	private static bool IsOurs(IEnumerator e)
	{
		return (e.GetType().FullName ?? "").Contains("SimulPicks");
	}

	private static IEnumerator Noop()
	{
		yield break;
	}

	private static IEnumerator DeferredPassThrough(IEnumerator original, int pickerID)
	{
		int myToken = State.phaseToken;
		float deadline = State.phaseStartTime + State.ClusterBudget() + 20f;
		while (true)
		{
			if (Time.realtimeSinceStartup < deadline)
			{
				if (myToken != State.phaseToken)
				{
					break;
				}
				if (State.phaseActive && (State.localQueue.Count != 0 || State.localRunnerBusy || State.localPickActive || !State.AllSessionsIn()))
				{
					yield return null;
					continue;
				}
			}
			if (Time.realtimeSinceStartup >= deadline && State.phaseActive)
			{
				SimulPicksPlugin.Warn("Hook-driven pick skipped to avoid a lobby hang.");
				break;
			}
			try
			{
				State.WithNativeVisuals(delegate
				{
					CardChoiceVisuals.instance.Show(pickerID, true);
				});
			}
			catch
			{
			}
			State.passThroughActive = true;
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			float watchdogAt = realtimeSinceStartup + (float)Mathf.Clamp(SimulPicksPlugin.PickTimeSeconds.Value, 10, 180) + 15f;
			bool barked = false;
			try
			{
				while (true)
				{
					object current;
					try
					{
						if (!original.MoveNext())
						{
							break;
						}
						current = original.Current;
					}
					catch (Exception ex)
					{
						SimulPicksPlugin.Warn("Pass-through pick threw: " + ex.Message);
						break;
					}
					if (!barked && Time.realtimeSinceStartup > watchdogAt)
					{
						CardChoice instance = CardChoice.instance;
						if ((Object)(object)instance != (Object)null && instance.IsPicking)
						{
							barked = true;
							SimulPicksPlugin.Warn("Released pick watchdog fired.");
							Player val = ((IEnumerable<Player>)PlayerManager.instance?.players).FirstOrDefault((Func<Player, bool>)((Player p) => (Object)(object)p != (Object)null && p.playerID == pickerID));
							if ((Object)(object)val != (Object)null && State.IsLocalPlayer(val))
							{
								State.ForceCompletePick(instance);
							}
							else
							{
								try
								{
									instance.IsPicking = false;
								}
								catch
								{
								}
							}
						}
					}
					yield return current;
				}
				break;
			}
			finally
			{
				State.passThroughActive = false;
			}
		}
	}

	private static bool CallerIsOrchestrator()
	{
		StackFrame[] frames = new StackTrace(fNeedFileInfo: false).GetFrames();
		if (frames == null)
		{
			return false;
		}
		StackFrame[] array = frames;
		for (int i = 0; i < array.Length; i++)
		{
			Type type = array[i].GetMethod()?.DeclaringType;
			while (type != null)
			{
				string text = type.Namespace ?? "";
				string text2 = type.Name ?? "";
				if (text.StartsWith("RWF.GameModes") || text.StartsWith("PickNCards") || text2.StartsWith("GM_ArmsRace") || text2.StartsWith("GM_Test") || text2.Contains("SimulPicks"))
				{
					return true;
				}
				type = type.DeclaringType;
			}
		}
		return false;
	}
}
[HarmonyPatch(typeof(CardChoice), "Pick")]
internal static class Patch_Pick_Capture
{
	private static void Prefix(CardChoice __instance, GameObject pickedCard)
	{
		//IL_0065: Unknown result type (might be due to invalid IL or missing references)
		if (State.localPickActive && !((Object)(object)pickedCard == (Object)null) && (int)AccessTools.Field(typeof(CardChoice), "pickrID").GetValue(__instance) == State.currentSessionPlayer)
		{
			CardInfo component = pickedCard.GetComponent<CardInfo>();
			CardInfo val = ((!((Object)(object)component != (Object)null)) ? ((CardInfo)null) : ((CardInfo)AccessTools.Field(typeof(CardInfo), "sourceCard").GetValue(component)));
			string text = (((Object)(object)val != (Object)null) ? ((Object)val).name : (((Object)(object)component != (Object)null) ? ((Object)component).name.Replace("(Clone)", "") : null));
			bool cursed = SoftPatches.IsCursed(pickedCard);
			if (!string.IsNullOrEmpty(text))
			{
				State.currentSessionPicks.Add(new ResultEntry
				{
					card = text,
					cursed = cursed
				});
			}
		}
	}
}
[HarmonyPatch(typeof(PhotonNetwork), "Instantiate")]
internal static class Patch_Photon_Instantiate
{
	private static readonly FieldInfo instantiationDataField = AccessTools.Field(typeof(PhotonView), "instantiationDataField");

	private static bool Prefix(string prefabName, Vector3 position, Quaternion rotation, object[] data, ref GameObject __result)
	{
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
		if (!State.localPickActive)
		{
			return true;
		}
		GameObject val = null;
		try
		{
			val = PhotonNetwork.PrefabPool.Instantiate(prefabName, position, rotation);
		}
		catch
		{
		}
		if ((Object)(object)val == (Object)null)
		{
			SimulPicksPlugin.Warn("Local instantiate failed for " + prefabName + " - spawn suppressed.");
			__result = null;
			return false;
		}
		PhotonView[] componentsInChildren = val.GetComponentsInChildren<PhotonView>(true);
		foreach (PhotonView val2 in componentsInChildren)
		{
			if (val2.ViewID == 0)
			{
				try
				{
					PhotonNetwork.AllocateViewID(val2);
				}
				catch (Exception ex)
				{
					SimulPicksPlugin.Warn("AllocateViewID: " + ex.Message);
				}
			}
			if (data != null && instantiationDataField != null)
			{
				try
				{
					instantiationDataField.SetValue(val2, data);
				}
				catch
				{
				}
			}
		}
		val.SetActive(true);
		IPunInstantiateMagicCallback[] components = val.GetComponents<IPunInstantiateMagicCallback>();
		foreach (IPunInstantiateMagicCallback val3 in components)
		{
			try
			{
				val3.OnPhotonInstantiate(default(PhotonMessageInfo));
			}
			catch (Exception ex2)
			{
				SimulPicksPlugin.Warn("OnPhotonInstantiate: " + ex2.Message);
			}
		}
		State.localCards.Add(val);
		__result = val;
		return false;
	}
}
[HarmonyPatch(typeof(CardChoice), "Spawn")]
internal static class Patch_Spawn_Local
{
	private static bool Prefix(GameObject objToSpawn, Vector3 pos, Quaternion rot, ref GameObject __result)
	{
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		if (!State.localPickActive)
		{
			return true;
		}
		__result = Object.Instantiate<GameObject>(objToSpawn, pos, rot);
		PhotonView[] componentsInChildren = __result.GetComponentsInChildren<PhotonView>(true);
		foreach (PhotonView val in componentsInChildren)
		{
			if (val.ViewID == 0)
			{
				try
				{
					PhotonNetwork.AllocateViewID(val);
				}
				catch
				{
				}
			}
		}
		State.localCards.Add(__result);
		return false;
	}
}
[HarmonyPatch(typeof(CardChoice), "SpawnUniqueCard")]
internal static class Patch_SpawnUniqueCard_Track
{
	private static void Postfix(GameObject __result)
	{
		if (!State.localPickActive || (Object)(object)__result == (Object)null)
		{
			return;
		}
		if (!State.localCards.Contains(__result))
		{
			State.localCards.Add(__result);
		}
		PhotonView[] componentsInChildren = __result.GetComponentsInChildren<PhotonView>(true);
		foreach (PhotonView val in componentsInChildren)
		{
			if (val.ViewID == 0)
			{
				try
				{
					PhotonNetwork.AllocateViewID(val);
				}
				catch
				{
				}
			}
		}
	}
}
[HarmonyPatch(typeof(CardChoiceVisuals), "Show")]
internal static class Patch_Visuals_Show
{
	private static bool Prefix()
	{
		if (State.passThroughActive || State.allowNativeVisualCall)
		{
			return true;
		}
		if (State.phaseActive || State.localPickActive)
		{
			return false;
		}
		return true;
	}
}
[HarmonyPatch(typeof(CardChoiceVisuals), "Hide")]
internal static class Patch_Visuals_Hide
{
	private static bool Prefix()
	{
		if (State.passThroughActive || State.allowNativeVisualCall)
		{
			return true;
		}
		if (State.phaseActive || State.localPickActive)
		{
			return false;
		}
		return true;
	}
}
[HarmonyPatch(typeof(PhotonView), "RPC", new Type[]
{
	typeof(string),
	typeof(RpcTarget),
	typeof(object[])
})]
internal static class Patch_PhotonView_RPC
{
	private static bool Prefix(PhotonView __instance, string methodName, object[] parameters)
	{
		if (!State.localPickActive)
		{
			return true;
		}
		if (!State.RedirectLocalRpcs.Contains(methodName))
		{
			return true;
		}
		InvokeLocally(__instance, methodName, parameters);
		return false;
	}

	internal static void InvokeLocally(PhotonView view, string methodName, object[] parameters)
	{
		MonoBehaviour[] components = ((Component)view).GetComponents<MonoBehaviour>();
		foreach (MonoBehaviour val in components)
		{
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			MethodInfo methodInfo = AccessTools.Method(((object)val).GetType(), methodName, (Type[])null, (Type[])null);
			if (!(methodInfo == null))
			{
				try
				{
					methodInfo.Invoke(val, parameters ?? Array.Empty<object>());
					return;
				}
				catch (Exception ex)
				{
					SimulPicksPlugin.Warn("Local " + methodName + " threw: " + (ex.InnerException?.Message ?? ex.Message));
					return;
				}
			}
		}
		SimulPicksPlugin.Warn($"Local invoke: no handler for {methodName} on view {view.ViewID}");
	}
}
[HarmonyPatch(typeof(NetworkingManager), "RPC", new Type[]
{
	typeof(Type),
	typeof(string),
	typeof(object[])
})]
internal static class Patch_NetworkingManager_RPC
{
	private static bool Prefix(Type targetType, string methodName, object[] data)
	{
		return HandleManagerRpc(targetType, methodName, data);
	}

	internal static bool HandleManagerRpc(Type targetType, string methodName, object[] data)
	{
		if (!State.localPickActive)
		{
			return true;
		}
		if (!State.LocalizedManagerRpcs.Contains(methodName))
		{
			return true;
		}
		MethodInfo methodInfo = AccessTools.Method(targetType, methodName, (Type[])null, (Type[])null);
		if (methodInfo != null)
		{
			try
			{
				methodInfo.Invoke(null, data ?? Array.Empty<object>());
			}
			catch (Exception ex)
			{
				SimulPicksPlugin.Warn("Localized " + methodName + " threw: " + (ex.InnerException?.Message ?? ex.Message));
			}
		}
		return false;
	}
}
[HarmonyPatch(typeof(NetworkingManager), "RPC_Others", new Type[]
{
	typeof(Type),
	typeof(string),
	typeof(object[])
})]
internal static class Patch_NetworkingManager_RPC_Others
{
	private static bool Prefix(Type targetType, string methodName, object[] data)
	{
		if (targetType == typeof(SimulPicksPlugin))
		{
			return true;
		}
		return Patch_NetworkingManager_RPC.HandleManagerRpc(targetType, methodName, data);
	}
}
internal static class SoftPatches
{
	private static Type cursedCardType;

	private static Type curseMgrType;

	private static Type pickTrackerType;

	private static Type cardBarUtilsType;

	internal static void Apply(Harmony h)
	{
		//IL_004e: Unknown result type (might be due to invalid IL or missing references)
		//IL_005c: Expected O, but got Unknown
		//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b5: Expected O, but got Unknown
		Type type = AccessTools.TypeByName("Rounds2Mod.CardDeleteManager") ?? AccessTools.TypeByName("CardDeleteManager");
		MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, "BeginPickPhase", (Type[])null, (Type[])null) : null);
		if (methodInfo != null)
		{
			h.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(SoftPatches), "SkipWhenPhaseActive", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			SimulPicksPlugin.Log("Card Delete Mechanic detected - delete disabled during simultaneous picks.");
		}
		Type type2 = AccessTools.TypeByName("RWF.GameModes.RWFGameMode");
		MethodInfo methodInfo2 = ((type2 != null) ? AccessTools.Method(type2, "ResetMatch", (Type[])null, (Type[])null) : null);
		if (methodInfo2 != null)
		{
			h.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(SoftPatches), "OnResetMatch", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}
		if (AccessTools.TypeByName("PickTimer.PickTimer") != null)
		{
			SimulPicksPlugin.Log("PickTimer detected - it must be disabled; SimulPicks has its own pick timer.");
		}
		cursedCardType = AccessTools.TypeByName("AALUND13Cards.ExtraCards.Cards.CursedCard") ?? AccessTools.TypeByName("AALUND13Cards.Cards.CursedCard") ?? AccessTools.TypeByName("CursedCard");
		curseMgrType = AccessTools.TypeByName("WillsWackyManagers.Utils.CurseManager");
		pickTrackerType = AccessTools.TypeByName("AALUND13Cards.Handlers.PickCardTracker") ?? AccessTools.TypeByName("PickCardTracker");
		cardBarUtilsType = AccessTools.TypeByName("ModdingUtils.Utils.CardBarUtils");
	}

	private static bool SkipWhenPhaseActive()
	{
		return !State.phaseActive;
	}

	private static void OnResetMatch()
	{
		State.HardReset("RWFGameMode.ResetMatch");
	}

	private static object ResolveSingleton(Type t)
	{
		if (t == null)
		{
			return null;
		}
		FieldInfo fieldInfo = AccessTools.Field(t, "instance") ?? AccessTools.Field(t, "Instance");
		if (fieldInfo != null)
		{
			try
			{
				return fieldInfo.GetValue(null);
			}
			catch
			{
			}
		}
		MethodInfo methodInfo = AccessTools.PropertyGetter(t, "instance") ?? AccessTools.PropertyGetter(t, "Instance");
		if (methodInfo != null)
		{
			try
			{
				return methodInfo.Invoke(null, null);
			}
			catch
			{
			}
		}
		return null;
	}

	internal static bool IsCursed(GameObject card)
	{
		try
		{
			return cursedCardType != null && (Object)(object)card.GetComponent(cursedCardType) != (Object)null;
		}
		catch
		{
			return false;
		}
	}

	internal static void CursePlayer(Player p)
	{
		try
		{
			object obj = ResolveSingleton(curseMgrType);
			MethodInfo methodInfo = ((curseMgrType != null) ? AccessTools.Method(curseMgrType, "CursePlayer", new Type[1] { typeof(Player) }, (Type[])null) : null);
			if (!(methodInfo == null))
			{
				methodInfo.Invoke(methodInfo.IsStatic ? null : obj, new object[1] { p });
				SimulPicksPlugin.Log($"Cursed pick relayed: CursePlayer({p.playerID}).");
			}
		}
		catch (Exception ex)
		{
			SimulPicksPlugin.Warn("CursePlayer relay failed: " + (ex.InnerException?.Message ?? ex.Message));
		}
	}

	internal static void FeedPickTracker(CardInfo card)
	{
		try
		{
			if (!(pickTrackerType == null) && !((Object)(object)card == (Object)null))
			{
				MethodInfo methodInfo = AccessTools.Method(pickTrackerType, "AddCardPickedInPickPhase", (Type[])null, (Type[])null);
				if (!(methodInfo == null))
				{
					methodInfo.Invoke(methodInfo.IsStatic ? null : ResolveSingleton(pickTrackerType), new object[1] { card });
				}
			}
		}
		catch
		{
		}
	}

	internal static void QueueReveal(Player p, CardInfo card)
	{
		try
		{
			if (!(cardBarUtilsType == null))
			{
				MethodInfo methodInfo = AccessTools.Method(cardBarUtilsType, "ShowAtEndOfPhase", new Type[2]
				{
					typeof(Player),
					typeof(CardInfo)
				}, (Type[])null);
				object obj = ResolveSingleton(cardBarUtilsType);
				if (!(methodInfo == null) && obj != null)
				{
					methodInfo.Invoke(obj, new object[2] { p, card });
				}
			}
		}
		catch
		{
		}
	}
}