Decompiled source of InscryptionOnline v1.0.2

plugins\InscryptionMP.dll

Decompiled 4 hours ago
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<CardInfo> __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<string> _deck;

		private static List<CardInfo> _pool;

		private static string Path
		{
			get
			{
				string path = Paths.ConfigPath ?? ".";
				return System.IO.Path.Combine(path, "inscryptionmp-deck.txt");
			}
		}

		public static List<string> Deck
		{
			get
			{
				if (_deck == null)
				{
					Load();
				}
				return _deck;
			}
		}

		public static bool IsValid => Deck.Count >= 6 && Deck.Count <= 20;

		public static List<CardInfo> Pool
		{
			get
			{
				if (_pool != null)
				{
					return _pool;
				}
				try
				{
					_pool = (from c in ScriptableObjectLoader<CardInfo>.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<CardInfo>();
				}
				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<string>(Starter);
					Trace.Info("[deck] no saved deck - using starter");
				}
			}
			catch (Exception ex)
			{
				Trace.Error("[deck] load failed: " + ex.Message);
				_deck = new List<string>(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<string>(Starter);
			Match.Reset();
		}
	}
	public static class Match
	{
		private static List<CardInfo> _cache;

		public static bool Active => VersusMode.InMatch;

		public static List<CardInfo> Deck
		{
			get
			{
				if (_cache != null)
				{
					return new List<CardInfo>(_cache);
				}
				List<CardInfo> list = new List<CardInfo>();
				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<CardInfo>(_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<GUILayoutOption>());
			GUILayout.Label(text, _section, Array.Empty<GUILayoutOption>());
			GUILayout.FlexibleSpace();
			GUILayout.Label(text2, _small, Array.Empty<GUILayoutOption>());
			GUILayout.EndHorizontal();
			GUILayout.Space(4f);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			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<GUILayoutOption>());
			GUILayout.Label("INSCRYPTION ONLINE", _header, Array.Empty<GUILayoutOption>());
			GUILayout.FlexibleSpace();
			GUILayout.Label("v1.0.2", _small, Array.Empty<GUILayoutOption>());
			GUILayout.EndHorizontal();
			GUILayout.Label(Net.StatusLine, Net.Connected ? _label : _dim, Array.Empty<GUILayoutOption>());
			if (Net.HandshakeError != null)
			{
				GUILayout.Label("INCOMPATIBLE VERSIONS", _section, Array.Empty<GUILayoutOption>());
				GUILayout.Label(Net.HandshakeError, _small, Array.Empty<GUILayoutOption>());
				GUILayout.Label("Both players need the same build of the mod.", _small, Array.Empty<GUILayoutOption>());
			}
			GUILayout.Space(6f);
			GUI.enabled = !VersusMode.InMatch;
			if (GUILayout.Button($"EDIT DECK  ({DeckStore.Deck.Count}/{20})", _button, Array.Empty<GUILayoutOption>()))
			{
				OpenCardView();
			}
			GUI.enabled = true;
			if (VersusMode.InMatch)
			{
				GUILayout.Label("Finish the match to edit your deck.", _small, Array.Empty<GUILayoutOption>());
			}
			else if (!DeckStore.IsValid)
			{
				GUILayout.Label($"Your deck needs {6}-{20} cards.", _small, Array.Empty<GUILayoutOption>());
			}
			if (NativeDeckBuilder.LastError != null)
			{
				GUILayout.Label(NativeDeckBuilder.LastError, _small, Array.Empty<GUILayoutOption>());
			}
			Rule();
			bool enabled = !Net.Connected && !VersusMode.InMatch;
			GUILayout.Label("STEAM", _section, Array.Empty<GUILayoutOption>());
			if (!SteamTransport.Available)
			{
				GUILayout.Label("Steam not detected - use a direct address below.", _small, Array.Empty<GUILayoutOption>());
			}
			else
			{
				GUI.enabled = enabled;
				GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
				if (GUILayout.Button("Host Lobby", _button, Array.Empty<GUILayoutOption>()))
				{
					SteamTransport.HostLobby();
				}
				if (GUILayout.Button("Find Games", _button, Array.Empty<GUILayoutOption>()))
				{
					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<CSteamID, string> lobby in SteamTransport.Lobbies)
					{
						if (GUILayout.Button(lobby.Value, _button, Array.Empty<GUILayoutOption>()))
						{
							SteamTransport.JoinLobby(lobby.Key);
						}
					}
					GUILayout.EndScrollView();
				}
				GUI.enabled = true;
			}
			Rule();
			GUILayout.Label("DIRECT  (LAN / non-Steam)", _section, Array.Empty<GUILayoutOption>());
			GUI.enabled = enabled;
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			Ip = GUILayout.TextField(Ip, 64, _field, Array.Empty<GUILayoutOption>());
			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<GUILayoutOption>());
			if (GUILayout.Button("Host", _button, Array.Empty<GUILayoutOption>()))
			{
				Net.Host(ParsedPort);
			}
			if (GUILayout.Button("Join", _button, Array.Empty<GUILayoutOption>()))
			{
				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<GUILayoutOption>()))
			{
				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<GUILayoutOption>());
			}
			if (VersusMode.Suspended)
			{
				GUILayout.Space(6f);
				GUILayout.Label("OPPONENT DISCONNECTED", _section, Array.Empty<GUILayoutOption>());
				GUILayout.Label($"Holding the match for {Mathf.CeilToInt(VersusMode.SuspendedSecondsLeft)}s.", _small, Array.Empty<GUILayoutOption>());
				GUILayout.Label("They can relaunch and rejoin - nothing is lost.", _small, Array.Empty<GUILayoutOption>());
			}
			if (!Net.Connected && !VersusMode.InMatch)
			{
				GUILayout.Label("Host a lobby, or find one, to play someone.", _small, Array.Empty<GUILayoutOption>());
			}
			else if (Net.Connected && !VersusMode.InMatch && DeckStore.IsValid && Net.HandshakeError == null)
			{
				GUILayout.Label("Either player can start - you both enter together.", _small, Array.Empty<GUILayoutOption>());
			}
			GUILayout.Space(4f);
			GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
			if (VersusMode.InMatch && GUILayout.Button(VersusMode.Suspended ? "Give Up Waiting" : "Abort Match", _button, Array.Empty<GUILayoutOption>()))
			{
				VersusMode.Abort((MonoBehaviour)(object)this);
			}
			if (Net.Running && GUILayout.Button("Disconnect", _button, Array.Empty<GUILayoutOption>()))
			{
				Net.Shutdown();
			}
			GUILayout.EndHorizontal();
			if (VersusMode.LastResult != null && !VersusMode.InMatch)
			{
				GUILayout.Label("Last match: " + VersusMode.LastResult, _label, Array.Empty<GUILayoutOption>());
			}
			GUILayout.FlexibleSpace();
			GUILayout.Label("F7 menu     F8 start match     F12 abort", _small, Array.Empty<GUILayoutOption>());
			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<DeckReviewSequencer>.Instance != (Object)null;

		private static SelectableCardArray Array
		{
			get
			{
				DeckReviewSequencer instance = Singleton<DeckReviewSequencer>.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<GameFlowManager>.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<ViewManager>.Instance;
			GameFlowManager flow = Singleton<GameFlowManager>.Instance;
			ViewLockState prevLock = (ViewLockState)0;
			GameMap map = Singleton<GameMap>.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<CardInfo> cards = BuildList();
				if (cards.Count == 0)
				{
					yield return (object)new WaitUntil((Func<bool>)(() => _refresh || !IsOpen));
					continue;
				}
				SelectableCard picked = null;
				yield return Array.SelectCardFrom(new List<CardInfo>(cards), (CardPile)null, (Action<SelectableCard>)delegate(SelectableCard c)
				{
					picked = c;
				}, (Func<bool>)(() => !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<CardInfo> BuildList()
		{
			List<CardInfo> list = new List<CardInfo>();
			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<string> Inbox = new ConcurrentQueue<string>();

		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<ViewManager>.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<BoardManager>.Instance;
			List<CardSlot> 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<BoardManager>.Instance;
			if ((Object)(object)board == (Object)null)
			{
				yield break;
			}
			List<CardSlot> 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<BoardManager>.Instance;
			if ((Object)(object)board == (Object)null)
			{
				yield break;
			}
			List<CardSlot> 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<NetworkOpponent>();
				((Opponent)networkOpponent).NumLives = ((Opponent)networkOpponent).StartingLives;
				((Opponent)networkOpponent).TurnPlan = new List<List<CardInfo>>();
				((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 <Awake>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<MpMenu>();
			Log.LogInfo((object)"Press F7 for the multiplayer menu.  F8 = start match, F12 = abort.");
			ConfigEntry<bool> val2 = ((BaseUnityPlugin)this).Config.Bind<bool>("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<TurnManager>.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<LobbyCreated_t> _cbCreated;

		private static Callback<LobbyEnter_t> _cbEntered;

		private static Callback<P2PSessionRequest_t> _cbSession;

		private static Callback<LobbyChatUpdate_t> _cbChatUpdate;

		private static CallResult<LobbyMatchList_t> _crList;

		private static readonly ConcurrentQueue<string> Inbox = new ConcurrentQueue<string>();

		private static readonly StringBuilder RecvBuffer = new StringBuilder();

		public static readonly List<KeyValuePair<CSteamID, string>> Lobbies = new List<KeyValuePair<CSteamID, string>>();

		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<LobbyCreated_t>.Create((DispatchDelegate<LobbyCreated_t>)OnLobbyCreated);
				_cbEntered = Callback<LobbyEnter_t>.Create((DispatchDelegate<LobbyEnter_t>)OnLobbyEntered);
				_cbSession = Callback<P2PSessionRequest_t>.Create((DispatchDelegate<P2PSessionRequest_t>)OnSessionRequest);
				_cbChatUpdate = Callback<LobbyChatUpdate_t>.Create((DispatchDelegate<LobbyChatUpdate_t>)OnLobbyChatUpdate);
				_crList = CallResult<LobbyMatchList_t>.Create((APIDispatchDelegate<LobbyMatchList_t>)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<LobbyMatchList_t>)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<CSteamID, string>(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<TurnManager>.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<BoardManager>.Instance;
			List<CardSlot> 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<TurnManager>.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<TurnManager>.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<TurnManager>.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<TurnManager>.Instance == (Object)null) && !((Object)(object)Singleton<BoardManager>.Instance == (Object)null) && !((Object)(object)Singleton<PlayerHand>.Instance == (Object)null))
			{
				GameFlowManager instance = Singleton<GameFlowManager>.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<TurnManager>.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<GameFlowManager>.Instance;
			ViewManager views = Singleton<ViewManager>.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<GameMap>.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<List<CardInfo>>(),
				startConditions = new List<StartCondition>(),
				Difficulty = 0
			};
			Trace.Info("[versus] handing encounter to TurnManager");
			Singleton<TurnManager>.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();
		}
	}
}