Decompiled source of InscryptionOnline v1.5.0

plugins/InscryptionMP.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
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 GBC;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Steamworks;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("InscryptionMP")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.5.0.0")]
[assembly: AssemblyInformationalVersion("1.5.0+2f2221ee16fe0fa587caa372ffa93cbda707147e")]
[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.5.0.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
{
	public static class ActInfo
	{
		public static MatchAct Selected { get; set; } = MatchAct.Act1;

		public static MatchAct Current { get; internal set; } = MatchAct.Act1;

		public static string SceneFor(MatchAct act)
		{
			return act switch
			{
				MatchAct.Act2 => "GBC_CardBattle", 
				MatchAct.Act3 => "Part3_Cabin", 
				_ => "Part1_Cabin", 
			};
		}

		public static bool AcceptsTemple(MatchAct act, CardTemple temple)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Invalid comparison between Unknown and I4
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Invalid comparison between Unknown and I4
			return act switch
			{
				MatchAct.Act2 => true, 
				MatchAct.Act3 => (int)temple == 2, 
				_ => (int)temple == 0, 
			};
		}

		public static bool UsesPixelArt(MatchAct act)
		{
			return act == MatchAct.Act2;
		}

		public static bool HasFlowManager(MatchAct act)
		{
			return act != MatchAct.Act2;
		}

		public static BoardTheme ThemeForTemple(CardTemple temple)
		{
			//IL_0000: 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_0014: Expected I4, but got Unknown
			return (BoardTheme)((temple - 1) switch
			{
				1 => 2, 
				0 => 1, 
				2 => 3, 
				_ => 0, 
			});
		}

		public static bool GrantsEnergy(MatchAct act)
		{
			return act != MatchAct.Act1;
		}

		public static bool GrantsGems(MatchAct act)
		{
			return act == MatchAct.Act2;
		}

		public static CardMetaCategory[] OfferedCategories(MatchAct act)
		{
			return act switch
			{
				MatchAct.Act2 => (CardMetaCategory[])(object)new CardMetaCategory[2]
				{
					(CardMetaCategory)5,
					(CardMetaCategory)4
				}, 
				MatchAct.Act3 => (CardMetaCategory[])(object)new CardMetaCategory[2]
				{
					(CardMetaCategory)2,
					(CardMetaCategory)3
				}, 
				_ => (CardMetaCategory[])(object)new CardMetaCategory[2]
				{
					default(CardMetaCategory),
					(CardMetaCategory)3
				}, 
			};
		}

		public static string[] AlwaysOffered(MatchAct act)
		{
			return act switch
			{
				MatchAct.Act2 => new string[1] { "Squirrel" }, 
				MatchAct.Act3 => new string[1] { "EmptyVessel" }, 
				_ => new string[1] { "Squirrel" }, 
			};
		}

		public static string Name(MatchAct act)
		{
			return act switch
			{
				MatchAct.Act2 => "Act 2", 
				MatchAct.Act3 => "Act 3", 
				_ => "Act 1", 
			};
		}

		public static string DeckSuffix(MatchAct act)
		{
			int num = (int)act;
			return "-act" + num;
		}
	}
	internal static class Autopilot
	{
		public static bool Active { get; private set; }

		public static string Mode { get; private set; }

		public static string Address { get; private set; } = "127.0.0.1";

		public static int Port { get; private set; } = 27333;

		public static MatchAct Act { get; private set; } = MatchAct.Act1;

		public static string Draw { get; private set; }

		public static string RulesPayload { get; private set; }

		public static string Expect { get; private set; }

		public static float ReadyDelay { get; private set; }

		public static bool ShowPanel { get; private set; }

		public static string Leave { get; private set; }

		public static string[] Deck { get; private set; }

		public static int MaxTurns { get; private set; } = 8;

		public static bool QuitWhenDone { get; private set; }

		public static string Hold { get; private set; }

		private static string Env(string name)
		{
			try
			{
				string environmentVariable = Environment.GetEnvironmentVariable(name);
				return string.IsNullOrEmpty(environmentVariable) ? null : environmentVariable.Trim();
			}
			catch
			{
				return null;
			}
		}

		public static void Boot(MonoBehaviour plugin)
		{
			Mode = Env("INSCRYPTIONMP_AUTOPILOT");
			if (Mode != "host" && Mode != "join")
			{
				if (Mode != null)
				{
					Trace.Warn("[auto] unknown mode '" + Mode + "' - autopilot stays off");
				}
				return;
			}
			Active = true;
			Address = Env("INSCRYPTIONMP_AUTOPILOT_ADDR") ?? Address;
			if (int.TryParse(Env("INSCRYPTIONMP_AUTOPILOT_PORT") ?? "", out var result) && result > 0 && result < 65536)
			{
				Port = result;
			}
			if (int.TryParse(Env("INSCRYPTIONMP_AUTOPILOT_ACT") ?? "", out result) && result >= 1 && result <= 3)
			{
				Act = (MatchAct)result;
			}
			if (int.TryParse(Env("INSCRYPTIONMP_AUTOPILOT_TURNS") ?? "", out result) && result > 0)
			{
				MaxTurns = result;
			}
			Draw = Env("INSCRYPTIONMP_AUTOPILOT_DRAW");
			Hold = Env("INSCRYPTIONMP_AUTOPILOT_HOLD");
			RulesPayload = Env("INSCRYPTIONMP_AUTOPILOT_RULES");
			if (float.TryParse(Env("INSCRYPTIONMP_AUTOPILOT_READY_DELAY") ?? "", NumberStyles.Float, CultureInfo.InvariantCulture, out var result2) && result2 > 0f)
			{
				ReadyDelay = result2;
			}
			Expect = Env("INSCRYPTIONMP_AUTOPILOT_EXPECT");
			QuitWhenDone = Env("INSCRYPTIONMP_AUTOPILOT_QUIT") == "1";
			ShowPanel = Env("INSCRYPTIONMP_AUTOPILOT_SHOW_PANEL") == "1";
			Leave = Env("INSCRYPTIONMP_AUTOPILOT_LEAVE");
			string text = Env("INSCRYPTIONMP_AUTOPILOT_DECK");
			if (text != null)
			{
				Deck = text.Split(new char[1] { ',' });
			}
			Trace.Info($"[auto] autopilot on: {Mode} {Address}:{Port}, {ActInfo.Name(Act)}, " + string.Format("draw={0}, turns={1}, quit={2}", Draw ?? "(unset)", MaxTurns, QuitWhenDone));
			Background.ForceOn();
			plugin.StartCoroutine(Run(plugin));
		}

		private static void Report(bool ok, string what)
		{
			if (ok)
			{
				Trace.Info("[auto] PASS " + what);
			}
			else
			{
				Trace.Error("[auto] FAIL " + what);
			}
		}

		private static IEnumerator Run(MonoBehaviour plugin)
		{
			yield return WaitForCards();
			DumpCardNames();
			ActInfo.Selected = Act;
			if (Deck != null)
			{
				InstallDeck();
			}
			CheckPool();
			if (Hold == "menu")
			{
				Trace.Info("[auto] holding on the panel with no opponent");
				MpMenu.OpenFromMenuCard();
				Trace.Info("[auto] ==== RUN OK: holding ====");
				while (true)
				{
					yield return (object)new WaitForSeconds(5f);
				}
			}
			if (Mode == "host")
			{
				string text = RulesPayload;
				if (Draw != null)
				{
					text = ((text == null) ? "" : (text + ";")) + "custom=1;draw=" + Draw;
				}
				if (text != null)
				{
					MatchRules.Adopt(text);
				}
				Net.Host(Port);
			}
			else
			{
				Net.Join(Address, Port);
			}
			yield return WaitUntil(() => Net.Connected, 90f, "connect to the peer");
			if (!Net.Connected)
			{
				Finish(ok: false, "no connection");
				yield break;
			}
			Report(ok: true, "connected");
			Lobby.ChooseAct(Act);
			if (ShowPanel)
			{
				MpMenu.OpenFromMenuCard();
			}
			yield return WaitUntil(() => Lobby.CanReady, 60f, "the lobby to be startable");
			if (!Lobby.CanReady)
			{
				Trace.Error("[auto] lobby blocked: " + (Lobby.Blocker ?? "(no reason given)"));
				Finish(ok: false, "lobby never became startable");
				yield break;
			}
			Trace.Info("[auto] rules in effect: " + MatchRules.Effective.Encode());
			if (Hold != null)
			{
				yield return HoldStill(plugin);
				yield break;
			}
			yield return Commit(plugin);
			yield return WaitUntil(() => VersusMode.InMatch, 180f, "the match to start");
			if (!VersusMode.InMatch)
			{
				Finish(ok: false, "match never started");
				yield break;
			}
			Report(ok: true, "match started on " + ActInfo.Name(ActInfo.Current) + " with rules " + MatchRules.Live.Encode());
			TurnManager instance = Singleton<TurnManager>.Instance;
			string text2 = $"[auto] at match start: game turn {((instance != null) ? new int?(instance.TurnNumber) : ((int?)null))}, ";
			TurnManager instance2 = Singleton<TurnManager>.Instance;
			Trace.Info(text2 + $"setup={((instance2 != null) ? new bool?(instance2.IsSetupPhase) : ((bool?)null))}");
			CheckRules();
			yield return PlayMatch(plugin);
			CheckTalking();
			yield return CheckLeaving();
			Finish(ok: true, "run complete");
		}

		private static IEnumerator HoldStill(MonoBehaviour plugin)
		{
			if (Hold == "deck" || Hold == "reorder")
			{
				Trace.Info("[auto] holding on the deck table");
				MpMenu.OpenDeckTable();
				yield return WaitUntil(() => NativeDeckBuilder.IsOpen, 120f, "the card table");
				yield return (object)new WaitForSeconds(1.5f);
				NativeDeckBuilder.ToggleMode();
				yield return (object)new WaitForSeconds(1.5f);
				NativeDeckBuilder.SelectAt(2);
				Trace.Info("[auto] deck table: card 3 selected");
				if (Hold == "reorder")
				{
					yield return CheckReordering();
				}
			}
			else if (Hold == "rules")
			{
				Trace.Info("[auto] holding on the rules screen");
				MpMenu.OpenRules();
			}
			else if (Hold == "chat")
			{
				Trace.Info("[auto] holding with the chat box open and focused");
				MpMenu.OpenChat();
			}
			else
			{
				Trace.Info("[auto] holding on the lobby panel");
				MpMenu.OpenFromMenuCard();
			}
			Trace.Info("[auto] ==== RUN OK: holding ====");
			while (true)
			{
				yield return (object)new WaitForSeconds(5f);
			}
		}

		private static IEnumerator CheckReordering()
		{
			string card = NativeDeckBuilder.SelectedCardName;
			Report(NativeDeckBuilder.SelectedEntry == 2, "'" + card + "' starts third");
			NativeDeckBuilder.MoveSelected(-1);
			yield return (object)new WaitForSeconds(0.4f);
			Report(NativeDeckBuilder.SelectedEntry == 1 && NativeDeckBuilder.SelectedCardName == card, "'" + card + "' moved earlier and stayed chosen");
			Trace.Info("[auto] deck now: " + string.Join(", ", DeckStore.Deck.ToArray()));
			NativeDeckBuilder.MoveSelected(-1);
			yield return (object)new WaitForSeconds(0.4f);
			Report(NativeDeckBuilder.SelectedEntry == 0, "'" + card + "' is now first");
			NativeDeckBuilder.MoveSelected(-1);
			yield return (object)new WaitForSeconds(0.4f);
			Report(NativeDeckBuilder.SelectedEntry == 0, "the first card cannot move earlier");
			Report(!NativeDeckBuilder.CanMoveSelected(-1), "and the button says so");
			NativeDeckBuilder.MoveSelected(1);
			yield return (object)new WaitForSeconds(0.4f);
			Report(NativeDeckBuilder.SelectedEntry == 1, "'" + card + "' moved back to second");
			Trace.Info("[auto] deck now: " + string.Join(", ", DeckStore.Deck.ToArray()));
			NativeDeckBuilder.SelectAt(DeckStore.Deck.Count - 1);
			yield return (object)new WaitForSeconds(0.4f);
			Report(!NativeDeckBuilder.CanMoveSelected(1), "the last card cannot move later");
			DeckStore.Load();
			Trace.Info("[auto] deck on disk: " + string.Join(", ", DeckStore.Deck.ToArray()));
			Report(DeckStore.Deck.Count > 0 && DeckStore.BaseName(DeckStore.Deck[1]) == card, "the new order was saved");
		}

		private static IEnumerator Commit(MonoBehaviour plugin)
		{
			if (ReadyDelay > 0f)
			{
				Trace.Info($"[auto] sitting in the lobby for {ReadyDelay}s before committing");
				yield return (object)new WaitForSeconds(ReadyDelay);
			}
			float deadline = Time.realtimeSinceStartup + 120f;
			while (!VersusMode.InMatch && !VersusMode.PendingStart && Time.realtimeSinceStartup < deadline)
			{
				if (Lobby.Negotiating)
				{
					if (!Lobby.MyReady && Lobby.CanReady)
					{
						Lobby.ToggleReady();
					}
				}
				else if (Lobby.CanReady)
				{
					Trace.Info("[auto] peer can't vote - starting the old way");
					VersusMode.StartAnywhere(plugin);
				}
				yield return (object)new WaitForSeconds(1f);
			}
		}

		private static void Finish(bool ok, string why)
		{
			Trace.Info("[auto] ==== RUN " + (ok ? "OK" : "FAILED") + ": " + why + " ====");
			if (QuitWhenDone)
			{
				Trace.Info("[auto] quitting");
				Application.Quit();
			}
		}

		private static IEnumerator WaitForCards()
		{
			float deadline = Time.realtimeSinceStartup + 120f;
			while (Time.realtimeSinceStartup < deadline)
			{
				List<CardInfo> allData = ScriptableObjectLoader<CardInfo>.AllData;
				if (allData != null && allData.Count > 0)
				{
					Trace.Info($"[auto] card database ready ({allData.Count} cards)");
					yield return (object)new WaitForSeconds(2f);
					yield break;
				}
				yield return (object)new WaitForSeconds(0.5f);
			}
			Trace.Error("[auto] card database never loaded");
		}

		private static void DumpCardNames()
		{
			List<CardInfo> allData = ScriptableObjectLoader<CardInfo>.AllData;
			if (allData == null)
			{
				return;
			}
			List<string> list = new List<string>();
			foreach (CardInfo item in allData)
			{
				if ((Object)(object)item != (Object)null)
				{
					list.Add(((Object)item).name);
				}
			}
			list.Sort(StringComparer.OrdinalIgnoreCase);
			for (int i = 0; i < list.Count; i += 25)
			{
				Trace.Info($"[auto] cards {i}: " + string.Join(", ", list.GetRange(i, Math.Min(25, list.Count - i)).ToArray()));
			}
		}

		private static void CheckRules()
		{
			Rules live = MatchRules.Live;
			if (Expect != null)
			{
				Rules other = Rules.Decode(Expect);
				bool num = live.SameAs(other);
				Report(num, num ? "the match locked the rules that were asked for" : ("rules are " + live.Encode() + ", wanted " + other.Encode()));
			}
			bool flag = (Net.IsHost ? (!live.GuestFirst) : live.GuestFirst);
			Report(TurnOrder.IsMyTurn == flag, $"the right player has the first turn (ours={TurnOrder.IsMyTurn}, rule says {flag})");
		}

		private static IEnumerator CheckLeaving()
		{
			if (!(Leave != "game"))
			{
				if (!VersusMode.InMatch)
				{
					Trace.Info("[auto] match already over - nothing to walk out of");
					yield break;
				}
				Trace.Info("[auto] leaving the way the pause menu does");
				MenuController.ReturnToStartScreen();
				yield return (object)new WaitForSeconds(2.5f);
				Report(!VersusMode.InMatch, "the match ended when the game left the battle");
			}
		}

		private static void CheckTalking()
		{
			int num = 0;
			foreach (Chat.Line item in Chat.All)
			{
				if (!item.Mine && !item.System)
				{
					num++;
				}
			}
			Report(num > 0, $"heard {num} line(s) from the opponent");
		}

		private static void CheckPool()
		{
			List<CardInfo> pool = DeckStore.Pool;
			Trace.Info($"[auto] {ActInfo.Name(Act)} pool holds {pool.Count} cards");
			PixelArtCheck.AuditPool();
			string[] array = ActInfo.AlwaysOffered(Act);
			foreach (string name in array)
			{
				if (pool.Exists((CardInfo c) => (Object)(object)c != (Object)null && ((Object)c).name == name))
				{
					Report(ok: true, "'" + name + "' is offered in the " + ActInfo.Name(Act) + " pool");
				}
				else
				{
					Report(ok: false, "'" + name + "' is missing from the " + ActInfo.Name(Act) + " pool");
				}
			}
		}

		private static void InstallDeck()
		{
			DeckStore.BackUpOnce();
			if (Deck.Length == 1 && Deck[0].Trim() == "@pool")
			{
				List<string> list = new List<string>();
				foreach (CardInfo item in DeckStore.Pool)
				{
					if (list.Count >= 10)
					{
						break;
					}
					list.Add(((Object)item).name);
				}
				DeckStore.Deck.Clear();
				DeckStore.Deck.AddRange(list);
				Match.Reset();
				DeckStore.Save();
				Trace.Info("[auto] deck taken from the " + ActInfo.Name(Act) + " pool: " + string.Join(", ", list.ToArray()));
				return;
			}
			List<string> list2 = new List<string>();
			string[] deck = Deck;
			for (int i = 0; i < deck.Length; i++)
			{
				string text = deck[i].Trim();
				if (text.Length != 0)
				{
					CardInfo val = Cards.ByName(DeckStore.BaseName(text));
					if ((Object)(object)val == (Object)null)
					{
						Report(ok: false, "deck asks for '" + text + "', which is not a card");
					}
					else if (!DeckStore.CanUseInDeck(val, Act))
					{
						Report(ok: false, "'" + text + "' is not usable in " + ActInfo.Name(Act));
					}
					else
					{
						list2.Add(text);
					}
				}
			}
			DeckStore.Deck.Clear();
			DeckStore.Deck.AddRange(list2);
			Match.Reset();
			DeckStore.Save();
			Trace.Info("[auto] deck installed for " + ActInfo.Name(Act) + ": " + string.Join(", ", list2.ToArray()));
		}

		private static IEnumerator WaitUntil(Func<bool> done, float seconds, string what)
		{
			float deadline = Time.realtimeSinceStartup + seconds;
			float chirp = 0f;
			while (!done() && Time.realtimeSinceStartup < deadline)
			{
				if (Time.realtimeSinceStartup > chirp)
				{
					chirp = Time.realtimeSinceStartup + 5f;
					Trace.Info("[auto] waiting for " + what + "... " + $"(connected={Net.Connected}, ready={Lobby.ReadyCount}/2, " + $"match={VersusMode.InMatch}, pending={VersusMode.PendingStart})");
				}
				yield return (object)new WaitForSeconds(0.25f);
			}
			if (!done())
			{
				Trace.Error("[auto] timed out waiting for " + what);
			}
		}

		private static IEnumerator PlayMatch(MonoBehaviour plugin)
		{
			int taken = 0;
			while (VersusMode.InMatch && taken < MaxTurns)
			{
				TurnManager instance = Singleton<TurnManager>.Instance;
				PlayerHand instance2 = Singleton<PlayerHand>.Instance;
				if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null || !instance.IsPlayerMainPhase || instance2.PlayingLocked || !TurnOrder.IsMyTurn)
				{
					yield return (object)new WaitForSeconds(0.25f);
					continue;
				}
				yield return (object)new WaitForSeconds(0.6f);
				if (!VersusMode.InMatch)
				{
					break;
				}
				taken++;
				Trace.Info($"[auto] turn {taken} (game turn {Singleton<TurnManager>.Instance.TurnNumber}): " + "hand is " + HandNames() + ", " + Scales());
				if (taken == 1)
				{
					if (Singleton<TurnManager>.Instance.TurnNumber == 1)
					{
						int count = Singleton<PlayerHand>.Instance.CardsInHand.Count;
						int startingHand = MatchRules.Live.StartingHand;
						Report(count == startingHand, $"opening hand is {count}, rule says {startingHand}");
					}
					Chat.Say("turn 1 from " + (Net.IsHost ? "the host" : "the guest"));
					Chat.Emote(0);
				}
				yield return PlayWhatWeCan();
				if (!VersusMode.InMatch)
				{
					break;
				}
				Trace.Info($"[auto] turn {taken}: ringing the bell");
				Singleton<TurnManager>.Instance.OnCombatBellRang();
				yield return WaitUntil(() => !TurnOrder.IsMyTurn || !VersusMode.InMatch, 120f, "the turn to pass to the peer");
			}
			Trace.Info(string.Format("[auto] played {0} turn(s); match {1}", taken, VersusMode.InMatch ? "still running" : "over") + ((VersusMode.LastResult == null) ? "" : (" - " + VersusMode.LastResult)));
		}

		private static string Scales()
		{
			LifeManager instance = Singleton<LifeManager>.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return "no scales";
			}
			return $"balance {instance.Balance}, {instance.DamageUntilPlayerWin} to win " + $"(post at {MatchRules.Live.DamageToWin})";
		}

		private static string HandNames()
		{
			PlayerHand instance = Singleton<PlayerHand>.Instance;
			if ((Object)(object)instance == (Object)null || instance.CardsInHand == null || instance.CardsInHand.Count == 0)
			{
				return "(empty)";
			}
			List<string> list = new List<string>();
			foreach (PlayableCard item in instance.CardsInHand)
			{
				list.Add(((Object)(object)item == (Object)null || (Object)(object)((Card)item).Info == (Object)null) ? "?" : ((Object)((Card)item).Info).name);
			}
			return string.Join(", ", list.ToArray());
		}

		private static IEnumerator PlayWhatWeCan()
		{
			BoardManager instance = Singleton<BoardManager>.Instance;
			PlayerHand hand = Singleton<PlayerHand>.Instance;
			ResourcesManager res = Singleton<ResourcesManager>.Instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)hand == (Object)null)
			{
				yield break;
			}
			foreach (CardSlot slot in instance.PlayerSlotsCopy)
			{
				if ((Object)(object)slot == (Object)null || (Object)(object)slot.Card != (Object)null)
				{
					continue;
				}
				PlayableCard pick = null;
				foreach (PlayableCard item in new List<PlayableCard>(hand.CardsInHand))
				{
					if (!((Object)(object)item == (Object)null) && !((Object)(object)((Card)item).Info == (Object)null) && ((Card)item).Info.BloodCost <= 0 && (((Card)item).Info.GemsCost == null || ((Card)item).Info.GemsCost.Count <= 0) && (!((Object)(object)res != (Object)null) || ((Card)item).Info.BonesCost <= res.PlayerBones) && (!((Object)(object)res != (Object)null) || item.EnergyCost <= res.PlayerEnergy))
					{
						pick = item;
						break;
					}
				}
				if ((Object)(object)pick == (Object)null)
				{
					break;
				}
				if ((Object)(object)res != (Object)null && ((Card)pick).Info.BonesCost > 0)
				{
					yield return res.SpendBones(((Card)pick).Info.BonesCost);
				}
				if ((Object)(object)res != (Object)null && pick.EnergyCost > 0)
				{
					yield return res.SpendEnergy(pick.EnergyCost);
				}
				Trace.Info($"[auto] playing {((Object)((Card)pick).Info).name} into slot {slot.Index}");
				yield return hand.PlayCardOnSlot(pick, slot);
				yield return (object)new WaitForSeconds(0.35f);
			}
		}
	}
	[HarmonyPatch]
	internal static class AutopilotDraw
	{
		[HarmonyPatch(typeof(CardDrawPiles3D), "ChooseDraw", new Type[] { typeof(int) })]
		[HarmonyPrefix]
		private static bool DrawWithoutClicking(CardDrawPiles3D __instance, ref IEnumerator __result)
		{
			if (!Autopilot.Active || !VersusMode.InMatch)
			{
				return true;
			}
			__result = Take(__instance);
			return false;
		}

		private static IEnumerator Take(CardDrawPiles3D piles)
		{
			if ((Object)(object)((CardDrawPiles)piles).Deck != (Object)null && ((CardDrawPiles)piles).Deck.CardsInDeck > 0)
			{
				Trace.Info("[auto] draw phase: taking from the deck");
				yield return ((CardDrawPiles)piles).DrawCardFromDeck((CardInfo)null, (Action<PlayableCard>)null);
			}
			else
			{
				Trace.Info("[auto] draw phase: deck is out, taking from the side pile");
				yield return piles.DrawFromSidePile();
			}
		}
	}
	internal static class Background
	{
		private static bool _known;

		private static bool _original;

		private static bool _wanted;

		private static bool _pinned;

		private static float _nextBeat;

		public static void Tick()
		{
			Remember();
			bool flag = _pinned || Net.Running || Net.Connected || VersusMode.InMatch;
			if (flag && !Application.runInBackground)
			{
				Application.runInBackground = true;
			}
			if (flag != _wanted)
			{
				_wanted = flag;
				if (!flag)
				{
					Application.runInBackground = _original;
				}
				Trace.Info(flag ? "[focus] keeping the game awake while there is an opponent to keep up with" : $"[focus] no opponent - back to the game's own setting ({_original})");
			}
			Heartbeat();
		}

		private static void Remember()
		{
			if (!_known)
			{
				_original = Application.runInBackground;
				_known = true;
				Trace.Info($"[focus] the game runs in the background by default: {_original}");
			}
		}

		public static void ForceOn()
		{
			Remember();
			_pinned = true;
			_wanted = true;
			Application.runInBackground = true;
			Trace.Info("[focus] pinned awake for the autopilot");
		}

		private static void Heartbeat()
		{
			if (Autopilot.Active && !(Time.realtimeSinceStartup < _nextBeat))
			{
				_nextBeat = Time.realtimeSinceStartup + 5f;
				Trace.Info($"[focus] frame {Time.frameCount}, focused={Application.isFocused}, " + $"background={Application.runInBackground}");
			}
		}
	}
	[HarmonyPatch]
	internal static class CardRenderFallbacks
	{
		private static Vector2 _referenceSize = Vector2.zero;

		private static readonly Dictionary<Sprite, Sprite> Rescaled = new Dictionary<Sprite, Sprite>();

		private static readonly Dictionary<Sprite, Texture> BakedIcons = new Dictionary<Sprite, Texture>();

		[HarmonyPatch(typeof(Card), "SetInfo")]
		[HarmonyPrefix]
		private static void SubstitutePixelPortrait(CardInfo info)
		{
			//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_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_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_0041: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)info == (Object)null)
			{
				return;
			}
			if ((Object)(object)info.portraitTex != (Object)null)
			{
				if (_referenceSize == Vector2.zero)
				{
					Bounds bounds = info.portraitTex.bounds;
					_referenceSize = Vector2.op_Implicit(((Bounds)(ref bounds)).size);
				}
				if (VersusMode.VersusContext)
				{
					ClampOversizedPortrait(info);
				}
			}
			else if (ActInfo.UsesPixelArt(ActInfo.Selected) && !((Object)(object)info.pixelPortrait == (Object)null))
			{
				Sprite val = ScaleToPortraitSlot(info.pixelPortrait);
				if ((Object)(object)val != (Object)null)
				{
					info.portraitTex = val;
				}
			}
		}

		private static void ClampOversizedPortrait(CardInfo info)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: 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_0020: 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_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: 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_007d: 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)
			if (_referenceSize == Vector2.zero)
			{
				return;
			}
			Bounds bounds = info.portraitTex.bounds;
			Vector2 val = Vector2.op_Implicit(((Bounds)(ref bounds)).size);
			if (!(val.x <= _referenceSize.x * 1.05f) || !(val.y <= _referenceSize.y * 1.05f))
			{
				Sprite val2 = ScaleToPortraitSlot(info.portraitTex);
				if (!((Object)(object)val2 == (Object)null))
				{
					Trace.Info($"[art] '{((Object)info).name}' portrait {val.x:0.00}x{val.y:0.00} overflows the " + $"{_referenceSize.x:0.00}x{_referenceSize.y:0.00} slot - shrunk to fit");
					info.portraitTex = val2;
				}
			}
		}

		private static void LearnReferenceSize()
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: 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)
			//IL_005e: 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)
			string[] array = new string[4] { "Squirrel", "Stoat", "Wolf", "Bullfrog" };
			foreach (string text in array)
			{
				CardInfo val = Cards.ByName(text);
				if (!((Object)(object)val?.portraitTex == (Object)null))
				{
					Bounds bounds = val.portraitTex.bounds;
					_referenceSize = Vector2.op_Implicit(((Bounds)(ref bounds)).size);
					Trace.Info($"[art] portrait slot measured from {text}: {_referenceSize.x:0.00}x{_referenceSize.y:0.00}");
					break;
				}
			}
		}

		private static Sprite ScaleToPortraitSlot(Sprite source)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: 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_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			if (Rescaled.TryGetValue(source, out var value))
			{
				return value;
			}
			if (_referenceSize == Vector2.zero)
			{
				LearnReferenceSize();
			}
			if (_referenceSize == Vector2.zero)
			{
				Trace.Warn("[art] no reference portrait yet - leaving '" + ((Object)source).name + "' blank");
				return null;
			}
			try
			{
				Rect rect = source.rect;
				float num = Mathf.Max(((Rect)(ref rect)).width / _referenceSize.x, ((Rect)(ref rect)).height / _referenceSize.y);
				if (num <= 0f)
				{
					return source;
				}
				Sprite val = Sprite.Create(source.texture, rect, new Vector2(0.5f, 0.5f), num);
				((Object)val).name = ((Object)source).name + "_mp_scaled";
				Rescaled[source] = val;
				string text = $"[art] scaled '{((Object)source).name}' {((Rect)(ref rect)).width}x{((Rect)(ref rect)).height} -> ";
				object[] array = new object[4];
				Bounds bounds = val.bounds;
				array[0] = ((Bounds)(ref bounds)).size.x;
				bounds = val.bounds;
				array[1] = ((Bounds)(ref bounds)).size.y;
				array[2] = _referenceSize.x;
				array[3] = _referenceSize.y;
				Trace.Info(text + string.Format("{0:0.00}x{1:0.00} (slot {2:0.00}x{3:0.00})", array));
				return val;
			}
			catch (Exception ex)
			{
				Trace.Warn("[art] could not rescale '" + ((Object)source).name + "': " + ex.Message);
				return source;
			}
		}

		[HarmonyPatch(typeof(AbilityIconInteractable), "LoadIcon")]
		[HarmonyPostfix]
		private static void SubstitutePixelSigil(AbilityInfo ability, ref Texture __result)
		{
			if (!((Object)(object)__result != (Object)null) && !((Object)(object)ability == (Object)null) && VersusMode.VersusContext && !((Object)(object)ability.pixelIcon == (Object)null))
			{
				Texture val = BakeSprite(ability.pixelIcon);
				if ((Object)(object)val != (Object)null)
				{
					__result = val;
				}
			}
		}

		private static Texture BakeSprite(Sprite source)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Expected O, but got Unknown
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			if (BakedIcons.TryGetValue(source, out var value))
			{
				return value;
			}
			RenderTexture val = null;
			RenderTexture active = RenderTexture.active;
			try
			{
				Rect rect = source.rect;
				int num = Mathf.Max(1, (int)((Rect)(ref rect)).width);
				int num2 = Mathf.Max(1, (int)((Rect)(ref rect)).height);
				val = RenderTexture.GetTemporary(num, num2, 0);
				Graphics.Blit((Texture)(object)source.texture, val);
				RenderTexture.active = val;
				Texture2D texture = source.texture;
				Rect val2 = default(Rect);
				((Rect)(ref val2))..ctor(((Rect)(ref rect)).x / (float)((Texture)texture).width * (float)num, ((Rect)(ref rect)).y / (float)((Texture)texture).height * (float)num2, ((Rect)(ref rect)).width / (float)((Texture)texture).width * (float)num, ((Rect)(ref rect)).height / (float)((Texture)texture).height * (float)num2);
				Texture2D val3 = new Texture2D(Mathf.Max(1, (int)((Rect)(ref val2)).width), Mathf.Max(1, (int)((Rect)(ref val2)).height), (TextureFormat)4, false);
				val3.ReadPixels(val2, 0, 0);
				val3.Apply();
				((Texture)val3).filterMode = (FilterMode)0;
				((Object)val3).name = ((Object)source).name + "_mp_baked";
				BakedIcons[source] = (Texture)(object)val3;
				return (Texture)(object)val3;
			}
			catch (Exception ex)
			{
				Trace.Warn("[art] could not bake sigil '" + ((Object)source).name + "': " + ex.Message);
				BakedIcons[source] = null;
				return null;
			}
			finally
			{
				RenderTexture.active = active;
				if ((Object)(object)val != (Object)null)
				{
					RenderTexture.ReleaseTemporary(val);
				}
			}
		}

		[HarmonyPatch(typeof(CardDisplayer), "GetCostSpriteForCard")]
		[HarmonyPrefix]
		private static bool ClampCostSprite(CardDisplayer __instance, CardInfo card, ref Sprite __result)
		{
			if ((Object)(object)card == (Object)null)
			{
				return true;
			}
			if (card.BonesCost > 0)
			{
				if (!(AccessTools.Field(typeof(CardDisplayer), "boneCostTextures")?.GetValue(__instance) is Sprite[] array) || array.Length == 0)
				{
					return true;
				}
				if (card.BonesCost - 1 < array.Length)
				{
					return true;
				}
				__result = array[^1];
				return false;
			}
			if (card.GemsCost != null && card.GemsCost.Count > 0)
			{
				return true;
			}
			if (card.EnergyCost > 0)
			{
				return true;
			}
			if (!(AccessTools.Field(typeof(CardDisplayer), "costTextures")?.GetValue(__instance) is Sprite[] array2) || array2.Length == 0)
			{
				return true;
			}
			if (card.BloodCost < array2.Length)
			{
				return true;
			}
			__result = array2[^1];
			return false;
		}
	}
	internal static class Cards
	{
		public static CardInfo ByName(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return null;
			}
			try
			{
				List<CardInfo> allData = ScriptableObjectLoader<CardInfo>.AllData;
				if (allData == null)
				{
					return null;
				}
				CardInfo val = allData.Find((CardInfo x) => (Object)(object)x != (Object)null && ((Object)x).name == name);
				return (CardInfo)(((Object)(object)val == (Object)null) ? null : /*isinst with value type is only supported in some contexts*/);
			}
			catch (Exception ex)
			{
				Trace.Warn("[cards] lookup of '" + name + "' failed: " + ex.Message);
				return null;
			}
		}

		public static bool Exists(string name)
		{
			return (Object)(object)ByName(name) != (Object)null;
		}
	}
	internal static class Chat
	{
		internal struct Line
		{
			public string Who;

			public string Text;

			public float At;

			public bool Mine;

			public bool System;
		}

		public static readonly string[] Emotes = new string[6] { "Good luck!", "Nice play.", "One second...", "Sorry!", "Your turn.", "Good game." };

		public const int MaxLength = 120;

		private const int Keep = 60;

		private static readonly List<Line> Lines = new List<Line>();

		private static readonly ConcurrentQueue<Line> Waiting = new ConcurrentQueue<Line>();

		public static IList<Line> All => Lines;

		public static int Count => Lines.Count;

		public static Line? Latest
		{
			get
			{
				if (Lines.Count != 0)
				{
					return Lines[Lines.Count - 1];
				}
				return null;
			}
		}

		private static float Now => Time.realtimeSinceStartup;

		public static void Pump()
		{
			Line result;
			while (Waiting.TryDequeue(out result))
			{
				Add(result);
			}
		}

		private static void Add(Line line)
		{
			Lines.Add(line);
			if (Lines.Count > 60)
			{
				Lines.RemoveRange(0, Lines.Count - 60);
			}
		}

		internal static string Clean(string text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder(text.Length);
			foreach (char c in text)
			{
				if (c == '\n' || c == '\r' || c == '\t')
				{
					stringBuilder.Append(' ');
				}
				else if (!char.IsControl(c) && !char.IsSurrogate(c))
				{
					UnicodeCategory unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c);
					if (unicodeCategory != UnicodeCategory.Format && unicodeCategory != UnicodeCategory.PrivateUse && unicodeCategory != UnicodeCategory.OtherNotAssigned)
					{
						stringBuilder.Append(c);
					}
				}
			}
			string text2 = stringBuilder.ToString().Trim();
			if (text2.Length <= 120)
			{
				return text2;
			}
			return text2.Substring(0, 120);
		}

		public static bool Say(string text)
		{
			string text2 = Clean(text);
			if (text2.Length == 0)
			{
				return false;
			}
			Add(new Line
			{
				Who = Player.LocalName,
				Text = text2,
				At = Now,
				Mine = true
			});
			Net.Send(Protocol.ChatLine(text2));
			return true;
		}

		public static bool Emote(int index)
		{
			if (index < 0 || index >= Emotes.Length)
			{
				return false;
			}
			if (!Net.Connected)
			{
				return false;
			}
			Add(new Line
			{
				Who = Player.LocalName,
				Text = Emotes[index],
				At = Now,
				Mine = true
			});
			Net.Send(Protocol.EmoteLine(index));
			return true;
		}

		public static void NoteIncoming(string text)
		{
			string text2 = Clean(text);
			if (text2.Length != 0)
			{
				Waiting.Enqueue(new Line
				{
					Who = Player.PeerName,
					Text = text2,
					At = Now
				});
				Trace.Info("[chat] " + Player.PeerName + ": " + text2);
			}
		}

		public static void NoteEmote(int index)
		{
			if (index < 0 || index >= Emotes.Length)
			{
				Trace.Warn($"[chat] peer sent emote {index}, which we don't have");
			}
			else
			{
				NoteIncoming(Emotes[index]);
			}
		}

		public static void Note(string text)
		{
			Add(new Line
			{
				Who = "",
				Text = text,
				At = Now,
				System = true
			});
		}

		public static Line? Fresh(float seconds)
		{
			Line? latest = Latest;
			if (!latest.HasValue || latest.Value.System)
			{
				return null;
			}
			if (!(Now - latest.Value.At <= seconds))
			{
				return null;
			}
			return latest;
		}

		public static void Clear()
		{
			Lines.Clear();
			Line result;
			while (Waiting.TryDequeue(out result))
			{
			}
		}
	}
	internal static class Player
	{
		private static string _peer;

		private static ConfigEntry<string> _chosen;

		private const int MaxName = 24;

		public static string LocalName
		{
			get
			{
				try
				{
					string name = ((_chosen == null) ? null : Chat.Clean(_chosen.Value));
					if (Readable(name))
					{
						return Trim(name);
					}
					if (SteamTransport.Available)
					{
						string name2 = Chat.Clean(SteamFriends.GetPersonaName());
						if (Readable(name2))
						{
							return Trim(name2);
						}
					}
				}
				catch (Exception ex)
				{
					Trace.Warn("[player] could not read a name: " + ex.Message);
				}
				if (!Net.Running && !Net.Connected)
				{
					return "Player";
				}
				if (!Net.IsHost)
				{
					return "Guest";
				}
				return "Host";
			}
		}

		public static string PeerName
		{
			get
			{
				if (!string.IsNullOrEmpty(_peer))
				{
					return _peer;
				}
				return "Opponent";
			}
		}

		public static bool PeerNamed => !string.IsNullOrEmpty(_peer);

		public static void Bind(ConfigEntry<string> entry)
		{
			_chosen = entry;
		}

		private static string Trim(string name)
		{
			if (name.Length <= 24)
			{
				return name;
			}
			return name.Substring(0, 24);
		}

		private static bool Readable(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return false;
			}
			for (int i = 0; i < name.Length; i++)
			{
				if (char.IsLetterOrDigit(name[i]))
				{
					return true;
				}
			}
			return false;
		}

		public static void NotePeerName(string name)
		{
			string name2 = Chat.Clean(name);
			if (!Readable(name2))
			{
				Trace.Warn("[player] the opponent's name has nothing readable in it - keeping '" + PeerName + "'");
				return;
			}
			_peer = Trim(name2);
			Trace.Info("[player] opponent is " + _peer);
		}

		public static void ForgetPeer()
		{
			_peer = null;
		}
	}
	[HarmonyPatch]
	internal static class DamageTrace
	{
		[HarmonyPatch(typeof(LifeManager), "ShowDamageSequence")]
		[HarmonyPrefix]
		private static void LogDamage(int damage, int numWeights, bool toPlayer)
		{
			if (VersusMode.InMatch)
			{
				string arg = (toPlayer ? "player" : "opponent");
				Trace.Info($"[dmg] {damage} to {arg} (weights {numWeights})  " + "mine [" + Describe(playerSide: true) + "]  theirs [" + Describe(playerSide: false) + "]");
			}
		}

		private static string Describe(bool playerSide)
		{
			BoardManager instance = Singleton<BoardManager>.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return "no board";
			}
			List<CardSlot> list = (playerSide ? instance.PlayerSlotsCopy : instance.OpponentSlotsCopy);
			if (list == null)
			{
				return "no slots";
			}
			string[] array = new string[list.Count];
			for (int i = 0; i < list.Count; i++)
			{
				PlayableCard val = (((Object)(object)list[i] != (Object)null) ? list[i].Card : null);
				array[i] = (((Object)(object)((val != null) ? ((Card)val).Info : null) == (Object)null) ? "-" : $"{((Object)((Card)val).Info).name}({val.Attack}/{val.Health})");
			}
			return string.Join(" ", array);
		}
	}
	[HarmonyPatch]
	internal static class Dealing
	{
		private static bool Ours => VersusMode.InMatch;

		private static Rules Now => MatchRules.Live;

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		[HarmonyPrefix]
		private static bool DrawsPerTurnFlat(ref int __result)
		{
			if (!Ours)
			{
				return true;
			}
			__result = Now.DrawsPerTurn;
			return false;
		}

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		[HarmonyPrefix]
		private static bool DrawsPerTurn3D(ref int __result)
		{
			if (!Ours)
			{
				return true;
			}
			__result = Now.DrawsPerTurn;
			return false;
		}

		[HarmonyPatch(typeof(CardDrawPiles3D), "DrawOpeningHand")]
		[HarmonyPrefix]
		private static bool OpeningHand3D(CardDrawPiles3D __instance, List<CardInfo> fixedHand, ref IEnumerator __result)
		{
			if (!Ours)
			{
				return true;
			}
			__result = Deal3D(__instance, fixedHand);
			return false;
		}

		private static IEnumerator Deal3D(CardDrawPiles3D piles, List<CardInfo> fixedHand)
		{
			List<CardInfo> dealt = new List<CardInfo>();
			object? obj = AccessTools.Field(typeof(CardDrawPiles3D), "pile")?.GetValue(piles);
			CardPile pile = (CardPile)((obj is CardPile) ? obj : null);
			object? obj2 = AccessTools.Field(typeof(CardDrawPiles3D), "sidePile")?.GetValue(piles);
			CardPile val = (CardPile)((obj2 is CardPile) ? obj2 : null);
			if ((Object)(object)piles.SideDeck != (Object)null && piles.SideDeck.CardsInDeck > 0)
			{
				if ((Object)(object)val != (Object)null)
				{
					val.Draw();
				}
				yield return piles.DrawFromSidePile();
				PlayerHand instance = Singleton<PlayerHand>.Instance;
				if ((Object)(object)instance != (Object)null && instance.CardsInHand.Count > 0)
				{
					dealt.Add(((Card)instance.CardsInHand[0]).Info);
				}
			}
			if (fixedHand != null)
			{
				dealt.AddRange(fixedHand.FindAll((CardInfo x) => (Object)(object)x != (Object)null));
			}
			int startingHand = Now.StartingHand;
			Trace.Info($"[deal] opening hand of {startingHand}, {dealt.Count} of it from the side pile");
			List<CardInfo> fairHand = ((CardDrawPiles)piles).Deck.GetFairHand(startingHand, false, dealt);
			foreach (CardInfo item in fairHand)
			{
				if (((CardDrawPiles)piles).Deck.HasCard(item))
				{
					if ((Object)(object)pile != (Object)null)
					{
						pile.Draw();
					}
					yield return ((CardDrawPiles)piles).DrawCardFromDeck(item, (Action<PlayableCard>)null);
				}
			}
		}

		[HarmonyPatch(typeof(PixelCardDrawPiles), "DrawOpeningHand")]
		[HarmonyPrefix]
		private static bool OpeningHandPixel(PixelCardDrawPiles __instance, List<CardInfo> fixedHand, ref IEnumerator __result)
		{
			if (!Ours)
			{
				return true;
			}
			__result = DealPixel(__instance, fixedHand);
			return false;
		}

		private static IEnumerator DealPixel(PixelCardDrawPiles piles, List<CardInfo> fixedHand)
		{
			int want = Now.StartingHand;
			List<CardInfo> hand = ((fixedHand == null || fixedHand.Count == 0) ? ((CardDrawPiles)piles).Deck.GetFairHand(want, true, (List<CardInfo>)null) : fixedHand);
			Trace.Info($"[deal] GBC opening hand of {want}");
			for (int i = 0; i < want; i++)
			{
				if (i < hand.Count)
				{
					yield return ((CardDrawPiles)piles).DrawCardFromDeck(hand[i], (Action<PlayableCard>)null);
				}
				else
				{
					yield return ((CardDrawPiles)piles).DrawCardFromDeck((CardInfo)null, (Action<PlayableCard>)null);
				}
			}
		}

		[HarmonyPatch(typeof(TurnManager), "SetupPhase")]
		[HarmonyPostfix]
		private static IEnumerator StartingResources(IEnumerator __result)
		{
			while (__result.MoveNext())
			{
				yield return __result.Current;
			}
			if (!Ours)
			{
				yield break;
			}
			int startingBones = Now.StartingBones;
			if (startingBones > 0)
			{
				ResourcesManager instance = Singleton<ResourcesManager>.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					Trace.Warn("[deal] no resources manager - starting bones not given");
					yield break;
				}
				Trace.Info($"[deal] starting the match with {startingBones} bone(s)");
				yield return instance.AddBones(startingBones, (CardSlot)null);
			}
		}
	}
	[HarmonyPatch]
	internal static class DeckOverride
	{
		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		[HarmonyPrefix]
		private static bool DeckData3D(ref List<CardInfo> __result)
		{
			return Serve(ref __result);
		}

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		[HarmonyPrefix]
		private static bool DeckDataPixel(ref List<CardInfo> __result)
		{
			return Serve(ref __result);
		}

		private static bool Serve(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;

		private static readonly string[] NoSigils = new string[0];

		private static readonly string[] Act1Starter = new string[10] { "Stoat", "Stoat", "Bullfrog", "Bullfrog", "Wolf", "Wolf", "Adder", "Squirrel", "Squirrel", "Squirrel" };

		private const int StarterSize = 10;

		private static List<string> _deck;

		private static List<CardInfo> _pool;

		private static MatchAct _cachedFor = MatchAct.Act1;

		private static readonly Dictionary<MatchAct, int> Counts = new Dictionary<MatchAct, int>();

		public static int MaxCards => MatchRules.Effective.MaxDeck;

		public static int MaxAddedSigils => MatchRules.Effective.SigilsPerCard;

		private static string Path => System.IO.Path.Combine(Paths.ConfigPath ?? ".", "inscryptionmp-deck" + ActInfo.DeckSuffix(ActInfo.Selected) + ".txt");

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

		public static bool IsValid
		{
			get
			{
				if (Deck.Count >= 6)
				{
					return Deck.Count <= MaxCards;
				}
				return false;
			}
		}

		public static List<CardInfo> Pool
		{
			get
			{
				EnsureAct();
				if (_pool != null)
				{
					return _pool;
				}
				try
				{
					List<CardInfo> obj = ScriptableObjectLoader<CardInfo>.AllData ?? new List<CardInfo>();
					_pool = (from c in obj.Where(IsOfferedInPool)
						orderby c.BloodCost + c.BonesCost, c.DisplayedNameEnglish
						select c).ToList();
					Trace.Info(string.Format(arg1: obj.Count((CardInfo c) => (Object)(object)c != (Object)null && !IsOfferedInPool(c)), format: "[deck] card pool: {0} cards ({1} excluded as unrenderable here)", arg0: _pool.Count));
					LogPlayableButUnoffered(obj);
				}
				catch (Exception ex)
				{
					Trace.Error("[deck] pool build failed: " + ex.Message);
					_pool = new List<CardInfo>();
				}
				return _pool;
			}
		}

		internal static string BaseName(string entry)
		{
			if (string.IsNullOrEmpty(entry))
			{
				return entry;
			}
			int num = entry.IndexOf(':');
			if (num >= 0)
			{
				return entry.Substring(0, num);
			}
			return entry;
		}

		internal static string[] SigilsOf(string entry)
		{
			if (string.IsNullOrEmpty(entry))
			{
				return NoSigils;
			}
			int num = entry.IndexOf(':');
			if (num < 0 || num == entry.Length - 1)
			{
				return NoSigils;
			}
			return entry.Substring(num + 1).Split(new char[1] { ',' });
		}

		internal static string MakeEntry(string name, IList<string> sigils)
		{
			if (sigils == null || sigils.Count == 0)
			{
				return name;
			}
			return name + ":" + string.Join(",", sigils.ToArray());
		}

		public static void SetSigils(int index, IList<string> sigils)
		{
			if (index >= 0 && index < Deck.Count)
			{
				Deck[index] = MakeEntry(BaseName(Deck[index]), sigils);
				Match.Reset();
				Save();
			}
		}

		private static List<string> StarterFor(MatchAct act)
		{
			if (act == MatchAct.Act1)
			{
				return new List<string>(Act1Starter);
			}
			List<CardInfo> list = Pool.Take(5).ToList();
			if (list.Count == 0)
			{
				Trace.Warn("[deck] " + ActInfo.Name(act) + " pool is empty - no starter deck to build");
				return new List<string>();
			}
			List<string> list2 = new List<string>();
			while (list2.Count < 10)
			{
				foreach (CardInfo item in list)
				{
					if (list2.Count >= 10)
					{
						break;
					}
					list2.Add(((Object)item).name);
				}
			}
			Trace.Info($"[deck] built a {list2.Count}-card starter for {ActInfo.Name(act)} from its pool");
			return list2;
		}

		private static void EnsureAct()
		{
			if (_cachedFor != ActInfo.Selected)
			{
				_cachedFor = ActInfo.Selected;
				_deck = null;
				_pool = null;
				Match.Reset();
				Trace.Info("[deck] switched to " + ActInfo.Name(_cachedFor) + " deck and pool");
			}
		}

		public static int CountFor(MatchAct act)
		{
			if (act == ActInfo.Selected)
			{
				return Deck.Count;
			}
			if (Counts.TryGetValue(act, out var value))
			{
				return value;
			}
			value = CountOnDisk(act);
			Counts[act] = value;
			return value;
		}

		private static int CountOnDisk(MatchAct act)
		{
			try
			{
				string path = PathFor(act);
				if (!File.Exists(path))
				{
					return -1;
				}
				int num = 0;
				string[] array = File.ReadAllLines(path);
				for (int i = 0; i < array.Length; i++)
				{
					string text = array[i].Trim();
					if (text.Length > 0 && !text.StartsWith("#"))
					{
						num++;
					}
				}
				return num;
			}
			catch (Exception ex)
			{
				Trace.Warn("[deck] could not count the " + ActInfo.Name(act) + " deck: " + ex.Message);
				return -1;
			}
		}

		private static string PathFor(MatchAct act)
		{
			return System.IO.Path.Combine(Paths.ConfigPath ?? ".", "inscryptionmp-deck" + ActInfo.DeckSuffix(act) + ".txt");
		}

		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");
					PruneUnrenderable();
				}
				else
				{
					_deck = StarterFor(ActInfo.Selected);
					Trace.Info("[deck] no saved deck for " + ActInfo.Name(ActInfo.Selected) + " - using starter");
					if (_deck.Count == 0)
					{
						_cachedFor = (MatchAct)0;
					}
				}
			}
			catch (Exception ex)
			{
				Trace.Error("[deck] load failed: " + ex.Message);
				_deck = StarterFor(ActInfo.Selected);
			}
		}

		private static void PruneUnrenderable()
		{
			if (_deck == null)
			{
				return;
			}
			List<string> list = new List<string>();
			List<string> list2 = new List<string>();
			foreach (string item in _deck)
			{
				CardInfo val = Cards.ByName(BaseName(item));
				if ((Object)(object)val != (Object)null && CanUseInDeck(val))
				{
					list.Add(item);
				}
				else
				{
					list2.Add(item);
				}
			}
			if (list2.Count != 0)
			{
				_deck = list;
				Trace.Warn(string.Format("[deck] ignoring {0} card(s) not usable in {1}: {2}", list2.Count, ActInfo.Name(ActInfo.Selected), string.Join(", ", list2)));
			}
		}

		internal static void BackUpOnce()
		{
			try
			{
				string path = Path;
				string text = path + ".pre-autopilot";
				if (File.Exists(path) && !File.Exists(text))
				{
					File.Copy(path, text);
					Trace.Info("[deck] kept the existing " + ActInfo.Name(ActInfo.Selected) + " deck at " + text);
				}
			}
			catch (Exception ex)
			{
				Trace.Warn("[deck] could not keep a copy of the deck: " + ex.Message);
			}
		}

		public static void Save()
		{
			try
			{
				File.WriteAllLines(Path, Deck.ToArray());
				Counts.Clear();
				Trace.Info($"[deck] saved {Deck.Count} cards");
			}
			catch (Exception ex)
			{
				Notice.Bad("Couldn't save your deck: " + ex.Message);
				Trace.Error("[deck] save failed: " + ex.Message);
			}
		}

		public static bool Add(string cardName)
		{
			if (Deck.Count >= MaxCards)
			{
				return false;
			}
			Deck.Add(cardName);
			Match.Reset();
			return true;
		}

		public static bool Move(int index, int delta)
		{
			int num = index + delta;
			if (index < 0 || index >= Deck.Count)
			{
				return false;
			}
			if (num < 0 || num >= Deck.Count)
			{
				return false;
			}
			string item = Deck[index];
			Deck.RemoveAt(index);
			Deck.Insert(num, item);
			Match.Reset();
			Save();
			return true;
		}

		public static void RemoveAt(int index)
		{
			if (index >= 0 && index < Deck.Count)
			{
				Deck.RemoveAt(index);
				Match.Reset();
				Save();
			}
		}

		public static void Remove(string cardName)
		{
			Deck.Remove(cardName);
			Match.Reset();
		}

		public static int CountOf(string cardName)
		{
			return Deck.Count((string c) => BaseName(c) == cardName);
		}

		public static void ResetToStarter()
		{
			_deck = StarterFor(ActInfo.Selected);
			Match.Reset();
		}

		internal static bool CanUseInDeck(CardInfo c)
		{
			return CanUseInDeck(c, ActInfo.Selected);
		}

		internal static bool CanUseInDeck(CardInfo c, MatchAct act)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)c == (Object)null)
			{
				return false;
			}
			if (!ActInfo.AcceptsTemple(act, c.temple))
			{
				return false;
			}
			if (ActInfo.UsesPixelArt(act))
			{
				if ((Object)(object)c.pixelPortrait == (Object)null)
				{
					return false;
				}
			}
			else if ((Object)(object)c.portraitTex == (Object)null)
			{
				return false;
			}
			if (c.EnergyCost > 0 && !ActInfo.GrantsEnergy(act))
			{
				return false;
			}
			if (c.GemsCost != null && c.GemsCost.Count > 0 && !ActInfo.GrantsGems(act))
			{
				return false;
			}
			return true;
		}

		private static bool IsOfferedInPool(CardInfo c)
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			if (!CanUseInDeck(c))
			{
				return false;
			}
			string[] array = ActInfo.AlwaysOffered(ActInfo.Selected);
			foreach (string text in array)
			{
				if (((Object)c).name == text)
				{
					return true;
				}
			}
			if (c.metaCategories == null)
			{
				return false;
			}
			CardMetaCategory[] array2 = ActInfo.OfferedCategories(ActInfo.Selected);
			foreach (CardMetaCategory item in array2)
			{
				if (c.metaCategories.Contains(item))
				{
					return true;
				}
			}
			return false;
		}

		private static void LogPlayableButUnoffered(List<CardInfo> all)
		{
			List<string> list = (from c in all
				where (Object)(object)c != (Object)null && CanUseInDeck(c) && !IsOfferedInPool(c)
				select ((Object)c).name into n
				orderby n
				select n).ToList();
			if (list.Count != 0)
			{
				string text = string.Join(", ", list.Take(60).ToArray());
				if (list.Count > 60)
				{
					text += $", ... (+{list.Count - 60} more)";
				}
				Trace.Info($"[deck] playable here but not offered ({list.Count}): {text}");
			}
		}
	}
	[HarmonyPatch]
	internal static class DrawOrder
	{
		private static bool InOrder
		{
			get
			{
				if (VersusMode.InMatch)
				{
					return MatchRules.Live.Draw == CardDraw.InOrder;
				}
				return false;
			}
		}

		[HarmonyPatch(typeof(Deck), "Draw", new Type[] { typeof(CardInfo) })]
		[HarmonyPrefix]
		private static bool TopOfDeck(Deck __instance, CardInfo specificCard, ref CardInfo __result)
		{
			if (!InOrder)
			{
				return true;
			}
			List<CardInfo> cards = __instance.Cards;
			if (cards == null || cards.Count == 0)
			{
				return true;
			}
			if ((Object)(object)specificCard != (Object)null && cards.Contains(specificCard))
			{
				return true;
			}
			__result = cards[0];
			cards.RemoveAt(0);
			Trace.Info($"[draw] top of deck: {((Object)__result).name} ({cards.Count} left)");
			return false;
		}

		[HarmonyPatch(typeof(Deck), "GetFairHand", new Type[]
		{
			typeof(int),
			typeof(bool),
			typeof(List<CardInfo>)
		})]
		[HarmonyPrefix]
		private static bool OpeningHandInOrder(Deck __instance, int numCards, List<CardInfo> existingHand, ref List<CardInfo> __result)
		{
			if (!InOrder)
			{
				return true;
			}
			List<CardInfo> list = new List<CardInfo>();
			if (existingHand != null)
			{
				list.AddRange(existingHand);
			}
			foreach (CardInfo card in __instance.Cards)
			{
				if (list.Count >= numCards)
				{
					break;
				}
				if (existingHand == null || !existingHand.Contains(card))
				{
					list.Add(card);
				}
			}
			__result = list;
			Trace.Info("[draw] opening hand in deck order: " + Describe(list));
			return false;
		}

		internal static string Describe(IList<CardInfo> cards)
		{
			if (cards == null || cards.Count == 0)
			{
				return "(none)";
			}
			string[] array = new string[cards.Count];
			for (int i = 0; i < cards.Count; i++)
			{
				array[i] = (((Object)(object)cards[i] == (Object)null) ? "?" : ((Object)cards[i]).name);
			}
			return string.Join(", ", array);
		}
	}
	[HarmonyPatch]
	internal static class LatchSync
	{
		private const float ChoiceTimeout = 30f;

		private static PlayableCard _latcher;

		private static bool _inFallback;

		private static readonly MethodInfo VanillaAi = AccessTools.Method(typeof(Latch), "AISelectTarget", (Type[])null, (Type[])null);

		[HarmonyPatch(typeof(Latch), "OnPreDeathAnimation")]
		[HarmonyPrefix]
		private static void NoteWhoseLatcher(Latch __instance)
		{
			_latcher = (((Object)(object)__instance != (Object)null) ? ((Component)__instance).GetComponent<PlayableCard>() : null);
		}

		[HarmonyPatch(typeof(PlayableCard), "AddTemporaryMod")]
		[HarmonyPostfix]
		private static void TellPeerWhereWeLatched(PlayableCard __instance, CardModificationInfo mod)
		{
			if (mod != null && mod.fromLatch && VersusMode.InMatch && Net.Connected && Net.PeerSpeaksV3 && !((Object)(object)__instance == (Object)null) && !((Object)(object)_latcher == (Object)null) && !_latcher.OpponentCard)
			{
				CardSlot slot = __instance.Slot;
				if (!((Object)(object)slot == (Object)null))
				{
					Trace.Info("[latch] we latched onto " + Name(__instance) + " in " + string.Format("{0} slot {1} - telling the peer", slot.IsPlayerSlot ? "our" : "their", slot.Index));
					Net.Send(Protocol.LatchTarget(slot.IsPlayerSlot, slot.Index));
				}
			}
		}

		[HarmonyPatch(typeof(Latch), "AISelectTarget")]
		[HarmonyPrefix]
		private static bool UsePeersChoice(Latch __instance, List<CardSlot> validTargets, Action<CardSlot> chosenCallback, ref IEnumerator __result)
		{
			if (_inFallback)
			{
				return true;
			}
			if (!VersusMode.InMatch)
			{
				return true;
			}
			__result = TakePeersChoice(__instance, validTargets, chosenCallback);
			return false;
		}

		private static IEnumerator TakePeersChoice(Latch latch, List<CardSlot> validTargets, Action<CardSlot> chosenCallback)
		{
			if (!Net.PeerSpeaksV3)
			{
				Trace.Info("[latch] peer is on an older protocol - letting the AI choose");
				yield return RunVanillaAi(latch, validTargets, chosenCallback);
				yield break;
			}
			Trace.Info("[latch] waiting for the peer to say where they latched");
			float deadline = Time.realtimeSinceStartup + 30f;
			bool arrived = false;
			bool theirSide = false;
			int index = -1;
			while (Time.realtimeSinceStartup < deadline)
			{
				if (Net.TryTakeLatch(out theirSide, out index))
				{
					arrived = true;
					break;
				}
				if (!VersusMode.InMatch)
				{
					yield break;
				}
				yield return (object)new WaitForEndOfFrame();
			}
			if (!arrived)
			{
				Trace.Warn("[latch] peer never said - letting the AI choose");
				yield return RunVanillaAi(latch, validTargets, chosenCallback);
				yield break;
			}
			CardSlot val = Resolve(theirSide, index);
			if ((Object)(object)val == (Object)null || (Object)(object)val.Card == (Object)null || !validTargets.Contains(val))
			{
				Trace.Warn("[latch] peer latched onto a slot we can't use " + string.Format("({0} {1}) - letting the AI choose", theirSide ? "their" : "our", index));
				yield return RunVanillaAi(latch, validTargets, chosenCallback);
			}
			else
			{
				Trace.Info("[latch] peer latched onto " + Name(val.Card) + " in " + string.Format("{0} slot {1}", theirSide ? "their" : "our", index));
				chosenCallback(val);
				yield return (object)new WaitForSeconds(0.1f);
			}
		}

		private static CardSlot Resolve(bool sendersOwnSide, int index)
		{
			BoardManager instance = Singleton<BoardManager>.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return null;
			}
			List<CardSlot> list = (sendersOwnSide ? instance.OpponentSlotsCopy : instance.PlayerSlotsCopy);
			if (list == null || index < 0 || index >= list.Count)
			{
				return null;
			}
			return list[index];
		}

		private static IEnumerator RunVanillaAi(Latch latch, List<CardSlot> validTargets, Action<CardSlot> chosenCallback)
		{
			if (VanillaAi == null || (Object)(object)latch == (Object)null)
			{
				yield break;
			}
			IEnumerator enumerator = null;
			_inFallback = true;
			try
			{
				enumerator = VanillaAi.Invoke(latch, new object[2] { validTargets, chosenCallback }) as IEnumerator;
			}
			catch (Exception ex)
			{
				Trace.Error("[latch] could not run the vanilla AI: " + ex.Message);
			}
			finally
			{
				_inFallback = false;
			}
			if (enumerator != null)
			{
				yield return enumerator;
			}
		}

		private static string Name(PlayableCard card)
		{
			if (!((Object)(object)((card != null) ? ((Card)card).Info : null) != (Object)null))
			{
				return "a card";
			}
			return ((Object)((Card)card).Info).name;
		}
	}
	internal static class Lobby
	{
		private static volatile int _peerAct;

		private static volatile bool _peerReady;

		private static volatile bool _myReady;

		private static volatile bool _announcePending;

		private static int _announcedAt;

		private const int ReannounceMs = 2000;

		public static MatchAct? PeerAct
		{
			get
			{
				int peerAct = _peerAct;
				if (peerAct != 0)
				{
					return (MatchAct)peerAct;
				}
				return null;
			}
		}

		public static MatchAct MyAct => ActInfo.Selected;

		public static bool MyReady => _myReady;

		public static bool PeerReady => _peerReady;

		public static bool Negotiating
		{
			get
			{
				if (!Net.Connected)
				{
					return false;
				}
				if (PeerAct.HasValue)
				{
					return true;
				}
				return Net.PeerSpeaksV4;
			}
		}

		public static bool ActAgreed
		{
			get
			{
				if (PeerAct.HasValue)
				{
					return PeerAct.Value == MyAct;
				}
				return false;
			}
		}

		public static int ReadyCount => (MyReady ? 1 : 0) + (PeerReady ? 1 : 0);

		public static bool BothReady
		{
			get
			{
				if (MyReady)
				{
					return PeerReady;
				}
				return false;
			}
		}

		public static string Blocker
		{
			get
			{
				if (Net.HandshakeError != null)
				{
					return "Both players need the same build of the mod.";
				}
				if (!Net.Connected)
				{
					return "No opponent connected.";
				}
				if (!DeckStore.IsValid)
				{
					return "Your " + ActInfo.Name(MyAct) + " deck needs " + 6 + "-" + DeckStore.MaxCards + " cards.";
				}
				if (!Negotiating)
				{
					return null;
				}
				if (!PeerAct.HasValue)
				{
					return "Waiting for your opponent to pick an act.";
				}
				if (!ActAgreed)
				{
					return "They want " + ActInfo.Name(PeerAct.Value) + ". Agree on one act to start.";
				}
				return null;
			}
		}

		public static bool CanReady
		{
			get
			{
				if (Net.Connected)
				{
					return Blocker == null;
				}
				return false;
			}
		}

		public static int VotesFor(MatchAct act)
		{
			int num = ((MyAct == act) ? 1 : 0);
			if (PeerAct.HasValue && PeerAct.Value == act)
			{
				num++;
			}
			return num;
		}

		public static void ChooseAct(MatchAct act)
		{
			bool num = ActInfo.Selected != act;
			ActInfo.Selected = act;
			SteamTransport.PublishSelectedAct();
			if (num && MyReady)
			{
				SetReady(ready: false);
			}
			_announcePending = true;
		}

		public static void ToggleReady()
		{
			if (MyReady || CanReady)
			{
				SetReady(!MyReady);
			}
		}

		private static void SetReady(bool ready)
		{
			if (_myReady != ready)
			{
				_myReady = ready;
				_announcePending = true;
				Trace.Info("[lobby] we are " + (ready ? "ready" : "no longer ready"));
			}
		}

		public static void NotePeerAct(MatchAct act)
		{
			_peerAct = (int)act;
			Trace.Info("[lobby] opponent wants " + ActInfo.Name(act));
			if (!ActAgreed && MyReady)
			{
				SetReady(ready: false);
			}
		}

		public static void NotePeerReady(bool ready)
		{
			_peerReady = ready;
			Trace.Info("[lobby] opponent is " + (ready ? "ready" : "no longer ready"));
		}

		public static void NoteRulesChanged()
		{
			_announcePending = true;
			if (MyReady || _peerReady)
			{
				SetReady(ready: false);
				_peerReady = false;
				Trace.Info("[lobby] match rules changed - both players are no longer ready");
			}
		}

		public static void ResetForNewPeer()
		{
			_peerAct = 0;
			_peerReady = false;
			_myReady = false;
			_announcePending = true;
			MatchRules.ForgetPeer();
			Player.ForgetPeer();
			Chat.Clear();
			VersusMode.ForgetLastResult();
		}

		public static void Reset()
		{
			ResetForNewPeer();
		}

		public static void NoteMatchStarting()
		{
			_myReady = false;
			_peerReady = false;
		}

		public static void Tick(MonoBehaviour host)
		{
			if (!Net.Connected || VersusMode.InMatch || VersusMode.PendingStart)
			{
				return;
			}
			bool flag = !PeerAct.HasValue && Net.PeerSpeaksV4 && Environment.TickCount - _announcedAt > 2000;
			if (_announcePending || flag)
			{
				_announcePending = false;
				_announcedAt = Environment.TickCount;
				Net.Send(Protocol.Vote(MyAct));
				Net.Send(Protocol.Ready(MyReady));
				if (Net.IsHost)
				{
					string announcement = MatchRules.Announcement;
					if (announcement != null)
					{
						Net.Send(announcement);
					}
				}
			}
			if (Net.IsHost && Negotiating && ActAgreed && BothReady)
			{
				Trace.Info("[lobby] both players are ready - starting the match");
				VersusMode.StartAnywhere(host);
			}
		}
	}
	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)
				{
					string text = DeckStore.BaseName(item);
					CardInfo val = Cards.ByName(text);
					if ((Object)(object)val == (Object)null)
					{
						Trace.Warn("[match] card '" + text + "' did not resolve - skipping");
					}
					else if (!DeckStore.CanUseInDeck(val, ActInfo.Current))
					{
						Trace.Warn("[match] '" + text + "' can't be drawn in " + ActInfo.Name(ActInfo.Current) + " - leaving it out");
					}
					else
					{
						string[] array = DeckStore.SigilsOf(item);
						if (array.Length != 0)
						{
							val = WithSigils(val, array);
						}
						list.Add(val);
					}
				}
				Trace.Info($"[match] built versus deck with {list.Count} cards, in order: " + DrawOrder.Describe(list));
				_cache = list;
				return new List<CardInfo>(_cache);
			}
		}

		private static CardInfo WithSigils(CardInfo info, string[] sigils)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			object obj = info.Clone();
			CardInfo val = (CardInfo)((obj is CardInfo) ? obj : null);
			if ((Object)(object)val == (Object)null)
			{
				return info;
			}
			CardModificationInfo val2 = new CardModificationInfo
			{
				singletonId = "inscryptionmp-deck-sigils"
			};
			List<string> list = new List<string>();
			foreach (string text in sigils)
			{
				if (!Enum.IsDefined(typeof(Ability), text))
				{
					Trace.Warn("[match] '" + ((Object)info).name + "' asks for unknown sigil '" + text + "' - ignoring");
				}
				else
				{
					Ability item = (Ability)Enum.Parse(typeof(Ability), text);
					if (!val.Abilities.Contains(item))
					{
						val2.abilities.Add(item);
						list.Add(text);
					}
				}
			}
			if (val2.abilities.Count == 0)
			{
				return info;
			}
			val.Mods.Add(val2);
			Trace.Info("[match] " + ((Object)info).name + " carries added sigil(s): " + string.Join(", ", list.ToArray()));
			return val;
		}

		public static void Reset()
		{
			_cache = null;
		}
	}
	public enum MatchAct
	{
		Act1 = 1,
		Act2,
		Act3
	}
	[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(typeof(TurnManager), "CleanupPhase")]
		[HarmonyPostfix]
		private static IEnumerator EndWithoutFlowManager(IEnumerator __result, TurnManager __instance)
		{
			while (__result.MoveNext())
			{
				yield return __result.Current;
			}
			if (VersusMode.InMatch && !ActInfo.HasFlowManager(ActInfo.Current))
			{
				Trace.Info("[end] no flow manager in this act - finishing the match here");
				VersusMode.Finish((MonoBehaviour)(object)__instance, __instance.PlayerWon, "battle resolved");
			}
		}
	}
	[HarmonyPatch]
	internal static class MatchLock
	{
		[HarmonyPatch(typeof(GameFlowManager), "TransitionToFirstPerson")]
		[HarmonyPrefix]
		private static bool BlockStandingUp()
		{
			if (!VersusMode.InMatch)
			{
				return true;
			}
			return false;
		}

		[HarmonyPatch(typeof(Part1GameFlowManager), "SceneSpecificInitialization")]
		[HarmonyPrefix]
		private static bool SkipPart1SceneIntro()
		{
			if (!VersusMode.InMatch && !VersusMode.PendingStart)
			{
				return true;
			}
			ItemsManager instance = Singleton<ItemsManager>.Instance;
			if ((Object)(object)instance != (Object)null)
			{
				instance.SetSlotsAtEdge(true, true);
			}
			ExplorableAreaManager instance2 = Singleton<ExplorableAreaManager>.Instance;
			if ((Object)(object)instance2 != (Object)null)
			{
				instance2.SetHangingLightShadowStrength(0.5f, 0f);
			}
			Trace.Info("[lock] skipping Part 1 scene intro - a match is starting");
			return false;
		}

		[HarmonyPatch(typeof(Part3GameFlowManager), "SceneSpecificInitialization")]
		[HarmonyPrefix]
		private static bool SkipPart3SceneIntro()
		{
			if (!VersusMode.InMatch && !VersusMode.PendingStart)
			{
				return true;
			}
			Trace.Info("[lock] skipping Part 3 scene intro - a match is starting");
			return false;
		}

		[HarmonyPatch(typeof(GameFlowManager), "TransitionTo")]
		[HarmonyPrefix]
		private static bool BlockInternalTransition(GameState gameState, ref IEnumerator __result)
		{
			//IL_0010: 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)
			if (!VersusMode.InMatch && !VersusMode.PendingStart)
			{
				return true;
			}
			if ((int)gameState == 0)
			{
				return true;
			}
			Trace.Warn($"[lock] blocked internal transition to {gameState} during a match");
			__result = Nothing();
			return false;
		}

		[HarmonyPatch(typeof(MenuController), "ReturnToStartScreen")]
		[HarmonyPrefix]
		private static void LeavingByTheOtherDoor()
		{
			VersusMode.NoteLeftByGame("the game returned to the start screen");
		}

		private static IEnumerator Nothing()
		{
			yield break;
		}

		[HarmonyPatch(typeof(GameFlowManager), "TransitionToGameState")]
		[HarmonyPrefix]
		private static bool BlockStateChange(GameState gameState)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: 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;
		}
	}
	public static class MatchRules
	{
		private static ConfigEntry<string> _saved;

		private static Rules _desired = Rules.Default;

		private static Rules? _fromHost;

		private static Rules _live = Rules.Default;

		public static Rules Desired => _desired;

		public static bool WeDecide
		{
			get
			{
				if (Net.Connected)
				{
					return Net.IsHost;
				}
				return true;
			}
		}

		public static bool CustomOn => Effective.Custom;

		public static bool DowngradedForPeer
		{
			get
			{
				if (Net.Connected && Net.IsHost && !Net.PeerSpeaksV5)
				{
					return _desired.Custom;
				}
				return false;
			}
		}

		public static Rules Effective
		{
			get
			{
				Rules result = (_desired.Custom ? _desired : Keeping(Rules.Default, _desired.Custom));
				if (!Net.Connected)
				{
					return result;
				}
				if (!Net.PeerSpeaksV5)
				{
					return Rules.Default;
				}
				if (Net.IsHost)
				{
					return result;
				}
				return _fromHost ?? Rules.Default;
			}
		}

		public static Rules Live
		{
			get
			{
				if (!VersusMode.InMatch)
				{
					return Effective;
				}
				return _live;
			}
		}

		public static string Announcement
		{
			get
			{
				if (!Net.PeerSpeaksV5)
				{
					return null;
				}
				return Protocol.RulesLine(Effective.Encode());
			}
		}

		public static void Bind(ConfigEntry<string> entry)
		{
			_saved = entry;
			_desired = Rules.Decode(entry.Value);
			Trace.Info("[rules] loaded: " + _desired.Encode());
		}

		public static void Adopt(string payload)
		{
			if (WeDecide)
			{
				_desired = Rules.Decode(payload);
				Trace.Info("[rules] adopted: " + _desired.Encode());
				Changed();
			}
		}

		private static Rules Keeping(Rules b, bool custom)
		{
			b.Custom = custom;
			return b;
		}

		public static void Lock()
		{
			_live = Effective;
			Trace.Info("[rules] match locked to " + _live.Encode());
		}

		public static void SetCustom(bool on)
		{
			if (WeDecide && _desired.Custom != on)
			{
				_desired.Custom = on;
				Trace.Info("[rules] custom rules " + (on ? "on" : "off"));
				Changed();
			}
		}

		public static bool Cycle(string key, int delta)
		{
			if (!WeDecide)
			{
				return false;
			}
			RuleBook.Dial dial = RuleBook.Find(key);
			if (dial == null)
			{
				return false;
			}
			int num = dial.Choices.Length;
			int num2 = (dial.IndexOf(_desired) + delta % num + num) % num;
			_desired = dial.WithIndex(_desired, num2);
			_desired.Custom = true;
			Trace.Info("[rules] " + dial.Label + " is now " + dial.Choices[num2]);
			Changed();
			return true;
		}

		public static void ResetToDefaults()
		{
			if (WeDecide)
			{
				bool custom = _desired.Custom;
				_desired = Keeping(Rules.Default, custom);
				Trace.Info("[rules] reset to the game's own rules");
				Changed();
			}
		}

		private static void Changed()
		{
			Persist();
			Lobby.NoteRulesChanged();
		}

		private static void Persist()
		{
			try
			{
				if (_saved != null)
				{
					_saved.Value = _desired.Encode();
				}
			}
			catch (Exception ex)
			{
				Trace.Warn("[rules] could not save: " + ex.Message);
			}
		}

		public static void NoteHostRules(string payload)
		{
			Rules rules = Rules.Decode(payload);
			if (Net.Connected && Net.IsHost)
			{
				Trace.Warn("[rules] ignoring rules from a peer while hosting");
				return;
			}
			bool num = !_fromHost.HasValue || !_fromHost.Value.SameAs(rules);
			_fromHost = rules;
			if (num)
			{
				Trace.Info("[rules] host set: " + rules.Encode());
				Lobby.NoteRulesChanged();
			}
		}

		public static void ForgetPeer()
		{
			_fromHost = null;
		}
	}
	[HarmonyPatch]
	internal static class MenuCardEntry
	{
		private const string CardName = "InscryptionMP_MultiplayerCard";

		private static MenuCard _card;

		[HarmonyPatch(typeof(MenuController), "Start")]
		[HarmonyPostfix]
		private static void AddCard(MenuController __instance)
		{
			Trace.Info("[menucard] MenuController.Start - queueing card");
			((MonoBehaviour)__instance).StartCoroutine(AddCardWhenReady(__instance));
		}

		[HarmonyPatch(typeof(MenuController), "TweenInCards")]
		[HarmonyPrefix]
		private static void AddBeforeTween(MenuController __instance)
		{
			Trace.Info("[menucard] TweenInCards - ensuring our card exists");
			Build(__instance);
		}

		private static IEnumerator AddCardWhenReady(MenuController controller)
		{
			yield return null;
			Build(controller);
		}

		private static void Build(MenuController controller)
		{
			//IL_00c8: 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_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!IsTitleScreen(controller))
				{
					return;
				}
				if (AccessTools.Field(typeof(MenuController), "cards")?.GetValue(controller) is List<MenuCard> { Count: not 0 } list)
				{
					if (!list.Exists((MenuCard c) => (Object)(object)c != (Object)null && ((Object)c).name == "InscryptionMP_MultiplayerCard"))
					{
						DropHiddenAscensionCard(list);
						Trace.Info("[menucard] row: " + Describe(list));
						MenuCard val = PickTemplate(list);
						if (!((Object)(object)val == (Object)null))
						{
							MenuCard val2 = Object.Instantiate<MenuCard>(val, ((Component)val).transform.parent);
							((Object)val2).name = "InscryptionMP_MultiplayerCard";
							Vector3 val3 = NextPosition(list);
							((Component)val2).transform.localPosition = val3;
							((SlottableUIElement)val2).ReInitPosition(Vector2.op_Implicit(val3));
							Retitle(val2, "Multiplayer");
							TintBorder(val2, new Color(0.95f, 0.35f, 0.28f));
							GiveDistinctArt(val2, list);
							list.Add(val2);
							_card = val2;
							CentreRow(list);
							Trace.Info("[menucard] added the multiplayer card at x=" + val3.x.ToString("0.00"));
						}
					}
				}
				else
				{
					Trace.Warn("[menucard] no menu cards to clone from");
				}
			}
			catch (Exception ex)
			{
				Trace.Error("[menucard] could not add the card: " + ex.Message);
			}
		}

		private static bool IsTitleScreen(MenuController controller)
		{
			PauseMenu instance = PauseMenu.instance;
			if ((Object)(object)instance != (Object)null)
			{
				object? obj = AccessTools.Field(typeof(PauseMenu), "menuController")?.GetValue(instance);
				MenuController val = (MenuController)((obj is MenuController) ? obj : null);
				if ((Object)(object)val != (Object)null && (Object)(object)val == (Object)(object)controller)
				{
					Trace.Info("[menucard] that controller is the pause menu - leaving it alone");
					return false;
				}
			}
			if (AccessTools.Field(typeof(MenuController), "cards")?.GetValue(controller) is List<MenuCard> list && list.Exists((MenuCard c) => (Object)(object)c != (Object)null && ((int)c.MenuAction == 7 || (int)c.MenuAction == 4)))
			{
				Trace.Info("[menucard] that row concedes or exits a run - not the title screen");
				return false;
			}
			return true;
		}

		private static void DropHiddenAscensionCard(List<MenuCard> cards)
		{
			if (!StoryEventsData.EventCompleted((StoryEvent)162))
			{
				int num = cards.RemoveAll((MenuCard c) => (Object)(object)c != (Object)null && (int)c.MenuAction == 10);
				if (num > 0)
				{
					Trace.Info("[menucard] ignoring " + num + " locked ascension card(s)");
				}
			}
		}

		private static string Describe(List<MenuCard> cards)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			StringBuilder stringBuilder = new StringBuilder();
			foreach (MenuCard card in cards)
			{
				if (!((Object)(object)card == (Object)null))
				{
					stringBuilder.Append(card.MenuAction).Append("@").Append(((Component)card).transform.localPosition.x.ToString("0.00"))
						.Append(" ");
				}
			}
			return stringBuilder.ToString();
		}

		private static float RowPitch(List<MenuCard> cards)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			List<float> list = new List<float>();
			foreach (MenuCard card in cards)
			{
				if ((Object)(object)card != (Object)null)
				{
					list.Add(((Component)card).transform.localPosition.x);
				}
			}
			list.Sort();
			float num = float.MaxValue;
			for (int i = 1; i < list.Count; i++)
			{
				float num2 = list[i] - list[i - 1];
				if (num2 > 0.05f && num2 < num)
				{
					num = num2;
				}
			}
			if (num != float.MaxValue)
			{
				return num;
			}
			return 1.2f;
		}

		private static MenuCard PickTemplate(List<MenuCard> cards)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Invalid comparison between Unknown and I4
			MenuCard val = null;
			foreach (MenuCard card in cards)
			{
				if (!((Object)(object)card == (Object)null) && !card.IsAscensionCard && !card.Locked)
				{
					if ((int)card.MenuAction == 2)
					{
						return card;
					}
					if ((Object)(object)val == (Object)null)
					{
						val = card;
					}
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				Trace.Warn("[menucard] no suitable card to clone");
			}
			return val;
		}

		private static Vector3 NextPosition(List<MenuCard> cards)
		{
			//IL_002b: 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_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: 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_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			MenuCard val = null;
			float num = float.MinValue;
			foreach (MenuCard card in cards)
			{
				if (!((Object)(object)card == (Object)null))
				{
					float x = ((Component)card).transform.localPosition.x;
					if (x > num)
					{
						num = x;
						val = card;
					}
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				return Vector3.zero;
			}
			Vector3 localPosition = ((Component)val).transform.localPosition;
			return new Vector3(localPosition.x + RowPitch(cards), localPosition.y, localPosition.z);
		}

		private static void Retitle(MenuCard card, string title)
		{
			Set(card, "titleSprite", null);
			Set(card, "lockedTitleSprite", null);
			Set(card, "titleText", title);
			Set(card, "titleLocId", "");
			Set(card, "permanentlyLocked", false);
			Set(card, "lockBeforeStoryEvent", false);
			Set(card, "lockAfterStoryEvent", false);
			Set(card, "isAscensionCard", false);
		}

		private static void TintBorder(MenuCard card, Color c)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			Set(card, "defaultBorderColor", c);
			card.SetBorderColor(c);
		}

		private static void TintFace(MenuCard card, Color tint)
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			SpriteRenderer[] componentsInChildren = ((Component)card).GetComponentsInChildren<SpriteRenderer>(true);
			StringBuilder stringBuilder = new StringBuilder();
			SpriteRenderer[] array = componentsInChildren;
			foreach (SpriteRenderer val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					stringBuilder.Append(((Object)val).name).Append(" | ");
					if (!((Object)val).name.ToLowerInvariant().Contains("border"))
					{
						val.color = tint;
					}
				}
			}
			Trace.Info($"[menucard] renderers: {stringBuilder}");
		}

		private static void CentreRow(List<MenuCard> cards)
		{
			//IL_0038: 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_0050: 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_0058: Unknown result type (might be due to invalid IL or missing references)
			if (cards.Count < 2)
			{
				return;
			}
			float num = (0f - RowPitch(cards)) * 0.5f;
			foreach (MenuCard card in cards)
			{
				if (!((Object)(object)card == (Object)null))
				{
					Vector3 localPosition = ((Component)card).transform.localPosition;
					localPosition.x += num;
					((Component)card).transform.localPosition = localPosition;
					((SlottableUIElement)card).ReInitPosition(Vector2.op_Implicit(localPosition));
				}
			}
			Trace.Info($"[menucard] recentred the row by {num:0.00}");
		}

		private static void GiveDistinctArt(MenuCard clone, List<MenuCard> cards)
		{
			//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
			SpriteRenderer component = ((Component)clone).GetComponent<SpriteRenderer>();
			if ((Object)(object)component == (Object)null)
			{
				return;
			}
			List<string> list = new List<string>();
			foreach (MenuCard card in cards)
			{
				if (!((Object)(object)card == (Object)null))
				{
					SpriteRenderer component2 = ((Component)card).GetComponent<SpriteRenderer>();
					if ((Object)(object)((component2 != null) ? component2.sprite : null) != (Object)null)
					{
						list.Add(((Object)component2.sprite).name);
					}
				}
			}
			string[] array = new string[2] { "menucard_concede", "menucard_library" };
			Sprite val = null;
			Sprite val2 = null;
			StringBuilder stringBuilder = new StringBuilder();
			Sprite[] array2 = Resources.FindObjectsOfTypeAll<Sprite>();
			foreach (Sprite val3 in array2)
			{
				if ((Object)(object)val3 == (Object)null || !((Object)val3).name.StartsWith("menucard_") || ((Object)val3).name.Contains("_greyed") || ((Object)val3).name.Contains("_flash"))
				{
					continue;
				}
				stringBuilder.Append(((Object)val3).name).Append(" | ");
				if (!list.Contains(((Object)val3).name))
				{
					if (Array.IndexOf(array, ((Object)val3).name) == 0)
					{
						val = val3;
						break;
					}
					if ((Object)(object)val == (Object)null && Array.IndexOf(array, ((Object)val3).name) > 0)
					{
						val = val3;
					}
					if ((Object)(object)val2 == (Object)null)
					{
						val2 = val3;
					}
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				val = val2;
			}
			Trace.Info($"[menucard] menucard sprites: {stringBuilder}");
			if ((Object)(object)val == (Object)null)
			{
				Trace.Warn("[menucard] no unused card art - tinting instead");
				TintFace(clone, new Color(1f, 0.66f, 0.16f));
				return;
			}
			Sprite val4 = VersusCardArt.Build(val);
			component.sprite = val4 ?? val;
			component.color = (Color)(((Object)(object)val4 != (Object)null) ? Color.white : new Color(0.72f, 0.2f, 0.16f));
			Trace.Info("[menucard] using '" + ((Object)val).name + "' as the card face");
		}

		private static void Set(MenuCard card, string field, object value)
		{
			FieldInfo fieldInfo = AccessTools.Field(typeof(MenuCard), field);
			if (fieldInfo == null)
			{
				Trace.Warn("[menucard] MenuCard." + field + " not found");
			}
			else
			{
				fieldInfo.SetValue(card, value);
			}
		}

		[HarmonyPatch(typeof(MenuController), "OnCardReachedSlot")]
		[HarmonyPrefix]
		private static bool InterceptOurCard(MenuController __instance, MenuCard card)
		{
			if ((Object)(object)card == (Object)null || (Object)(object)_card == (Object)null || (Object)(object)card != (Object)(object)_card)
			{
				return true;
			}
			Trace.Info("[menucard] multiplayer card slotted - opening the panel");
			try
			{
				AccessTools.Property(typeof(MenuController), "DoingCardTransition")?.SetValue(__instance, false, null);
				__instance.ResetToDefaultState();
			}
			catch (Exception ex)
			{
				Trace.Error("[menucard] could not restore the menu: " + ex.Message);
			}
			MpMenu.OpenFromMenuCard();
			return false;
		}
	}
	internal class MpMenu : MonoBehaviour
	{
		private enum Page
		{
			Home,
			Online,
			Local,
			Deck,
			Rules
		}

		internal static string Ip = "127.0.0.1";

		internal static string PortText = 27333.ToString();

		private const float PanelW = 510f;

		private float _measuredH = 300f;

		private float _barH = 110f;

		private bool _open;

		private bool _matchWasActive;

		private Page _page;

		private string _draft = "";

		private Vector2 _chatScroll;

		private string _explaining;

		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 GUIStyle _primary;

		private GUIStyle _quiet;

		private GUIStyle _warn;

		private GUIStyle _resultText;

		private GUIStyle _good;

		private GUIStyle _busy;

		private GUIStyle _close;

		private GUIStyle _pageLabel;

		private GUIStyle _countLabel;

		private GUIStyle _version;

		private GUIStyle _seat;

		private GUIStyle _seatState;

		private GUIStyle _seatReady;

		private GUIStyle _chatLine;

		private GUIStyle _chatMine;

		private GUIStyle _chatSystem;

		private GUIStyle _chatHead;

		private GUIStyle _scoreName;

		private GUIStyle _scoreNum;

		private GUIStyle _scoreTurn;

		private GUIStyle _arrow;

		private GUIStyle _dialValue;

		private GUIStyle _vote;

		private GUIStyle _voteBoth;

		private GUIStyle _chipState;

		private GUIStyle _chipInfo;

		private Texture2D _panelBg;

		private Texture2D _chipBg;

		private Texture2D _accent;

		private static readonly Color Ink = new Color(0.055f, 0.048f, 0.042f, 0.97f);

		private static readonly Color InkSoft = new Color(0.105f, 0.092f, 0.078f, 1f);

		private static readonly Color Bone = new Color(0.93f, 0.9f, 0.83f, 1f);

		private static readonly Color BoneDim = new Color(0.6f, 0.57f, 0.51f, 1f);

		private static readonly Color BoneFaint = new Color(0.4f, 0.38f, 0.35f, 1f);

		private static readonly Color Gold = new Color(0.84f, 0.64f, 0.28f, 1f);

		private static readonly Color GoldSoft = new Color(0.84f, 0.64f, 0.28f, 0.28f);

		private static readonly Color Rust = new Color(0.72f, 0.34f, 0.26f, 1f);

		private static readonly Color Moss = new Color(0.45f, 0.74f, 0.36f, 1f);

		private static Font _gameFont;

		private static bool _fontSearched;

		private float _barScale = 1.6f;

		private readonly List<string> _said = new List<string>();

		private float _orderH = 60f;

		private const float MaxScale = 1.6f;

		private const float MinScale = 0.8f;

		private float _panelScale = 1.6f;

		private float _viewScale = 1.6f;

		private const float CardBarW = 860f;

		private const float Pad = 24f;

		private const float Gap = 14f;

		private const float Section = 22f;

		private const float ButtonRowHeight = 44f;

		private const float ChatW = 330f;

		private const float ChatLogH = 132f;

		private bool _chatOpen;

		private int _chatSeen;

		private bool _refocusChat;

		private static MpMenu _instance;

		private static int ParsedPort
		{
			get
			{
				if (!int.TryParse(PortText, out var result) || result <= 0 || result >= 65536)
				{
					return 27333;
				}
				return result;
			}
		}

		internal static bool ChatHasFocus { get; private set; }

		private bool Unread => Chat.Count > _chatSeen;

		private void Update()
		{
			Background.Tick();
			Chat.Pump();
			SteamTransport.Poll();
			EmoteKeys();
			if (Input.GetKeyDown((KeyCode)288))
			{
				if (NativeDeckBuilder.IsOpen)
				{
					NativeDeckBuilder.Close();
				}
				else
				{
					_open = !_open;
				}
			}
			if (Input.GetKeyDown((KeyCode)289))
			{
				if (Lobby.Negotiating)
				{
					Lobby.ToggleReady();
				}
				else
				{
					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);
			Lobby.Tick((MonoBehaviour)(object)this);
			if (_open && !NativeDeckBuilder.IsOpen && !VersusMode.InMatch && !Net.Connected && !Net.Running)
			{
				SteamTransport.AutoRefresh();
			}
			bool flag = VersusMode.InMatch || VersusMode.PendingStart;
			if (flag && !_matchWasActive)
			{
				_open = false;
			}
			_matchWasActive = flag;
			if (Net.PendingStartRequest.HasValue)
			{
				MatchAct value = Net.PendingStartRequest.Value;
				Net.PendingStartRequest = null;
				if (!VersusMode.InMatch && !VersusMode.PendingStart)
				{
					ActInfo.Current = value;
					ActInfo.Selected = value;
					Trace.Info("[versus] peer started a " + ActInfo.Name(value) + " match - joining");
					VersusMode.StartAnywhere((MonoBehaviour)(object)this, tellPeer: false);
				}
			}
			if (Net.PendingResult.HasValue)
			{
				bool value2 = Net.PendingResult.Value;
				Net.PendingResult = null;
				if (VersusMode.InMatch)
				{
					VersusMode.Finish((MonoBehaviour)(object)this, value2, "peer reported the result");
				}
			}
			if (VersusMode.InMatch && !VersusMode.PendingStart && (Object)(object)Singleton<TurnManager>.Instance == (Object)null)
			{
				VersusMode.NoteLeftByGame("the battle is no longer loaded");
			}
			if (VersusMode.InMatch && !Net.Connected)
			{
				VersusMode.NoteDisconnected();
			}
			if (VersusMode.InMatch && Net.Connected && VersusMode.Suspended)
			{
				VersusMode.NoteReconnected();
			}
			VersusMode.TickSuspension((MonoBehaviour)(object)this);
		}

		private void EmoteKeys()
		{
			if (NativeDeckBuilder.IsOpen || ChatHasFocus || !Net.Connected || !VersusMode.InMatch)
			{
				return;
			}
			for (int i = 0; i < Chat.Emotes.Length; i++)
			{
				if (Input.GetKeyDown((KeyCode)(49 + i)) || Input.GetKeyDown((KeyCode)(257 + i)))
				{
					Chat.Emote(i);
					break;
				}
			}
		}

		private void Frame(Rect r, Color c, float t = 1f)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: 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)
			Color color = GUI.color;
			GUI.color = c;
			GUI.DrawTexture(new Rect(((Rect)(ref r)).x, ((Rect)(ref r)).y, ((Rect)(ref r)).width, t), (Texture)(object)Texture2D.whiteTexture);
			GUI.DrawTexture(new Rect(((Rect)(ref r)).x, ((Rect)(ref r)).yMax - t, ((Rect)(ref r)).width, t), (Texture)(object)Texture2D.whiteTexture);
			GUI.DrawTexture(new Rect(((Rect)(ref r)).x, ((Rect)(ref r)).y, t, ((Rect)(ref r)).height), (Texture)(object)Texture2D.whiteTexture);
			GUI.DrawTexture(new Rect(((Rect)(ref r)).xMax - t, ((Rect)(ref r)).y, t, ((Rect)(ref r)).height), (Texture)(object)Texture2D.whiteTexture);
			GUI.color = color;
		}

		private static Texture2D Solid(Color c)
		{
			//IL_0002: 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_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			Texture2D val = new Texture2D(1, 1);
			val.SetPixel(0, 0, c);
			val.Apply();
			((Object)val).hideFlags = (HideFlags)61;
			return val;
		}

		private static Font GameFont()
		{
			if (_fontSearched)
			{
				return _gameFont;
			}
			_fontSearched = true;
			try
			{
				Font[] array = Resources.FindObjectsOfTypeAll<Font>();
				StringBuilder stringBuilder = new StringBuilder();
				Font val = null;
				Font[] array2 = array;
				foreach (Font val2 in array2)
				{
					if ((Object)(object)val2 == (Object)null)
					{
						continue;
					}
					stringBuilder.Append(((Object)val2).name).Append(" | ");
					string text = ((Object)val2).name.ToLowerInvariant();
					if (!text.Contains("arial"))
					{
						if (text.Contains("marksman") && text.Contains("crisp"))
						{
							_gameFont = val2;
							break;
						}
						if (text.Contains("marksman") && (Object)(object)val == (Object)null)
						{
							val = val2;
						}
					}
				}
				if ((Object)(object)_gameFont == (Object)null)
				{
					_gameFont = val;
				}
				Trace.Info($"[ui] fonts available: {stringBuilder}");
				Trace.Info("[ui] using font: " + (((Object)(object)_gameFont == (Object)null) ? "built-in" : ((Object)_gameFont).name) + (((Object)(object)_gameFont == (Object)null) ? "" : $" (dynamic={_gameFont.dynamic}, baked size={_gameFont.fontSize})"));
			}
			catch (Exception ex)
			{
				Trace.Warn("[ui] font lookup failed: " + ex.Message);
			}
			return _gameFont;
		}

		private void ApplyFont()
		{
			Font val = GameFont();
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			GUIStyle[] array = (GUIStyle[])(object)new GUIStyle[34]
			{
				_chip, _label, _dim, _small, _header, _section, _field, _button, _primary, _quiet,
				_warn, _good, _busy, _close, _resultText, _chipState, _chipInfo, _pageLabel, _countLabel, _vote,
				_voteBoth, _version, _seat, _seatState, _seatReady, _chatLine, _chatMine, _chatSystem, _chatHead, _arrow,
				_dialValue, _scoreName, _scoreNum, _scoreTurn
			};
			foreach (GUIStyle val2 in array)
			{
				if (val2 != null)
				{
					val2.font = val;
					if (!val.dynamic)
					{
						val2.fontSize = 0;
					}
				}
			}
		}

		private void EnsureStyles()
		{
			//IL_000a: 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_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: 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_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Expected O, but got Unknown
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid I