using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using DiskCardGame; using HarmonyLib; using Microsoft.CodeAnalysis; using Steamworks; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("InscryptionMP")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.2.0")] [assembly: AssemblyInformationalVersion("1.0.2+8f6fd59bdc97ea4068e67397f3da01bf95cd2e24")] [assembly: AssemblyProduct("InscryptionMP")] [assembly: AssemblyTitle("InscryptionMP")] [assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly was partially or fully created with the assistance of Generative AI (e.g., Code Suggestions, Refactoring, Documentation Generation).")] [assembly: AssemblyMetadata("AI_Model_Vendor", "Anthropic")] [assembly: AssemblyMetadata("AI_Model", "Claude Opus 5")] [assembly: AssemblyVersion("1.0.2.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace InscryptionMP { [HarmonyPatch] internal static class DeckOverride { [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] private static bool DeckData(ref List __result) { if (!Match.Active) { return true; } __result = Match.Deck; Trace.Info($"[deck] serving versus deck ({__result.Count} cards) instead of save deck"); return false; } } public static class DeckStore { public const int MinCards = 6; public const int MaxCards = 20; private static readonly string[] Starter = new string[10] { "Stoat", "Stoat", "Bullfrog", "Bullfrog", "Wolf", "Wolf", "Adder", "Squirrel", "Squirrel", "Squirrel" }; private static List _deck; private static List _pool; private static string Path { get { string path = Paths.ConfigPath ?? "."; return System.IO.Path.Combine(path, "inscryptionmp-deck.txt"); } } public static List Deck { get { if (_deck == null) { Load(); } return _deck; } } public static bool IsValid => Deck.Count >= 6 && Deck.Count <= 20; public static List Pool { get { if (_pool != null) { return _pool; } try { _pool = (from c in ScriptableObjectLoader.AllData where (Object)(object)c != (Object)null && (int)c.temple == 0 && c.metaCategories != null && c.metaCategories.Contains((CardMetaCategory)0) orderby c.BloodCost, c.DisplayedNameEnglish select c).ToList(); Trace.Info($"[deck] card pool: {_pool.Count} cards"); } catch (Exception ex) { Trace.Error("[deck] pool build failed: " + ex.Message); _pool = new List(); } return _pool; } } public static void Load() { try { if (File.Exists(Path)) { _deck = (from l in File.ReadAllLines(Path) select l.Trim() into l where l.Length > 0 && !l.StartsWith("#") select l).ToList(); Trace.Info($"[deck] loaded {_deck.Count} cards from disk"); } else { _deck = new List(Starter); Trace.Info("[deck] no saved deck - using starter"); } } catch (Exception ex) { Trace.Error("[deck] load failed: " + ex.Message); _deck = new List(Starter); } } public static void Save() { try { File.WriteAllLines(Path, Deck.ToArray()); Trace.Info($"[deck] saved {Deck.Count} cards"); } catch (Exception ex) { Trace.Error("[deck] save failed: " + ex.Message); } } public static void Add(string cardName) { if (Deck.Count < 20) { Deck.Add(cardName); Match.Reset(); } } public static void Remove(string cardName) { Deck.Remove(cardName); Match.Reset(); } public static int CountOf(string cardName) { return Deck.Count((string c) => c == cardName); } public static void ResetToStarter() { _deck = new List(Starter); Match.Reset(); } } public static class Match { private static List _cache; public static bool Active => VersusMode.InMatch; public static List Deck { get { if (_cache != null) { return new List(_cache); } List list = new List(); foreach (string item in DeckStore.Deck) { CardInfo cardByName = CardLoader.GetCardByName(item); if ((Object)(object)cardByName == (Object)null) { Trace.Warn("[match] card '" + item + "' did not resolve - skipping"); } else { list.Add(cardByName); } } Trace.Info($"[match] built versus deck with {list.Count} cards from the player's list"); _cache = list; return new List(_cache); } } public static void Reset() { _cache = null; } } [HarmonyPatch] internal static class MatchEnd { [HarmonyPatch(typeof(TurnManager), "TransitionToNextGameState")] [HarmonyPrefix] private static bool InterceptEnd(TurnManager __instance) { if (!VersusMode.InMatch) { return true; } VersusMode.Finish((MonoBehaviour)(object)__instance, __instance.PlayerWon, "battle resolved"); return false; } } [HarmonyPatch] internal static class MatchLock { [HarmonyPatch(typeof(GameFlowManager), "TransitionToFirstPerson")] [HarmonyPrefix] private static bool BlockStandingUp() { if (!VersusMode.InMatch) { return true; } return false; } [HarmonyPatch(typeof(GameFlowManager), "TransitionToGameState")] [HarmonyPrefix] private static bool BlockStateChange(GameState gameState) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (!VersusMode.InMatch) { return true; } if ((int)gameState == 0) { return true; } Trace.Warn($"[lock] blocked transition to {gameState} during match"); return false; } } internal class MpMenu : MonoBehaviour { internal static string Ip = "127.0.0.1"; internal static string PortText = 27333.ToString(); private const float PanelW = 480f; private const float PanelH = 520f; private bool _open; private bool _matchWasActive; private Rect _rect; private Vector2 _lobbyScroll; private GUIStyle _chip; private GUIStyle _label; private GUIStyle _dim; private GUIStyle _header; private GUIStyle _section; private GUIStyle _field; private GUIStyle _button; private GUIStyle _small; private Texture2D _panelBg; private Texture2D _chipBg; private Texture2D _accent; private Texture2D _rule; private static int ParsedPort { get { int result; return (int.TryParse(PortText, out result) && result > 0 && result < 65536) ? result : 27333; } } private void Update() { SteamTransport.Poll(); if (Input.GetKeyDown((KeyCode)288)) { if (NativeDeckBuilder.IsOpen) { NativeDeckBuilder.Close(); } else { _open = !_open; } } if (Input.GetKeyDown((KeyCode)289)) { VersusMode.StartAnywhere((MonoBehaviour)(object)this); } if (Input.GetKeyDown((KeyCode)293)) { VersusMode.Abort((MonoBehaviour)(object)this); } VersusMode.TickPendingStart((MonoBehaviour)(object)this); NativeDeckBuilder.TickPendingOpen((MonoBehaviour)(object)this); bool flag = VersusMode.InMatch || VersusMode.PendingStart; if (flag && !_matchWasActive) { _open = false; } _matchWasActive = flag; if (Net.PendingStartRequest) { Net.PendingStartRequest = false; if (!VersusMode.InMatch && !VersusMode.PendingStart) { Trace.Info("[versus] peer started a match - joining"); VersusMode.StartAnywhere((MonoBehaviour)(object)this, tellPeer: false); } } if (Net.PendingResult.HasValue) { bool value = Net.PendingResult.Value; Net.PendingResult = null; if (VersusMode.InMatch) { VersusMode.Finish((MonoBehaviour)(object)this, value, "peer reported the result"); } } if (VersusMode.InMatch && !Net.Connected) { VersusMode.NoteDisconnected(); } if (VersusMode.InMatch && Net.Connected && VersusMode.Suspended) { VersusMode.NoteReconnected(); } VersusMode.TickSuspension((MonoBehaviour)(object)this); } private static Texture2D Solid(Color c) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Expected O, but got Unknown //IL_000c: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(1, 1); val.SetPixel(0, 0, c); val.Apply(); ((Object)val).hideFlags = (HideFlags)61; return val; } private void EnsureStyles() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Expected O, but got Unknown //IL_00e4: Expected O, but got Unknown //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Expected O, but got Unknown //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Expected O, but got Unknown //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Expected O, but got Unknown //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Expected O, but got Unknown //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0222: Expected O, but got Unknown //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Unknown result type (might be due to invalid IL or missing references) //IL_023b: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Expected O, but got Unknown //IL_024b: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_025c: Unknown result type (might be due to invalid IL or missing references) //IL_0276: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Unknown result type (might be due to invalid IL or missing references) //IL_02b1: Unknown result type (might be due to invalid IL or missing references) //IL_02c6: Expected O, but got Unknown //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02d6: Unknown result type (might be due to invalid IL or missing references) //IL_02df: Unknown result type (might be due to invalid IL or missing references) //IL_02e8: Unknown result type (might be due to invalid IL or missing references) //IL_02f2: Expected O, but got Unknown //IL_02f3: Unknown result type (might be due to invalid IL or missing references) //IL_0308: Unknown result type (might be due to invalid IL or missing references) //IL_0313: Unknown result type (might be due to invalid IL or missing references) //IL_032d: Unknown result type (might be due to invalid IL or missing references) //IL_033d: Unknown result type (might be due to invalid IL or missing references) //IL_0343: Unknown result type (might be due to invalid IL or missing references) //IL_034e: Unknown result type (might be due to invalid IL or missing references) //IL_0368: Unknown result type (might be due to invalid IL or missing references) //IL_0378: Unknown result type (might be due to invalid IL or missing references) //IL_037e: Unknown result type (might be due to invalid IL or missing references) //IL_0389: Unknown result type (might be due to invalid IL or missing references) //IL_03a3: Unknown result type (might be due to invalid IL or missing references) //IL_03b8: Expected O, but got Unknown if (_chip == null) { _panelBg = Solid(new Color(0.06f, 0.05f, 0.04f, 0.98f)); _chipBg = Solid(new Color(0.06f, 0.05f, 0.04f, 0.88f)); _accent = Solid(new Color(0.85f, 0.62f, 0.25f, 1f)); _rule = Solid(new Color(1f, 1f, 1f, 0.1f)); GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = 14 }; val.normal.textColor = Color.white; val.padding = new RectOffset(10, 10, 5, 5); _chip = val; GUIStyle val2 = new GUIStyle(GUI.skin.label) { fontSize = 15 }; val2.normal.textColor = new Color(0.96f, 0.94f, 0.88f); _label = val2; GUIStyle val3 = new GUIStyle(_label) { fontSize = 13 }; val3.normal.textColor = new Color(0.6f, 0.57f, 0.51f); _dim = val3; GUIStyle val4 = new GUIStyle(_label) { fontSize = 12 }; val4.normal.textColor = new Color(0.55f, 0.52f, 0.47f); _small = val4; GUIStyle val5 = new GUIStyle(GUI.skin.label) { fontSize = 21, fontStyle = (FontStyle)1 }; val5.normal.textColor = new Color(1f, 0.8f, 0.38f); _header = val5; GUIStyle val6 = new GUIStyle(GUI.skin.label) { fontSize = 12, fontStyle = (FontStyle)1 }; val6.normal.textColor = new Color(0.85f, 0.62f, 0.25f); _section = val6; GUIStyle val7 = new GUIStyle(GUI.skin.textField) { fontSize = 15, padding = new RectOffset(8, 8, 6, 6) }; val7.normal.textColor = Color.white; val7.normal.background = Solid(new Color(0.15f, 0.14f, 0.12f, 1f)); val7.focused.textColor = Color.white; val7.focused.background = Solid(new Color(0.21f, 0.19f, 0.16f, 1f)); _field = val7; GUIStyle val8 = new GUIStyle(GUI.skin.button) { fontSize = 15, padding = new RectOffset(12, 12, 9, 9) }; val8.normal.textColor = new Color(0.96f, 0.94f, 0.88f); val8.normal.background = Solid(new Color(0.18f, 0.16f, 0.13f, 1f)); val8.hover.textColor = Color.white; val8.hover.background = Solid(new Color(0.31f, 0.26f, 0.18f, 1f)); val8.active.textColor = Color.white; val8.active.background = Solid(new Color(0.44f, 0.35f, 0.2f, 1f)); _button = val8; } } private void OnGUI() { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) EnsureStyles(); if (NativeDeckBuilder.IsOpen) { DrawCardViewHint(); DrawCursor(); return; } if (!_open) { DrawChip(); return; } float num = 520f; _rect = new Rect(((float)Screen.width - 480f) * 0.5f, ((float)Screen.height - num) * 0.5f, 480f, num); GUI.BeginGroup(_rect); DrawWindow(0); GUI.EndGroup(); DrawCursor(); } private void DrawChip() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) string text = ((!VersusMode.InMatch) ? "" : (TurnOrder.IsMyTurn ? " | YOUR TURN" : " | opponent's turn")); string text2 = "MP: " + Net.StatusLine + text + " [F7] menu"; Vector2 val = _chip.CalcSize(new GUIContent(text2)); Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(10f, 10f, val.x + 16f, val.y + 8f); GUI.DrawTexture(val2, (Texture)(object)_chipBg); bool flag = VersusMode.InMatch && TurnOrder.IsMyTurn; GUI.DrawTexture(new Rect(((Rect)(ref val2)).x, ((Rect)(ref val2)).y, 3f, ((Rect)(ref val2)).height), (Texture)(object)(flag ? _accent : (Net.Connected ? _rule : _accent))); GUI.Label(val2, text2, _chip); } private void DrawCursor() { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) DrawCursor(default(Rect)); } private void DrawCursor(Rect _unused) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) Vector3 mousePosition = Input.mousePosition; float x = mousePosition.x; float num = (float)Screen.height - mousePosition.y; Color color = default(Color); ((Color)(ref color))..ctor(0f, 0f, 0f, 0.9f); Color color2 = GUI.color; GUI.color = color; GUI.DrawTexture(new Rect(x - 11f - 1f, num - 1f, 24f, 5f), (Texture)(object)Texture2D.whiteTexture); GUI.DrawTexture(new Rect(x - 1f, num - 11f - 1f, 5f, 24f), (Texture)(object)Texture2D.whiteTexture); GUI.color = new Color(1f, 0.85f, 0.35f, 1f); GUI.DrawTexture(new Rect(x - 11f, num, 22f, 3f), (Texture)(object)Texture2D.whiteTexture); GUI.DrawTexture(new Rect(x, num - 11f, 3f, 22f), (Texture)(object)Texture2D.whiteTexture); GUI.color = color2; } private void DrawCardViewHint() { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) bool poolMode = NativeDeckBuilder.PoolMode; string text = (poolMode ? "CLICK A CARD TO ADD IT" : "CLICK A CARD TO REMOVE IT"); string text2 = $"deck {DeckStore.Deck.Count}/{20}" + $" page {NativeDeckBuilder.Page + 1}/{NativeDeckBuilder.PageCount}"; Rect val = default(Rect); ((Rect)(ref val))..ctor(((float)Screen.width - 700f) * 0.5f, 12f, 700f, 84f); GUI.DrawTexture(val, (Texture)(object)_accent); GUI.DrawTexture(new Rect(((Rect)(ref val)).x + 2f, ((Rect)(ref val)).y + 2f, ((Rect)(ref val)).width - 4f, ((Rect)(ref val)).height - 4f), (Texture)(object)_panelBg); GUILayout.BeginArea(new Rect(((Rect)(ref val)).x + 12f, ((Rect)(ref val)).y + 8f, ((Rect)(ref val)).width - 24f, ((Rect)(ref val)).height - 16f)); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(text, _section, Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.Label(text2, _small, Array.Empty()); GUILayout.EndHorizontal(); GUILayout.Space(4f); GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button("< Prev", _button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) })) { NativeDeckBuilder.PrevPage(); } if (GUILayout.Button("Next >", _button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) })) { NativeDeckBuilder.NextPage(); } GUILayout.FlexibleSpace(); if (GUILayout.Button(poolMode ? "View My Deck" : "Browse All Cards", _button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(190f) })) { NativeDeckBuilder.ToggleMode(); } GUILayout.FlexibleSpace(); if (GUILayout.Button("Done [F7]", _button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) })) { NativeDeckBuilder.Close(); } GUILayout.EndHorizontal(); GUILayout.EndArea(); if (!poolMode && DeckStore.Deck.Count == 0) { Rect val2 = default(Rect); ((Rect)(ref val2))..ctor(((float)Screen.width - 400f) * 0.5f, ((Rect)(ref val)).yMax + 16f, 400f, 30f); GUI.DrawTexture(val2, (Texture)(object)_panelBg); GUI.Label(new Rect(((Rect)(ref val2)).x + 10f, ((Rect)(ref val2)).y + 5f, ((Rect)(ref val2)).width, ((Rect)(ref val2)).height), "Your deck is empty - browse all cards to add some.", _chip); } } private void Rule() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) GUILayout.Space(6f); GUILayout.Box(GUIContent.none, GUIStyle.none, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(1f) }); Rect lastRect = GUILayoutUtility.GetLastRect(); GUI.DrawTexture(new Rect(((Rect)(ref lastRect)).x, ((Rect)(ref lastRect)).y, 452f, 1f), (Texture)(object)_rule); GUILayout.Space(6f); } private void DrawWindow(int id) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_02da: Unknown result type (might be due to invalid IL or missing references) //IL_02f2: Unknown result type (might be due to invalid IL or missing references) //IL_02f7: Unknown result type (might be due to invalid IL or missing references) //IL_0334: Unknown result type (might be due to invalid IL or missing references) float num = 520f; GUI.DrawTexture(new Rect(0f, 0f, 480f, num), (Texture)(object)_accent); GUI.DrawTexture(new Rect(2f, 2f, 476f, num - 4f), (Texture)(object)_panelBg); GUILayout.BeginArea(new Rect(14f, 12f, 452f, num - 24f)); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("INSCRYPTION ONLINE", _header, Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.Label("v1.0.2", _small, Array.Empty()); GUILayout.EndHorizontal(); GUILayout.Label(Net.StatusLine, Net.Connected ? _label : _dim, Array.Empty()); if (Net.HandshakeError != null) { GUILayout.Label("INCOMPATIBLE VERSIONS", _section, Array.Empty()); GUILayout.Label(Net.HandshakeError, _small, Array.Empty()); GUILayout.Label("Both players need the same build of the mod.", _small, Array.Empty()); } GUILayout.Space(6f); GUI.enabled = !VersusMode.InMatch; if (GUILayout.Button($"EDIT DECK ({DeckStore.Deck.Count}/{20})", _button, Array.Empty())) { OpenCardView(); } GUI.enabled = true; if (VersusMode.InMatch) { GUILayout.Label("Finish the match to edit your deck.", _small, Array.Empty()); } else if (!DeckStore.IsValid) { GUILayout.Label($"Your deck needs {6}-{20} cards.", _small, Array.Empty()); } if (NativeDeckBuilder.LastError != null) { GUILayout.Label(NativeDeckBuilder.LastError, _small, Array.Empty()); } Rule(); bool enabled = !Net.Connected && !VersusMode.InMatch; GUILayout.Label("STEAM", _section, Array.Empty()); if (!SteamTransport.Available) { GUILayout.Label("Steam not detected - use a direct address below.", _small, Array.Empty()); } else { GUI.enabled = enabled; GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button("Host Lobby", _button, Array.Empty())) { SteamTransport.HostLobby(); } if (GUILayout.Button("Find Games", _button, Array.Empty())) { SteamTransport.RefreshLobbies(); } GUILayout.EndHorizontal(); if (SteamTransport.Lobbies.Count > 0) { GUILayout.Space(4f); _lobbyScroll = GUILayout.BeginScrollView(_lobbyScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(86f) }); foreach (KeyValuePair lobby in SteamTransport.Lobbies) { if (GUILayout.Button(lobby.Value, _button, Array.Empty())) { SteamTransport.JoinLobby(lobby.Key); } } GUILayout.EndScrollView(); } GUI.enabled = true; } Rule(); GUILayout.Label("DIRECT (LAN / non-Steam)", _section, Array.Empty()); GUI.enabled = enabled; GUILayout.BeginHorizontal(Array.Empty()); Ip = GUILayout.TextField(Ip, 64, _field, Array.Empty()); GUILayout.Label(":", _label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(8f) }); PortText = GUILayout.TextField(PortText, 5, _field, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(64f) }); GUILayout.EndHorizontal(); GUILayout.Space(4f); GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button("Host", _button, Array.Empty())) { Net.Host(ParsedPort); } if (GUILayout.Button("Join", _button, Array.Empty())) { Net.Join(Ip, ParsedPort); } GUILayout.EndHorizontal(); GUI.enabled = true; Rule(); GUI.enabled = Net.Connected && !VersusMode.InMatch && !VersusMode.PendingStart && DeckStore.IsValid && Net.HandshakeError == null; if (GUILayout.Button("START MATCH", _button, Array.Empty())) { VersusMode.StartAnywhere((MonoBehaviour)(object)this); } GUI.enabled = true; if (!DeckStore.IsValid) { GUILayout.Label($"Your deck needs {6}-{20} cards - see the DECK tab.", _small, Array.Empty()); } if (VersusMode.Suspended) { GUILayout.Space(6f); GUILayout.Label("OPPONENT DISCONNECTED", _section, Array.Empty()); GUILayout.Label($"Holding the match for {Mathf.CeilToInt(VersusMode.SuspendedSecondsLeft)}s.", _small, Array.Empty()); GUILayout.Label("They can relaunch and rejoin - nothing is lost.", _small, Array.Empty()); } if (!Net.Connected && !VersusMode.InMatch) { GUILayout.Label("Host a lobby, or find one, to play someone.", _small, Array.Empty()); } else if (Net.Connected && !VersusMode.InMatch && DeckStore.IsValid && Net.HandshakeError == null) { GUILayout.Label("Either player can start - you both enter together.", _small, Array.Empty()); } GUILayout.Space(4f); GUILayout.BeginHorizontal(Array.Empty()); if (VersusMode.InMatch && GUILayout.Button(VersusMode.Suspended ? "Give Up Waiting" : "Abort Match", _button, Array.Empty())) { VersusMode.Abort((MonoBehaviour)(object)this); } if (Net.Running && GUILayout.Button("Disconnect", _button, Array.Empty())) { Net.Shutdown(); } GUILayout.EndHorizontal(); if (VersusMode.LastResult != null && !VersusMode.InMatch) { GUILayout.Label("Last match: " + VersusMode.LastResult, _label, Array.Empty()); } GUILayout.FlexibleSpace(); GUILayout.Label("F7 menu F8 start match F12 abort", _small, Array.Empty()); GUILayout.EndArea(); } private void OpenCardView() { _open = false; if (NativeDeckBuilder.Available) { NativeDeckBuilder.Open((MonoBehaviour)(object)this, poolMode: true); return; } NativeDeckBuilder.PendingOpen = true; VersusMode.LoadTableOnly(); } } internal static class NativeDeckBuilder { public const int PageSize = 10; private static bool _refresh; public static bool IsOpen { get; private set; } public static bool PoolMode { get; private set; } public static string LastError { get; private set; } public static int Page { get; private set; } public static int PageCount { get { int num = SourceCount(); return (num <= 0) ? 1 : ((num + 10 - 1) / 10); } } public static bool Available => (Object)(object)Singleton.Instance != (Object)null; private static SelectableCardArray Array { get { DeckReviewSequencer instance = Singleton.Instance; if ((Object)(object)instance == (Object)null) { return null; } object? obj = AccessTools.Field(typeof(DeckReviewSequencer), "cardArray")?.GetValue(instance); return (SelectableCardArray)((obj is SelectableCardArray) ? obj : null); } } public static bool PendingOpen { get; set; } public static void NextPage() { Page = (Page + 1) % PageCount; _refresh = true; } public static void PrevPage() { Page = (Page - 1 + PageCount) % PageCount; _refresh = true; } public static void ToggleMode() { PoolMode = !PoolMode; Page = 0; _refresh = true; } private static int SourceCount() { return PoolMode ? DeckStore.Pool.Count : DeckStore.Deck.Count; } public static void TickPendingOpen(MonoBehaviour host) { if (PendingOpen && !IsOpen && Available) { GameFlowManager instance = Singleton.Instance; if (!((Object)(object)instance == (Object)null) && !instance.Transitioning) { PendingOpen = false; Trace.Info("[deckui] table ready - opening card view"); Open(host, poolMode: true); } } } public static void Open(MonoBehaviour host, bool poolMode) { if (!IsOpen) { if (VersusMode.InMatch) { LastError = "finish the match before editing your deck"; Trace.Warn("[deckui] " + LastError); return; } if (!Available) { LastError = "card view needs the Act 1 table - use Load Table first"; Trace.Warn("[deckui] " + LastError); return; } if ((Object)(object)Array == (Object)null) { LastError = "could not reach the card array"; Trace.Error("[deckui] " + LastError); return; } LastError = null; PoolMode = poolMode; Page = 0; _refresh = false; host.StartCoroutine(Loop(host)); } } public static void Close() { IsOpen = false; } private static IEnumerator Loop(MonoBehaviour host) { IsOpen = true; Trace.Info("[deckui] opening card view (" + (PoolMode ? "pool" : "deck") + ")"); ViewManager views = Singleton.Instance; GameFlowManager flow = Singleton.Instance; ViewLockState prevLock = (ViewLockState)0; GameMap map = Singleton.Instance; if ((Object)(object)map != (Object)null && (Object)(object)flow != (Object)null && (int)flow.CurrentGameState == 1) { Trace.Info("[deckui] hiding map"); views.Controller.SwitchToControlMode((ControlMode)16, false); yield return map.HideMapSequence(); yield return (object)new WaitForSeconds(0.2f); } TableProps.HidePlayerMarker(); if ((Object)(object)views != (Object)null) { prevLock = views.Controller.LockState; views.SwitchToView((View)23, false, false); views.Controller.LockState = (ViewLockState)1; } yield return (object)new WaitForSeconds(0.3f); while (IsOpen) { _refresh = false; if (Page >= PageCount) { Page = 0; } List cards = BuildList(); if (cards.Count == 0) { yield return (object)new WaitUntil((Func)(() => _refresh || !IsOpen)); continue; } SelectableCard picked = null; yield return Array.SelectCardFrom(new List(cards), (CardPile)null, (Action)delegate(SelectableCard c) { picked = c; }, (Func)(() => !IsOpen || _refresh), true); if (!IsOpen) { break; } if (_refresh) { continue; } if ((Object)(object)picked == (Object)null) { break; } string id = (((Object)(object)((Card)picked).Info != (Object)null) ? ((Object)((Card)picked).Info).name : null); if (!string.IsNullOrEmpty(id)) { if (PoolMode) { DeckStore.Add(id); Trace.Info($"[deckui] added {id} ({DeckStore.Deck.Count} cards)"); } else { DeckStore.Remove(id); Trace.Info($"[deckui] removed {id} ({DeckStore.Deck.Count} cards)"); } DeckStore.Save(); if ((Object)(object)picked != (Object)null) { Object.Destroy((Object)(object)((Component)picked).gameObject); } yield return (object)new WaitForSeconds(0.15f); } } IsOpen = false; if ((Object)(object)views != (Object)null) { views.SwitchToView((View)1, false, false); views.Controller.LockState = prevLock; } Trace.Info("[deckui] closed card view"); if (VersusMode.LoadedTableForDeck && !VersusMode.InMatch) { yield return (object)new WaitForSeconds(0.35f); VersusMode.LeaveDeckTable(); } else { TableProps.RestorePlayerMarker(); } } private static List BuildList() { List list = new List(); if (PoolMode) { list.AddRange(DeckStore.Pool); } else { foreach (string item in DeckStore.Deck) { CardInfo cardByName = CardLoader.GetCardByName(item); if ((Object)(object)cardByName != (Object)null) { list.Add(cardByName); } } } int num = Page * 10; if (num >= list.Count) { Page = 0; num = 0; } int count = Mathf.Min(10, list.Count - num); return list.GetRange(num, count); } } public static class Net { public const int DefaultPort = 27333; private static TcpListener _listener; private static TcpClient _client; private static StreamWriter _writer; private static Thread _thread; private static volatile bool _running; private static readonly ConcurrentQueue Inbox = new ConcurrentQueue(); private static bool TcpIsHost { get; set; } public static bool IsHost => SteamTransport.Active ? SteamTransport.IsHost : TcpIsHost; public static bool TcpConnected { get; private set; } public static bool Running => _running || SteamTransport.Active; public static bool Connected => SteamTransport.Active ? SteamTransport.Connected : TcpConnected; public static bool? PendingResult { get; set; } public static string HandshakeError { get; set; } public static bool PeerVerified { get; private set; } public static bool PendingStartRequest { get; set; } public static bool Reconnecting { get; set; } public static string StatusLine { get { if (SteamTransport.Active) { return "steam: " + SteamTransport.Status; } if (TcpConnected) { return TcpIsHost ? "connected (host)" : "connected (client)"; } if (_running) { return TcpIsHost ? "hosting - waiting for peer" : "connecting..."; } return "offline"; } } public static void Host(int port = 27333) { if (_running) { Trace.Info("[net] already " + (Connected ? "connected" : "hosting") + " - ignoring"); return; } Shutdown(); TcpIsHost = true; _running = true; _thread = new Thread((ThreadStart)delegate { HostLoop(port); }) { IsBackground = true, Name = "InscryptionMP-Host" }; _thread.Start(); Trace.Info($"[net] hosting on port {port}, waiting for peer..."); } public static void Join(string host, int port = 27333) { if (_running) { Trace.Info("[net] already " + (Connected ? "connected" : "connecting") + " - ignoring"); return; } Shutdown(); TcpIsHost = false; _running = true; _thread = new Thread((ThreadStart)delegate { JoinLoop(host, port); }) { IsBackground = true, Name = "InscryptionMP-Client" }; _thread.Start(); Trace.Info($"[net] connecting to {host}:{port}..."); } private static void HostLoop(int port) { try { _listener = new TcpListener(IPAddress.Any, port); _listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, optionValue: true); _listener.Start(); while (_running) { _client = _listener.AcceptTcpClient(); Trace.Info("[net] peer connected."); Pump(); TcpConnected = false; if (!_running) { break; } Trace.Warn("[net] peer dropped - listening again"); } } catch (Exception ex) { if (_running) { Trace.Error("[net] host error: " + ex.Message); } } finally { TcpConnected = false; _running = false; } } private static void JoinLoop(string host, int port) { try { while (_running) { try { _client = new TcpClient(); _client.Connect(host, port); Trace.Info("[net] connected to host."); Pump(); TcpConnected = false; } catch (Exception ex) { if (!_running) { break; } Trace.Warn("[net] connect failed (" + ex.Message + ") - retrying"); } if (!_running || !Reconnecting) { break; } Thread.Sleep(2000); } } catch (Exception ex2) { if (_running) { Trace.Error("[net] join error: " + ex2.Message); } } finally { TcpConnected = false; _running = false; } } private static void Pump() { NetworkStream stream = _client.GetStream(); _writer = new StreamWriter(stream) { AutoFlush = true, NewLine = "\n" }; TcpConnected = true; SendHello(); using StreamReader streamReader = new StreamReader(stream); string text; while (_running && (text = streamReader.ReadLine()) != null) { text = text.Trim(); if (text.Length != 0) { Trace.Info("[net] <- " + text); if (!CaptureResult(text)) { Inbox.Enqueue(text); } } } } public static void Send(string msg) { if (SteamTransport.Active) { SteamTransport.Send(msg); return; } if (!TcpConnected || _writer == null) { Trace.Warn("[net] dropped (not connected): " + msg); return; } try { _writer.WriteLine(msg); Trace.Info("[net] -> " + msg); } catch (Exception ex) { Trace.Error("[net] send failed: " + ex.Message); } } public static bool CaptureResult(string line) { switch (line) { case "START": PendingStartRequest = true; return true; case "OVER WON": PendingResult = false; return true; case "OVER LOST": PendingResult = true; return true; default: { if (Protocol.TryParseHello(line, out var protocolVersion, out var modVersion)) { if (protocolVersion != 1) { HandshakeError = "version mismatch - you have mod 1.0.2 (protocol " + 1 + "), they have " + modVersion + " (protocol " + protocolVersion + ")"; Trace.Error("[net] " + HandshakeError); PeerVerified = false; } else { PeerVerified = true; Trace.Info("[net] peer verified - mod " + modVersion + ", protocol " + protocolVersion); } return true; } return false; } } } public static void SendHello() { HandshakeError = null; PeerVerified = false; Send(Protocol.Hello); } public static bool TryDequeue(out string msg) { if (SteamTransport.Active) { return SteamTransport.TryDequeue(out msg); } return Inbox.TryDequeue(out msg); } public static void Shutdown() { if (SteamTransport.Active) { SteamTransport.Shutdown(); } _running = false; TcpConnected = false; try { _writer?.Dispose(); } catch { } try { _client?.Close(); } catch { } try { _listener?.Stop(); } catch { } string result; while (Inbox.TryDequeue(out result)) { } PendingResult = null; PendingStartRequest = false; HandshakeError = null; PeerVerified = false; _writer = null; _client = null; _listener = null; } } public class NetworkOpponent : Opponent { protected override string BlueprintSubfolderName => ""; public override bool QueueFirstCardBeforePlayer => false; public override IEnumerator QueueNewCards(bool doTween = true, bool changeView = true) { Trace.Info("[opp] waiting for peer's turn..."); ViewManager views = Singleton.Instance; ViewLockState previousLock = (ViewLockState)1; if ((Object)(object)views != (Object)null) { previousLock = views.Controller.LockState; views.SwitchToView((View)1, false, false); views.Controller.LockState = (ViewLockState)0; } try { while (true) { if (Net.TryDequeue(out var msg)) { if (msg == "END") { Trace.Info("[opp] peer ended turn."); TurnOrder.TakenFromPeer(); yield break; } if (Protocol.TryParseSacrifice(msg, out var sacSlot)) { yield return SacrificePeerCard(sacSlot); continue; } if (Protocol.TryParseBoard(msg, out var slotNames)) { yield return ReconcileBoard(slotNames); continue; } if (Protocol.TryParsePlay(msg, out var cardName, out var slotIndex)) { yield return PlacePeerCard(cardName, slotIndex); } slotNames = null; cardName = null; } if (!VersusMode.InMatch) { break; } if (!Net.Connected) { VersusMode.NoteDisconnected(); } yield return (object)new WaitForEndOfFrame(); msg = null; } Trace.Info("[opp] match ended while waiting"); } finally { if ((Object)(object)views != (Object)null) { views.Controller.LockState = previousLock; } } } private IEnumerator PlacePeerCard(string cardName, int slotIndex) { CardInfo info = CardLoader.GetCardByName(cardName); if ((Object)(object)info == (Object)null) { Trace.Error("[opp] unknown card '" + cardName + "' - skipping"); yield break; } BoardManager board = Singleton.Instance; List slots = board.OpponentSlotsCopy; if (slotIndex < 0 || slotIndex >= slots.Count) { Trace.Error($"[opp] slot {slotIndex} out of range - skipping"); yield break; } CardSlot slot = slots[slotIndex]; if ((Object)(object)slot.Card != (Object)null) { Trace.Info($"[opp] slot {slotIndex} occupied - replacing"); RemoveCard(slot); } Trace.Info($"[opp] placing peer card '{cardName}' in opponent slot {slotIndex}"); yield return board.CreateCardInSlot(info, slot, 0.1f, true); yield return (object)new WaitForSeconds(0.15f); } private IEnumerator ReconcileBoard(string[] slotNames) { BoardManager board = Singleton.Instance; if ((Object)(object)board == (Object)null) { yield break; } List slots = board.OpponentSlotsCopy; int count = Mathf.Min(slotNames.Length, slots.Count); for (int i = 0; i < count; i++) { string wanted = slotNames[i]; CardSlot slot = slots[i]; string actual = (((Object)(object)slot.Card != (Object)null && (Object)(object)((Card)slot.Card).Info != (Object)null) ? ((Object)((Card)slot.Card).Info).name : "-"); if (wanted == actual) { continue; } if ((Object)(object)slot.Card != (Object)null) { Trace.Info($"[opp] reconcile: clearing slot {i} ({actual})"); yield return RemoveCardAnimated(slot); } if (wanted != "-") { CardInfo info = CardLoader.GetCardByName(wanted); if ((Object)(object)info == (Object)null) { Trace.Error("[opp] reconcile: unknown card '" + wanted + "'"); continue; } Trace.Info($"[opp] reconcile: slot {i} -> {wanted}"); yield return board.CreateCardInSlot(info, slot, 0.1f, true); yield return (object)new WaitForSeconds(0.05f); } } } private IEnumerator SacrificePeerCard(int slotIndex) { BoardManager board = Singleton.Instance; if ((Object)(object)board == (Object)null) { yield break; } List slots = board.OpponentSlotsCopy; if (slotIndex >= 0 && slotIndex < slots.Count) { PlayableCard card = slots[slotIndex].Card; if ((Object)(object)card == (Object)null) { Trace.Warn($"[opp] sacrifice: slot {slotIndex} already empty"); yield break; } Trace.Info($"[opp] peer sacrificed slot {slotIndex}"); yield return card.Die(true, (PlayableCard)null, true); } } private static IEnumerator RemoveCardAnimated(CardSlot slot) { PlayableCard card = slot.Card; if (!((Object)(object)card == (Object)null)) { yield return card.Die(false, (PlayableCard)null, false); } } private static void RemoveCard(CardSlot slot) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) PlayableCard card = slot.Card; if (!((Object)(object)card == (Object)null)) { card.UnassignFromSlot(); ((Card)card).ExitBoard(0.2f, Vector3.zero); } } } [HarmonyPatch] internal static class OpponentInjector { [HarmonyPatch(typeof(Opponent), "SpawnOpponent")] [HarmonyPostfix] private static void Swap(ref Opponent __result, EncounterData encounterData) { if (VersusMode.InMatch && !((Object)(object)__result == (Object)null)) { GameObject gameObject = ((Component)__result).gameObject; Trace.Info("[inject] replacing " + ((object)__result).GetType().Name + " with NetworkOpponent"); Object.DestroyImmediate((Object)(object)__result); NetworkOpponent networkOpponent = gameObject.AddComponent(); ((Opponent)networkOpponent).NumLives = ((Opponent)networkOpponent).StartingLives; ((Opponent)networkOpponent).TurnPlan = new List>(); ((Opponent)networkOpponent).Difficulty = 0; __result = (Opponent)(object)networkOpponent; } } } [BepInPlugin("dev.snoz.inscryptionmp", "InscryptionMP", "1.0.2")] public class Plugin : BaseUnityPlugin { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static LogCallback <>9__6_0; internal void b__6_0(string condition, string stack, LogType type) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_0015: Unknown result type (might be due to invalid IL or missing references) if ((int)type == 4 || (int)type == 0) { Trace.Error($"[unity] {type}: {condition} || {stack}"); } } } public const string Guid = "dev.snoz.inscryptionmp"; public const string Name = "InscryptionMP"; public const string Version = "1.0.2"; internal static ManualLogSource Log; internal static MonoBehaviour Runner; private Harmony _harmony; private void Awake() { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Expected O, but got Unknown //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Runner = (MonoBehaviour)(object)this; Trace.Init(); object obj = <>c.<>9__6_0; if (obj == null) { LogCallback val = delegate(string condition, string stack, LogType type) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Invalid comparison between Unknown and I4 //IL_0015: Unknown result type (might be due to invalid IL or missing references) if ((int)type == 4 || (int)type == 0) { Trace.Error($"[unity] {type}: {condition} || {stack}"); } }; <>c.<>9__6_0 = val; obj = (object)val; } Application.logMessageReceived += (LogCallback)obj; Log.LogInfo((object)"====================================="); Log.LogInfo((object)"InscryptionMP v1.0.2 loaded."); Log.LogInfo((object)("Unity: " + Application.unityVersion)); Log.LogInfo((object)"====================================="); _harmony = new Harmony("dev.snoz.inscryptionmp"); _harmony.PatchAll(typeof(Plugin).Assembly); Log.LogInfo((object)"Harmony patches applied."); ((Component)this).gameObject.AddComponent(); Log.LogInfo((object)"Press F7 for the multiplayer menu. F8 = start match, F12 = abort."); ConfigEntry val2 = ((BaseUnityPlugin)this).Config.Bind("Dev", "AutoHost", true, "Start hosting automatically on launch. Convenient while iterating."); if (val2.Value) { Trace.Info("[boot] AutoHost enabled - hosting immediately."); Net.Host(); } } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } [HarmonyPatch] internal static class Probe { [HarmonyPatch(typeof(TurnManager), "OnCombatBellRang")] [HarmonyPostfix] private static void BellRang() { if (VersusMode.InMatch) { TurnManager instance = Singleton.Instance; Trace.Info($"[probe] bell rang - local player ended turn {((instance != null) ? new int?(instance.TurnNumber) : ((int?)null))}"); } } } public static class Protocol { public const string EndTurn = "END"; public const int Version = 1; public const string HelloPrefix = "HELLO "; public const string StartMatch = "START"; public const string SacrificePrefix = "SAC "; public const string BoardPrefix = "BOARD "; public const string EmptySlot = "-"; public const string Won = "OVER WON"; public const string Lost = "OVER LOST"; public static string Hello => "HELLO " + 1 + " 1.0.2"; public static bool TryParseHello(string msg, out int protocolVersion, out string modVersion) { protocolVersion = 0; modVersion = "?"; if (msg == null || !msg.StartsWith("HELLO ")) { return false; } string[] array = msg.Substring("HELLO ".Length).Split(new char[1] { ' ' }); if (array.Length < 1 || !int.TryParse(array[0], out protocolVersion)) { return false; } if (array.Length > 1) { modVersion = array[1]; } return true; } public static string Sacrifice(int slotIndex) { return "SAC " + slotIndex; } public static bool TryParseSacrifice(string msg, out int slotIndex) { slotIndex = -1; return msg != null && msg.StartsWith("SAC ") && int.TryParse(msg.Substring("SAC ".Length), out slotIndex); } public static string Board(string[] slotCardNames) { return "BOARD " + string.Join("|", slotCardNames); } public static bool TryParseBoard(string msg, out string[] slots) { slots = null; if (msg == null || !msg.StartsWith("BOARD ")) { return false; } slots = msg.Substring("BOARD ".Length).Split(new char[1] { '|' }); return true; } public static string Play(string cardName, int slotIndex) { return $"PLAY {cardName} {slotIndex}"; } public static bool TryParsePlay(string msg, out string cardName, out int slotIndex) { cardName = null; slotIndex = -1; if (msg == null || !msg.StartsWith("PLAY ")) { return false; } string[] array = msg.Split(new char[1] { ' ' }); if (array.Length != 3) { return false; } if (!int.TryParse(array[2], out slotIndex)) { return false; } cardName = array[1]; return true; } } [HarmonyPatch] internal static class SilenceLeshy { [HarmonyPatch(typeof(TextDisplayer), "PlayDialogueEvent")] [HarmonyPrefix] private static bool SkipDialogueEvent(ref IEnumerator __result, string eventId) { if (!VersusMode.InMatch) { return true; } Trace.Info("[quiet] suppressed dialogue '" + eventId + "'"); __result = Nothing(); return false; } [HarmonyPatch(typeof(TextDisplayer), "ShowUntilInput")] [HarmonyPrefix] private static bool SkipShowUntilInput(ref IEnumerator __result) { if (!VersusMode.InMatch) { return true; } __result = Nothing(); return false; } [HarmonyPatch(typeof(TextDisplayer), "ShowThenClear")] [HarmonyPrefix] private static bool SkipShowThenClear(ref IEnumerator __result) { if (!VersusMode.InMatch) { return true; } __result = Nothing(); return false; } private static IEnumerator Nothing() { yield break; } } public static class SteamTransport { private class PropertyInfoCache { private readonly PropertyInfo _prop; public PropertyInfoCache() { Type type = AccessTools.TypeByName("SteamManager"); _prop = ((type == null) ? null : AccessTools.Property(type, "Initialized")); if (_prop == null) { Trace.Warn("[steam] SteamManager.Initialized not found"); } } public bool Read() { if (_prop == null) { return false; } object value = _prop.GetValue(null, null); bool flag = default(bool); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } } private const string LobbyKey = "inscryption_mp"; private const string LobbyValue = "1"; private const string LobbyHostKey = "host_name"; private static PropertyInfoCache _initCache; private static CSteamID _lobby; private static CSteamID _peer; private static Callback _cbCreated; private static Callback _cbEntered; private static Callback _cbSession; private static Callback _cbChatUpdate; private static CallResult _crList; private static readonly ConcurrentQueue Inbox = new ConcurrentQueue(); private static readonly StringBuilder RecvBuffer = new StringBuilder(); public static readonly List> Lobbies = new List>(); public static bool Available { get { try { if (_initCache == null) { _initCache = new PropertyInfoCache(); } return _initCache.Read(); } catch (Exception ex) { Trace.Warn("[steam] availability check failed: " + ex.Message); return false; } } } public static bool Active { get; private set; } public static bool Connected { get; private set; } public static bool IsHost { get; private set; } public static string Status { get; private set; } = "idle"; private static void EnsureCallbacks() { if (_cbCreated == null) { _cbCreated = Callback.Create((DispatchDelegate)OnLobbyCreated); _cbEntered = Callback.Create((DispatchDelegate)OnLobbyEntered); _cbSession = Callback.Create((DispatchDelegate)OnSessionRequest); _cbChatUpdate = Callback.Create((DispatchDelegate)OnLobbyChatUpdate); _crList = CallResult.Create((APIDispatchDelegate)OnLobbyList); } } public static void HostLobby() { //IL_004d: Unknown result type (might be due to invalid IL or missing references) if (!Available) { Trace.Warn("[steam] not initialised"); return; } EnsureCallbacks(); Reset(); Active = true; IsHost = true; Status = "creating lobby..."; Trace.Info("[steam] creating lobby"); SteamMatchmaking.CreateLobby((ELobbyType)2, 2); } public static void RefreshLobbies() { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) if (!Available) { Trace.Warn("[steam] not initialised"); return; } EnsureCallbacks(); Lobbies.Clear(); Status = "searching..."; Trace.Info("[steam] requesting lobby list"); SteamMatchmaking.AddRequestLobbyListStringFilter("inscryption_mp", "1", (ELobbyComparison)0); SteamAPICall_t val = SteamMatchmaking.RequestLobbyList(); _crList.Set(val, (APIDispatchDelegate)null); } public unsafe static void JoinLobby(CSteamID lobby) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) if (Available) { EnsureCallbacks(); Reset(); Active = true; IsHost = false; Status = "joining..."; CSteamID val = lobby; Trace.Info("[steam] joining lobby " + ((object)(*(CSteamID*)(&val))/*cast due to .constrained prefix*/).ToString()); SteamMatchmaking.JoinLobby(lobby); } } private unsafe static void OnLobbyCreated(LobbyCreated_t e) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) if ((int)e.m_eResult != 1) { Status = "lobby failed (" + ((object)(*(EResult*)(&e.m_eResult))/*cast due to .constrained prefix*/).ToString() + ")"; Trace.Error("[steam] lobby creation failed: " + ((object)(*(EResult*)(&e.m_eResult))/*cast due to .constrained prefix*/).ToString()); Active = false; } else { _lobby = new CSteamID(e.m_ulSteamIDLobby); SteamMatchmaking.SetLobbyData(_lobby, "inscryption_mp", "1"); SteamMatchmaking.SetLobbyData(_lobby, "host_name", SteamFriends.GetPersonaName()); Status = "waiting for opponent"; Trace.Info("[steam] lobby created - waiting for opponent"); } } private unsafe static void OnLobbyList(LobbyMatchList_t e, bool failed) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) Lobbies.Clear(); if (failed) { Status = "search failed"; return; } for (int i = 0; i < e.m_nLobbiesMatching; i++) { CSteamID lobbyByIndex = SteamMatchmaking.GetLobbyByIndex(i); string value = SteamMatchmaking.GetLobbyData(lobbyByIndex, "host_name"); if (string.IsNullOrEmpty(value)) { value = ((object)(*(CSteamID*)(&lobbyByIndex))/*cast due to .constrained prefix*/).ToString(); } Lobbies.Add(new KeyValuePair(lobbyByIndex, value)); } Status = ((Lobbies.Count == 0) ? "no lobbies found" : (Lobbies.Count + " lobbies")); Trace.Info("[steam] found " + Lobbies.Count + " lobbies"); } private static void OnLobbyEntered(LobbyEnter_t e) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: 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) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) _lobby = new CSteamID(e.m_ulSteamIDLobby); Active = true; CSteamID lobbyOwner = SteamMatchmaking.GetLobbyOwner(_lobby); CSteamID steamID = SteamUser.GetSteamID(); IsHost = lobbyOwner == steamID; TryFindPeer(steamID); if (((CSteamID)(ref _peer)).IsValid()) { Connected = true; Status = "connected to " + SteamFriends.GetFriendPersonaName(_peer); Trace.Info("[steam] " + Status); Net.SendHello(); } else { Status = "waiting for opponent"; Trace.Info("[steam] in lobby, waiting for opponent"); } } private static void TryFindPeer(CSteamID me) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (!((CSteamID)(ref _lobby)).IsValid()) { return; } int numLobbyMembers = SteamMatchmaking.GetNumLobbyMembers(_lobby); for (int i = 0; i < numLobbyMembers; i++) { CSteamID lobbyMemberByIndex = SteamMatchmaking.GetLobbyMemberByIndex(_lobby, i); if (lobbyMemberByIndex != me) { _peer = lobbyMemberByIndex; break; } } } private static void OnLobbyChatUpdate(LobbyChatUpdate_t e) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) CSteamID val = default(CSteamID); ((CSteamID)(ref val))..ctor(e.m_ulSteamIDUserChanged); if ((e.m_rgfChatMemberStateChange & 0xE) != 0 && val == _peer) { Trace.Warn("[steam] opponent left the lobby"); SteamNetworking.CloseP2PSessionWithUser(_peer); _peer = CSteamID.Nil; Connected = false; Status = "opponent disconnected"; } else if ((e.m_rgfChatMemberStateChange & 1) != 0 && val != SteamUser.GetSteamID()) { Trace.Info("[steam] opponent (re)joined the lobby"); _peer = val; Connected = true; Status = "connected to " + SteamFriends.GetFriendPersonaName(_peer); Net.SendHello(); } } private unsafe static void OnSessionRequest(P2PSessionRequest_t e) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: 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) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) CSteamID steamIDRemote = e.m_steamIDRemote; Trace.Info("[steam] accepting P2P session from " + ((object)(*(CSteamID*)(&steamIDRemote))/*cast due to .constrained prefix*/).ToString()); SteamNetworking.AcceptP2PSessionWithUser(e.m_steamIDRemote); if (!((CSteamID)(ref _peer)).IsValid()) { _peer = e.m_steamIDRemote; Connected = true; Status = "connected to " + SteamFriends.GetFriendPersonaName(_peer); } } public static void Send(string msg) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) if (!Connected || !((CSteamID)(ref _peer)).IsValid()) { Trace.Warn("[steam] dropped (no peer): " + msg); return; } byte[] bytes = Encoding.UTF8.GetBytes(msg + "\n"); if (SteamNetworking.SendP2PPacket(_peer, bytes, (uint)bytes.Length, (EP2PSend)2, 0)) { Trace.Info("[steam] -> " + msg); } else { Trace.Error("[steam] send failed: " + msg); } } public static void Poll() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) if (!Active || !Available) { return; } if (!Connected && IsHost && ((CSteamID)(ref _lobby)).IsValid()) { CSteamID steamID = SteamUser.GetSteamID(); TryFindPeer(steamID); if (((CSteamID)(ref _peer)).IsValid()) { Connected = true; Status = "connected to " + SteamFriends.GetFriendPersonaName(_peer); Trace.Info("[steam] opponent joined"); Net.SendHello(); } } uint num = default(uint); uint count = default(uint); CSteamID val = default(CSteamID); while (SteamNetworking.IsP2PPacketAvailable(ref num, 0)) { byte[] array = new byte[num]; if (!SteamNetworking.ReadP2PPacket(array, num, ref count, ref val, 0)) { break; } RecvBuffer.Append(Encoding.UTF8.GetString(array, 0, (int)count)); } string text = RecvBuffer.ToString(); int num2; while ((num2 = text.IndexOf('\n')) >= 0) { string text2 = text.Substring(0, num2).Trim(); text = text.Substring(num2 + 1); if (text2.Length != 0) { Trace.Info("[steam] <- " + text2); if (!Net.CaptureResult(text2)) { Inbox.Enqueue(text2); } } } RecvBuffer.Length = 0; RecvBuffer.Append(text); } public static bool TryDequeue(out string msg) { return Inbox.TryDequeue(out msg); } public static void Reset() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) string result; while (Inbox.TryDequeue(out result)) { } RecvBuffer.Length = 0; Connected = false; _peer = CSteamID.Nil; } public static void Shutdown() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) if (((CSteamID)(ref _peer)).IsValid()) { SteamNetworking.CloseP2PSessionWithUser(_peer); } if (((CSteamID)(ref _lobby)).IsValid() && Available) { SteamMatchmaking.LeaveLobby(_lobby); } _lobby = CSteamID.Nil; Reset(); Active = false; IsHost = false; Status = "idle"; Trace.Info("[steam] shut down"); } } [HarmonyPatch] internal static class Sync { [HarmonyPatch(typeof(PlayableCard), "Sacrifice")] [HarmonyPrefix] private static void OnLocalSacrifice(PlayableCard __instance) { if (!Net.Connected || !VersusMode.InMatch || (Object)(object)__instance == (Object)null) { return; } CardSlot slot = __instance.Slot; if (!((Object)(object)slot == (Object)null) && slot.IsPlayerSlot) { TurnManager instance = Singleton.Instance; if (!((Object)(object)instance == (Object)null) && instance.IsPlayerTurn) { Net.Send(Protocol.Sacrifice(slot.Index)); } } } internal static string[] SnapshotPlayerSlotsPublic() { return SnapshotPlayerSlots(); } private static string[] SnapshotPlayerSlots() { BoardManager instance = Singleton.Instance; List list = (((Object)(object)instance != (Object)null) ? instance.PlayerSlotsCopy : null); int num = list?.Count ?? 4; string[] array = new string[num]; for (int i = 0; i < num; i++) { PlayableCard val = (((Object)(object)list[i] != (Object)null) ? list[i].Card : null); array[i] = (((Object)(object)val != (Object)null && (Object)(object)((Card)val).Info != (Object)null) ? ((Object)((Card)val).Info).name : "-"); } Trace.Info("[sync] board snapshot: " + string.Join("|", array)); return array; } [HarmonyPatch(typeof(BoardManager), "ResolveCardOnBoard")] [HarmonyPrefix] private static void OnLocalCardPlayed(PlayableCard card, CardSlot slot) { if (!Net.Connected || !VersusMode.InMatch || (Object)(object)slot == (Object)null || (Object)(object)card == (Object)null || !slot.IsPlayerSlot) { return; } TurnManager instance = Singleton.Instance; if (!((Object)(object)instance == (Object)null) && instance.IsPlayerTurn) { string text = (((Object)(object)((Card)card).Info != (Object)null) ? ((Object)((Card)card).Info).name : null); if (!string.IsNullOrEmpty(text)) { Net.Send(Protocol.Play(text, slot.Index)); } } } [HarmonyPatch(typeof(TurnManager), "OnCombatBellRang")] [HarmonyPostfix] private static void OnLocalTurnEnded() { if (Net.Connected && VersusMode.InMatch) { Net.Send(Protocol.Board(SnapshotPlayerSlots())); TurnOrder.PassedToPeer(); Net.Send("END"); } } } internal static class TableProps { private static bool _markerHidden; public static void HidePlayerMarker() { PlayerMarker instance = PlayerMarker.Instance; if (!((Object)(object)instance == (Object)null) && !_markerHidden) { ((Component)instance).gameObject.SetActive(false); _markerHidden = true; Trace.Info("[props] hid the player figurine"); } } public static void RestorePlayerMarker() { if (_markerHidden) { PlayerMarker instance = PlayerMarker.Instance; if ((Object)(object)instance != (Object)null) { ((Component)instance).gameObject.SetActive(true); } _markerHidden = false; Trace.Info("[props] restored the player figurine"); } } } internal static class Trace { private static readonly object Gate = new object(); private static string _path; public static void Init() { try { string path = Path.Combine(Paths.BepInExRootPath ?? ".", ""); _path = Path.Combine(path, "mp-trace.log"); File.AppendAllText(_path, $"--- InscryptionMP trace pid={Process.GetCurrentProcess().Id} {DateTime.Now:HH:mm:ss} ---{Environment.NewLine}"); } catch { _path = null; } } public static void Info(string msg) { Plugin.Log.LogInfo((object)msg); Write("INFO ", msg); } public static void Warn(string msg) { Plugin.Log.LogWarning((object)msg); Write("WARN ", msg); } public static void Error(string msg) { Plugin.Log.LogError((object)msg); Write("ERROR", msg); } private static void Write(string level, string msg) { if (_path == null) { return; } try { lock (Gate) { using StreamWriter streamWriter = new StreamWriter(_path, append: true); streamWriter.WriteLine($"{DateTime.Now:HH:mm:ss.fff} [{Process.GetCurrentProcess().Id}] {level} {msg}"); streamWriter.Flush(); } } catch { } } } [HarmonyPatch] internal static class TurnOrder { public static bool IsMyTurn { get; private set; } public static void BeginMatch(bool goFirst) { IsMyTurn = goFirst; Trace.Info("[turn] match begins - " + (goFirst ? "we go first" : "peer goes first")); } public static void PassedToPeer() { IsMyTurn = false; Trace.Info("[turn] passed to peer"); } public static void TakenFromPeer() { IsMyTurn = true; Trace.Info("[turn] taken from peer"); } [HarmonyPatch(typeof(TurnManager), "PlayerTurn")] [HarmonyPrefix] private static bool SkipWhenNotOurTurn(ref IEnumerator __result) { if (!VersusMode.InMatch) { return true; } if (IsMyTurn) { return true; } Trace.Info("[turn] not our turn - skipping player phase"); __result = NoTurn(); return false; } private static IEnumerator NoTurn() { yield break; } } internal static class VersusMode { private const string Act1Scene = "Part1_Cabin"; private static RunState _stashedRun; private static string _stashedScene; public const float ReconnectWindowSeconds = 120f; private static float _suspendedAt; public static bool InMatch { get; private set; } public static bool PendingStart { get; private set; } public static string Blocker { get { if (Net.HandshakeError != null) { return Net.HandshakeError; } if (!Net.Connected) { return "no peer connected"; } if (InMatch) { return null; } if (PendingStart) { return "loading..."; } if (!DeckStore.IsValid) { return $"deck needs {6}-{20} cards"; } return null; } } public static bool LoadedTableForDeck { get; private set; } public static string LastResult { get; private set; } public static bool Suspended { get; private set; } public static float SuspendedSecondsLeft => Mathf.Max(0f, 120f - (Time.realtimeSinceStartup - _suspendedAt)); public static void StartAnywhere(MonoBehaviour host) { StartAnywhere(host, tellPeer: true); } public static void StartAnywhere(MonoBehaviour host, bool tellPeer) { if (!Net.Connected) { Trace.Warn("[versus] no peer connected"); } else { if (InMatch || PendingStart) { return; } if (Net.HandshakeError != null) { Trace.Warn("[versus] refusing to start: " + Net.HandshakeError); return; } if (Net.HandshakeError != null) { Trace.Warn("[versus] refusing to start: " + Net.HandshakeError); return; } if (!DeckStore.IsValid) { Trace.Warn($"[versus] deck has {DeckStore.Deck.Count} cards - needs {6}-{20}"); return; } DeckStore.Save(); if (tellPeer) { Trace.Info("[versus] telling peer to start"); Net.Send("START"); } if ((Object)(object)Singleton.Instance != (Object)null) { Start(host); return; } Trace.Info("[versus] not in gameplay scene - loading Part1_Cabin"); SaveManager.savingDisabled = true; PrepareIsolatedRun(); PendingStart = true; LoadingScreenManager.LoadScene("Part1_Cabin"); } } private static void PrepareIsolatedRun() { try { if (SaveManager.SaveFile == null) { Trace.Info("[versus] no save file - creating one"); SaveManager.CreateNewSaveFile(); } SaveFile saveFile = SaveManager.SaveFile; if (_stashedRun == null) { _stashedRun = saveFile.currentRun; _stashedScene = saveFile.currentScene; } else { Trace.Info("[versus] campaign run already stashed - keeping it"); } saveFile.ResetPart1Run(); saveFile.currentScene = "Part1_Cabin"; if (saveFile.currentRun != null) { saveFile.currentRun.runIntroCompleted = true; } Opponent.debugSkipIntro = true; Trace.Info("[versus] synthesised an isolated Act 1 run for the match"); } catch (Exception ex) { Trace.Error("[versus] could not prepare run state: " + ex.Message); } } private static void RestoreCampaignRun() { try { if (_stashedRun == null) { return; } SaveFile saveFile = SaveManager.SaveFile; if (saveFile != null) { saveFile.currentRun = _stashedRun; if (_stashedScene != null) { saveFile.currentScene = _stashedScene; } Trace.Info("[versus] restored the campaign run"); } } catch (Exception ex) { Trace.Error("[versus] could not restore run state: " + ex.Message); } finally { _stashedRun = null; _stashedScene = null; } } public static void LoadTableOnly() { if ((Object)(object)Singleton.Instance != (Object)null) { Trace.Info("[versus] already in the gameplay scene"); return; } LoadedTableForDeck = true; Trace.Info("[versus] loading the table for deck building"); SaveManager.savingDisabled = true; PrepareIsolatedRun(); LoadingScreenManager.LoadScene("Part1_Cabin"); } public static void TickPendingStart(MonoBehaviour host) { if (PendingStart && !InMatch && !((Object)(object)Singleton.Instance == (Object)null) && !((Object)(object)Singleton.Instance == (Object)null) && !((Object)(object)Singleton.Instance == (Object)null)) { GameFlowManager instance = Singleton.Instance; if (!((Object)(object)instance == (Object)null) && !instance.Transitioning) { PendingStart = false; Trace.Info("[versus] scene ready - starting match"); Start(host); } } } public static bool CanStart() { if (!Net.Connected) { Trace.Warn("[versus] no peer connected"); return false; } if (InMatch) { Trace.Warn("[versus] already in a match"); return false; } if ((Object)(object)Singleton.Instance == (Object)null) { Trace.Warn("[versus] no TurnManager - must be in the Act 1 scene"); return false; } return true; } public static void Start(MonoBehaviour host) { if (CanStart()) { host.StartCoroutine(StartSequence()); } } private static IEnumerator StartSequence() { InMatch = true; LoadedTableForDeck = false; Suspended = false; Net.Reconnecting = true; SaveManager.savingDisabled = true; TurnOrder.BeginMatch(Net.IsHost); Trace.Info("[versus] starting match"); GameFlowManager flow = Singleton.Instance; ViewManager views = Singleton.Instance; if ((Object)(object)flow != (Object)null && (int)flow.CurrentGameState == 2) { Trace.Info("[versus] sitting down at the table"); flow.TransitionFromFirstPerson(true); yield return (object)new WaitForSeconds(1f); } GameMap map = Singleton.Instance; if ((Object)(object)map != (Object)null && (Object)(object)flow != (Object)null && (int)flow.CurrentGameState == 1) { Trace.Info("[versus] hiding map"); views.Controller.SwitchToControlMode((ControlMode)16, false); yield return map.HideMapSequence(); yield return (object)new WaitForSeconds(0.25f); } TableProps.HidePlayerMarker(); views.Controller.SwitchToControlMode((ControlMode)0, false); if ((Object)(object)flow != (Object)null) { flow.CurrentGameState = (GameState)0; } views.SwitchToView((View)1, false, false); yield return (object)new WaitForSeconds(0.35f); EncounterData encounter = new EncounterData { opponentType = (Type)0, aiId = "AI", opponentTurnPlan = new List>(), startConditions = new List(), Difficulty = 0 }; Trace.Info("[versus] handing encounter to TurnManager"); Singleton.Instance.StartGame(encounter); } public static void NoteDisconnected() { if (InMatch && !Suspended) { Suspended = true; _suspendedAt = Time.realtimeSinceStartup; Trace.Warn("[versus] peer lost - match suspended, waiting for them to return"); } } public static void NoteReconnected() { if (Suspended) { Suspended = false; Trace.Info("[versus] peer returned - resuming match"); Net.Send(Protocol.Board(Sync.SnapshotPlayerSlotsPublic())); } } public static void TickSuspension(MonoBehaviour host) { if (Suspended && !(SuspendedSecondsLeft > 0f)) { Trace.Warn("[versus] reconnect window expired"); Suspended = false; Finish(host, playerWon: true, "opponent did not return"); } } public static void Finish(MonoBehaviour host, bool playerWon, string reason) { if (InMatch) { LastResult = (playerWon ? "you won" : "you lost"); Trace.Info("[versus] match over - " + LastResult + " (" + reason + ")"); Net.Send(playerWon ? "OVER LOST" : "OVER WON"); InMatch = false; Suspended = false; Net.Reconnecting = false; PendingStart = false; Match.Reset(); RestoreCampaignRun(); TableProps.RestorePlayerMarker(); MonoBehaviour runner = Plugin.Runner; if ((Object)(object)runner != (Object)null) { runner.StartCoroutine(ReturnToMenu()); } else { MenuController.ReturnToStartScreen(); } } } private static IEnumerator ReturnToMenu() { yield return (object)new WaitForSeconds(1.5f); Trace.Info("[versus] returning to main menu"); MenuController.ReturnToStartScreen(); } public static void LeaveDeckTable() { if (LoadedTableForDeck && !InMatch) { LoadedTableForDeck = false; Trace.Info("[versus] leaving the deck table - back to the title"); RestoreCampaignRun(); TableProps.RestorePlayerMarker(); MenuController.ReturnToStartScreen(); } } public static void Abort(MonoBehaviour host) { if (!InMatch && !PendingStart) { Trace.Info("[versus] abort requested but no match is running"); return; } Trace.Warn("[versus] match aborted by player"); InMatch = false; Suspended = false; Net.Reconnecting = false; PendingStart = false; Match.Reset(); RestoreCampaignRun(); TableProps.RestorePlayerMarker(); MenuController.ReturnToStartScreen(); } } }