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
{
}
}
}