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 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("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 gm) => State.OnGameStart())); GameModeManager.AddHook("PickStart", (Func)((IGameModeHandler gm) => State.OnPickStart())); GameModeManager.AddHook("PickEnd", (Func)((IGameModeHandler gm) => State.Barrier()), 600); GameModeManager.AddHook("GameEnd", (Func)((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)PlayerManager.instance.players).FirstOrDefault((Func)((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 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 entitlements = new Dictionary(); internal static readonly Dictionary entitledType = new Dictionary(); internal static readonly Dictionary sessionsDone = new Dictionary(); internal static readonly Dictionary> results = new Dictionary>(); internal static readonly Dictionary producedCount = new Dictionary(); internal static readonly HashSet locallyProduced = new HashSet(); internal static string[] pendingApplySet; internal static bool localPickActive; internal static bool allowNativeVisualCall; internal static int currentSessionPlayer = -1; internal static readonly List localCards = new List(); internal static readonly List currentSessionPicks = new List(); internal static readonly Queue> localQueue = new Queue>(); internal static bool localRunnerBusy; private static Coroutine runner; internal static readonly HashSet RedirectLocalRpcs = new HashSet { "RPCA_DoEndPick", "RPCA_ChangeSelected", "RPCA_SetCurrentSelected", "RPCA_DonePicking", "RPCA_SetFace", "RPCA_Pick" }; internal static readonly HashSet LocalizedManagerRpcs = new HashSet { "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(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(); 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(); } } 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 dictionary = new Dictionary(); foreach (KeyValuePair kv in entitlements) { Player val = ((IEnumerable)PlayerManager.instance?.players).FirstOrDefault((Func)((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 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()); } } internal static void ReportSession(int token, int playerID, int baseIdx, List 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 list = (List)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 list = ((List)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(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(currentSessionPicks)); } localPickActive = false; currentSessionPlayer = -1; } while (localQueue.Count > 0) { KeyValuePair keyValuePair = localQueue.Dequeue(); producedCount.TryGetValue(keyValuePair.Key, out var value2); ReportSession(token, keyValuePair.Key, value2, new List()); } 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 kv in entitlements) { sessionsDone.TryGetValue(kv.Key, out var value); if (value >= kv.Value) { continue; } Player val = null; try { val = ((IEnumerable)PlayerManager.instance.players).FirstOrDefault((Func)((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 list = new List(); foreach (int item in results.Keys.OrderBy((int k) => k)) { List 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)PlayerManager.instance?.players).FirstOrDefault((Func)((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)PlayerManager.instance?.players).FirstOrDefault((Func)((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)PlayerManager.instance?.players).FirstOrDefault((Func)((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 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(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(); 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(objToSpawn, pos, rot); PhotonView[] componentsInChildren = __result.GetComponentsInChildren(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(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(); 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()); 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()); } 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 { } } }