Decompiled source of InscryptionOnline v1.5.0
plugins/InscryptionMP.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
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