using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Security; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using DiskCardGame; using HarmonyLib; using InscryptionAPI.Ascension; using InscryptionAPI.Card; using InscryptionAPI.Dialogue; using InscryptionAPI.Helpers; using InscryptionAPI.Localizing; using InscryptionAPI.Saves; using KayceePvP.Net; using KayceePvP.Patches; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.UI; [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("KayceePvP")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0+adf3ca5a33fa548510640132f97ae0010ff6aa7c")] [assembly: AssemblyProduct("KayceePvP")] [assembly: AssemblyTitle("KayceePvP")] [assembly: AssemblyVersion("0.1.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 KayceePvP { public enum CardChoiceEventKind { Cost, Tribe, Random } public static class BalancedPathCardChoiceSeed { private const int CostDomain = 700000; private const int TribeDomain = 800000; private const int RandomDomain = 900000; public static bool TryGetEventKind(CardChoicesType choicesType, out CardChoiceEventKind kind) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected I4, but got Unknown switch ((int)choicesType) { case 1: kind = CardChoiceEventKind.Cost; return true; case 2: kind = CardChoiceEventKind.Tribe; return true; case 0: kind = CardChoiceEventKind.Random; return true; default: kind = CardChoiceEventKind.Cost; return false; } } public static bool ShouldOverride(bool isPvpRun, bool isBalancedPathMode, CardChoicesType choicesType) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) CardChoiceEventKind kind; if (isPvpRun && isBalancedPathMode) { return TryGetEventKind(choicesType, out kind); } return false; } public static int Compute(int sharedMapSeed, int currentNodeId, CardChoiceEventKind kind) { return sharedMapSeed + currentNodeId + DomainOffset(kind); } private static int DomainOffset(CardChoiceEventKind kind) { return kind switch { CardChoiceEventKind.Cost => 700000, CardChoiceEventKind.Tribe => 800000, CardChoiceEventKind.Random => 900000, _ => 0, }; } } internal static class BalancedPathMap { public const int RowCount = 19; public static List> BuildRows() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Expected O, but got Unknown //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Expected O, but got Unknown //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Expected O, but got Unknown //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Expected O, but got Unknown List> list = new List>(); list.Add(Row(new NodeData())); list.Add(Row((NodeData)new TradePeltsNodeData())); list.Add(Row((NodeData)CardChoiceNodeData((CardChoicesType)2))); list.Add(Row((NodeData)CardChoiceNodeData((CardChoicesType)2))); list.Add(Row((NodeData)CardChoiceNodeData((CardChoicesType)2))); list.Add(Row((NodeData)CardChoiceNodeData((CardChoicesType)2))); list.Add(Row((NodeData)CardChoiceNodeData((CardChoicesType)2))); list.Add(Row((NodeData)CardChoiceNodeData((CardChoicesType)2))); list.Add(Row((NodeData)CardChoiceNodeData((CardChoicesType)2))); list.Add(Row((NodeData)new CardBattleNodeData { difficulty = 1 })); list.Add(Row((NodeData)CardChoiceNodeData((CardChoicesType)2))); list.Add(Row((NodeData)CardChoiceNodeData((CardChoicesType)2))); list.Add(Row((NodeData)CardChoiceNodeData((CardChoicesType)2))); list.Add(Row((NodeData)CardChoiceNodeData((CardChoicesType)2))); list.Add(Row((NodeData)new CopyCardNodeData())); list.Add(Row((NodeData)new CardRemoveNodeData())); list.Add(Row((NodeData)new CardRemoveNodeData())); list.Add(Row((NodeData)new RareRewardNodeData())); list.Add(Row(new NoBattleExitNodeData())); return list; } private static CardChoicesNodeData CardChoiceNodeData(CardChoicesType choiceType) { //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_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown return new CardChoicesNodeData { choicesType = choiceType }; } private static List Row(params NodeData[] nodes) { return new List(nodes); } } internal static class BalancedRouteMap { public const int RowCount = 20; public static List> BuildRows() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Expected O, but got Unknown //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Expected O, but got Unknown //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Expected O, but got Unknown //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Expected O, but got Unknown //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Expected O, but got Unknown List> list = new List>(); list.Add(Row(new NodeData())); list.Add(Row((NodeData)new TradePeltsNodeData())); list.Add(Row((NodeData)Choices((CardChoicesType)1))); list.Add(Row((NodeData)Choices((CardChoicesType)1))); list.Add(Row((NodeData)Choices((CardChoicesType)1))); list.Add(Row((NodeData)Choices((CardChoicesType)1))); list.Add(Row((NodeData)Choices((CardChoicesType)1))); list.Add(Row((NodeData)Choices((CardChoicesType)1))); list.Add(Row((NodeData)new CardMergeNodeData())); list.Add(Row((NodeData)new CardStatBoostNodeData())); list.Add(Row((NodeData)Battle(1))); list.Add(Row((NodeData)Choices((CardChoicesType)1))); list.Add(Row((NodeData)Choices((CardChoicesType)1))); list.Add(Row((NodeData)Choices((CardChoicesType)1))); list.Add(Row((NodeData)Choices((CardChoicesType)1))); list.Add(Row((NodeData)new CopyCardNodeData())); list.Add(Row((NodeData)new DuplicateMergeNodeData())); list.Add(Row((NodeData)new CardStatBoostNodeData())); list.Add(Row((NodeData)new RareRewardNodeData())); list.Add(Row(new NoBattleExitNodeData())); return list; } private static CardChoicesNodeData Choices(CardChoicesType type) { //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_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown return new CardChoicesNodeData { choicesType = type }; } private static CardBattleNodeData Battle(int difficulty) { //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_000d: Expected O, but got Unknown return new CardBattleNodeData { difficulty = difficulty }; } private static List Row(params NodeData[] nodes) { return new List(nodes); } } internal static class CardEdits { private static readonly string MayonnaiseEnglish = PvpLocalization.L("KayceePvP.Card.Mayonnaise", "Mayonnaise", "Maionese"); private static readonly string StepfatherEnglish = PvpLocalization.L("KayceePvP.Card.Stepfather", "Stepfather", "Padrasto"); private static readonly string BluePenEnglish = PvpLocalization.L("KayceePvP.Card.BluePen", "Blue Pen", "Caneta Azul"); private static readonly string CannabisEnglish = PvpLocalization.L("KayceePvP.Card.Cannabis", "Cannabis", "Cannabis"); public static void Apply() { RenameAndReskin("Moose", MayonnaiseEnglish, "portrait_maionese.png"); RenameAndReskin("Goat", StepfatherEnglish, "portrait_padrasto.png"); RenameAndReskin("Mole", BluePenEnglish, "portrait_caneta_azul.png"); RenameAndReskin("Squirrel", CannabisEnglish, "portrait_cannabis.png"); } private static void RenameAndReskin(string cardName, string englishDisplayedName, string embeddedPortraitFileName) { CardInfo val = CardExtensions.CardByName((IEnumerable)CardManager.BaseGameCards, cardName); if ((Object)(object)val == (Object)null) { Plugin.Log.LogWarning((object)("CardEdits: could not find base game card '" + cardName + "' to edit.")); return; } CardExtensions.SetDisplayedName(val, englishDisplayedName); Traverse.Create((object)val).Field("displayedNameLocId").Value = null; Texture2D imageAsTexture = TextureHelper.GetImageAsTexture(embeddedPortraitFileName, Assembly.GetExecutingAssembly(), (FilterMode)0); CardExtensions.SetPortrait(val, imageAsTexture, (FilterMode?)null); Plugin.Log.LogInfo((object)("CardEdits: renamed '" + cardName + "' to '" + englishDisplayedName + "' with a custom portrait (Portuguese translation registered separately).")); } } public static class CobaiasStarterDeck { public const string Title = "COBAIAS"; public static readonly string[] CardNames = new string[3] { "PackRat", "MealWorm", "Skink" }; public static string DeckId => "jgs.inscryption.kayceepvp_COBAIAS"; internal static void Apply() { StarterDeckManager.New("jgs.inscryption.kayceepvp", "COBAIAS", BuildIcon(), CardNames, 0); } private static Texture2D BuildIcon() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(35, 44, (TextureFormat)4, false); Color val2 = default(Color); ((Color)(ref val2))..ctor(0.35f, 0.18f, 0.45f, 1f); Color val3 = default(Color); ((Color)(ref val3))..ctor(0.85f, 0.75f, 0.95f, 1f); Color val4 = default(Color); ((Color)(ref val4))..ctor(0.15f, 0.07f, 0.2f, 1f); for (int i = 0; i < 44; i++) { for (int j = 0; j < 35; j++) { bool flag = j == 0 || i == 0 || j == 34 || i == 43; bool flag2 = (j >= 15 && j <= 19) || (i >= 20 && i <= 24); val.SetPixel(j, i, flag ? val4 : (flag2 ? val3 : val2)); } } val.Apply(); ((Object)val).name = "CobaiasStarterDeckIcon"; return val; } } internal sealed class CreatorCreditOverlay : MonoBehaviour { private const string CreatorName = "YNASTRA"; private const float AnimationInterval = 18f; private const float AnimationDuration = 2.4f; private CanvasGroup _group; private Text _creatorText; private Shadow _creatorGlow; private RectTransform _creatorRect; private Text _feedbackText; private float _feedbackCooldownUntil; internal static void RegisterLocalizedText() { PvpLocalization.L("KayceePvP.Feedback.Hint", "Press {0} to send feedback or suggestions", "Pressione {0} para enviar feedback ou sugestões"); } private void Awake() { BuildUI(); _group.alpha = 0.62f; } private void Update() { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: 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_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) if (Plugin.CreatorCreditEnabled == null || !Plugin.CreatorCreditEnabled.Value) { _group.alpha = 0f; return; } _group.alpha = 0.62f; _creatorText.text = "YNASTRA"; float num = Mathf.Repeat(Time.unscaledTime, 18f); float num2 = ((num < 2.4f) ? Mathf.Sin(num / 2.4f * (float)Math.PI) : 0f); float num3 = num2; _creatorRect.anchoredPosition = new Vector2(280f + num2 * 4f, 0f); ((Graphic)_creatorText).color = Color.Lerp(new Color(0.92f, 0.83f, 0.64f, 1f), new Color(1f, 0.43f, 0.08f, 1f), num3); _creatorGlow.effectColor = new Color(1f, 0.22f, 0.03f, 0.12f + num3 * 0.5f); UpdateFeedbackLine(); } private unsafe void UpdateFeedbackLine() { //IL_0013: 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_0018: Unknown result type (might be due to invalid IL or missing references) KeyboardShortcut val = ((Plugin.FeedbackShortcut != null) ? Plugin.FeedbackShortcut.Value : FeedbackConfig.DefaultShortcut); _feedbackText.text = PvpLocalization.Format("KayceePvP.Feedback.Hint", ((object)(*(KeyboardShortcut*)(&val))/*cast due to .constrained prefix*/).ToString()); if (!(Time.unscaledTime < _feedbackCooldownUntil) && ((KeyboardShortcut)(ref val)).IsDown()) { _feedbackCooldownUntil = Time.unscaledTime + 3f; Plugin.Log.LogInfo((object)"feedback page requested"); Application.OpenURL("https://github.com/Ynastra/act1pvpkayceesmod/issues/new/choose"); } } private void BuildUI() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Expected O, but got Unknown //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: 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_0128: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0270: Unknown result type (might be due to invalid IL or missing references) //IL_0292: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Expected O, but got Unknown //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Unknown result type (might be due to invalid IL or missing references) //IL_02e5: Unknown result type (might be due to invalid IL or missing references) //IL_02fa: Unknown result type (might be due to invalid IL or missing references) //IL_030e: Unknown result type (might be due to invalid IL or missing references) //IL_032e: Unknown result type (might be due to invalid IL or missing references) //IL_033d: Unknown result type (might be due to invalid IL or missing references) //IL_037f: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("KayceePvP_CreatorCredit", new Type[1] { typeof(RectTransform) }); val.transform.SetParent(((Component)this).transform, false); Canvas obj = val.AddComponent(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 10; CanvasScaler obj2 = val.AddComponent(); obj2.uiScaleMode = (ScaleMode)1; obj2.referenceResolution = new Vector2(1920f, 1080f); obj2.matchWidthOrHeight = 0.5f; _group = val.AddComponent(); _group.interactable = false; _group.blocksRaycasts = false; GameObject val2 = new GameObject("Signature", new Type[1] { typeof(RectTransform) }); val2.transform.SetParent(val.transform, false); RectTransform component = val2.GetComponent(); component.anchorMin = new Vector2(1f, 0f); component.anchorMax = new Vector2(1f, 0f); component.pivot = new Vector2(1f, 0f); component.anchoredPosition = new Vector2(-24f, 20f); component.sizeDelta = new Vector2(390f, 34f); Text obj3 = CreateText(val2.transform, "Caption", new Vector2(0f, 0f), new Vector2(273f, 34f)); obj3.text = "KayceePvP · Designed by"; obj3.fontSize = 17; obj3.alignment = (TextAnchor)5; ((Graphic)obj3).color = new Color(0.72f, 0.68f, 0.59f, 1f); _creatorText = CreateText(val2.transform, "Creator", new Vector2(280f, 0f), new Vector2(110f, 34f)); _creatorRect = ((Graphic)_creatorText).rectTransform; _creatorText.fontSize = 18; _creatorText.fontStyle = (FontStyle)1; _creatorText.alignment = (TextAnchor)3; ((Graphic)_creatorText).color = new Color(0.92f, 0.83f, 0.64f, 1f); _creatorGlow = ((Component)_creatorText).gameObject.AddComponent(); _creatorGlow.effectDistance = new Vector2(1f, -1f); _creatorGlow.effectColor = new Color(1f, 0.22f, 0.03f, 0.12f); GameObject val3 = new GameObject("FeedbackHint", new Type[1] { typeof(RectTransform) }); val3.transform.SetParent(val.transform, false); RectTransform component2 = val3.GetComponent(); component2.anchorMin = new Vector2(1f, 0f); component2.anchorMax = new Vector2(1f, 0f); component2.pivot = new Vector2(1f, 0f); component2.anchoredPosition = new Vector2(-24f, 58f); component2.sizeDelta = new Vector2(440f, 26f); _feedbackText = CreateText(val3.transform, "FeedbackHintText", new Vector2(0f, 0f), new Vector2(440f, 26f)); _feedbackText.fontSize = 13; _feedbackText.alignment = (TextAnchor)5; ((Graphic)_feedbackText).color = new Color(0.72f, 0.68f, 0.59f, 1f); _feedbackText.horizontalOverflow = (HorizontalWrapMode)0; } private static Text CreateText(Transform parent, string name, Vector2 position, Vector2 size) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0038: 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_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) }); val.transform.SetParent(parent, false); RectTransform component = val.GetComponent(); component.anchorMin = Vector2.zero; component.anchorMax = Vector2.zero; component.pivot = Vector2.zero; component.anchoredPosition = position; component.sizeDelta = size; Text obj = val.AddComponent(); obj.font = LobbyUI.ResolveGameFont(); ((Graphic)obj).raycastTarget = false; obj.horizontalOverflow = (HorizontalWrapMode)1; obj.verticalOverflow = (VerticalWrapMode)1; return obj; } } internal sealed class DeathcardNodeData : NodeData { public override string PrefabPath => "Prefabs/Map/MapNodesPart1/MapNode_CardBattle"; } internal static class DuelDebug { public static void StartRandomDuel(int cardCount = 10) { if ((Object)(object)Singleton.Instance == (Object)null) { Plugin.Log.LogWarning((object)"Duel debug: no TurnManager in the current scene - load into the map first."); return; } if (PvpRunState.IsDuelActive) { Plugin.Log.LogWarning((object)"Duel debug: a duel is already active."); return; } if (RunState.Run == null) { Plugin.Log.LogWarning((object)"Duel debug: no active run to source a deck from."); return; } List list = CardManager.AllCardsCopy.Where((CardInfo c) => (Object)(object)c != (Object)null && !string.IsNullOrEmpty(((Object)c).name) && ((Object)c).name != "DeathCard" && (int)c.temple == 0).ToList(); if (list.Count == 0) { Plugin.Log.LogWarning((object)"Duel debug: no Nature-temple cards available to build a random deck."); return; } ((CardCollectionInfo)RunState.Run.playerDeck).Cards.Clear(); for (int num = 0; num < cardCount; num++) { ((CardCollectionInfo)RunState.Run.playerDeck).AddCard(list[Random.Range(0, list.Count)]); } PvpRunState.MatchId = Random.Range(int.MinValue, int.MaxValue); DuelSession.Instance.PrepareForMatch(PvpRunState.MatchId); LobbySession.Instance.ResetBattleFlowForMatch(PvpRunState.MatchId, "DuelDebug.StartRandomDuel (F10 fast-path)"); DuelStart.Begin(); } } public static class DuelDeckShuffleSeed { private const int MatchIdMultiplier = -1640531535; private const int BattleNumberMultiplier = 104729; private const int DomainSalt = 1592614637; public static bool ShouldOverride(bool isPvpRun, bool isDuelActive) { return isPvpRun && isDuelActive; } public static int Compute(int matchId, int battleNumber) { return (matchId * -1640531535) ^ (battleNumber * 104729) ^ 0x5EED5EED; } } internal static class DuelStart { public static void Begin() { //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Expected O, but got Unknown PvpCardMutations.ApplyToDeck(RunState.Run?.playerDeck); PvpDuelItems.ApplyForSeries(PvpRunState.MatchId); if ((Object)(object)Singleton.Instance != (Object)null) { Singleton.Instance.HideMapImmediate(); } Singleton.Instance.Controller.SwitchToControlMode((ControlMode)0, true); if ((Object)(object)Singleton.Instance != (Object)null) { Singleton.Instance.SetSlotsAtEdge(false, true); } if ((Object)(object)Singleton.Instance != (Object)null) { Singleton.Instance.CurrentGameState = (GameState)0; } PvpRunState.IsDuelActive = true; PvpRoundState.BeginMatch(); LobbyMatchState.BeginMatch(PvpRunState.MatchId); LobbyMatchState.BeginBattle(); DuelSession.Instance.ArmTurnOwnershipForBattle(); Plugin.Log.LogInfo((object)$"Duel: starting with {((CardCollectionInfo)RunState.Run.playerDeck).Cards.Count} cards."); Telemetry.LogDuelStart((Object)(object)LobbySession.Instance != (Object)null && LobbySession.Instance.IsHost); LogTotemSnapshot("battle begin (battle 1)"); Singleton.Instance.StartGame(new EncounterData()); } internal static void LogTotemSnapshot(string context) { string text = ((RunState.Run.totems.Count == 0) ? "none" : string.Join(" | ", RunState.Run.totems.Select((TotemDefinition t) => $"tribe={t.tribe} ability={t.ability}"))); Plugin.Log.LogInfo((object)$"Duel (totem): SNAPSHOT at {context} | matchId={PvpRunState.MatchId} battleNumber={LobbyMatchState.BattleNumber} SELF={PvpOwnership.LocalAbsoluteSide()} ownTotems=[{text}]"); } public static void BeginNextBattle() { //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Expected O, but got Unknown PvpCardMutations.ApplyToDeck(RunState.Run?.playerDeck); PvpDuelItems.ApplyForSeries(PvpRunState.MatchId); if ((Object)(object)Singleton.Instance != (Object)null) { Singleton.Instance.HideMapImmediate(); } Singleton.Instance.Controller.SwitchToControlMode((ControlMode)0, true); if ((Object)(object)Singleton.Instance != (Object)null) { Singleton.Instance.SetSlotsAtEdge(false, true); } if ((Object)(object)Singleton.Instance != (Object)null) { Singleton.Instance.CurrentGameState = (GameState)0; } PvpRunState.IsDuelActive = true; LobbyMatchState.BeginBattle(); Plugin.Log.LogInfo((object)$"Duel: starting battle {LobbyMatchState.BattleNumber} with {((CardCollectionInfo)RunState.Run.playerDeck).Cards.Count} cards (hostWins={LobbyMatchState.HostWins} peerWins={LobbyMatchState.PeerWins})."); Telemetry.LogRoundBegin(LobbyMatchState.BattleNumber); LogTotemSnapshot($"battle begin (battle {LobbyMatchState.BattleNumber})"); Singleton.Instance.StartGame(new EncounterData()); } } internal static class FeedbackConfig { internal const string IssuesUrl = "https://github.com/Ynastra/act1pvpkayceesmod/issues/new/choose"; internal static readonly KeyboardShortcut DefaultShortcut = new KeyboardShortcut((KeyCode)291, Array.Empty()); internal const float CooldownSeconds = 3f; } internal sealed class FinalMatchScreen : MonoBehaviour { private const string BackgroundResourceName = "KayceePvP.Assets.tela_final_kayceepvp.png"; private const float InputArmDelaySeconds = 0.75f; private GameObject _canvasRoot; private Text _winnerText; private Text _loserText; private Text _scoreText; private Text _instructionText; private Text _rematchStatusText; private float _inputArmedAt; private bool _resetRequested; private bool _previousCursorVisible; private CursorLockMode _previousCursorLockState; private bool _previousInteractionDisabled; private bool _previousInteractionCursorEnabled; public static FinalMatchScreen Instance { get; private set; } internal static void RegisterLocalizedText() { PvpLocalization.L("KayceePvP.Final.WinnerLine", "{0} IS THE WINNER", "{0} É O VENCEDOR"); PvpLocalization.L("KayceePvP.Final.LoserLine", "{0} IS ABSOLUTE GARBAGE", "{0} É UM LIXO IMUNDO"); PvpLocalization.L("KayceePvP.Final.ScoreLine", "FINAL SCORE: {0} - {1}", "PLACAR FINAL: {0} - {1}"); PvpLocalization.L("KayceePvP.Final.Instructions", "SPACE: REMATCH WITH THE SAME DECKS · N: NEW PREPARATION AND NEW LOBBY\nESC OR ENTER: CLOSE THE GAME", "ESPAÇO: REVANCHE COM OS MESMOS DECKS · N: NOVA PREPARAÇÃO E NOVO LOBBY\nESC OU ENTER: FECHAR O JOGO"); PvpLocalization.L("KayceePvP.Final.ClosingGame", "CLOSING THE GAME...", "FECHANDO O JOGO..."); PvpLocalization.L("KayceePvP.Final.ReturningToPreparation", "RETURNING TO DECK SELECTION...", "VOLTANDO À SELEÇÃO DE BARALHO..."); PvpLocalization.L("KayceePvP.Final.RematchUnavailable", "REMATCH UNAVAILABLE - CONNECTION TO THE OPPONENT LOST", "REVANCHE INDISPONÍVEL - CONEXÃO COM O OPONENTE PERDIDA"); PvpLocalization.L("KayceePvP.Final.RematchAccepted", "REMATCH ACCEPTED - PREPARING THE NEXT SERIES...", "REVANCHE ACEITA - PREPARANDO A PRÓXIMA SÉRIE..."); PvpLocalization.L("KayceePvP.Final.RematchRequestedByYou", "YOU REQUESTED A REMATCH - WAITING FOR THE OPPONENT...", "VOCÊ PEDIU REVANCHE - AGUARDANDO O OPONENTE..."); PvpLocalization.L("KayceePvP.Final.RematchRequestedByPeer", "THE OPPONENT REQUESTED A REMATCH - PRESS SPACE TO ACCEPT", "O OPONENTE PEDIU REVANCHE - PRESSIONE ESPAÇO PRA ACEITAR"); } private void Awake() { Instance = this; BuildUI(); _canvasRoot.SetActive(false); } private void OnDestroy() { if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } private void Update() { if ((Object)(object)_canvasRoot == (Object)null || !_canvasRoot.activeSelf || _resetRequested) { return; } UpdateRematchStatusText(); if (!(Time.realtimeSinceStartup < _inputArmedAt)) { if (Input.GetKeyDown((KeyCode)27) || Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)271)) { _resetRequested = true; _instructionText.text = PvpLocalization.Get("KayceePvP.Final.ClosingGame"); Plugin.Log.LogInfo((object)"Duel (match-result): QUIT_REQUESTED"); LobbySession.Instance?.QuitGameAfterMatch(); } else if (Input.GetKeyDown((KeyCode)32)) { LobbySession.Instance?.RequestRematch(); } else if (Input.GetKeyDown((KeyCode)110)) { _resetRequested = true; _instructionText.text = PvpLocalization.Get("KayceePvP.Final.ReturningToPreparation"); Plugin.Log.LogInfo((object)"Duel (match-result): NEW_PREPARATION_REQUESTED"); LobbySession.Instance?.ReturnToAscensionMenuAfterMatch(); } } } private void UpdateRematchStatusText() { LobbySession instance = LobbySession.Instance; if (!((Object)(object)instance == (Object)null)) { if (!instance.RematchAvailable) { _rematchStatusText.text = PvpLocalization.Get("KayceePvP.Final.RematchUnavailable"); } else if (instance.RematchAccepted) { _rematchStatusText.text = PvpLocalization.Get("KayceePvP.Final.RematchAccepted"); } else if (instance.LocalRematchRequested && !instance.PeerRematchRequested) { _rematchStatusText.text = PvpLocalization.Get("KayceePvP.Final.RematchRequestedByYou"); } else if (!instance.LocalRematchRequested && instance.PeerRematchRequested) { _rematchStatusText.text = PvpLocalization.Get("KayceePvP.Final.RematchRequestedByPeer"); } else { _rematchStatusText.text = ""; } } } public static void Show(string winnerAbsoluteSide, int hostWins, int peerWins) { Instance?.ShowInternal(winnerAbsoluteSide, hostWins, peerWins); } public static void Hide() { Instance?.HideInternal(); } private void ShowInternal(string winnerAbsoluteSide, int hostWins, int peerWins) { //IL_010f: 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) bool num = string.Equals(winnerAbsoluteSide, "HOST", StringComparison.OrdinalIgnoreCase); LobbySession instance = LobbySession.Instance; string text = (((Object)(object)instance != (Object)null) ? instance.HostDisplayName : PvpLocalization.Get("KayceePvP.Lobby.DefaultPlayer1")); string text2 = (((Object)(object)instance != (Object)null) ? instance.PeerDisplayName : PvpLocalization.Get("KayceePvP.Lobby.DefaultPlayer2")); string text3 = (num ? text : text2); string text4 = (num ? text2 : text); _winnerText.text = PvpLocalization.Format("KayceePvP.Final.WinnerLine", text3); _loserText.text = PvpLocalization.Format("KayceePvP.Final.LoserLine", text4); _scoreText.text = PvpLocalization.Format("KayceePvP.Final.ScoreLine", hostWins, peerWins); _instructionText.text = PvpLocalization.Get("KayceePvP.Final.Instructions"); _rematchStatusText.text = ""; _resetRequested = false; _inputArmedAt = Time.realtimeSinceStartup + 0.75f; _previousCursorVisible = Cursor.visible; _previousCursorLockState = Cursor.lockState; Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; InteractionCursor instance2 = Singleton.Instance; _previousInteractionDisabled = (Object)(object)instance2 != (Object)null && instance2.InteractionDisabled; _previousInteractionCursorEnabled = (Object)(object)instance2 == (Object)null || ((Behaviour)instance2).enabled; if ((Object)(object)instance2 != (Object)null) { instance2.InteractionDisabled = true; instance2.SetEnabled(false); } _canvasRoot.SetActive(true); Plugin.Log.LogInfo((object)$"Duel (match-result): FINAL_SCREEN_SHOWN | winner={winnerAbsoluteSide} hostWins={hostWins} peerWins={peerWins}"); } private void HideInternal() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_canvasRoot == (Object)null) && _canvasRoot.activeSelf) { Cursor.visible = _previousCursorVisible; Cursor.lockState = _previousCursorLockState; InteractionCursor instance = Singleton.Instance; if ((Object)(object)instance != (Object)null) { instance.SetEnabled(_previousInteractionCursorEnabled); instance.InteractionDisabled = _previousInteractionDisabled; } _canvasRoot.SetActive(false); _resetRequested = false; } } private void BuildUI() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_0281: Unknown result type (might be due to invalid IL or missing references) _canvasRoot = new GameObject("KayceePvP_FinalMatchCanvas"); _canvasRoot.transform.SetParent(((Component)this).transform, false); Canvas obj = _canvasRoot.AddComponent(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 32760; CanvasScaler obj2 = _canvasRoot.AddComponent(); obj2.uiScaleMode = (ScaleMode)1; obj2.referenceResolution = new Vector2(1920f, 1080f); obj2.screenMatchMode = (ScreenMatchMode)0; obj2.matchWidthOrHeight = 0.5f; _canvasRoot.AddComponent(); Stretch(((Graphic)CreateImage("BlackBackdrop", _canvasRoot.transform, Color.black)).rectTransform); Image obj3 = CreateImage("Artwork", _canvasRoot.transform, Color.white); Stretch(((Graphic)obj3).rectTransform); obj3.preserveAspect = true; obj3.sprite = LoadEmbeddedSprite(); ((Graphic)obj3).raycastTarget = true; Image val = CreateImage("TopTextPanel", _canvasRoot.transform, new Color(0f, 0f, 0f, 0.76f)); AnchorBand(((Graphic)val).rectTransform, 0.72f, 1f); Image val2 = CreateImage("BottomTextPanel", _canvasRoot.transform, new Color(0f, 0f, 0f, 0.78f)); AnchorBand(((Graphic)val2).rectTransform, 0f, 0.18f); _winnerText = CreateText("Winner", ((Component)val).transform, new Vector2(0f, 55f), new Vector2(1700f, 64f), "", 42); _loserText = CreateText("Loser", ((Component)val).transform, new Vector2(0f, -15f), new Vector2(1700f, 54f), "", 30); _scoreText = CreateText("Score", ((Component)val).transform, new Vector2(0f, -75f), new Vector2(1200f, 46f), "", 26); _instructionText = CreateText("Instruction", ((Component)val2).transform, new Vector2(0f, 24f), new Vector2(1700f, 70f), "", 22); _rematchStatusText = CreateText("RematchStatus", ((Component)val2).transform, new Vector2(0f, -34f), new Vector2(1700f, 40f), "", 20); } private static Sprite LoadEmbeddedSprite() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("KayceePvP.Assets.tela_final_kayceepvp.png"); if (stream == null) { Plugin.Log.LogError((object)"Duel (match-result): embedded background not found: KayceePvP.Assets.tela_final_kayceepvp.png"); return null; } byte[] array = new byte[stream.Length]; int num; for (int i = 0; i < array.Length; i += num) { num = stream.Read(array, i, array.Length - i); if (num <= 0) { break; } } Texture2D val = new Texture2D(2, 2, (TextureFormat)3, false); if (!ImageConversion.LoadImage(val, array, true)) { Plugin.Log.LogError((object)"Duel (match-result): failed to decode embedded final-screen PNG."); Object.Destroy((Object)(object)val); return null; } ((Object)val).name = "KayceePvP_FinalMatchBackground"; ((Texture)val).wrapMode = (TextureWrapMode)1; return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f); } private static Image CreateImage(string name, Transform parent, Color color) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) }); val.transform.SetParent(parent, false); Image obj = val.AddComponent(); ((Graphic)obj).color = color; return obj; } private static Text CreateText(string name, Transform parent, Vector2 anchoredPosition, Vector2 size, string content, int fontSize) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: 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_0072: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) }); val.transform.SetParent(parent, false); RectTransform component = val.GetComponent(); component.anchorMin = new Vector2(0.5f, 0.5f); component.anchorMax = new Vector2(0.5f, 0.5f); component.pivot = new Vector2(0.5f, 0.5f); component.anchoredPosition = anchoredPosition; component.sizeDelta = size; Text obj = val.AddComponent(); obj.text = content; obj.font = LobbyUI.ResolveGameFont(); obj.fontSize = fontSize; obj.alignment = (TextAnchor)4; ((Graphic)obj).color = Color.white; obj.horizontalOverflow = (HorizontalWrapMode)0; obj.verticalOverflow = (VerticalWrapMode)1; ((Graphic)obj).raycastTarget = false; return obj; } private static void Stretch(RectTransform rect) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = Vector2.zero; rect.offsetMax = Vector2.zero; } private static void AnchorBand(RectTransform rect, float minY, float maxY) { //IL_0007: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = new Vector2(0f, minY); rect.anchorMax = new Vector2(1f, maxY); rect.offsetMin = Vector2.zero; rect.offsetMax = Vector2.zero; } } internal static class FixedMap { public static PredefinedNodes BuildFirstRegionMap() { PredefinedNodes obj = ScriptableObject.CreateInstance(); obj.nodeRows = BuildRows(); return obj; } public static List> BuildRows() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Expected O, but got Unknown //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Expected O, but got Unknown //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Expected O, but got Unknown //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Expected O, but got Unknown //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Expected O, but got Unknown //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Expected O, but got Unknown //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Expected O, but got Unknown //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Expected O, but got Unknown //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Expected O, but got Unknown //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Expected O, but got Unknown //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Expected O, but got Unknown //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Expected O, but got Unknown //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Expected O, but got Unknown List> list = new List>(); list.Add(Row(new NodeData())); list.Add(Row((NodeData)new TradePeltsNodeData())); list.Add(Row((NodeData)Choices((CardChoicesType)1))); list.Add(Row((NodeData)new CardStatBoostNodeData(), (NodeData)new BuildTotemNodeData(), (NodeData)Choices((CardChoicesType)1))); list.Add(Row((NodeData)Choices((CardChoicesType)2), (NodeData)Choices((CardChoicesType)2), (NodeData)Choices((CardChoicesType)1))); list.Add(Row((NodeData)Choices((CardChoicesType)1), (NodeData)Choices((CardChoicesType)2), (NodeData)Choices((CardChoicesType)2))); list.Add(Row((NodeData)new CardRemoveNodeData(), (NodeData)Choices((CardChoicesType)2), (NodeData)new DeckTrialNodeData())); list.Add(Row((NodeData)Battle(1), (NodeData)Battle(1), (NodeData)Battle(1))); list.Add(Row((NodeData)Choices((CardChoicesType)2), (NodeData)new BuildTotemNodeData(), (NodeData)new BuyPeltsNodeData())); list.Add(Row((NodeData)new CopyCardNodeData(), (NodeData)Choices((CardChoicesType)2), (NodeData)new TradePeltsNodeData())); list.Add(Row((NodeData)new DuplicateMergeNodeData(), (NodeData)new CardMergeNodeData(), (NodeData)Choices((CardChoicesType)2))); list.Add(Row((NodeData)new CardMergeNodeData(), (NodeData)new CardStatBoostNodeData(), (NodeData)new CardMergeNodeData())); list.Add(Row((NodeData)new CardRemoveNodeData(), (NodeData)new CardRemoveNodeData(), (NodeData)Choices((CardChoicesType)1))); list.Add(Row((NodeData)new RareRewardNodeData())); list.Add(Row(new DeathcardNodeData())); return list; } private static List Row(params NodeData[] nodes) { return new List(nodes); } private static CardChoicesNodeData Choices(CardChoicesType type) { //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_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown return new CardChoicesNodeData { choicesType = type }; } private static CardBattleNodeData Battle(int difficulty) { //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_000d: Expected O, but got Unknown return new CardBattleNodeData { difficulty = difficulty }; } } internal static class LobbyMatchState { public const int WinsNeeded = 2; private static List _frozenLocalDeck; public static int MatchId { get; private set; } public static int BattleNumber { get; private set; } = 1; public static int HostWins { get; private set; } public static int PeerWins { get; private set; } public static bool BattleInProgress { get; private set; } public static bool HostGoesFirstThisBattle => (uint)(MatchId + BattleNumber - 1) % 2u == 0; public static bool InterBattleSceneReloadActive { get; private set; } public static void BeginMatch(int matchId) { MatchId = matchId; BattleNumber = 1; HostWins = 0; PeerWins = 0; BattleInProgress = false; _frozenLocalDeck = CloneDeck(((CardCollectionInfo)RunState.Run.playerDeck).Cards); Plugin.Log.LogInfo((object)$"Duel (lobby-match): BeginMatch | match={matchId} frozenLocalDeckCount={_frozenLocalDeck.Count}"); } public static void EndMatch() { BattleInProgress = false; Plugin.Log.LogInfo((object)$"Duel (lobby-match): EndMatch | match={MatchId} finalHostWins={HostWins} finalPeerWins={PeerWins}"); } public static void BeginBattle() { BattleInProgress = true; } public static void RestoreLocalDeckForNextBattle() { if (_frozenLocalDeck == null) { Plugin.Log.LogWarning((object)"Duel (lobby-match): RestoreLocalDeckForNextBattle called with no frozen deck - BeginMatch was never called this match."); return; } DeckInfo playerDeck = RunState.Run.playerDeck; ((CardCollectionInfo)playerDeck).Cards.Clear(); foreach (CardInfo item in CloneDeck(_frozenLocalDeck)) { ((CardCollectionInfo)playerDeck).Cards.Add(item); } } public static bool ApplyBattleResult(bool loserIsHost) { if (loserIsHost) { PeerWins++; } else { HostWins++; } BattleInProgress = false; bool flag = HostWins >= 2 || PeerWins >= 2; Plugin.Log.LogInfo((object)$"Duel (lobby-match): ApplyBattleResult | match={MatchId} battle={BattleNumber} loserIsHost={loserIsHost} hostWins={HostWins} peerWins={PeerWins} isMatchFinal={flag}"); return flag; } public static void ApplyAuthoritativeWins(int hostWins, int peerWins) { HostWins = hostWins; PeerWins = peerWins; BattleInProgress = false; } public static void AdvanceToNextBattle() { BattleNumber++; } public static void BeginInterBattleSceneReload() { InterBattleSceneReloadActive = true; Plugin.Log.LogInfo((object)$"Duel (lobby-match): INTERBATTLE_RELOAD_LATCH_ON | match={MatchId} battle={BattleNumber}"); } public static void EndInterBattleSceneReload() { InterBattleSceneReloadActive = false; Plugin.Log.LogInfo((object)$"Duel (lobby-match): INTERBATTLE_RELOAD_LATCH_OFF | match={MatchId} battle={BattleNumber}"); } private static List CloneDeck(List source) { List list = new List(source.Count); foreach (CardInfo item in source) { object obj = item.Clone(); list.Add((CardInfo)((obj is CardInfo) ? obj : null)); } return list; } } internal enum MapCompletionPolicy { Boss, RouteExit } internal static class MapDebug { private class MapNodeDump { public int id; public int gridX; public int gridY; public string nodeType; public int[] connectedNodeIds; public string extra; } public static void DumpNewMap() { if (RunState.Run == null) { Plugin.Log.LogWarning((object)"MapDebug: no active run - start a run first (e.g. via 'Testar sozinho')."); return; } RunState.Run.GenerateMapDataForCurrentRegion((PredefinedNodes)null); MapNodeDump[] array = RunState.Run.map.nodeData.Select((NodeData n) => new MapNodeDump { id = ((MapElementData)n).id, gridX = n.gridX, gridY = n.gridY, nodeType = ((object)n).GetType().Name, connectedNodeIds = n.connectedNodes.Select((NodeData c) => ((MapElementData)c).id).ToArray(), extra = DescribeExtra(n) }).ToArray(); string contents = BuildJson(array); string path = $"map_dump_{DateTime.Now:yyyyMMdd_HHmmss}.json"; string text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), path); File.WriteAllText(text, contents); Plugin.Log.LogInfo((object)$"MapDebug: dumped {array.Length} nodes to {text}"); } private static string BuildJson(MapNodeDump[] nodes) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("{\n \"nodes\": [\n"); for (int i = 0; i < nodes.Length; i++) { MapNodeDump mapNodeDump = nodes[i]; stringBuilder.Append(" {"); stringBuilder.Append($"\"id\":{mapNodeDump.id},"); stringBuilder.Append($"\"gridX\":{mapNodeDump.gridX},"); stringBuilder.Append($"\"gridY\":{mapNodeDump.gridY},"); stringBuilder.Append("\"nodeType\":\"" + Escape(mapNodeDump.nodeType) + "\","); stringBuilder.Append("\"connectedNodeIds\":[" + string.Join(",", mapNodeDump.connectedNodeIds) + "],"); stringBuilder.Append("\"extra\":\"" + Escape(mapNodeDump.extra) + "\""); stringBuilder.Append((i < nodes.Length - 1) ? "},\n" : "}\n"); } stringBuilder.Append(" ]\n}"); return stringBuilder.ToString(); } private static string Escape(string s) { if (!string.IsNullOrEmpty(s)) { return s.Replace("\\", "\\\\").Replace("\"", "\\\""); } return ""; } private static string DescribeExtra(NodeData node) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) BossBattleNodeData val = (BossBattleNodeData)(object)((node is BossBattleNodeData) ? node : null); if (val == null) { CardBattleNodeData val2 = (CardBattleNodeData)(object)((node is CardBattleNodeData) ? node : null); if (val2 == null) { CardChoicesNodeData val3 = (CardChoicesNodeData)(object)((node is CardChoicesNodeData) ? node : null); if (val3 != null) { return $"choicesType={val3.choicesType}"; } return ""; } return $"difficulty={val2.difficulty}"; } return $"bossType={val.bossType}"; } } internal enum MapMode { Battles, NoBattles, SymmetricForge, PeltDraft, BalancedPath, OpenDraftPath, BalancedRoute } internal static class MapModeCatalog { internal const int UnifiedDuelThreshold = 15; public static readonly IReadOnlyDictionary All = new Dictionary { [MapMode.Battles] = new MapModeDescriptor(MapMode.Battles, RegisterLabel("KayceePvP.MapMode.Battles", "WITH BATTLES", "COM BATALHAS"), FixedMap.BuildRows, 15, 15, MapCompletionPolicy.Boss, 3, new Dictionary { [0] = "Refinamento", [1] = "Totem", [2] = "Expansao" }), [MapMode.NoBattles] = new MapModeDescriptor(MapMode.NoBattles, RegisterLabel("KayceePvP.MapMode.NoBattles", "WITHOUT BATTLES", "SEM BATALHAS"), NoBattleMap.BuildRows, 19, 15, MapCompletionPolicy.RouteExit, 6, new Dictionary { [0] = "Refinamento", [1] = "Totem", [2] = "Replicacao" }), [MapMode.SymmetricForge] = new MapModeDescriptor(MapMode.SymmetricForge, RegisterLabel("KayceePvP.MapMode.SymmetricForge", "SYMMETRIC FORGE", "FORJA SIMÉTRICA"), SymmetricForgeMap.BuildRows, 20, 15, MapCompletionPolicy.Boss, null, null), [MapMode.PeltDraft] = new MapModeDescriptor(MapMode.PeltDraft, RegisterLabel("KayceePvP.MapMode.PeltDraft", "PELT DRAFT", "DRAFT DE PELES"), PeltDraftMap.BuildRows, 5, 15, MapCompletionPolicy.RouteExit, null, null), [MapMode.BalancedPath] = new MapModeDescriptor(MapMode.BalancedPath, RegisterLabel("KayceePvP.MapMode.BalancedPath", "TRIBE FIGHT", "TRIBE FIGHT"), BalancedPathMap.BuildRows, 19, 15, MapCompletionPolicy.RouteExit, null, null), [MapMode.OpenDraftPath] = new MapModeDescriptor(MapMode.OpenDraftPath, RegisterLabel("KayceePvP.MapMode.OpenDraftPath", "OPEN DRAFT", "DRAFT ABERTO"), OpenDraftPathMap.BuildRows, 3, 15, MapCompletionPolicy.RouteExit, null, null), [MapMode.BalancedRoute] = new MapModeDescriptor(MapMode.BalancedRoute, RegisterLabel("KayceePvP.MapMode.BalancedRoute", "BALANCED ROUTE", "CAMINHO BALANCEADO"), BalancedRouteMap.BuildRows, 20, 15, MapCompletionPolicy.RouteExit, null, null) }; private static string RegisterLabel(string id, string english, string portuguese) { PvpLocalization.L(id, english, portuguese); return id; } public static bool TryGet(MapMode mode, out MapModeDescriptor descriptor) { return All.TryGetValue(mode, out descriptor); } public static bool IsSupported(int rawMapMode) { return All.ContainsKey((MapMode)rawMapMode); } public static MapModeDescriptor Get(MapMode mode) { return All[mode]; } } internal sealed class MapModeDescriptor { private readonly string _uiLabelKey; public MapMode Mode { get; } public string UiLabel => PvpLocalization.Get(_uiLabelKey); public Func>> BuildRows { get; } public int RowCount { get; } public int DuelThreshold { get; } public MapCompletionPolicy CompletionPolicy { get; } public int? ForkGridY { get; } public IReadOnlyDictionary RouteNamesByGridX { get; } public MapModeDescriptor(MapMode mode, string uiLabelKey, Func>> buildRows, int rowCount, int duelThreshold, MapCompletionPolicy completionPolicy, int? forkGridY, IReadOnlyDictionary routeNamesByGridX) { Mode = mode; _uiLabelKey = uiLabelKey; BuildRows = buildRows; RowCount = rowCount; DuelThreshold = duelThreshold; CompletionPolicy = completionPolicy; ForkGridY = forkGridY; RouteNamesByGridX = routeNamesByGridX; } } internal class NoBattleExitNodeData : NodeData { public override string PrefabPath => "Prefabs/Map/MapNodesPart1/MapNode_CardBattle"; } internal static class NoBattleMap { public const int RowCount = 19; public static PredefinedNodes BuildFirstRegionMap() { PredefinedNodes obj = ScriptableObject.CreateInstance(); obj.nodeRows = BuildRows(); return obj; } public static List> BuildRows() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Expected O, but got Unknown //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Expected O, but got Unknown //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Expected O, but got Unknown //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Expected O, but got Unknown //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Expected O, but got Unknown //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Expected O, but got Unknown //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Expected O, but got Unknown //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Expected O, but got Unknown //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Expected O, but got Unknown //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Expected O, but got Unknown //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Expected O, but got Unknown //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Expected O, but got Unknown //IL_027d: Unknown result type (might be due to invalid IL or missing references) //IL_0283: Expected O, but got Unknown List> list = new List>(); list.Add(Row(new NodeData())); list.Add(Row((NodeData)new TradePeltsNodeData())); list.Add(Row((NodeData)Choices((CardChoicesType)2))); list.Add(Row((NodeData)Choices((CardChoicesType)0))); list.Add(Row((NodeData)Choices((CardChoicesType)2))); list.Add(Row((NodeData)Choices((CardChoicesType)0))); list.Add(Row((NodeData)new CardRemoveNodeData(), (NodeData)Choices((CardChoicesType)2), (NodeData)Choices((CardChoicesType)1))); list.Add(Row((NodeData)Choices((CardChoicesType)1), (NodeData)new BuildTotemNodeData(), (NodeData)new CardRemoveNodeData())); list.Add(Row((NodeData)Choices((CardChoicesType)0), (NodeData)Choices((CardChoicesType)2), (NodeData)Choices((CardChoicesType)0))); list.Add(Row((NodeData)new CardStatBoostNodeData(), (NodeData)new CardRemoveNodeData(), (NodeData)new CopyCardNodeData())); list.Add(Row((NodeData)Choices((CardChoicesType)0), (NodeData)new DeckTrialNodeData(), (NodeData)Choices((CardChoicesType)2))); list.Add(Row((NodeData)new CardStatBoostNodeData(), (NodeData)Choices((CardChoicesType)2), (NodeData)new DuplicateMergeNodeData())); list.Add(Row((NodeData)new CardStatBoostNodeData(), (NodeData)Choices((CardChoicesType)2), (NodeData)Choices((CardChoicesType)0))); list.Add(Row((NodeData)Choices((CardChoicesType)2), (NodeData)Choices((CardChoicesType)2), (NodeData)Choices((CardChoicesType)2))); list.Add(Row((NodeData)new DeckTrialNodeData(), (NodeData)new BuildTotemNodeData(), (NodeData)Choices((CardChoicesType)1))); list.Add(Row((NodeData)Choices((CardChoicesType)1), new NoBattleExitNodeData(), new NoBattleExitNodeData())); list.Add(Row((NodeData)Choices((CardChoicesType)1), new NoBattleExitNodeData(), new NoBattleExitNodeData())); list.Add(Row((NodeData)new CardMergeNodeData(), new NoBattleExitNodeData(), new NoBattleExitNodeData())); list.Add(Row(new NoBattleExitNodeData(), new NoBattleExitNodeData(), new NoBattleExitNodeData())); return list; } private static List Row(params NodeData[] nodes) { return new List(nodes); } private static CardChoicesNodeData Choices(CardChoicesType type) { //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_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown return new CardChoicesNodeData { choicesType = type }; } } internal static class NoBattleRouteCompletionFlow { internal static void RegisterLocalizedText() { PvpLocalization.L("KayceePvP.Duel.WaitingForOpponentRouteTitle", "WAITING FOR THE OPPONENT TO FINISH THE ROUTE", "AGUARDANDO O OPONENTE TERMINAR O CAMINHO"); } public static void OnLocalRouteCompleted() { if (!PvpRunState.LocalRouteCompletedNoBattles) { PvpRunState.LocalRouteCompletedNoBattles = true; RunState run = RunState.Run; int? obj; if (run == null) { obj = null; } else { DeckInfo playerDeck = run.playerDeck; obj = ((playerDeck != null) ? new int?(((CardCollectionInfo)playerDeck).Cards.Count) : ((int?)null)); } int num = obj ?? (-1); Plugin.Log.LogInfo((object)$"PvP (sem batalhas): rota local concluída ({num} cartas) - iniciando o handoff compartilhado."); Telemetry.Log("route_completed_no_battles", $"route={PvpRunState.Route} deckSize={num}"); ((MonoBehaviour)LobbySession.Instance).StartCoroutine(PvpCardCreationFlow.WaitForPeerDeckThenDuel(PvpLocalization.Get("KayceePvP.Duel.WaitingForOpponentRouteTitle"))); } } } internal class NoBattleWaitingScreen : MonoBehaviour { private GameObject _canvasRoot; private Text _titleText; private Text _subtitleText; private Text _deckListText; private Text _cancelHintText; private bool _errorMode; private bool _prevCursorVisible; private CursorLockMode _prevCursorLockState; private bool _prevInteractionDisabled; private bool _prevInteractionCursorEnabled; public static NoBattleWaitingScreen Instance { get; private set; } internal static void RegisterLocalizedText() { PvpLocalization.L("KayceePvP.Waiting.YourDeckSubtitle", "Your current deck:", "Seu baralho atual:"); PvpLocalization.L("KayceePvP.Waiting.EmptyDeck", "(empty deck)", "(baralho vazio)"); PvpLocalization.L("KayceePvP.Waiting.CancelHint", "PRESS ESC TO CANCEL", "PRESSIONE ESC PARA CANCELAR"); } private void Awake() { Instance = this; BuildUI(); _canvasRoot.SetActive(false); } private void Update() { if (_errorMode && (Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)271) || Input.GetKeyDown((KeyCode)32))) { Plugin.Log.LogInfo((object)"PvP handoff: peer-wait error screen - player requested quit."); LobbySession.Instance?.QuitGameAfterMatch(); } } public static void Show(List deck, string title = null) { Instance?.ShowInternal(deck, title ?? PvpLocalization.Get("KayceePvP.Duel.WaitingForOpponentTitle")); } public static void ShowError(string message) { Instance?.ShowErrorInternal(message); } public static void Hide() { Instance?.HideInternal(); } private void ShowInternal(List deck, string title) { _errorMode = false; _titleText.text = title; ((Component)_subtitleText).gameObject.SetActive(true); _subtitleText.text = PvpLocalization.Get("KayceePvP.Waiting.YourDeckSubtitle"); ((Component)_deckListText).gameObject.SetActive(true); _deckListText.text = BuildDeckListText(deck); _cancelHintText.text = PvpLocalization.Get("KayceePvP.Waiting.CancelHint"); ((Component)_cancelHintText).gameObject.SetActive(true); ApplyOverlayState(); } private void ShowErrorInternal(string message) { _errorMode = true; _titleText.text = message; ((Component)_subtitleText).gameObject.SetActive(false); ((Component)_deckListText).gameObject.SetActive(false); ((Component)_cancelHintText).gameObject.SetActive(false); ApplyOverlayState(); } private void ApplyOverlayState() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (!_canvasRoot.activeSelf) { _prevCursorVisible = Cursor.visible; _prevCursorLockState = Cursor.lockState; InteractionCursor instance = Singleton.Instance; _prevInteractionDisabled = (Object)(object)instance != (Object)null && instance.InteractionDisabled; _prevInteractionCursorEnabled = (Object)(object)instance == (Object)null || ((Behaviour)instance).enabled; } Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; InteractionCursor instance2 = Singleton.Instance; if ((Object)(object)instance2 != (Object)null) { instance2.InteractionDisabled = true; instance2.SetEnabled(false); } _canvasRoot.SetActive(true); } private void HideInternal() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) _errorMode = false; if (_canvasRoot.activeSelf) { Cursor.visible = _prevCursorVisible; Cursor.lockState = _prevCursorLockState; InteractionCursor instance = Singleton.Instance; if ((Object)(object)instance != (Object)null) { instance.SetEnabled(_prevInteractionCursorEnabled); instance.InteractionDisabled = _prevInteractionDisabled; } _canvasRoot.SetActive(false); } } private static string BuildDeckListText(List deck) { if (deck == null || deck.Count == 0) { return PvpLocalization.Get("KayceePvP.Waiting.EmptyDeck"); } return string.Join("\n", from c in deck group c by c.DisplayedNameLocalized into g orderby g.Key select (g.Count() <= 1) ? g.Key : $"{g.Key} x{g.Count()}"); } private void BuildUI() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: 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_0229: Unknown result type (might be due to invalid IL or missing references) _canvasRoot = new GameObject("KayceePvP_NoBattleWaitingCanvas"); _canvasRoot.transform.SetParent(((Component)this).transform, false); Canvas obj = _canvasRoot.AddComponent(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 31000; CanvasScaler obj2 = _canvasRoot.AddComponent(); obj2.uiScaleMode = (ScaleMode)1; obj2.referenceResolution = new Vector2(1920f, 1080f); _canvasRoot.AddComponent(); RectTransform component = CreateImage("Backdrop", _canvasRoot.transform, new Color(0f, 0f, 0f, 0.85f)).GetComponent(); component.anchorMin = Vector2.zero; component.anchorMax = Vector2.one; component.offsetMin = Vector2.zero; component.offsetMax = Vector2.zero; _titleText = CreateText("Title", _canvasRoot.transform, new Vector2(0f, 320f), new Vector2(700f, 80f), PvpLocalization.Get("KayceePvP.Duel.WaitingForOpponentTitle"), 24, (TextAnchor)4); ((Graphic)_titleText).color = Color.white; _subtitleText = CreateText("Subtitle", _canvasRoot.transform, new Vector2(0f, 270f), new Vector2(700f, 30f), "Seu baralho atual:", 16, (TextAnchor)4); _deckListText = CreateText("DeckList", _canvasRoot.transform, new Vector2(0f, 0f), new Vector2(700f, 500f), "", 15, (TextAnchor)1); ((Graphic)_deckListText).color = new Color(0.9f, 0.9f, 0.9f); _cancelHintText = CreateText("CancelHint", _canvasRoot.transform, new Vector2(0f, -380f), new Vector2(700f, 30f), PvpLocalization.Get("KayceePvP.Waiting.CancelHint"), 14, (TextAnchor)4); ((Graphic)_cancelHintText).color = new Color(0.7f, 0.7f, 0.7f); ((Component)_cancelHintText).gameObject.SetActive(false); } private static GameObject CreateImage(string name, Transform parent, Color color) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) }); val.transform.SetParent(parent, false); Image obj = val.AddComponent(); ((Graphic)obj).color = color; ((Graphic)obj).raycastTarget = true; return val; } private Text CreateText(string name, Transform parent, Vector2 anchoredPos, Vector2 size, string content, int fontSize, TextAnchor alignment) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) }); val.transform.SetParent(parent, false); RectTransform component = val.GetComponent(); component.anchorMin = new Vector2(0.5f, 0.5f); component.anchorMax = new Vector2(0.5f, 0.5f); component.pivot = new Vector2(0.5f, 0.5f); component.sizeDelta = size; component.anchoredPosition = anchoredPos; Text obj = val.AddComponent(); obj.text = content; obj.font = LobbyUI.ResolveGameFont(); obj.fontSize = fontSize; obj.alignment = alignment; ((Graphic)obj).color = Color.white; obj.horizontalOverflow = (HorizontalWrapMode)0; return obj; } } public enum OpenDraftTier { Rabbit, Wolf, Golden } public static class OpenDraftBudget { public const int RabbitLimit = 8; public const int WolfLimit = 4; public const int GoldenLimit = 2; public static OpenDraftTier TierForPower(int powerLevel) { if (powerLevel < 4) { if (powerLevel < 3) { return OpenDraftTier.Rabbit; } return OpenDraftTier.Wolf; } return OpenDraftTier.Golden; } public static int Limit(OpenDraftTier tier) { return tier switch { OpenDraftTier.Wolf => 4, OpenDraftTier.Rabbit => 8, _ => 2, }; } public static bool CanAdd(IReadOnlyCollection selected, OpenDraftTier tier) { int num = 0; foreach (OpenDraftTier item in selected) { if (item == tier) { num++; } } return num < Limit(tier); } public static bool IsComplete(IReadOnlyCollection selected) { if (selected.Count != 14) { return false; } int num = 0; int num2 = 0; int num3 = 0; using (IEnumerator enumerator = selected.GetEnumerator()) { while (enumerator.MoveNext()) { switch (enumerator.Current) { case OpenDraftTier.Rabbit: num++; break; case OpenDraftTier.Wolf: num2++; break; default: num3++; break; } } } if (num == 8 && num2 == 4) { return num3 == 2; } return false; } } public static class OpenDraftCardPool { public const int PageSize = 8; private const int SeedDomain = 500000; public static bool IsAllowedName(string cardName) { if (cardName != "Lice" && cardName != "Hrokkall") { return PvpCardPoolRules.IsAllowedByName(cardName, null); } return false; } public static int ComputeSeed(int sharedMapSeed, int currentNodeId) { return sharedMapSeed + 500000 + currentNodeId; } public static List DeriveOrder(IReadOnlyList cardNames, int sharedMapSeed, int currentNodeId = 0) { List list = new List(cardNames); Random random = new Random(ComputeSeed(sharedMapSeed, currentNodeId)); for (int num = list.Count - 1; num > 0; num--) { int num2 = random.Next(num + 1); List list2 = list; int index = num; List list3 = list; int index2 = num2; string value = list[num2]; string value2 = list[num]; list2[index] = value; list3[index2] = value2; } return list; } public static int PageCount(int totalCount) { if (totalCount > 0) { return (totalCount + 8 - 1) / 8; } return 0; } public static int ClampPage(int requestedPage, int totalCount) { int num = PageCount(totalCount); if (num == 0) { return 0; } return Math.Max(0, Math.Min(requestedPage, num - 1)); } public static List GetPage(IReadOnlyList orderedNames, int pageIndex) { int num = pageIndex * 8; if (pageIndex < 0 || num >= orderedNames.Count) { return new List(); } return orderedNames.Skip(num).Take(8).ToList(); } } internal sealed class OpenDraftNodeData : SpecialNodeData { public override string PrefabPath => ((MapElementData)new CardChoicesNodeData { choicesType = (CardChoicesType)2 }).PrefabPath; } internal static class OpenDraftPathMap { public const int RowCount = 3; public static List> BuildRows() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown List> list = new List>(); list.Add(Row(new NodeData())); list.Add(Row((NodeData)new OpenDraftNodeData())); list.Add(Row(new NoBattleExitNodeData())); return list; } private static List Row(params NodeData[] nodes) { return new List(nodes); } } public static class OpenDraftSelectionState { public const int TargetCount = 14; public static bool CanFinalize(IReadOnlyCollection selected) { return selected.Count == 14; } public static List ApplyPick(IReadOnlyList selected, string cardName) { List list = new List(selected); int num = list.IndexOf(cardName); if (num >= 0) { list.RemoveAt(num); return list; } if (list.Count >= 14) { return list; } list.Add(cardName); return list; } public static List UndoLast(IReadOnlyList selected) { if (selected.Count == 0) { return new List(selected); } List list = new List(selected); list.RemoveAt(list.Count - 1); return list; } public static bool IsSelected(IReadOnlyCollection selected, string cardName) { return selected.Contains(cardName); } } public static class PeltDraftAcquiredCards { public const string MarkerId = "pelt_draft_trader_acquired"; public static void Mark(CardInfo card) { //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_002d: Expected O, but got Unknown if (!((Object)(object)card == (Object)null) && !IsMarked(card)) { card.Mods.Add(new CardModificationInfo { singletonId = "pelt_draft_trader_acquired" }); } } public static bool IsMarked(CardInfo card) { if ((Object)(object)card != (Object)null && card.Mods != null) { return card.Mods.Exists((CardModificationInfo m) => m.singletonId == "pelt_draft_trader_acquired"); } return false; } public static List OnlyMarked(List cards) { cards.RemoveAll((CardInfo c) => !IsMarked(c)); return cards; } } internal static class PeltDraftMap { public const int RowCount = 5; public static List> BuildRows() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown List> list = new List>(); list.Add(Row(new NodeData())); list.Add(Row((NodeData)new TradePeltsNodeData())); list.Add(Row((NodeData)new TradePeltsNodeData())); list.Add(Row((NodeData)new TradePeltsNodeData())); list.Add(Row(new NoBattleExitNodeData())); return list; } private static List Row(params NodeData[] nodes) { return new List(nodes); } } public static class PeltDraftPlan { public const int StartingRabbit = 3; public const int StartingWolf = 2; public const int StartingGolden = 1; public const int SecondRabbit = 3; public const int SecondWolf = 2; public const int SecondGolden = 1; public const int FinalRabbit = 2; public const int FirstVisitCards = 6; public const int SecondVisitCards = 6; public const int ThirdVisitCards = 2; public const int FinalDeckSize = 14; public static bool IsForbiddenOfferName(string cardName) { if (!(cardName == "Lice")) { return cardName == "Hrokkall"; } return true; } } public static class PeltDraftTraderSeed { public static int Compute(int sharedMapSeed, int regionTier, int currentNodeId) { return sharedMapSeed + regionTier * 100 + currentNodeId; } } [BepInPlugin("jgs.inscryption.kayceepvp", "KayceePvP", "0.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "jgs.inscryption.kayceepvp"; public const string PluginName = "KayceePvP"; public const string PluginVersion = "0.1.0"; internal static ManualLogSource Log; internal static ConfigEntry MultiplayerEnabled; internal static ConfigEntry CreatorCreditEnabled; internal static ConfigEntry FeedbackShortcut; internal static ConfigEntry PvpItemsEnabled; internal static ConfigEntry RelayHost; internal static ConfigEntry RelayPort; private Harmony _harmony; private void Awake() { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; MultiplayerEnabled = ((BaseUnityPlugin)this).Config.Bind("General", "EnableMultiplayer", true, "Liga/desliga o lobby e o duelo em rede do KayceePvP. Desligado, o jogo roda normal (vanilla + as trocas de carta permanentes)."); CreatorCreditEnabled = ((BaseUnityPlugin)this).Config.Bind("General", "ShowCreatorCredit", true, "Mostra periodicamente a assinatura discreta 'KayceePvP · Designed by YNASTRA' em um canto da tela."); PvpItemsEnabled = ((BaseUnityPlugin)this).Config.Bind("General", "EnablePvpItems", false, "Libera o uso de um subconjunto auditado de itens consumíveis (bateria, cofrinho, lupa, garrafa de carta, garrafa de goo) durante o duelo PvP. Itens que afetam o tabuleiro do oponente continuam bloqueados independente disso."); FeedbackShortcut = ((BaseUnityPlugin)this).Config.Bind("General", "FeedbackShortcut", FeedbackConfig.DefaultShortcut, "Tecla que abre a página de feedback/sugestões do KayceePvP no navegador (padrão F10 - F12 conflita com o print padrão da Steam). Não coleta nem envia nenhum log/dado automaticamente."); RelayHost = ((BaseUnityPlugin)this).Config.Bind("Online", "RelayHost", "kayceepvp.duckdns.org", "Hostname público do relay KayceePvP. Deve corresponder ao certificado TLS; deixe vazio para desativar o online."); RelayPort = ((BaseUnityPlugin)this).Config.Bind("Online", "RelayPort", 443, "Porta TLS do relay KayceePvP (normalmente 443)."); LobbySession.RegisterLocalizedText(); DuelHud.RegisterLocalizedText(); PvpCardCreationFlow.RegisterLocalizedText(); NoBattleRouteCompletionFlow.RegisterLocalizedText(); NoBattleWaitingScreen.RegisterLocalizedText(); FinalMatchScreen.RegisterLocalizedText(); ProtocolDesyncOverlay.RegisterLocalizedText(); OpenDraftFlow.RegisterLocalizedText(); LobbyUI.RegisterLocalizedText(); CreatorCreditOverlay.RegisterLocalizedText(); Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); ((Component)this).gameObject.AddComponent(); ((Component)this).gameObject.AddComponent(); ((Component)this).gameObject.AddComponent(); ((Component)this).gameObject.AddComponent(); ((Component)this).gameObject.AddComponent(); ((Component)this).gameObject.AddComponent(); ((Component)this).gameObject.AddComponent(); ((Component)this).gameObject.AddComponent(); _harmony = new Harmony("jgs.inscryption.kayceepvp"); _harmony.PatchAll(); PvpDialogueOverrides.Initialize(); CardEdits.Apply(); _ = PvpCardMutations.PackRatMarker; TvfsStarterDeckAllowlist.Apply(); CobaiasStarterDeck.Apply(); SelfCheck.RunOwnershipMatrix(); SelfCheck.RunStarterDeckGateMatrix(); SelfCheck.RunStarterDeckAllowlistMatrix(); SelfCheck.RunConvergenceCheck(); SelfCheck.RunRematchProtocolMatrix(); SelfCheck.RunPvpItemAllowlistMatrix(); Log.LogInfo((object)"KayceePvP 0.1.0 loaded."); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } internal class ProtocolDesyncOverlay : MonoBehaviour { private GameObject _root; private Text _text; private bool _shown; internal static void RegisterLocalizedText() { PvpLocalization.L("KayceePvP.Protocol.DesyncMessage", "SYNCHRONIZATION LOST\nCLOSE AND RESTART THE MATCH\n\nPRESS ENTER OR SPACE TO CLOSE THE GAME", "SINCRONIZAÇÃO PERDIDA\nFECHE E REINICIE A PARTIDA\n\nPRESSIONE ENTER OU ESPAÇO PARA FECHAR O JOGO"); PvpLocalization.L("KayceePvP.Protocol.ConnectionLostMessage", "CONNECTION TO THE OPPONENT LOST\nCLOSE AND RESTART THE MATCH\n\nPRESS ENTER OR SPACE TO CLOSE THE GAME", "CONEXÃO COM O OPONENTE PERDIDA\nFECHE E REINICIE A PARTIDA\n\nPRESSIONE ENTER OU ESPAÇO PARA FECHAR O JOGO"); } private void Awake() { BuildUI(); _root.SetActive(false); } private void Update() { DuelSession instance = DuelSession.Instance; bool flag = (Object)(object)instance != (Object)null && instance.ProtocolDesync; bool flag2 = (Object)(object)instance != (Object)null && instance.ConnectionLost; bool flag3 = flag || flag2; if (flag3 != _shown) { _root.SetActive(flag3); _shown = flag3; if (flag3) { if (flag) { _text.text = PvpLocalization.Get("KayceePvP.Protocol.DesyncMessage"); Plugin.Log.LogError((object)("Duel: PROTOCOL_DESYNC_OVERLAY_SHOWN | reason=" + instance.ProtocolDesyncReason)); } else { _text.text = PvpLocalization.Get("KayceePvP.Protocol.ConnectionLostMessage"); Plugin.Log.LogError((object)("Duel: CONNECTION_LOST_OVERLAY_SHOWN | reason=" + instance.ConnectionLostReason)); } } } if (flag3 && (Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)271) || Input.GetKeyDown((KeyCode)32))) { Plugin.Log.LogInfo((object)(flag ? "Duel: PROTOCOL_DESYNC_QUIT_REQUESTED" : "Duel: CONNECTION_LOST_QUIT_REQUESTED")); LobbySession.Instance?.QuitGameAfterMatch(); } } private void BuildUI() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Expected O, but got Unknown //IL_00c5: 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_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Expected O, but got Unknown //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) _root = new GameObject("KayceePvP_ProtocolDesyncOverlay"); _root.transform.SetParent(((Component)this).transform, false); Canvas obj = _root.AddComponent(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 33000; _root.AddComponent(); CanvasScaler obj2 = _root.AddComponent(); obj2.uiScaleMode = (ScaleMode)1; obj2.referenceResolution = new Vector2(1920f, 1080f); GameObject val = new GameObject("Backdrop", new Type[1] { typeof(RectTransform) }); val.transform.SetParent(_root.transform, false); Image obj3 = val.AddComponent(); ((Graphic)obj3).color = new Color(0f, 0f, 0f, 0.92f); ((Graphic)obj3).raycastTarget = true; RectTransform component = val.GetComponent(); component.anchorMin = Vector2.zero; component.anchorMax = Vector2.one; component.offsetMin = Vector2.zero; component.offsetMax = Vector2.zero; GameObject val2 = new GameObject("Text", new Type[1] { typeof(RectTransform) }); val2.transform.SetParent(val.transform, false); RectTransform component2 = val2.GetComponent(); component2.anchorMin = new Vector2(0.1f, 0.35f); component2.anchorMax = new Vector2(0.9f, 0.65f); component2.offsetMin = Vector2.zero; component2.offsetMax = Vector2.zero; _text = val2.AddComponent(); _text.font = LobbyUI.ResolveGameFont(); _text.fontSize = 28; _text.alignment = (TextAnchor)4; ((Graphic)_text).color = Color.white; ((Graphic)_text).raycastTarget = false; _text.horizontalOverflow = (HorizontalWrapMode)1; _text.verticalOverflow = (VerticalWrapMode)1; _text.text = PvpLocalization.Get("KayceePvP.Protocol.DesyncMessage"); } } internal static class PvpCardCreationFlow { internal readonly struct F6DeckLineEntry { internal readonly string RequestedName; internal readonly int Count; internal F6DeckLineEntry(string requestedName, int count) { RequestedName = requestedName; Count = count; } } internal enum PeerWaitOutcome { Waiting, Success, Cancelled, Disconnected, TimedOut } internal const int F6DeckMinCards = 4; internal const int F6DeckMaxCards = 40; internal const string F6DeckConfigFileName = "KayceePvP.F6Deck.txt"; internal static readonly string[] DefaultF6DeckLines = new string[4] { "# COBAIAS default (arquivo de config ausente/recriado automaticamente)", "PackRat x4", "MealWorm x3", "Skink x3" }; private static readonly Regex F6DeckQuantityPattern = new Regex("^(?.+)[xX*](?-?\\d+)$", RegexOptions.Compiled); private static readonly float PeerDeckWaitTimeoutSeconds = ResolvePeerDeckWaitTimeoutSeconds(); private const float DefaultPeerDeckWaitTimeoutSeconds = 21600f; private static bool WaitOrShowRecoverableErrorFailed; internal static void RegisterLocalizedText() { PvpLocalization.L("KayceePvP.Duel.F6InvalidDeckOnScreen", "F6 DEBUG: INVALID DECK - SEE LogOutput.log", "F6 DEBUG: DECK INVALIDO - VER LogOutput.log"); PvpLocalization.L("KayceePvP.Duel.ErrorConnectionLostDuringWait", "CONNECTION TO THE OPPONENT LOST.\n{0}", "CONEXÃO COM O OPONENTE PERDIDA.\n{0}"); PvpLocalization.L("KayceePvP.Duel.ErrorWaitCancelled", "WAIT CANCELLED.", "ESPERA CANCELADA."); PvpLocalization.L("KayceePvP.Duel.WaitingForOpponentTitle", "WAITING FOR OPPONENT", "AGUARDANDO OPONENTE"); PvpLocalization.L("KayceePvP.Duel.ErrorTimeoutWaitingDeck", "TIMED OUT WAITING FOR THE OPPONENT TO FINISH PREPARING.", "TEMPO ESGOTADO ESPERANDO O OPONENTE TERMINAR A PREPARAÇÃO."); PvpLocalization.L("KayceePvP.Duel.ErrorTimeoutWaitingReady", "TIMED OUT WAITING FOR THE OPPONENT.", "TEMPO ESGOTADO ESPERANDO O OPONENTE."); PvpLocalization.L("KayceePvP.Duel.ReadyPrompt", "PRESS SPACE WHEN YOU'RE READY", "PRESSIONE ESPAÇO QUANDO ESTIVER PRONTO"); PvpLocalization.L("KayceePvP.Duel.WaitingOpponentReadyPrompt", "WAITING FOR OPPONENT... (ESC TO CANCEL)", "AGUARDANDO OPONENTE... (ESC PARA CANCELAR)"); PvpLocalization.L("KayceePvP.Duel.GetReadyLine", "GET READY. NOW YOUR DECK WILL HAVE TO JUSTIFY ALL THAT EFFORT.", "PREPAREM-SE. AGORA O BARALHO TERÁ DE JUSTIFICAR TODO ESSE ESFORÇO."); PvpLocalization.L("KayceePvP.Duel.PressEnterOrSpaceToClose", "PRESS ENTER OR SPACE TO CLOSE THE GAME", "PRESSIONE ENTER OU ESPAÇO PARA FECHAR O JOGO"); } public static void BeginAfterFirstBoss() { SaveManager.SaveToFile(true); SceneLoader.Load("Part1_Sanctum"); } public static void StartFixedDeckDuelDebug() { if (!PvpRunState.IsPvpRun || RunState.Run == null) { Plugin.Log.LogWarning((object)"Duel debug (fixed deck): no active PvP run - connect through the lobby and start a run first."); return; } if (PvpRunState.IsDuelActive || PvpRunState.BossDefeated) { Plugin.Log.LogWarning((object)"Duel debug (fixed deck): already past this point in the flow this run."); return; } ITransport transport = LobbySession.Instance.Transport; if (transport == null || !transport.IsConnected) { string text = ((transport == null) ? "Transport==null" : ("IsConnected=false LastError='" + transport.LastError + "'")); Plugin.Log.LogWarning((object)("Duel debug (fixed deck): no live lobby connection (" + text + ") - this tool is for exercising the real LanTransport, not solo mode.")); return; } string text2 = ResolveF6DeckConfigPath(); string parseError; List list = ParseF6DeckFileLines(ReadOrCreateF6DeckFileLines(text2), out parseError); if (list == null) { FailF6DeckDebug("Duel debug (F6 deck): erro de formato em '" + text2 + "': " + parseError + " NENHUMA carta foi trocada (fail-closed)."); return; } if (list.Count == 0) { FailF6DeckDebug("Duel debug (F6 deck): '" + text2 + "' nao tem nenhuma linha de carta valida (so comentarios/linhas vazias?). NENHUMA carta foi trocada (fail-closed)."); return; } if (!TryResolveF6Deck(list, out var deck, out var resolutionError, out var requestedSummary, out var resolvedSummary)) { FailF6DeckDebug("Duel debug (F6 deck): " + resolutionError + " Deck solicitado: [" + requestedSummary + "]. NENHUMA carta foi trocada (fail-closed)."); return; } PvpRunState.BossDefeated = true; ((CardCollectionInfo)RunState.Run.playerDeck).Cards.Clear(); foreach (CardInfo item in deck) { ((CardCollectionInfo)RunState.Run.playerDeck).AddCard(item); } Plugin.Log.LogInfo((object)$"Duel debug (F6 deck): arquivo='{text2}' solicitado=[{requestedSummary}] resolvido=[{resolvedSummary}] total={deck.Count} cartas - handing off to the shared peer-deck wait (sending is now done inside it), then the real ready screen + DuelStart.Begin() (map/boss/Death Card skipped)."); ((MonoBehaviour)LobbySession.Instance).StartCoroutine(WaitForPeerDeckThenDuel()); } internal static string ResolveF6DeckConfigPath() { return Path.Combine(Paths.ConfigPath, "KayceePvP.F6Deck.txt"); } internal static List ReadOrCreateF6DeckFileLines(string path) { if (File.Exists(path)) { return new List(File.ReadAllLines(path)); } Plugin.Log.LogWarning((object)("Duel debug (F6 deck): '" + path + "' nao existe - criando com o default COBAIAS (PackRat x4, MealWorm x3, Skink x3).")); try { string directoryName = Path.GetDirectoryName(path); if (!string.IsNullOrEmpty(directoryName)) { Directory.CreateDirectory(directoryName); } File.WriteAllLines(path, DefaultF6DeckLines); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Duel debug (F6 deck): falha ao criar '" + path + "' (" + ex.Message + ") - usando o default apenas em memoria nesta tentativa.")); } return new List(DefaultF6DeckLines); } internal static List ParseF6DeckFileLines(IEnumerable rawLines, out string parseError) { parseError = null; List list = new List(); Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); int num = 0; foreach (string rawLine in rawLines) { num++; string text = rawLine ?? string.Empty; int num2 = text.IndexOf('#'); if (num2 >= 0) { text = text.Substring(0, num2); } text = text.Trim(); if (text.Length == 0) { continue; } Match match = F6DeckQuantityPattern.Match(text); string text2; int result; if (match.Success) { text2 = match.Groups["name"].Value.Trim(); string value = match.Groups["qty"].Value; if (!int.TryParse(value, out result)) { parseError = $"linha {num} ('{rawLine.Trim()}'): quantidade '{value}' nao e um numero valido."; return null; } } else { text2 = text; result = 1; } if (text2.Length == 0) { parseError = $"linha {num} ('{rawLine.Trim()}'): nome de carta vazio."; return null; } if (result <= 0) { parseError = $"linha {num} ('{rawLine.Trim()}'): quantidade deve ser positiva, lida={result} para '{text2}'."; return null; } if (dictionary.TryGetValue(text2, out var value2)) { F6DeckLineEntry f6DeckLineEntry = list[value2]; list[value2] = new F6DeckLineEntry(f6DeckLineEntry.RequestedName, f6DeckLineEntry.Count + result); } else { dictionary[text2] = list.Count; list.Add(new F6DeckLineEntry(text2, result)); } } return list; } internal static bool ValidateF6DeckSize(int totalRequested, out string sizeError) { sizeError = null; if (totalRequested < 4) { sizeError = $"deck com {totalRequested} carta(s) no total - minimo exigido e {4}."; return false; } if (totalRequested > 40) { sizeError = $"deck com {totalRequested} carta(s) no total - maximo permitido e {40} (limite defensivo contra erro de digitacao)."; return false; } return true; } internal static bool TryResolveF6Deck(List entries, out List deck, out string resolutionError, out string requestedSummary, out string resolvedSummary) { deck = null; resolvedSummary = null; requestedSummary = string.Join(", ", entries.Select((F6DeckLineEntry e) => $"{e.RequestedName} x{e.Count}")); if (!ValidateF6DeckSize(entries.Sum((F6DeckLineEntry e) => e.Count), out resolutionError)) { return false; } List source = CardManager.AllCardsCopy.Where((CardInfo c) => (Object)(object)c != (Object)null && !string.IsNullOrEmpty(((Object)c).name)).ToList(); List list = new List(); List list2 = new List(); foreach (F6DeckLineEntry entry in entries) { CardInfo val = ((IEnumerable)source).FirstOrDefault((Func)((CardInfo c) => string.Equals(((Object)c).name, entry.RequestedName, StringComparison.OrdinalIgnoreCase))); if ((Object)(object)val == (Object)null) { resolutionError = "nome de carta invalido: '" + entry.RequestedName + "' (nao encontrado via CardLoader.GetCardByName/pool de cartas - confira a grafia contra CardInfo.name)."; return false; } for (int num = 0; num < entry.Count; num++) { list.Add(CardLoader.GetCardByName(((Object)val).name)); } list2.Add($"{((Object)val).name} x{entry.Count}"); } resolvedSummary = string.Join(", ", list2); deck = list; return true; } private static void FailF6DeckDebug(string message) { Plugin.Log.LogError((object)message); if ((Object)(object)Singleton.Instance != (Object)null) { Singleton.Instance.ShowMessage(PvpLocalization.English("KayceePvP.Duel.F6InvalidDeckOnScreen"), (Emotion)0, (LetterAnimation)0, (Speaker)0, (string[])null); } } private static float ResolvePeerDeckWaitTimeoutSeconds() { string environmentVariable = Environment.GetEnvironmentVariable("KAYCEEPVP_PEER_WAIT_TIMEOUT_SECONDS"); if (!string.IsNullOrEmpty(environmentVariable) && float.TryParse(environmentVariable, out var result) && result > 0f) { return result; } return 21600f; } internal static PeerWaitOutcome EvaluatePeerWaitState(bool conditionMet, bool cancelRequested, bool hasRealTransport, bool transportConnected, float elapsedSeconds, float timeoutSeconds) { if (conditionMet) { return PeerWaitOutcome.Success; } if (cancelRequested) { return PeerWaitOutcome.Cancelled; } if (hasRealTransport && !transportConnected) { return PeerWaitOutcome.Disconnected; } if (elapsedSeconds > timeoutSeconds) { return PeerWaitOutcome.TimedOut; } return PeerWaitOutcome.Waiting; } private static string DescribeWaitFailureReason(PeerWaitOutcome outcome, LobbySession session, string timeoutMessage) { return outcome switch { PeerWaitOutcome.Disconnected => PvpLocalization.Format("KayceePvP.Duel.ErrorConnectionLostDuringWait", session.Transport?.LastError), PeerWaitOutcome.Cancelled => PvpLocalization.Get("KayceePvP.Duel.ErrorWaitCancelled"), _ => timeoutMessage, }; } internal static IEnumerator WaitForPeerDeckThenDuel(string waitingTitle = null) { LobbySession session = LobbySession.Instance; session.SendLocalDeck(((CardCollectionInfo)RunState.Run.playerDeck).Cards); NoBattleWaitingScreen.Show(((CardCollectionInfo)RunState.Run.playerDeck).Cards, waitingTitle ?? PvpLocalization.Get("KayceePvP.Duel.WaitingForOpponentTitle")); bool hasRealTransport = session.Transport != null; float startTime = Time.realtimeSinceStartup; while (true) { bool transportConnected = session.Transport != null && session.Transport.IsConnected; PeerWaitOutcome peerWaitOutcome = EvaluatePeerWaitState(session.PeerDeck != null, Input.GetKeyDown((KeyCode)27), hasRealTransport, transportConnected, Time.realtimeSinceStartup - startTime, PeerDeckWaitTimeoutSeconds); switch (peerWaitOutcome) { case PeerWaitOutcome.Waiting: yield return null; break; default: { string text = DescribeWaitFailureReason(peerWaitOutcome, session, PvpLocalization.Get("KayceePvP.Duel.ErrorTimeoutWaitingDeck")); Plugin.Log.LogError((object)$"PvP handoff: {peerWaitOutcome} waiting for the peer's deck (LastError='{session.Transport?.LastError}') - showing the recoverable error screen."); Telemetry.Log("peer_deck_wait_failed", "reason=" + peerWaitOutcome.ToString().ToLowerInvariant()); NoBattleWaitingScreen.ShowError(text + "\n\n" + PvpLocalization.Get("KayceePvP.Duel.PressEnterOrSpaceToClose")); yield break; } case PeerWaitOutcome.Success: Plugin.Log.LogInfo((object)$"PvP handoff: peer deck received ({session.PeerDeck.Length} cards) - continuing to the duel."); NoBattleWaitingScreen.Hide(); if ((Object)(object)Singleton.Instance != (Object)null) { Plugin.Log.LogInfo((object)"PvP handoff: already in the gameplay scene - skipping the scene reload, going straight to the ready screen."); yield return ShowReadyScreenAndCountdown(); } else { Plugin.Log.LogInfo((object)"PvP handoff: no TurnManager yet - loading Part1_Cabin for the duel."); yield return ContinueIntoDuelScene(); } yield break; } } } private static IEnumerator WaitOrShowRecoverableError(LobbySession session, Func conditionMet) { bool hasRealTransport = session.Transport != null; float startTime = Time.realtimeSinceStartup; while (true) { bool transportConnected = session.Transport != null && session.Transport.IsConnected; PeerWaitOutcome peerWaitOutcome = EvaluatePeerWaitState(conditionMet(), Input.GetKeyDown((KeyCode)27), hasRealTransport, transportConnected, Time.realtimeSinceStartup - startTime, PeerDeckWaitTimeoutSeconds); switch (peerWaitOutcome) { case PeerWaitOutcome.Waiting: yield return null; continue; case PeerWaitOutcome.Success: WaitOrShowRecoverableErrorFailed = false; yield break; } string text = DescribeWaitFailureReason(peerWaitOutcome, session, PvpLocalization.Get("KayceePvP.Duel.ErrorTimeoutWaitingReady")); Plugin.Log.LogError((object)$"PvP handoff: {peerWaitOutcome} waiting for the peer during the ready phase (LastError='{session.Transport?.LastError}') - showing the recoverable error screen."); Telemetry.Log("peer_ready_wait_failed", "reason=" + peerWaitOutcome.ToString().ToLowerInvariant()); NoBattleWaitingScreen.ShowError(text + "\n\n" + PvpLocalization.Get("KayceePvP.Duel.PressEnterOrSpaceToClose")); WaitOrShowRecoverableErrorFailed = true; yield break; } } public static IEnumerator TakePhotoAndContinueToPvp(string dialogueId, string animationId) { LeshyAnimationController.Instance.LeftArm.PlayAnimation(animationId); yield return (object)new WaitForSeconds(1.5f); yield return Singleton.Instance.PlayDialogueEvent(dialogueId, (MessageAdvanceMode)1, (EventIntersectMode)0, (string[])null, (Action)null); Singleton.Instance.SetHidden(true); yield return (object)new WaitForSeconds(0.25f); if (Random.value > 0.8f) { Singleton.Instance.PlayCameraAnim("refocus_medium"); } LeshyAnimationController.Instance.LeftArm.SetTrigger("photo_flare"); yield return (object)new WaitForSeconds(1f); AudioSource obj = AudioController.Instance.PlaySound2D("camera_flash_gameover", (MixerGroup)0, 0.85f, 0f, (Pitch)null, (Repetition)null, (Randomization)null, (Distortion)null, false); ((Object)((Component)obj).gameObject).name = "flashSound"; Object.DontDestroyOnLoad((Object)(object)((Component)obj).gameObject); AudioController.Instance.StopAllLoops(); Singleton.Instance.Effects.GetEffect().SetColor(GameColors.Instance.nearWhite); ((ScreenEffect)Singleton.Instance.Effects.GetEffect()).SetIntensity(1f, 100f); yield return (object)new WaitForSeconds(2f); ApplyCreatedCardToDeck(); ((ScreenEffect)Singleton.Instance.Effects.GetEffect()).SetIntensity(0f, 2f); ((MonoBehaviour)LobbySession.Instance).StartCoroutine(WaitForPeerDeckThenDuel()); } private static IEnumerator ContinueIntoDuelScene() { SaveManager.SaveToFile(true); AsyncOperation asyncOp = SceneLoader.StartAsyncLoad("Part1_Cabin"); asyncOp.allowSceneActivation = false; yield return (object)new WaitForSeconds(6f); SceneLoader.CompleteAsyncLoad(asyncOp); Plugin.Log.LogInfo((object)"PvP handoff: Part1_Cabin loaded - waiting for TurnManager before starting the duel."); yield return (object)new WaitUntil((Func)(() => (Object)(object)Singleton.Instance != (Object)null)); Plugin.Log.LogInfo((object)"PvP handoff: TurnManager found - showing the ready screen before the duel."); yield return ShowReadyScreenAndCountdown(); } internal static IEnumerator ShowReadyScreenAndCountdown() { LobbySession session = LobbySession.Instance; DuelSession.Instance.PrepareForMatch(PvpRunState.MatchId); LobbySession.Instance.ResetBattleFlowForMatch(PvpRunState.MatchId, "PvpCardCreationFlow.ShowReadyScreenAndCountdown (real ready phase)"); session.BeginDuelReadyPhase(); try { yield return WaitForTextDisplayerReady(); bool textDisplayerAvailable = (Object)(object)Singleton.Instance != (Object)null; if (!textDisplayerAvailable) { Telemetry.Log("ready_ui_fallback", "TextDisplayer unavailable at duel handoff - auto-readying without a visual prompt."); Plugin.Log.LogWarning((object)"PvP: TextDisplayer not available at duel handoff - auto-readying after a short delay instead of waiting on Space."); } if (!session.LocalDuelReady) { if (textDisplayerAvailable) { Singleton.Instance.ShowMessage(PvpLocalization.English("KayceePvP.Duel.ReadyPrompt"), (Emotion)0, (LetterAnimation)0, (Speaker)0, (string[])null); yield return (object)new WaitUntil((Func)(() => session.LocalDuelReady)); } else { yield return (object)new WaitForSeconds(1.5f); session.MarkLocalDuelReady(); } } if (!session.CountdownStarted) { if (textDisplayerAvailable) { Singleton.Instance.ShowMessage(PvpLocalization.English("KayceePvP.Duel.WaitingOpponentReadyPrompt"), (Emotion)0, (LetterAnimation)0, (Speaker)0, (string[])null); } yield return WaitOrShowRecoverableError(session, () => session.CountdownStarted); if (WaitOrShowRecoverableErrorFailed) { yield break; } } for (int i = 5; i >= 1; i--) { if (textDisplayerAvailable) { Singleton.Instance.ShowMessage(i.ToString(), (Emotion)0, (LetterAnimation)0, (Speaker)0, (string[])null); } yield return (object)new WaitForSeconds(1f); } if (textDisplayerAvailable) { Singleton.Instance.ShowMessage(PvpLocalization.English("KayceePvP.Duel.GetReadyLine"), (Emotion)0, (LetterAnimation)0, (Speaker)0, (string[])null); } yield return (object)new WaitForSeconds(1.75f); if (textDisplayerAvailable) { Singleton.Instance.Clear(); } session.MarkLocalCountdownDone(); yield return WaitOrShowRecoverableError(session, () => session.DuelBegun); if (!WaitOrShowRecoverableErrorFailed && !PvpRunState.IsDuelActive) { Plugin.Log.LogInfo((object)"PvP: duel_begin confirmed by host protocol - starting the duel now."); DuelStart.Begin(); } } finally { session.EndDuelReadyPhase(); } } private static IEnumerator WaitForTextDisplayerReady() { float deadline = Time.realtimeSinceStartup + 3f; while ((Object)(object)Singleton.Instance == (Object)null && Time.realtimeSinceStartup < deadline) { yield return null; } } private static void ApplyCreatedCardToDeck() { List deathCardMods = SaveManager.SaveFile.deathCardMods; if (deathCardMods == null || deathCardMods.Count == 0) { Plugin.Log.LogWarning((object)"Expected a freshly-created death card mod but found none - deck unchanged."); return; } CardModificationInfo val = deathCardMods[deathCardMods.Count - 1]; CardInfo val2 = CardLoader.CreateDeathCard(val); ((CardCollectionInfo)RunState.Run.playerDeck).AddCard(val2); Plugin.Log.LogInfo((object)("Added created card '" + val.nameReplacement + "' to the PvP run's deck.")); Telemetry.Log("death_card", $"name={val.nameReplacement} atk={val.attackAdjustment} hp={val.healthAdjustment} " + string.Format("abilities=[{0}] bonesCost={1} bloodCost={2} ", string.Join("+", val.abilities), val.bonesCostAdjustment, val.bloodCostAdjustment) + "route=" + PvpRunState.Route); } } internal static class PvpCardMutations { internal sealed class CardMutationRule { internal readonly string CardName; internal readonly string DisplayName; internal readonly string Marker; internal readonly string[] LegacyMarkers; internal readonly List AbilitiesToAdd; internal readonly List AbilitiesToNegate; internal CardMutationRule(string cardName, string displayNameEnglish, string displayNamePortuguese, string marker, string[] legacyMarkers, List abilitiesToAdd, List abilitiesToNegate) { CardName = cardName; DisplayName = PvpLocalization.L((cardName == "PackRat") ? "KayceePvP.Card.CuriousRat" : "KayceePvP.Card.AlphaAmoeba", displayNameEnglish, displayNamePortuguese); Marker = marker; LegacyMarkers = legacyMarkers ?? Array.Empty(); AbilitiesToAdd = abilitiesToAdd ?? new List(); AbilitiesToNegate = abilitiesToNegate ?? new List(); } } internal static readonly CardMutationRule PackRatRule; internal static readonly CardMutationRule AmoebaRule; internal static readonly CardMutationRule[] Rules; internal static string PackRatMarker => PackRatRule.Marker; static PvpCardMutations() { PackRatRule = new CardMutationRule("PackRat", "Curious Rat", "Rato Curioso", "KayceePvP_PackRatCurioso", new string[1] { "KayceePvP_PackRatAmorphous" }, new List { (Ability)7 }, new List { (Ability)29, (Ability)31 }); AmoebaRule = new CardMutationRule("Amoeba", "Alpha Amoeba", "Ameba Alfa", "KayceePvP_AmebaAlfa", Array.Empty(), new List { (Ability)34 }, new List { (Ability)31 }); Rules = new CardMutationRule[2] { PackRatRule, AmoebaRule }; try { ApplyGlobalTemplates(); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Card mutation templates deferred: " + ex.Message)); } } internal static void ApplyToDeck(DeckInfo deck) { if (((deck != null) ? ((CardCollectionInfo)deck).Cards : null) == null) { return; } foreach (CardInfo card in ((CardCollectionInfo)deck).Cards) { ApplyToCard(card); } } internal static void ApplyGlobalTemplates() { CardMutationRule[] rules = Rules; foreach (CardMutationRule cardMutationRule in rules) { ReadOnlyCollection baseGameCards = CardManager.BaseGameCards; ApplyToCard((baseGameCards != null) ? CardExtensions.CardByName((IEnumerable)baseGameCards, cardMutationRule.CardName) : null); } } internal static CardMutationRule RuleForCardName(string cardName) { return Rules.FirstOrDefault((CardMutationRule r) => r.CardName == cardName); } internal static void ApplyToCard(CardInfo card) { if ((Object)(object)card == (Object)null) { return; } foreach (CardModificationInfo mod in card.Mods) { mod.abilities?.Remove((Ability)29); } CardMutationRule cardMutationRule = RuleForCardName(((Object)card).name); if (cardMutationRule != null) { CardExtensions.SetDisplayedName(card, cardMutationRule.DisplayName); Traverse.Create((object)card).Field("displayedNameLocId").Value = null; NormalizeMarkerMod(card.Mods, cardMutationRule); Plugin.Log.LogInfo((object)("Card mutation: " + cardMutationRule.CardName + " -> " + cardMutationRule.DisplayName + " (add=[" + string.Join("+", cardMutationRule.AbilitiesToAdd) + "] negate=[" + string.Join("+", cardMutationRule.AbilitiesToNegate) + "]).")); } } internal static void NormalizeMarkerMod(List mods, CardMutationRule rule) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown mods.RemoveAll((CardModificationInfo m) => m.singletonId == rule.Marker || Array.IndexOf(rule.LegacyMarkers, m.singletonId) >= 0); mods.Add(new CardModificationInfo { singletonId = rule.Marker, nameReplacement = rule.DisplayName, abilities = new List(rule.AbilitiesToAdd), negateAbilities = new List(rule.AbilitiesToNegate) }); } } [HarmonyPatch(typeof(AscensionSaveData), "NewRun")] internal static class PackRatMutationOnNewRunPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix() { PvpCardMutations.ApplyToDeck(RunState.Run?.playerDeck); } } [HarmonyPatch(typeof(DeckInfo), "AddCard")] internal static class PackRatMutationOnAddPatch { private static void Postfix(ref CardInfo __result) { PvpCardMutations.ApplyToCard(__result); } } [HarmonyPatch(typeof(CardLoader), "GetCardByName", new Type[] { typeof(string) })] internal static class PackRatMutationOnLoadPatch { private static void Postfix(ref CardInfo __result) { PvpCardMutations.ApplyToCard(__result); } } [HarmonyPatch(typeof(CardLoader), "GetDistinctCardsFromPool")] internal static class PackRatMutationOnDistinctPoolPatch { private static void Postfix(ref List __result) { if (__result == null) { return; } foreach (CardInfo item in __result) { PvpCardMutations.ApplyToCard(item); } } } internal static class PvpDialogueOverrides { private static readonly Dictionary EventOverrides = new Dictionary(); private static readonly Dictionary LiteralOverrides = new Dictionary(); private static bool _initialized; public static void Initialize() { if (!_initialized) { _initialized = true; BuildTraderOverrides(); BuildCampfireOverrides(); BuildWoodcarverOverrides(); Register("KayceePvPDeathcardIntro", Lines(PvpLocalization.L("KayceePvP.Dlg.DeathcardIntro.1", "LET'S SEE IF LUCK IS ON YOUR SIDE. USING YOUR DECK, WE'LL CREATE A NEW CARD. MAYBE IT'LL BE POWERFUL. MAYBE IT'LL BE GARBAGE.", "VEJAMOS SE A SORTE ESTÁ DO SEU LADO. USANDO SEU BARALHO, VAMOS CRIAR UMA NOVA CARTA. TALVEZ ELA SEJA PODEROSA. TALVEZ ELA SEJA UM LIXO."))); LiteralOverrides["Choose."] = PvpLocalization.L("KayceePvP.Dlg.WoodcarverChoose", "João pointed at the pieces.\n[c:bG]Just pick already, man. I'm already late for something else.[c:]", "João apontou para as peças.\n[c:bG]Escolhe logo, mano. Já tô atrasado pra outra coisa.[c:]"); string value = PvpLocalization.L("KayceePvP.Dlg.PushLuckPrompt", "Nicolas slowly pulled the pot closer. Push your luck, or save your creature?", "Nicolas aproximou lentamente o tacho. Vai insistir ou salvar sua criatura?"); LiteralOverrides["Push your luck? Or pull away?"] = value; LiteralOverrides["Push your luck further? Or run back?"] = value; LiteralOverrides["Recklessly continue?"] = value; Plugin.Log.LogInfo((object)$"PvpDialogueOverrides: initialized - {EventOverrides.Count} event override(s), {LiteralOverrides.Count} literal override(s)."); } } public static bool TryGetEventOverride(string id, out DialogueEvent dialogueEvent) { return EventOverrides.TryGetValue(id, out dialogueEvent); } public static bool TryGetLiteralOverride(string original, out string replacement) { return LiteralOverrides.TryGetValue(original, out replacement); } private static void BuildTraderOverrides() { Register("TutorialTradePeltsIntro", Lines(PvpLocalization.L("KayceePvP.Dlg.TraderTutorialIntro.1", "The trader examined your pelts with an almost offensive amount of attention.", "A comerciante examinou suas peles com uma atenção quase ofensiva."), PvpLocalization.L("KayceePvP.Dlg.TraderTutorialIntro.2", "In exchange, she offered creatures. Some might even be worth the price.", "Em troca, ofereceu criaturas. Algumas talvez até valham o preço."), PvpLocalization.L("KayceePvP.Dlg.TraderTutorialIntro.3", "Choose carefully. She certainly won't do it for you.", "Escolha com cuidado. Ela certamente não fará isso por você."))); Register("TraderIntro", Lines(PvpLocalization.L("KayceePvP.Dlg.TraderIntro.1", "Your pelts for my creatures. Try not to waste either.", "Suas peles por minhas criaturas. Tente não desperdiçar ambas."))); Register("TutorialTradePeltsCards", Lines(PvpLocalization.L("KayceePvP.Dlg.TraderTutorialCards.1", "Choose one creature per pelt. Eternity is not included in the offer.", "Escolha uma criatura para cada pele. A eternidade não está incluída na oferta."))); Register("TraderOutro", Lines(PvpLocalization.L("KayceePvP.Dlg.TraderOutro.1", "Deal's done. We'll see if your choices survive the road.", "Negócio encerrado. Veremos se suas escolhas sobrevivem ao caminho."))); } private static void BuildCampfireOverrides() { List> variants = Variants(Lines(PvpLocalization.L("KayceePvP.Dlg.CampfireIntro.A1", "You found Nicolas crouched by a campfire, stirring an empty pot.", "Você encontrou Nicolas agachado ao lado de uma fogueira, mexendo um tacho vazio."), PvpLocalization.L("KayceePvP.Dlg.CampfireIntro.A2", "He looked at your creatures, then at the pot.", "Ele olhou para suas criaturas, depois para o tacho."), PvpLocalization.L("KayceePvP.Dlg.CampfireIntro.A3", "\"[c:bG]Bring one of them near the fire. I'm definitely not waiting for you to get distracted.[c:]\"", "\"[c:bG]Pode chegar uma delas perto do fogo. Eu definitivamente não estou esperando você se distrair.[c:]\"")), Lines(PvpLocalization.L("KayceePvP.Dlg.CampfireIntro.B1", "Nicolas stood before the campfire, pot ready and far too much seasoning for someone who claimed he wasn't cooking.", "Nicolas estava diante da fogueira, com um tacho pronto e tempero demais para alguém que dizia não estar cozinhando."), PvpLocalization.L("KayceePvP.Dlg.CampfireIntro.B2", "\"[c:bG]Bring a creature closer. The flames will improve its [c:][c:bR][v:0][c:][c:bG].[c:]\"", "\"[c:bG]Traga uma criatura para perto. As chamas vão melhorar seu [c:][c:bR][v:0][c:][c:bG].[c:]\""), PvpLocalization.L("KayceePvP.Dlg.CampfireIntro.B3", "He smiled like someone who had already picked dinner.", "Ele sorriu como quem já havia escolhido o jantar."))); RegisterVariants("StatBoostIntro", variants); string text = PvpLocalization.L("KayceePvP.Dlg.PushLuckPrompt", "Nicolas slowly pulled the pot closer. Push your luck, or save your creature?", "Nicolas aproximou lentamente o tacho. Vai insistir ou salvar sua criatura?"); string text2 = PvpLocalization.L("KayceePvP.Dlg.CampfirePushLuck.B", "You looked away for half a second. Nicolas was already reaching for the seasoning.", "Você desviou o olhar por meio segundo. Nicolas já estava procurando o tempero."); List> variants2 = Variants(Lines(text), Lines(text2)); RegisterVariants("StatBoostPushLuck1", variants2); RegisterVariants("StatBoostPushLuck2", variants2); RegisterVariants("StatBoostPushLuck3", variants2); List> variants3 = Variants(Lines(PvpLocalization.L("KayceePvP.Dlg.CardEaten.A", "You pushed your luck. Nicolas immediately threw the [c:bR][v:0][c:] into the pot. A memorable decision, no doubt.", "Você insistiu. Nicolas imediatamente jogou o [c:bR][v:0][c:] no tacho. Uma decisão memorável, sem dúvida.")), Lines(PvpLocalization.L("KayceePvP.Dlg.CardEaten.B", "Nicolas stole the [c:bR][v:0][c:] while you weren't looking and ran off into the forest, chewing on the burnt remains.", "Nicolas roubou o [c:bR][v:0][c:] enquanto você não estava olhando e fugiu pela floresta mastigando os restos queimados."))); RegisterVariants("StatBoostCardEaten", variants3); List> variants4 = Variants(Lines(PvpLocalization.L("KayceePvP.Dlg.CardEatenBones.A", "Nicolas ate so fast he left the bones scattered everywhere. You picked them up.", "Nicolas comeu tão rápido que deixou os ossos espalhados. Você os recolheu.")), Lines(PvpLocalization.L("KayceePvP.Dlg.CardEatenBones.B", "Nicolas devoured everything and left only the bones. At least the disaster had some yield.", "Nicolas devorou tudo e largou apenas os ossos. Ao menos o desastre teve algum rendimento."))); RegisterVariants("StatBoostCardEatenBones", variants4); List> variants5 = Variants(Lines(PvpLocalization.L("KayceePvP.Dlg.CampfireOutro.A", "You pulled the [c:bR][v:1][c:] back before Nicolas could grab it. Your [c:bR][v:0][c:] had been improved by the flames.", "Você puxou o [c:bR][v:1][c:] de volta antes que Nicolas conseguisse agarrá-lo. Seu [c:bR][v:0][c:] havia sido aprimorado pelas chamas.")), Lines(PvpLocalization.L("KayceePvP.Dlg.CampfireOutro.B", "The [c:bR][v:1][c:] came out stronger. Nicolas stared at the empty pot, visibly disappointed.", "O [c:bR][v:1][c:] saiu mais forte. Nicolas encarou o tacho vazio, visivelmente decepcionado."))); RegisterVariants("StatBoostOutro", variants5); } private static void BuildWoodcarverOverrides() { List> variants = Variants(Lines(PvpLocalization.L("KayceePvP.Dlg.WoodcarverTutorialIntro.A1", "João Maionese showed up late, coughing behind an oak tree and carrying a bag full of handmade crafts.", "João Maionese apareceu atrasado, tossindo atrás de um carvalho e carregando uma sacola cheia de artesanato."), PvpLocalization.L("KayceePvP.Dlg.WoodcarverTutorialIntro.A2", "After C# went extinct as a programming language, he had to find another source of income.", "Depois que o C# foi extinto da programação, ele precisou arrumar outra fonte de renda."), PvpLocalization.L("KayceePvP.Dlg.WoodcarverTutorialIntro.A3", "\"[c:bG]Buy one of my Totems, man.[c:]\"", "\"[c:bG]Compra aí meus Totem, mano.[c:]\"")), Lines(PvpLocalization.L("KayceePvP.Dlg.WoodcarverTutorialIntro.B1", "João spread his wooden pieces on the ground and blew tobacco smoke in your face.", "João espalhou suas peças de madeira pelo chão e jogou fumaça de tabaco na sua cara."), PvpLocalization.L("KayceePvP.Dlg.WoodcarverTutorialIntro.B2", "\"[c:bG]Pick one already. My boss told me to sell everything before I go back.[c:]\"", "\"[c:bG]Escolhe uma aí. Meu patrão mandou vender tudo antes de eu voltar.[c:]\""))); RegisterVariants("TutorialWoodcarverIntro", variants); List> variants2 = Variants(Lines(PvpLocalization.L("KayceePvP.Dlg.WoodcarverReencounter.A1", "João Maionese showed up again, forty minutes late, with an excuse even more tangled than the last one.", "João Maionese surgiu novamente, quarenta minutos atrasado e com uma desculpa ainda mais enrolada que a anterior."), PvpLocalization.L("KayceePvP.Dlg.WoodcarverReencounter.A2", "He coughed, lit another cigarette, and laid the Totems out in front of you.", "Ele tossiu, acendeu outro cigarro e colocou os Totens diante de você.")), Lines(PvpLocalization.L("KayceePvP.Dlg.WoodcarverReencounter.B1", "You found João arguing on the phone while someone gave him new orders.", "Você encontrou João discutindo pelo telefone enquanto alguém lhe passava novas ordens."), PvpLocalization.L("KayceePvP.Dlg.WoodcarverReencounter.B2", "He hung up, blew smoke in your face, and said:", "Ele desligou, jogou fumaça na sua cara e falou:"), PvpLocalization.L("KayceePvP.Dlg.WoodcarverReencounter.B3", "\"[c:bG]Buy one of my Totems, man. I'm just doing what I was told.[c:]\"", "\"[c:bG]Compra aí meus Totem, mano. Tô fazendo o que mandaram.[c:]\""))); RegisterVariants("WoodcarverIntro", variants2); Register("TutorialWoodcarverIncomplete", Lines(PvpLocalization.L("KayceePvP.Dlg.WoodcarverIncomplete.1", "You accepted the piece João Maionese offered. On its own, it's completely useless. A deal worthy of him.", "Você aceitou a peça oferecida por João Maionese. Sozinha, ela não serve para absolutamente nada. Um negócio digno dele."))); Register("TutorialWoodcarverComplete", Lines(PvpLocalization.L("KayceePvP.Dlg.WoodcarverComplete.1", "João fit the two pieces together after trying three times on the wrong side.", "João encaixou as duas partes depois de tentar três vezes do lado errado."), PvpLocalization.L("KayceePvP.Dlg.WoodcarverComplete.2", "The [c:bR]Totem[c:] was finally complete.", "O [c:bR]Totem[c:] finalmente estava completo."), PvpLocalization.L("KayceePvP.Dlg.WoodcarverComplete.3", "The [c:bR][v:0][c:] sigil will now be granted to all of your [c:bR][v:1][c:] creatures.", "O sigilo [c:bR][v:0][c:] agora será concedido a todas as suas criaturas [c:bR][v:1][c:]."))); List> variants3 = Variants(Lines(PvpLocalization.L("KayceePvP.Dlg.WoodcarverOutro.A", "João gathered up his crafts, lit another cigarette, and walked off coughing into the forest. When you went to smoke, you realized he'd stolen your lighter.", "João recolheu o artesanato, acendeu outro cigarro e saiu tossindo pela floresta. Quando você foi fumar, percebeu que ele tinha roubado seu isqueiro.")), Lines(PvpLocalization.L("KayceePvP.Dlg.WoodcarverOutro.B", "When you looked away, João had vanished. Along with him, apparently, so had your lighter.", "Quando você desviou o olhar, João havia desaparecido. Junto com ele, aparentemente, também havia sumido o seu isqueiro."))); RegisterVariants("WoodcarverOutro", variants3); } private static void Register(string id, List mainLines) { EventOverrides[id] = BuildEvent(id, mainLines, null); } private static void RegisterVariants(string id, List> variants) { EventOverrides[id] = BuildEvent(id, variants[0], variants); } private static List Lines(params string[] texts) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) List list = new List(texts.Length); foreach (string text in texts) { list.Add(CustomLine.op_Implicit(text)); } return list; } private static List> Variants(params List[] variants) { return new List>(variants); } private static DialogueEvent BuildEvent(string id, List mainLines, List> repeatLines) { //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_0024: 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_0041: Expected O, but got Unknown //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown List speakers = new List { (Speaker)0 }; return new DialogueEvent { id = id, mainLines = new LineSet(mainLines.ConvertAll((CustomLine line) => ((CustomLine)(ref line)).ToLine(speakers, (Speaker)0))), repeatLines = (repeatLines?.ConvertAll((Converter, LineSet>)((List variant) => new LineSet(variant.ConvertAll((CustomLine line) => ((CustomLine)(ref line)).ToLine(speakers, (Speaker)0))))) ?? new List()), maxRepeatsBehaviour = (MaxRepeatsBehaviour)0, speakers = speakers }; } } internal static class PvpDuelItems { internal const string SquirrelBottleId = "SquirrelBottle"; private static List _preDuelItems; private static int _appliedMatchId; private static bool _hasAppliedMatch; internal static void ApplyForSeries(int matchId) { if (RunState.Run != null) { if (_preDuelItems == null) { _preDuelItems = new List(RunState.Run.consumables); } if (!_hasAppliedMatch || _appliedMatchId != matchId) { RunState.Run.consumables.Clear(); RunState.Run.consumables.Add("SquirrelBottle"); _appliedMatchId = matchId; _hasAppliedMatch = true; Plugin.Log.LogInfo((object)string.Format("PvP items: replaced preparation inventory with exactly one {0} for series {1}.", "SquirrelBottle", matchId)); } RefreshVisibleSlots(); } } internal static void RestorePreparationInventory() { if (_preDuelItems != null && RunState.Run != null) { RunState.Run.consumables.Clear(); RunState.Run.consumables.AddRange(_preDuelItems); _preDuelItems = null; _hasAppliedMatch = false; _appliedMatchId = 0; } } private static void RefreshVisibleSlots() { ItemsManager instance = Singleton.Instance; if ((Object)(object)instance == (Object)null) { return; } foreach (ConsumableItemSlot item in instance.Slots.OfType()) { if ((Object)(object)((ItemSlot)item).Item != (Object)null) { ((ItemSlot)item).DestroyItem(); } } instance.UpdateItems(true); } } public static class PvpItemAllowlist { public static readonly Type[] SafeTypes = new Type[5] { typeof(BatteryItem), typeof(PiggyBankItem), typeof(MagnifyingGlassItem), typeof(CardBottleItem), typeof(GooBottleItem) }; public static bool IsSafe(Type itemType) { if (itemType != null) { return Array.IndexOf(SafeTypes, itemType) >= 0; } return false; } } internal static class PvpLocalization { private readonly struct Entry { internal readonly string English; internal readonly string Portuguese; internal Entry(string english, string portuguese) { English = english; Portuguese = portuguese; } } internal static class Keys { internal const string LobbyNameLabel = "KayceePvP.Lobby.NameLabel"; internal const string LobbyCreateOnline = "KayceePvP.Lobby.CreateOnline"; internal const string LobbyInviteLabel = "KayceePvP.Lobby.InviteLabel"; internal const string LobbyJoinOnline = "KayceePvP.Lobby.JoinOnline"; internal const string LobbyHostLan = "KayceePvP.Lobby.HostLan"; internal const string LobbyIpLabel = "KayceePvP.Lobby.IpLabel"; internal const string LobbyPortLabel = "KayceePvP.Lobby.PortLabel"; internal const string LobbyJoinLan = "KayceePvP.Lobby.JoinLan"; internal const string LobbySoloDebug = "KayceePvP.Lobby.SoloDebug"; internal const string LobbyOnlineBetaNotice = "KayceePvP.Lobby.OnlineBetaNotice"; internal const string LobbyCancelAttempt = "KayceePvP.Lobby.CancelAttempt"; internal const string LobbyCloseEsc = "KayceePvP.Lobby.CloseEsc"; internal const string LobbyDisconnected = "KayceePvP.Lobby.Disconnected"; internal const string LobbyConnectedRunStarting = "KayceePvP.Lobby.ConnectedRunStarting"; internal const string LobbyConnected = "KayceePvP.Lobby.Connected"; internal const string LobbyOnlineRoomCreated = "KayceePvP.Lobby.OnlineRoomCreated"; internal const string LobbyHostingWaiting = "KayceePvP.Lobby.HostingWaiting"; internal const string LobbyConnecting = "KayceePvP.Lobby.Connecting"; internal const string LobbyErrorPrefix = "KayceePvP.Lobby.ErrorPrefix"; internal const string LobbyStarterDeck = "KayceePvP.Lobby.StarterDeck"; internal const string LobbyMapPrefix = "KayceePvP.Lobby.MapPrefix"; internal const string LobbyMapYouChoose = "KayceePvP.Lobby.MapYouChoose"; internal const string LobbyMapHostChoice = "KayceePvP.Lobby.MapHostChoice"; internal const string LobbyMapUnknown = "KayceePvP.Lobby.MapUnknown"; internal const string LobbyReadyStatusLine = "KayceePvP.Lobby.ReadyStatusLine"; internal const string LobbyReady = "KayceePvP.Lobby.Ready"; internal const string LobbyWaiting = "KayceePvP.Lobby.Waiting"; internal const string LobbySeedMatchLine = "KayceePvP.Lobby.SeedMatchLine"; internal const string LobbySeedMatchEmpty = "KayceePvP.Lobby.SeedMatchEmpty"; internal const string LobbyHostTag = "KayceePvP.Lobby.HostTag"; internal const string LobbyPeerTag = "KayceePvP.Lobby.PeerTag"; internal const string LobbyDefaultPlayer1 = "KayceePvP.Lobby.DefaultPlayer1"; internal const string LobbyDefaultPlayer2 = "KayceePvP.Lobby.DefaultPlayer2"; internal const string ErrorInvalidPort = "KayceePvP.Error.InvalidPort"; internal const string ErrorMissingIp = "KayceePvP.Error.MissingIp"; internal const string ErrorMalformedNetworkMessage = "KayceePvP.Error.MalformedNetworkMessage"; internal const string ErrorUnsupportedMapModeFromHost = "KayceePvP.Error.UnsupportedMapModeFromHost"; internal const string ErrorMapModeDivergence = "KayceePvP.Error.MapModeDivergence"; internal const string ErrorUnsupportedMapModeLocal = "KayceePvP.Error.UnsupportedMapModeLocal"; internal const string ErrorInvalidMapModeLocal = "KayceePvP.Error.InvalidMapModeLocal"; internal const string ErrorInvalidInviteCode = "KayceePvP.Error.InvalidInviteCode"; internal const string ErrorInviteCodeFormatInvalid = "KayceePvP.Error.InviteCodeFormatInvalid"; internal const string ErrorRelayNotConfigured = "KayceePvP.Error.RelayNotConfigured"; internal const string ErrorInvalidRelayPort = "KayceePvP.Error.InvalidRelayPort"; internal const string ErrorProtocolVersionMismatch = "KayceePvP.Error.ProtocolVersionMismatch"; internal const string ErrorBuildVersionMismatch = "KayceePvP.Error.BuildVersionMismatch"; internal const string ErrorOpponentDisconnected = "KayceePvP.Error.OpponentDisconnected"; internal const string RouteExplanation1 = "KayceePvP.Route.Explanation1"; internal const string RouteExplanation2 = "KayceePvP.Route.Explanation2"; internal const string RouteExplanation3 = "KayceePvP.Route.Explanation3"; internal const string WaitingForOpponentTitle = "KayceePvP.Duel.WaitingForOpponentTitle"; internal const string WaitingForOpponentRouteTitle = "KayceePvP.Duel.WaitingForOpponentRouteTitle"; internal const string ReadyPrompt = "KayceePvP.Duel.ReadyPrompt"; internal const string WaitingOpponentReadyPrompt = "KayceePvP.Duel.WaitingOpponentReadyPrompt"; internal const string GetReadyLine = "KayceePvP.Duel.GetReadyLine"; internal const string ErrorConnectionLostDuringWait = "KayceePvP.Duel.ErrorConnectionLostDuringWait"; internal const string ErrorWaitCancelled = "KayceePvP.Duel.ErrorWaitCancelled"; internal const string ErrorTimeoutWaitingDeck = "KayceePvP.Duel.ErrorTimeoutWaitingDeck"; internal const string ErrorTimeoutWaitingReady = "KayceePvP.Duel.ErrorTimeoutWaitingReady"; internal const string PressEnterOrSpaceToClose = "KayceePvP.Duel.PressEnterOrSpaceToClose"; internal const string F6InvalidDeckOnScreen = "KayceePvP.Duel.F6InvalidDeckOnScreen"; internal const string WaitingYourDeckSubtitle = "KayceePvP.Waiting.YourDeckSubtitle"; internal const string WaitingEmptyDeck = "KayceePvP.Waiting.EmptyDeck"; internal const string WaitingCancelHint = "KayceePvP.Waiting.CancelHint"; internal const string FinalWinnerLine = "KayceePvP.Final.WinnerLine"; internal const string FinalLoserLine = "KayceePvP.Final.LoserLine"; internal const string FinalScoreLine = "KayceePvP.Final.ScoreLine"; internal const string FinalInstructions = "KayceePvP.Final.Instructions"; internal const string FinalClosingGame = "KayceePvP.Final.ClosingGame"; internal const string FinalReturningToPreparation = "KayceePvP.Final.ReturningToPreparation"; internal const string FinalRematchUnavailable = "KayceePvP.Final.RematchUnavailable"; internal const string FinalRematchAccepted = "KayceePvP.Final.RematchAccepted"; internal const string FinalRematchRequestedByYou = "KayceePvP.Final.RematchRequestedByYou"; internal const string FinalRematchRequestedByPeer = "KayceePvP.Final.RematchRequestedByPeer"; internal const string ProtocolDesyncMessage = "KayceePvP.Protocol.DesyncMessage"; internal const string ConnectionLostMessage = "KayceePvP.Protocol.ConnectionLostMessage"; internal const string OpenDraftInitialFeedback = "KayceePvP.OpenDraft.InitialFeedback"; internal const string OpenDraftReturned = "KayceePvP.OpenDraft.Returned"; internal const string OpenDraftPickedFormat = "KayceePvP.OpenDraft.PickedFormat"; internal const string OpenDraftNoBudgetLeft = "KayceePvP.OpenDraft.NoBudgetLeft"; internal const string OpenDraftUndoDone = "KayceePvP.OpenDraft.UndoDone"; internal const string OpenDraftIncompleteBudget = "KayceePvP.OpenDraft.IncompleteBudget"; internal const string OpenDraftCounterLine = "KayceePvP.OpenDraft.CounterLine"; internal const string OpenDraftControlsLine = "KayceePvP.OpenDraft.ControlsLine"; internal const string OpenDraftTierRabbit = "KayceePvP.OpenDraft.TierRabbit"; internal const string OpenDraftTierWolf = "KayceePvP.OpenDraft.TierWolf"; internal const string OpenDraftTierGolden = "KayceePvP.OpenDraft.TierGolden"; internal const string CardMayonnaise = "KayceePvP.Card.Mayonnaise"; internal const string CardStepfather = "KayceePvP.Card.Stepfather"; internal const string CardBluePen = "KayceePvP.Card.BluePen"; internal const string CardCannabis = "KayceePvP.Card.Cannabis"; internal const string CardCuriousRat = "KayceePvP.Card.CuriousRat"; internal const string CardAlphaAmoeba = "KayceePvP.Card.AlphaAmoeba"; internal const string DuelBalanceLine = "KayceePvP.Duel.BalanceLine"; internal const string DuelSeriesLine = "KayceePvP.Duel.SeriesLine"; internal const string DuelYourTurn = "KayceePvP.Duel.YourTurn"; internal const string DuelOpponentTotem = "KayceePvP.Duel.OpponentTotem"; internal const string DuelOpponentTotemWithDetail = "KayceePvP.Duel.OpponentTotemWithDetail"; internal const string FeedbackHint = "KayceePvP.Feedback.Hint"; internal const string MapModeBattles = "KayceePvP.MapMode.Battles"; internal const string MapModeNoBattles = "KayceePvP.MapMode.NoBattles"; internal const string MapModeSymmetricForge = "KayceePvP.MapMode.SymmetricForge"; internal const string MapModePeltDraft = "KayceePvP.MapMode.PeltDraft"; internal const string MapModeBalancedPath = "KayceePvP.MapMode.BalancedPath"; internal const string MapModeOpenDraftPath = "KayceePvP.MapMode.OpenDraftPath"; internal const string MapModeBalancedRoute = "KayceePvP.MapMode.BalancedRoute"; } private static readonly Dictionary Catalog = new Dictionary(); public static string L(string id, string english, string portuguese) { if (Catalog.TryGetValue(id, out var value)) { if (value.English != english || value.Portuguese != portuguese) { LogWarningSafe("PvpLocalization: id '" + id + "' registered twice with DIFFERENT text - keeping the first registration."); } return value.English; } Catalog[id] = new Entry(english, portuguese); try { LocalizationManager.Translate("jgs.inscryption.kayceepvp", id, english, portuguese, (Language)5); } catch (Exception ex) { LogWarningSafe("PvpLocalization: LocalizationManager.Translate('" + id + "') threw (" + ex.GetType().Name + ": " + ex.Message + ") - KayceePvP's own catalog is still registered; only the InscryptionAPI-side lookup for non-ShowMessage/dialogue callers may be affected."); } return english; } private static void LogWarningSafe(string message) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)message); } } public static string Get(string id) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return ResolveForLanguage(Localization.CurrentLanguage, id); } public static string Format(string id, params object[] args) { return string.Format(Get(id), args); } public static string English(string id) { if (!Catalog.TryGetValue(id, out var value)) { return id; } return value.English; } internal static string ResolveForLanguage(Language language, string id) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Invalid comparison between Unknown and I4 if (!Catalog.TryGetValue(id, out var value)) { Plugin.Log.LogWarning((object)("PvpLocalization: Get() with unknown id '" + id + "'.")); return id; } if ((int)language != 5) { return value.English; } return value.Portuguese; } internal static IReadOnlyDictionary Snapshot() { Dictionary dictionary = new Dictionary(Catalog.Count); foreach (KeyValuePair item in Catalog) { dictionary[item.Key] = (item.Value.English, item.Value.Portuguese); } return dictionary; } } internal static class PvpOwnership { public static bool IsRemoteMirroredCard(PlayableCard card) { if (PvpRunState.IsDuelActive && (Object)(object)card != (Object)null) { return card.OpponentCard; } return false; } public static string AbsoluteOwnerOf(PlayableCard card) { if ((Object)(object)card == (Object)null || (Object)(object)LobbySession.Instance == (Object)null) { return "UNKNOWN"; } if (!CardIsHost(LobbySession.Instance.IsHost, !card.OpponentCard)) { return "PEER"; } return "HOST"; } public static bool CardIsHost(bool localIsHost, bool cardIsLocal) { return cardIsLocal == localIsHost; } public static string LocalAbsoluteSide() { if ((Object)(object)LobbySession.Instance == (Object)null) { return "UNKNOWN"; } if (!LobbySession.Instance.IsHost) { return "PEER"; } return "HOST"; } public static string LocalSideOf(PlayableCard card) { if ((Object)(object)card == (Object)null) { return "UNKNOWN"; } if (!card.OpponentCard) { return "PLAYER"; } return "OPPONENT"; } } internal static class PvpRoundState { public const int StartingCandles = 2; public static bool IsActive; public static int RoundNumber; public static int HostCandles; public static int PeerCandles; private static List _localDeckSnapshot; public static void BeginMatch() { IsActive = true; RoundNumber = 1; HostCandles = 2; PeerCandles = 2; _localDeckSnapshot = CloneDeck(((CardCollectionInfo)RunState.Run.playerDeck).Cards); } public static void EndMatch() { IsActive = false; _localDeckSnapshot = null; } public static void RestoreLocalDeckForNewRound() { DeckInfo playerDeck = RunState.Run.playerDeck; ((CardCollectionInfo)playerDeck).Cards.Clear(); foreach (CardInfo item in CloneDeck(_localDeckSnapshot)) { ((CardCollectionInfo)playerDeck).Cards.Add(item); } } private static List CloneDeck(List source) { List list = new List(source.Count); foreach (CardInfo item in source) { object obj = item.Clone(); list.Add((CardInfo)((obj is CardInfo) ? obj : null)); } return list; } public static bool ApplyRoundLoss(bool loserIsHost) { if (loserIsHost) { HostCandles--; } else { PeerCandles--; } if (HostCandles > 0) { return PeerCandles <= 0; } return true; } } internal static class PvpRunState { public static bool IsPvpRun; public const int FixedSeed = 20260727; public static int SharedMapSeed; public static int MatchId; public static string Route; public static bool IsDuelActive; public static bool ApplyingRemoteAction; public static float RunStartRealtime; public static bool BossDefeated; public static MapMode SelectedMapMode; public static bool LocalRouteCompletedNoBattles; public static bool BackspaceTutorialShown; public static bool HostGoesFirst => (uint)MatchId % 2u == 0; public static void Reset() { IsPvpRun = false; SharedMapSeed = 0; MatchId = 0; Route = null; IsDuelActive = false; ApplyingRemoteAction = false; RunStartRealtime = 0f; BossDefeated = false; SelectedMapMode = MapMode.Battles; LocalRouteCompletedNoBattles = false; BackspaceTutorialShown = false; PvpRoundState.EndMatch(); LobbyMatchState.EndMatch(); if ((Object)(object)LobbySession.Instance != (Object)null) { LobbySession.Instance.ResetBattleFlowForMatch(0, "PvpRunState.Reset (cancel/abandon)"); } } } internal sealed class RareRewardNodeData : ChooseRareCardNodeData { public override string PrefabPath => "Prefabs/Map/MapNodesPart1/MapNode_CardBattle"; } internal static class ResearchDump { public static void DumpAll() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("{\n"); AppendStarterDecks(stringBuilder); stringBuilder.Append(",\n"); AppendCardPool(stringBuilder); stringBuilder.Append(",\n"); AppendTotems(stringBuilder); stringBuilder.Append(",\n"); AppendConsumables(stringBuilder); stringBuilder.Append(",\n"); AppendEventNotes(stringBuilder); stringBuilder.Append("\n}"); string path = $"research_dump_{DateTime.Now:yyyyMMdd_HHmmss}.json"; string text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), path); File.WriteAllText(text, stringBuilder.ToString()); Plugin.Log.LogInfo((object)("ResearchDump: wrote " + text)); } private static void AppendStarterDecks(StringBuilder sb) { sb.Append(" \"starterDecks\": [\n"); List allData = ScriptableObjectLoader.AllData; for (int i = 0; i < allData.Count; i++) { StarterDeckInfo val = allData[i]; sb.Append(" {"); sb.Append("\"id\":\"" + Escape(((Object)val).name) + "\","); sb.Append("\"title\":\"" + Escape(val.title) + "\","); sb.Append($"\"cardCount\":{val.cards.Count},"); sb.Append($"\"sizeAfterInitialPeltTrade\":{val.cards.Count},"); sb.Append("\"cards\":[\n"); for (int j = 0; j < val.cards.Count; j++) { AppendCardDetail(sb, val.cards[j], " "); sb.Append((j < val.cards.Count - 1) ? ",\n" : "\n"); } sb.Append(" ]"); sb.Append((i < allData.Count - 1) ? "},\n" : "}\n"); } sb.Append(" ]"); } private static void AppendCardPool(StringBuilder sb) { List list = (from val in CardManager.AllCardsCopy where (Object)(object)val != (Object)null && !string.IsNullOrEmpty(((Object)val).name) && (int)val.temple == 0 orderby ((Object)val).name select val).ToList(); List allData = ScriptableObjectLoader.AllData; sb.Append(" \"natureCardPool\": [\n"); for (int num = 0; num < list.Count; num++) { CardInfo c = list[num]; List source = (from r in allData where r.likelyCards != null && r.likelyCards.Any((CardInfo rc) => (Object)(object)rc != (Object)null && ((Object)rc).name == ((Object)c).name) select ((Object)r).name).ToList(); sb.Append(" {"); AppendCardDetailFields(sb, c); sb.Append(",\"eligibleEvents\":[").Append(string.Join(",", c.metaCategories.Select((CardMetaCategory m) => $"\"{m}\""))).Append("]"); sb.Append(",\"likelyInRegions\":[").Append(string.Join(",", source.Select((string r) => "\"" + Escape(r) + "\""))).Append("]"); sb.Append(",\"exclusionFlags\":[").Append(string.Join(",", from f in ExclusionFlags(c) select "\"" + f + "\"")).Append("]"); sb.Append("}"); sb.Append((num < list.Count - 1) ? ",\n" : "\n"); } sb.Append(" ]"); } private static IEnumerable ExclusionFlags(CardInfo c) { if (c.traits.Contains((Trait)15)) { yield return "SatisfiesRingTrial(excluded from Boulder's Insect pool)"; } if (!c.metaCategories.Contains((CardMetaCategory)0)) { yield return "NotChoiceNodeEligible"; } if (c.traits.Contains((Trait)13)) { yield return "Pelt(not offered as a normal draft reward)"; } if (((Object)c).name == "!DEATHCARD_BASE") { yield return "DeathCardTemplate(never offered directly, only via CreateDeathCard)"; } } private static void AppendCardDetail(StringBuilder sb, CardInfo c, string indent) { sb.Append(indent).Append("{"); AppendCardDetailFields(sb, c); sb.Append("}"); } private static void AppendCardDetailFields(StringBuilder sb, CardInfo c) { sb.Append("\"name\":\"" + Escape(((Object)c).name) + "\","); sb.Append("\"displayedName\":\"" + Escape(c.DisplayedNameLocalized) + "\","); sb.Append($"\"attack\":{c.Attack},"); sb.Append($"\"health\":{c.Health},"); sb.Append($"\"bloodCost\":{c.BloodCost},"); sb.Append($"\"bonesCost\":{c.BonesCost},"); sb.Append($"\"energyCost\":{c.EnergyCost},"); sb.Append("\"tribes\":[").Append(string.Join(",", c.tribes.Select((Tribe t) => $"\"{t}\""))).Append("],"); sb.Append("\"abilities\":[").Append(string.Join(",", c.Abilities.Select((Ability a) => $"\"{a}\""))).Append("],"); sb.Append("\"traits\":[").Append(string.Join(",", c.traits.Select((Trait t) => $"\"{t}\""))).Append("]"); } private static void AppendTotems(StringBuilder sb) { //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) string[] source = new string[5] { "Bird", "Canine", "Hooved", "Reptile", "Insect" }; List list = AbilitiesUtil.GetLearnedAbilities(false, 0, 5, (AbilityMetaCategory)1).FindAll((Ability a) => AbilitiesUtil.GetInfo(a).metaCategories.Contains((AbilityMetaCategory)1)); List source2 = AbilitiesUtil.GetLearnedAbilities(false, 0, 5, (AbilityMetaCategory)1).Except(list).ToList(); sb.Append(" \"totems\": {\n"); sb.Append(" \"availableHeads\": [").Append(string.Join(",", source.Select((string h) => "\"" + h + "\""))).Append("],\n"); sb.Append(" \"availableBases\": [\n"); for (int num = 0; num < list.Count; num++) { Ability val = list[num]; AbilityInfo info = AbilitiesUtil.GetInfo(val); sb.Append(" {"); sb.Append($"\"ability\":\"{val}\","); sb.Append($"\"powerLevel\":{info.powerLevel},"); sb.Append("\"canStack\":" + info.canStack.ToString().ToLowerInvariant() + ","); sb.Append("\"riskyForDesync\":" + Telemetry.RiskyAbilities.Contains(val).ToString().ToLowerInvariant()); sb.Append("}"); sb.Append((num < list.Count - 1) ? ",\n" : "\n"); } sb.Append(" ],\n"); sb.Append(" \"excludedFromBasePool\": [").Append(string.Join(",", source2.Select((Ability a) => $"\"{a}\""))).Append("],\n"); sb.Append(" \"riskyForDesyncAbilities\": [").Append(string.Join(",", Telemetry.RiskyAbilities.Select((Ability a) => $"\"{a}\""))).Append("]\n"); sb.Append(" }"); } private static void AppendConsumables(StringBuilder sb) { List allData = ScriptableObjectLoader.AllData; sb.Append(" \"consumableItems\": [\n"); for (int i = 0; i < allData.Count; i++) { ConsumableItemData val = allData[i]; sb.Append(" {"); sb.Append("\"name\":\"" + Escape(((Object)val).name) + "\","); sb.Append($"\"powerLevel\":{val.powerLevel},"); sb.Append("\"regionSpecific\":" + val.regionSpecific.ToString().ToLowerInvariant() + ","); sb.Append("\"notRandomlyGiven\":" + val.notRandomlyGiven.ToString().ToLowerInvariant()); sb.Append((i < allData.Count - 1) ? "},\n" : "}\n"); } sb.Append(" ]"); } private static void AppendEventNotes(StringBuilder sb) { (string, string)[] array = new(string, string)[13] { ("CostChoice", "Part1CardChoiceGenerator.GenerateCostChoices builds [Blood:1,Blood:2,Blood:3,Bone], removes ONE at random if MechanicsConcept.Bones is learned (always true in PvP - ProgressionUnlockPatch), else always removes Bone. Actual card resolved later (CardSingleChoicesSequencer.CostChoiceChosen) via CardLoader.GetRandomChoosableCardWithCost/GetRandomChoosableBonesCard, plus a 10-26% chance of substituting a bonus Death Card (Part1CardChoiceGenerator.RollRandomDeathCardOfCost)."), ("TribeChoice", "GenerateTribeChoices offers 3 of the 5 non-region-dominant tribes (Bird/Canine/Hooved/Insect/Reptile only - never Random/crash-prone). Resolved via CardLoader.GetRandomChoosableCardOfTribe, fallback 'Amalgam' if the tribe's pool is empty."), ("Campfire", "CardStatBoostNodeData -> CardStatBoostSequencer (not separately researched this pass - stat-only, no card add/remove, safe reused as-is)."), ("BoneLord", "CardRemoveNodeData -> CardRemoveSequencer.RemoveSequence: player sacrifices 1 deck card (removed permanently). Pelt sacrifice = no boon. Goat sacrifice = BoonData.Type.StartingBones (bigger). Anything else = BoonData.Type.MinorStartingBones. Placed directly in FixedMap's Refinamento lane (row 5), dispatched by the real vanilla SpecialNodeHandler - no custom node wrapper."), ("CardMerge", "CardMergeNodeData -> CardMergeSequencer.MergeSequence: player picks host + sacrifice (sacrifice must have >=1 ability the host lacks); sacrifice permanently removed; host gains sacrifice's FULL current ability list (base+gained), NOT stats; duplicate non-canStack sigils silently absorbed (CardTriggerHandler.AddAbility). Placed directly in FixedMap's Refinamento lane (row 8), dispatched by the real vanilla SpecialNodeHandler - no custom node wrapper."), ("Woodcarver", "BuildTotemNodeData -> BuildTotemSequencer.BuildTotem: GenerateTotemChoices offers a mix of new heads/bases based on how many the player already has - confirmed that once ALL 5 heads are owned (RunSetup.GrantAllTotemTops grants them at run start for every PvP player), the head slot count computes to 0 and every single visit offers exactly 3 bases, with no code change needed to enforce 'no new heads' - it falls out of the existing formula. AutoAssembleTotem only auto-builds when tops.Count==1 (never true here), so every visit also opens the manual top+bottom re-assembly screen (any owned head + any owned base) after the new-piece pick."), ("CaveTrial", "DeckTrialNodeData -> DeckTrialSequencer (not separately researched this pass - single reward node, safe reused as-is)."), ("Trapper", "BuyPeltsNodeData -> BuyPeltsSequencer.BuyPelts: sells the player's Pelt cards for a currency ('weights'/bones), 3 pelts offered at prices scaling with region tier and the ExpensivePelts challenge, halved once the Trapper/Trader boss is defeated. No card added to the deck."), ("Trader", "TradePeltsNodeData -> TradePeltsSequencer.TradePelts (not separately researched this pass beyond confirming it is the vanilla 'trade a Pelt for a random card' node used both for the mandatory row-1 visit and Expansao's 2nd visit)."), ("Mochila", "GainConsumablesNodeData -> GainConsumablesSequencer.ReplenishConsumables: offers items one at a time from GenerateItemChoices until RunState.Run.consumables reaches MaxConsumables (3) or the node's own choice loop ends - in practice a single visit typically grants exactly 1 item since the deck already starts non-empty. Items persist in RunState.Run.consumables with no PvP-specific reset code found (confirmed via grep) - unused items carry into the duel, used ones stay used, matching the balance spec's 'one Mochila per lane, no restoration' requirement with zero code change needed."), ("Pedregulho", "BoulderChoiceNodeData -> BoulderChoiceSequencer.RewardSequence: NOT deck-neutral - always grants exactly 1 real card (66% 'PeltGolden', 34% an Insect-tribe card or 'MantisGod' fallback). Not used anywhere in FixedMap.cs (the 14-row simplified map has no Boulder node)."), ("Mycologists", "DuplicateMergeNodeData -> DuplicateMergeSequencer: fuses 2 copies of the SAME card the player already owns into 1 upgraded copy (net -1 card), only appears from region 2 onward per its own GenerationPrerequisiteConditions (WithinRegionIndexRange(1,max)) - confirmed via decompile this makes it NOT naturally available in region 0/Kaycee's Mod at all without a predefined-node override, and NOT used anywhere in FixedMap.cs (Totem/Expansao explicitly forbid it per spec)."), ("Goobert", "NOT a map node at all - confirmed via decompile grep: 'Goobert' only appears as a Cabin dialogue/story-event easter egg (StoryEvent.GooPlaneGoobertRevealed/Complete, a poke-the-jar interaction), with no GoobertNodeData/GoobertSequencer class anywhere in the assembly. The spec's 'no Goobert' requirement for Totem/Expansao is automatically satisfied - there is nothing to exclude.") }; sb.Append(" \"vanillaEventNotes\": {\n"); for (int i = 0; i < array.Length; i++) { sb.Append(" \"" + array[i].Item1 + "\": \"" + Escape(array[i].Item2) + "\""); sb.Append((i < array.Length - 1) ? ",\n" : "\n"); } sb.Append(" }"); } private static string Escape(string s) { if (!string.IsNullOrEmpty(s)) { return s.Replace("\\", "\\\\").Replace("\"", "\\\""); } return ""; } } internal static class RunSetup { private static readonly Tribe[] StartingTotemTribes; public static void GrantAllTotemTops() { //IL_0024: 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_0041: Unknown result type (might be due to invalid IL or missing references) if (RunState.Run == null) { Plugin.Log.LogWarning((object)"RunSetup: no active run - can't grant totem tops."); return; } Tribe[] startingTotemTribes = StartingTotemTribes; foreach (Tribe item in startingTotemTribes) { if (!RunState.Run.totemTops.Contains(item)) { RunState.Run.totemTops.Add(item); } } Plugin.Log.LogInfo((object)"RunSetup: granted starting totem heads for this run."); } static RunSetup() { Tribe[] array = new Tribe[5]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); StartingTotemTribes = (Tribe[])(object)array; } } internal static class SelfCheck { public static void RunOwnershipMatrix() { (bool, bool, bool, string)[] obj = new(bool, bool, bool, string)[4] { (true, true, true, "HOST process, own card (PLAYER side) -> card's absolute owner is HOST"), (true, false, false, "HOST process, mirrored card (OPPONENT side) -> card's absolute owner is PEER"), (false, true, false, "PEER process, own card (PLAYER side) -> card's absolute owner is PEER"), (false, false, true, "PEER process, mirrored card (OPPONENT side) -> card's absolute owner is HOST") }; int num = 0; (bool, bool, bool, string)[] array = obj; for (int i = 0; i < array.Length; i++) { (bool, bool, bool, string) tuple = array[i]; bool item = tuple.Item1; bool item2 = tuple.Item2; bool item3 = tuple.Item3; string item4 = tuple.Item4; bool flag = PvpOwnership.CardIsHost(item, item2); if (flag != item3) { num++; Plugin.Log.LogError((object)$"SelfCheck FAILED: {item4} - expected CardIsHost={item3}, got {flag}."); } } if (num == 0) { Plugin.Log.LogInfo((object)"SelfCheck: ownership matrix (HOST/PEER x local/remote card, 4/4 cases) passed."); } else { Plugin.Log.LogError((object)$"SelfCheck: ownership matrix FAILED {num}/4 cases - see above. This is the exact logic TotemAppliesOnlyToOwnersOwnCardsPatch and every ABSOLUTE_OWNER log line depend on."); } } public static void RunStarterDeckGateMatrix() { (bool, bool, string)[] obj = new(bool, bool, string)[2] { (true, true, "multiplayer ON -> override forces the deck-select screen open"), (false, false, "multiplayer OFF -> vanilla NumStarterDecksUnlocked runs untouched") }; int num = 0; (bool, bool, string)[] array = obj; for (int i = 0; i < array.Length; i++) { (bool, bool, string) tuple = array[i]; bool item = tuple.Item1; bool item2 = tuple.Item2; string item3 = tuple.Item3; bool flag = StarterDeckGate.ShouldOverride(item); if (flag != item2) { num++; Plugin.Log.LogError((object)$"SelfCheck FAILED: {item3} - expected ShouldOverride={item2}, got {flag}."); } } if (num == 0) { Plugin.Log.LogInfo((object)"SelfCheck: starter deck gate (multiplayer on/off, 2/2 cases) passed."); } else { Plugin.Log.LogError((object)$"SelfCheck: starter deck gate FAILED {num}/2 cases - see above."); } } public static void RunStarterDeckAllowlistMatrix() { string text = "tvflabs.inscryption.TVFsStarterDecks_"; (string, bool, bool, string)[] array = new(string, bool, bool, string)[11] { (text + "TrueAnts", true, true, "TrueAnts shown, multiplayer ON"), (text + "TrueBones", true, true, "TrueBones shown, multiplayer ON"), (text + "Pelts", true, true, "Pelts shown, multiplayer ON"), (text + "Tentacles", true, false, "Tentacles hidden, multiplayer ON"), (text + "SliderPuzzles", true, false, "SliderPuzzles hidden, multiplayer ON"), (text + "SideDecks", true, false, "SideDecks hidden, multiplayer ON"), (text + "Deathcards", true, false, "Deathcards hidden, multiplayer ON"), (text + "TalkingCards", true, false, "TalkingCards hidden, multiplayer ON"), ("Draft", true, true, "vanilla deck untouched, multiplayer ON"), (text + "Tentacles", false, true, "Tentacles shown, multiplayer OFF (external mod untouched)"), (text + "Deathcards", false, true, "Deathcards shown, multiplayer OFF (external mod untouched)") }; int num = 0; (string, bool, bool, string)[] array2 = array; for (int i = 0; i < array2.Length; i++) { (string, bool, bool, string) tuple = array2[i]; string item = tuple.Item1; bool item2 = tuple.Item2; bool item3 = tuple.Item3; string item4 = tuple.Item4; bool flag = TvfsStarterDeckAllowlist.ShouldShow(item, item2); if (flag != item3) { num++; Plugin.Log.LogError((object)$"SelfCheck FAILED: {item4} - expected ShouldShow={item3}, got {flag}."); } } if (num == 0) { Plugin.Log.LogInfo((object)$"SelfCheck: TVFsStarterDecks allowlist ({array.Length}/{array.Length} cases) passed."); } else { Plugin.Log.LogError((object)$"SelfCheck: TVFsStarterDecks allowlist FAILED {num}/{array.Length} cases - see above."); } } public static void RunConvergenceCheck() { int num = 0; if (typeof(PvpCardCreationFlow).GetMethod("WaitForPeerDeckThenDuel", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) == null) { num++; Plugin.Log.LogError((object)"SelfCheck FAILED: PvpCardCreationFlow.WaitForPeerDeckThenDuel (the shared convergence point for all 3 map modes) not found."); } if (typeof(NoBattleRouteCompletionFlow).GetMethod("WaitForPeerThenContinue", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) != null) { num++; Plugin.Log.LogError((object)"SelfCheck FAILED: NoBattleRouteCompletionFlow.WaitForPeerThenContinue still exists - the old duplicate PeerDeck wait was supposed to be removed, not left alongside the shared one."); } if (num == 0) { Plugin.Log.LogInfo((object)"SelfCheck: map-mode convergence (shared WaitForPeerDeckThenDuel entry point, no duplicate wait) passed."); } else { Plugin.Log.LogError((object)$"SelfCheck: map-mode convergence FAILED {num} case(s) - see above."); } } public static void RunRematchProtocolMatrix() { int failures = 0; Check("IsMatchingSeries: same id -> true", expected: true, RematchProtocol.IsMatchingSeries(42, 42)); Check("IsMatchingSeries: different id -> false", expected: false, RematchProtocol.IsMatchingSeries(42, 7)); Check("ShouldBroadcastAccept: peer only, not host -> false", expected: false, RematchProtocol.ShouldBroadcastAccept(isHost: false, alreadyAccepted: false, localRequested: true, peerRequested: true, transportConnectedOrSolo: true)); Check("ShouldBroadcastAccept: host, only local requested -> false", expected: false, RematchProtocol.ShouldBroadcastAccept(isHost: true, alreadyAccepted: false, localRequested: true, peerRequested: false, transportConnectedOrSolo: true)); Check("ShouldBroadcastAccept: host, only peer requested -> false", expected: false, RematchProtocol.ShouldBroadcastAccept(isHost: true, alreadyAccepted: false, localRequested: false, peerRequested: true, transportConnectedOrSolo: true)); Check("ShouldBroadcastAccept: host, both requested, connected -> true", expected: true, RematchProtocol.ShouldBroadcastAccept(isHost: true, alreadyAccepted: false, localRequested: true, peerRequested: true, transportConnectedOrSolo: true)); Check("ShouldBroadcastAccept: host, both requested, connection lost -> false", expected: false, RematchProtocol.ShouldBroadcastAccept(isHost: true, alreadyAccepted: false, localRequested: true, peerRequested: true, transportConnectedOrSolo: false)); Check("ShouldBroadcastAccept: host, both requested, already accepted -> false (monotonic, no second broadcast)", expected: false, RematchProtocol.ShouldBroadcastAccept(isHost: true, alreadyAccepted: true, localRequested: true, peerRequested: true, transportConnectedOrSolo: true)); if (failures == 0) { Plugin.Log.LogInfo((object)$"SelfCheck: rematch protocol ({8}/{8} cases) passed."); } else { Plugin.Log.LogError((object)$"SelfCheck: rematch protocol FAILED {failures}/{8} cases - see above."); } void Check(string label, bool expected, bool actual) { if (actual != expected) { failures++; Plugin.Log.LogError((object)$"SelfCheck FAILED: {label} - expected {expected}, got {actual}."); } } } public static void RunPvpItemAllowlistMatrix() { int failures = 0; Check("BatteryItem is safe", expected: true, PvpItemAllowlist.IsSafe(typeof(BatteryItem))); Check("PiggyBankItem is safe", expected: true, PvpItemAllowlist.IsSafe(typeof(PiggyBankItem))); Check("MagnifyingGlassItem is safe", expected: true, PvpItemAllowlist.IsSafe(typeof(MagnifyingGlassItem))); Check("CardBottleItem is safe", expected: true, PvpItemAllowlist.IsSafe(typeof(CardBottleItem))); Check("GooBottleItem is safe", expected: true, PvpItemAllowlist.IsSafe(typeof(GooBottleItem))); Check("ScissorsItem is NOT safe (destroys opponent's queued card)", expected: false, PvpItemAllowlist.IsSafe(typeof(ScissorsItem))); Check("BleachPotItem is NOT safe (strips opponent board abilities)", expected: false, PvpItemAllowlist.IsSafe(typeof(BleachPotItem))); Check("PocketWatchItem is NOT safe (rotates the whole board)", expected: false, PvpItemAllowlist.IsSafe(typeof(PocketWatchItem))); Check("HourglassItem is NOT safe (skips the opponent's real turn locally only)", expected: false, PvpItemAllowlist.IsSafe(typeof(HourglassItem))); Check("Exactly 5 safe types (no silent addition)", expected: true, PvpItemAllowlist.SafeTypes.Length == 5); Check("IsSafe(null) is false, never throws", expected: false, PvpItemAllowlist.IsSafe(null)); if (failures == 0) { Plugin.Log.LogInfo((object)$"SelfCheck: PvP item allowlist ({11}/{11} cases) passed."); } else { Plugin.Log.LogError((object)$"SelfCheck: PvP item allowlist FAILED {failures}/{11} cases - see above."); } void Check(string label, bool expected, bool actual) { if (actual != expected) { failures++; Plugin.Log.LogError((object)$"SelfCheck FAILED: {label} - expected {expected}, got {actual}."); } } } public static void RunPeltDraftAcquiredCardsMatrix() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown int failures = 0; CardInfo val = new CardInfo(); Check("fresh CardInfo starts unmarked", expected: false, PeltDraftAcquiredCards.IsMarked(val)); PeltDraftAcquiredCards.Mark(val); Check("Mark() makes IsMarked() true", expected: true, PeltDraftAcquiredCards.IsMarked(val)); int count = val.Mods.Count; PeltDraftAcquiredCards.Mark(val); Check("Mark() is idempotent - no duplicate mod on a second call", expected: true, val.Mods.Count == count); Check("marker mod has no ability effect (inert tag only)", expected: true, val.Mods.TrueForAll((CardModificationInfo m) => m.abilities == null || m.abilities.Count == 0)); Check("marker mod has no stat effect (inert tag only)", expected: true, val.Mods.TrueForAll((CardModificationInfo m) => m.attackAdjustment == 0 && m.healthAdjustment == 0)); Check("IsMarked(null) is false, never throws", expected: false, PeltDraftAcquiredCards.IsMarked(null)); if (failures == 0) { Plugin.Log.LogInfo((object)$"SelfCheck: Pelt Draft acquired-card marker ({6}/{6} cases) passed."); } else { Plugin.Log.LogError((object)$"SelfCheck: Pelt Draft acquired-card marker FAILED {failures}/{6} cases - see above."); } void Check(string label, bool expected, bool actual) { if (actual != expected) { failures++; Plugin.Log.LogError((object)$"SelfCheck FAILED: {label} - expected {expected}, got {actual}."); } } } } internal static class SymmetricForgeMap { public const int RowCount = 20; public static PredefinedNodes BuildFirstRegionMap() { PredefinedNodes obj = ScriptableObject.CreateInstance(); obj.nodeRows = BuildRows(); return obj; } public static List> BuildRows() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Expected O, but got Unknown //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Expected O, but got Unknown //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Expected O, but got Unknown //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Expected O, but got Unknown //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Expected O, but got Unknown //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Expected O, but got Unknown List> list = new List>(); list.Add(Row(new NodeData())); list.Add(Row((NodeData)new TradePeltsNodeData())); list.Add(Row((NodeData)Choices((CardChoicesType)1))); list.Add(Row((NodeData)Choices((CardChoicesType)2))); list.Add(Row((NodeData)new CardStatBoostNodeData())); list.Add(Row((NodeData)Choices((CardChoicesType)2))); list.Add(Row((NodeData)new CardMergeNodeData())); list.Add(Row((NodeData)Choices((CardChoicesType)1))); list.Add(Row((NodeData)new RareRewardNodeData())); list.Add(Row((NodeData)Battle(1))); list.Add(Row((NodeData)new CopyCardNodeData())); list.Add(Row((NodeData)new DuplicateMergeNodeData())); list.Add(Row((NodeData)Choices((CardChoicesType)2))); list.Add(Row((NodeData)new CardRemoveNodeData())); list.Add(Row((NodeData)new CardStatBoostNodeData())); list.Add(Row((NodeData)Choices((CardChoicesType)1))); list.Add(Row((NodeData)new CardMergeNodeData())); list.Add(Row((NodeData)Choices((CardChoicesType)2))); list.Add(Row((NodeData)new RareRewardNodeData())); list.Add(Row(new DeathcardNodeData())); return list; } private static List Row(params NodeData[] nodes) { return new List(nodes); } private static CardChoicesNodeData Choices(CardChoicesType type) { //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_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown return new CardChoicesNodeData { choicesType = type }; } private static CardBattleNodeData Battle(int difficulty) { //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_000d: Expected O, but got Unknown return new CardBattleNodeData { difficulty = difficulty }; } } internal static class Telemetry { public static readonly HashSet RiskyAbilities = new HashSet { (Ability)31, (Ability)29, (Ability)99, (Ability)78, (Ability)85, (Ability)89 }; private static string _path; private static bool _active; private static DateTime _duelStartTime; private static List _lastTotemTops = new List(); private static List _lastTotemBottoms = new List(); private static int _lastTotemsCount; private static List _lastConsumables = new List(); public static int MatchId => PvpRunState.MatchId; public static void StartRun() { _active = true; string path = $"telemetry_{DateTime.Now:yyyyMMdd_HHmmss}.log"; _path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), path); _lastTotemTops = new List(RunState.Run.totemTops); _lastTotemBottoms = new List(RunState.Run.totemBottoms); _lastTotemsCount = RunState.Run.totems.Count; _lastConsumables = new List(RunState.Run.consumables); Log("run_start", $"matchId={MatchId} deck=[{DeckSummary(((CardCollectionInfo)RunState.Run.playerDeck).Cards)}]"); } public static void Log(string category, string details) { if (!_active || string.IsNullOrEmpty(_path)) { return; } string text = $"{DateTime.Now:HH:mm:ss.fff} | {category} | {details}"; try { File.AppendAllText(_path, text + Environment.NewLine); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Telemetry: failed to write log line - " + ex.Message)); } } public static string DeckSummary(IEnumerable cards) { return string.Join(",", cards.Select(CardSummary)); } public static string CardSummary(CardInfo c) { string text = ((c.Abilities.Count > 0) ? string.Join("+", c.Abilities) : "-"); return $"{((Object)c).name}({c.Attack}/{c.Health})[{text}]"; } public static void Poll() { //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) if (!_active || RunState.Run == null) { return; } List totemTops = RunState.Run.totemTops; if (!totemTops.SequenceEqual(_lastTotemTops)) { Log("totem_tops", string.Join(",", totemTops)); _lastTotemTops = new List(totemTops); } List totemBottoms = RunState.Run.totemBottoms; if (!totemBottoms.SequenceEqual(_lastTotemBottoms)) { Log("totem_bottoms", string.Join(",", totemBottoms)); _lastTotemBottoms = new List(totemBottoms); } if (RunState.Run.totems.Count != _lastTotemsCount) { foreach (TotemDefinition totem in RunState.Run.totems) { Log("totem_built", $"tribe={totem.tribe} ability={totem.ability}"); } _lastTotemsCount = RunState.Run.totems.Count; } List consumables = RunState.Run.consumables; if (!consumables.SequenceEqual(_lastConsumables)) { Log("consumables", string.Join(",", consumables)); _lastConsumables = new List(consumables); } } public static List GetLocalRiskHits() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0041: 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_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) List list = new List(); foreach (CardInfo card in ((CardCollectionInfo)RunState.Run.playerDeck).Cards) { foreach (Ability ability in card.Abilities) { if (RiskyAbilities.Contains(ability)) { list.Add($"{((Object)card).name}:{ability}"); } } } foreach (Ability totemBottom in RunState.Run.totemBottoms) { if (RiskyAbilities.Contains(totemBottom)) { list.Add($"totem:{totemBottom}"); } } return list; } public static void LogDuelStart(bool isHost) { _duelStartTime = DateTime.Now; string text = LobbySession.Instance?.PeerRoute ?? "?"; Log("duel_start", $"matchId={MatchId} isHost={isHost} route={PvpRunState.Route} peerRoute={text} deck=[{DeckSummary(((CardCollectionInfo)RunState.Run.playerDeck).Cards)}]"); LogCombinedRiskFlag(); } private static void LogCombinedRiskFlag() { List localRiskHits = GetLocalRiskHits(); string[] array = LobbySession.Instance?.PeerRiskHits ?? Array.Empty(); string arg = ((localRiskHits.Count == 0) ? "clean" : string.Join("+", localRiskHits)); string arg2 = ((array.Length == 0) ? "clean" : string.Join("+", array)); Log("risk_flag", $"matchId={MatchId} local={arg} peer={arg2}"); } public static void LogDuelEnd(string outcome) { TimeSpan timeSpan = DateTime.Now - _duelStartTime; string text = LobbySession.Instance?.PeerRoute ?? "?"; Log("duel_end", $"matchId={MatchId} outcome={outcome} route={PvpRunState.Route} peerRoute={text} durationSeconds={timeSpan.TotalSeconds:F1}"); } public static void LogFirstPlayer() { bool? flag = LobbySession.Instance?.IsHost; bool flag2 = flag.HasValue && PvpRunState.HostGoesFirst == flag.Value; Log("first_player", $"matchId={MatchId} hostGoesFirst={PvpRunState.HostGoesFirst} isHost={flag} localGoesFirst={flag2}"); } public static void LogRoundBegin(int battleNumber) { bool hostGoesFirstThisBattle = LobbyMatchState.HostGoesFirstThisBattle; Log("round_begin", $"matchId={MatchId} battle={battleNumber} hostGoesFirst={hostGoesFirstThisBattle} hostWins={LobbyMatchState.HostWins} peerWins={LobbyMatchState.PeerWins}"); } public static void LogBattleResult(int battleNumber, string loserAbsoluteSide, int hostWins, int peerWins, int finalBalance, bool isMatchFinal) { Log("battle_result", $"matchId={MatchId} battle={battleNumber} loser={loserAbsoluteSide} hostWins={hostWins} peerWins={peerWins} balance={finalBalance} isMatchFinal={isMatchFinal}"); } public static void LogStarterDeck() { string details = SaveManager.SaveFile?.ascensionData?.currentStarterDeck ?? "?"; Log("starter_deck", details); } public static void LogBossCompletion() { float num = ((PvpRunState.RunStartRealtime > 0f) ? (Time.realtimeSinceStartup - PvpRunState.RunStartRealtime) : (-1f)); Log("boss_completed", $"matchId={MatchId} route={PvpRunState.Route} elapsedSeconds={num:F1}"); } } public static class TvfsStarterDeckAllowlist { public const string TvfsPluginGuid = "tvflabs.inscryption.TVFsStarterDecks"; public static readonly string[] AllowedTitles = new string[3] { "TrueAnts", "TrueBones", "Pelts" }; public static readonly string[] AllowedDeckIds = AllowedTitles.Select((string t) => "tvflabs.inscryption.TVFsStarterDecks_" + t).ToArray(); public static bool IsTvfsDeck(string deckId) { if (!string.IsNullOrEmpty(deckId)) { return deckId.StartsWith("tvflabs.inscryption.TVFsStarterDecks_"); } return false; } public static bool ShouldShow(string deckId, bool multiplayerEnabled) { if (!multiplayerEnabled) { return true; } if (!IsTvfsDeck(deckId)) { return true; } return AllowedDeckIds.Contains(deckId); } public static void Apply() { StarterDeckManager.ModifyDeckList += FilterDecks; } private static List FilterDecks(List decks) { return decks.Where((FullStarterDeck d) => ShouldShow(((Object)d.Info).name, Plugin.MultiplayerEnabled.Value)).ToList(); } } } namespace KayceePvP.Patches { [HarmonyPatch(typeof(Strafe), "RespondsToTurnEnd")] internal static class StrafeRespondsToTurnEndDiagnosticPatch { private static void Postfix(Strafe __instance, bool playerTurnEnd, bool __result) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (PvpRunState.IsDuelActive) { PlayableCard component = ((Component)__instance).GetComponent(); if (!((Object)(object)component == (Object)null)) { Plugin.Log.LogInfo((object)$"Duel (ability-trigger): Strafe.RespondsToTurnEnd | ABILITY={((AbilityBehaviour)__instance).Ability} CARD='{((Object)((Card)component).Info).name}' CARD_ABSOLUTE_OWNER={PvpOwnership.AbsoluteOwnerOf(component)} LOCAL_SIDE={PvpOwnership.LocalSideOf(component)} SELF={PvpOwnership.LocalAbsoluteSide()} playerTurnEnd={playerTurnEnd} RESULT={__result}"); } } } } [HarmonyPatch(typeof(Strafe), "OnTurnEnd")] internal static class StrafeOnTurnEndDiagnosticPatch { private static void Postfix(Strafe __instance, bool playerTurnEnd) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (PvpRunState.IsDuelActive) { PlayableCard component = ((Component)__instance).GetComponent(); if (!((Object)(object)component == (Object)null)) { Plugin.Log.LogInfo((object)string.Format("Duel (ability-trigger): Strafe.OnTurnEnd STARTED | ABILITY={0} CARD='{1}' CARD_ABSOLUTE_OWNER={2} LOCAL_SIDE={3} SELF={4} playerTurnEnd={5} cardSlot={6}", ((AbilityBehaviour)__instance).Ability, ((Object)((Card)component).Info).name, PvpOwnership.AbsoluteOwnerOf(component), PvpOwnership.LocalSideOf(component), PvpOwnership.LocalAbsoluteSide(), playerTurnEnd, ((Object)(object)component.Slot != (Object)null) ? component.Slot.Index.ToString() : "NULL")); } } } } [HarmonyPatch(typeof(GlobalTriggerHandler), "TriggerCardsOnBoard")] internal static class TurnEndTriggerDispatchDiagnosticPatch { private static void Prefix(Trigger trigger, bool triggerFacedown, object[] otherArgs) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 if (PvpRunState.IsDuelActive && (int)trigger == 13) { bool? flag = ((otherArgs != null && otherArgs.Length != 0 && otherArgs[0] is bool value) ? new bool?(value) : ((bool?)null)); Plugin.Log.LogInfo((object)("Duel (ability-trigger): GlobalTriggerHandler.TriggerCardsOnBoard(Trigger.TurnEnd) DISPATCHED | SELF=" + PvpOwnership.LocalAbsoluteSide() + " playerTurnEndArg=" + (flag.HasValue ? flag.Value.ToString() : "?"))); } } } internal static class ActivatedAbilityDuelGate { private static bool _loggedThisDuel; internal static bool ShouldBlock(ActivatedAbilityBehaviour instance) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) if (!PvpRunState.IsDuelActive) { _loggedThisDuel = false; return false; } if (!_loggedThisDuel) { _loggedThisDuel = true; Plugin.Log.LogInfo((object)$"PvP: blocked Activated ability ({((AbilityBehaviour)instance).Ability}) - not synced over the network, release limitation (thunderstore/README.md, Known limitations)."); } return true; } } [HarmonyPatch(typeof(ActivatedAbilityBehaviour), "CanActivate")] internal static class ActivatedAbilityBaseDuelDisablePatch { private static bool Prefix(ActivatedAbilityBehaviour __instance, ref bool __result) { if (!ActivatedAbilityDuelGate.ShouldBlock(__instance)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(ActivatedDealDamage), "CanActivate")] internal static class ActivatedDealDamageDuelDisablePatch { private static bool Prefix(ActivatedDealDamage __instance, ref bool __result) { if (!ActivatedAbilityDuelGate.ShouldBlock((ActivatedAbilityBehaviour)(object)__instance)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(AscensionSaveData), "NewRun")] internal static class AquaticStarterDeckPatch { private static void Postfix() { if (!PvpRunState.IsPvpRun) { return; } DeckInfo val = RunState.Run?.playerDeck; if (val != null) { CardInfo val2 = ((CardCollectionInfo)val).Cards.Find(PvpCardPoolRules.IsGreatKraken); if (!((Object)(object)val2 == (Object)null)) { ((CardCollectionInfo)val).RemoveCard(val2); ((CardCollectionInfo)val).AddCard(CardLoader.GetCardByName("Otter")); Plugin.Log.LogInfo((object)"PvP: baralho aquático inicial - substituído Great Kraken por River Otter (2 Kingfishers preservados)."); Telemetry.Log("great_kraken_filtered", "source=starter_deck replaced_with=Otter"); PvpCardPoolRules.FilterFromDeck(((CardCollectionInfo)val).Cards, "starter_deck"); } } } } [HarmonyPatch(typeof(Part1CardChoiceGenerator), "GenerateChoices")] [HarmonyPriority(800)] internal static class BalancedPathCardChoiceSeedPatch { private static void Prefix(CardChoicesNodeData data, ref int randomSeed) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) if (data != null) { bool isBalancedPathMode = PvpRunState.SelectedMapMode == MapMode.BalancedPath || PvpRunState.SelectedMapMode == MapMode.BalancedRoute; if (BalancedPathCardChoiceSeed.ShouldOverride(PvpRunState.IsPvpRun, isBalancedPathMode, data.choicesType)) { BalancedPathCardChoiceSeed.TryGetEventKind(data.choicesType, out var kind); int currentNodeId = RunState.Run?.currentNodeId ?? 0; randomSeed = BalancedPathCardChoiceSeed.Compute(PvpRunState.SharedMapSeed, currentNodeId, kind); } } } } [HarmonyPatch(typeof(TurnManager), "LifeLossConditionsMet")] internal static class BattlesLaneStipendPatch { internal const string StipendGrantedKey = "battles_lane_stipend_granted"; internal const int StipendAmount = 10; private static void Postfix(TurnManager __instance, bool __result) { if (PvpRunState.IsPvpRun && !PvpRunState.IsDuelActive && __result && PvpRunState.SelectedMapMode == MapMode.Battles && !((Object)(object)Singleton.Instance == (Object)null) && !ModdedSaveManager.RunState.GetValueAsBoolean("jgs.inscryption.kayceepvp", "battles_lane_stipend_granted")) { ModdedSaveManager.RunState.SetValueAsObject("jgs.inscryption.kayceepvp", "battles_lane_stipend_granted", true); RunState run = RunState.Run; run.currency += 10; Plugin.Log.LogInfo((object)$"PvP Battles: shared mid-route battle completed (win or lose) - granting the +{10} teeth stipend once (currency now {RunState.Run.currency})."); Telemetry.Log("battles_lane_stipend_granted", $"amount={10} currency={RunState.Run.currency}"); } } } [HarmonyPatch(typeof(CombatPhaseManager), "DoCombatPhase")] internal static class CombatResolutionSignalPatch { public enum CombatGateOutcome { StillWaiting, Release, BattleEnded, AlreadyDesynced, TimedOut } private static int _generation; private static int _resolvedGeneration; public static int CurrentGeneration => _generation; public static int ResolvedGeneration => _resolvedGeneration; public static CombatGateOutcome EvaluateCombatGate(int resolvedGeneration, int generationBefore, bool protocolDesync, bool battleEnded, bool deadlinePassed) { if (protocolDesync) { return CombatGateOutcome.AlreadyDesynced; } if (battleEnded) { return CombatGateOutcome.BattleEnded; } if (resolvedGeneration > generationBefore) { return CombatGateOutcome.Release; } if (deadlinePassed) { return CombatGateOutcome.TimedOut; } return CombatGateOutcome.StillWaiting; } public static void ResetForMatch(string reason) { int generation = _generation; _generation = 0; _resolvedGeneration = 0; Plugin.Log.LogInfo((object)$"Duel (combat): CombatResolutionSignalPatch.ResetForMatch - reason='{reason}', generation {generation} -> 0."); } private static IEnumerator Postfix(IEnumerator __result, bool playerIsAttacker) { if (!PvpRunState.IsDuelActive) { while (__result.MoveNext()) { yield return __result.Current; } yield break; } int generation = ++_generation; string self = (((Object)(object)LobbySession.Instance != (Object)null && LobbySession.Instance.IsHost) ? "HOST" : "PEER"); Plugin.Log.LogInfo((object)$"Duel (combat): COMBAT_PHASE_STARTED | self={self} generation={generation} playerIsAttacker={playerIsAttacker}"); while (__result.MoveNext()) { yield return __result.Current; } _resolvedGeneration = generation; Plugin.Log.LogInfo((object)$"Duel (combat): COMBAT_PHASE_RESOLVED | self={self} generation={generation} playerIsAttacker={playerIsAttacker}"); } } [HarmonyPatch(typeof(SanctumSceneSequencer), "DeathCardSequence")] internal static class DeathcardIntroDialoguePatch { private const string DialogueId = "KayceePvPDeathcardIntro"; private static void Postfix(ref IEnumerator __result) { if (PvpRunState.IsPvpRun) { __result = WithIntro(__result); } } private static IEnumerator WithIntro(IEnumerator original) { yield return Singleton.Instance.PlayDialogueEvent("KayceePvPDeathcardIntro", (MessageAdvanceMode)1, (EventIntersectMode)0, (string[])null, (Action)null); while (original.MoveNext()) { yield return original.Current; } } } [HarmonyPatch(typeof(MapNodeManager), "DoMoveToNewNode")] internal static class DeathcardNodePatch { private static void Prefix(MapNode newNode) { if (PvpRunState.IsPvpRun && ((newNode != null) ? newNode.Data : null) is DeathcardNodeData && MapModeCatalog.TryGet(PvpRunState.SelectedMapMode, out var descriptor) && descriptor.CompletionPolicy == MapCompletionPolicy.Boss && !PvpRunState.BossDefeated) { PvpRunState.BossDefeated = true; Plugin.Log.LogInfo((object)"Explicit Deathcard node reached - entering PvP card creation."); Telemetry.LogBossCompletion(); PvpCardCreationFlow.BeginAfterFirstBoss(); } } } [HarmonyPatch(typeof(PlayableCard), "Sacrifice")] internal static class SacrificeCapturePatch { internal static readonly List PendingSacrificedSlotIndices = new List(); private static void Postfix(PlayableCard __instance) { if (PvpRunState.IsDuelActive && !PvpRunState.ApplyingRemoteAction && !((Object)(object)__instance.Slot == (Object)null)) { PendingSacrificedSlotIndices.Add(__instance.Slot.Index); Plugin.Log.LogInfo((object)$"Duel: captured sacrifice of '{((Object)((Card)__instance).Info).name}' at slot {__instance.Slot.Index} - will attach to the next card_played broadcast."); } } } [HarmonyPatch(typeof(PlayerHand), "PlayCardOnSlot")] internal static class CardPlayCapturePatch { private static void Postfix(PlayableCard card, CardSlot slot) { if (!PvpRunState.IsDuelActive) { Plugin.Log.LogInfo((object)("Duel: PlayCardOnSlot fired for '" + ((Object)((Card)card).Info).name + "' but IsDuelActive is false - not broadcasting.")); SacrificeCapturePatch.PendingSacrificedSlotIndices.Clear(); } else if ((Object)(object)DuelSession.Instance != (Object)null && DuelSession.Instance.MatchEnded) { Plugin.Log.LogInfo((object)("Duel: PlayCardOnSlot fired for '" + ((Object)((Card)card).Info).name + "' but the match already ended - not broadcasting.")); SacrificeCapturePatch.PendingSacrificedSlotIndices.Clear(); } else { int[] sacrificedSlotIndices = SacrificeCapturePatch.PendingSacrificedSlotIndices.ToArray(); SacrificeCapturePatch.PendingSacrificedSlotIndices.Clear(); DuelSession.Instance?.SendCardPlayed(card, slot.Index, sacrificedSlotIndices); } } } [HarmonyPatch(typeof(TurnManager), "OnCombatBellRang")] internal static class CombatBellCapturePatch { private static void Postfix() { if (PvpRunState.IsDuelActive && (!((Object)(object)DuelSession.Instance != (Object)null) || !DuelSession.Instance.MatchEnded)) { DuelSession.Instance?.SendEndTurn(); } } } [HarmonyPatch(typeof(DrawCreatedCard), "CreateDrawnCard")] internal static class SuppressRemoteDrawCreatedCardPatch { private static bool Prefix(DrawCreatedCard __instance) { PlayableCard component = ((Component)__instance).GetComponent(); if (!PvpOwnership.IsRemoteMirroredCard(component)) { if (PvpRunState.IsDuelActive && (Object)(object)component != (Object)null) { Plugin.Log.LogInfo((object)("Duel (ownership): DrawCreatedCard fired | ABSOLUTE_OWNER=" + PvpOwnership.AbsoluteOwnerOf(component) + " LOCAL_SIDE=" + PvpOwnership.LocalSideOf(component) + " SELF=" + PvpOwnership.LocalAbsoluteSide() + " ABILITY=DrawCreatedCard card='" + ((Object)((Card)component).Info).name + "' -> proceeding (local card).")); } return true; } Plugin.Log.LogInfo((object)("Duel (ownership): suppressed DrawCreatedCard's card-to-hand | ABSOLUTE_OWNER=" + PvpOwnership.AbsoluteOwnerOf(component) + " LOCAL_SIDE=" + PvpOwnership.LocalSideOf(component) + " SELF=" + PvpOwnership.LocalAbsoluteSide() + " ABILITY=DrawCreatedCard card='" + ((Object)((Card)component).Info).name + "' - would have wrongly gone to the local player's hand.")); return false; } } [HarmonyPatch(typeof(QuadrupleBones), "OnDie")] internal static class SuppressRemoteQuadrupleBonesPatch { private static bool Prefix(QuadrupleBones __instance, ref IEnumerator __result) { PlayableCard component = ((Component)__instance).GetComponent(); if (!PvpOwnership.IsRemoteMirroredCard(component)) { if (PvpRunState.IsDuelActive && (Object)(object)component != (Object)null) { Plugin.Log.LogInfo((object)("Duel (ownership): QuadrupleBones.OnDie fired | ABSOLUTE_OWNER=" + PvpOwnership.AbsoluteOwnerOf(component) + " LOCAL_SIDE=" + PvpOwnership.LocalSideOf(component) + " SELF=" + PvpOwnership.LocalAbsoluteSide() + " ABILITY=QuadrupleBones card='" + ((Object)((Card)component).Info).name + "' -> proceeding (local card, granting 4 bones).")); } return true; } Plugin.Log.LogInfo((object)("Duel (ownership): suppressed QuadrupleBones.OnDie | ABSOLUTE_OWNER=" + PvpOwnership.AbsoluteOwnerOf(component) + " LOCAL_SIDE=" + PvpOwnership.LocalSideOf(component) + " SELF=" + PvpOwnership.LocalAbsoluteSide() + " ABILITY=QuadrupleBones card='" + ((Object)((Card)component).Info).name + "' - would have wrongly granted 4 bones to the local player.")); __result = Empty(); return false; } private static IEnumerator Empty() { yield break; } } [HarmonyPatch(typeof(Tutor), "OnResolveOnBoard")] internal static class SuppressRemoteTutorPatch { private static bool Prefix(Tutor __instance, ref IEnumerator __result) { PlayableCard component = ((Component)__instance).GetComponent(); if (!PvpOwnership.IsRemoteMirroredCard(component)) { if (PvpRunState.IsDuelActive && (Object)(object)component != (Object)null) { Plugin.Log.LogInfo((object)("Duel (ownership): Tutor.OnResolveOnBoard fired | ABSOLUTE_OWNER=" + PvpOwnership.AbsoluteOwnerOf(component) + " LOCAL_SIDE=" + PvpOwnership.LocalSideOf(component) + " SELF=" + PvpOwnership.LocalAbsoluteSide() + " ABILITY=Tutor card='" + ((Object)((Card)component).Info).name + "' -> proceeding (local card, local player chooses).")); } return true; } Plugin.Log.LogInfo((object)("Duel (ownership): suppressed Tutor.OnResolveOnBoard | ABSOLUTE_OWNER=" + PvpOwnership.AbsoluteOwnerOf(component) + " LOCAL_SIDE=" + PvpOwnership.LocalSideOf(component) + " SELF=" + PvpOwnership.LocalAbsoluteSide() + " ABILITY=Tutor card='" + ((Object)((Card)component).Info).name + "' - would have wrongly let the local player choose from their own deck.")); __result = Empty(); return false; } private static IEnumerator Empty() { yield break; } } [HarmonyPatch(typeof(Morsel), "OnSacrifice")] internal static class SuppressRemoteMorselPatch { private static bool Prefix(Morsel __instance, ref IEnumerator __result) { PlayableCard component = ((Component)__instance).GetComponent(); if (!PvpOwnership.IsRemoteMirroredCard(component)) { if (PvpRunState.IsDuelActive && (Object)(object)component != (Object)null) { Plugin.Log.LogInfo((object)("Duel (ownership): Morsel.OnSacrifice fired | ABSOLUTE_OWNER=" + PvpOwnership.AbsoluteOwnerOf(component) + " LOCAL_SIDE=" + PvpOwnership.LocalSideOf(component) + " SELF=" + PvpOwnership.LocalAbsoluteSide() + " ABILITY=Morsel card='" + ((Object)((Card)component).Info).name + "' -> proceeding (local card, boosting the real sacrifice-demanding card).")); } return true; } Plugin.Log.LogInfo((object)("Duel (ownership): suppressed Morsel.OnSacrifice | ABSOLUTE_OWNER=" + PvpOwnership.AbsoluteOwnerOf(component) + " LOCAL_SIDE=" + PvpOwnership.LocalSideOf(component) + " SELF=" + PvpOwnership.LocalAbsoluteSide() + " ABILITY=Morsel card='" + ((Object)((Card)component).Info).name + "' - mirrored replay has no real CurrentSacrificeDemandingCard and would have crashed (NullReferenceException, confirmed by a real playtest log).")); __result = Empty(); return false; } private static IEnumerator Empty() { yield break; } } [HarmonyPatch(typeof(RandomAbility), "OnDrawn")] internal static class SuppressRemoteRandomAbilityPatch { private static bool Prefix(RandomAbility __instance, ref IEnumerator __result) { PlayableCard component = ((Component)__instance).GetComponent(); if (!PvpOwnership.IsRemoteMirroredCard(component)) { if (PvpRunState.IsDuelActive && (Object)(object)component != (Object)null) { Plugin.Log.LogInfo((object)("Duel (ownership): RandomAbility local proceeds | ABSOLUTE_OWNER=" + PvpOwnership.AbsoluteOwnerOf(component) + " LOCAL_SIDE=" + PvpOwnership.LocalSideOf(component) + " SELF=" + PvpOwnership.LocalAbsoluteSide() + " ABILITY=RandomAbility card='" + ((Object)((Card)component).Info).name + "' -> proceeding (local card, real roll).")); } return true; } Plugin.Log.LogInfo((object)("Duel (ownership): RandomAbility remote suppressed | ABSOLUTE_OWNER=" + PvpOwnership.AbsoluteOwnerOf(component) + " LOCAL_SIDE=" + PvpOwnership.LocalSideOf(component) + " SELF=" + PvpOwnership.LocalAbsoluteSide() + " ABILITY=RandomAbility card='" + ((Object)((Card)component).Info).name + "' - mirrored replay must never roll independently, the canonical resolved ability already arrived over the wire.")); __result = Empty(); return false; } private static IEnumerator Empty() { yield break; } } [HarmonyPatch(typeof(TurnManager), "GameIsOver")] internal static class GameIsOverDiagnosticPatch { private static void Postfix(TurnManager __instance, bool __result) { if (PvpRunState.IsDuelActive) { Opponent value = Traverse.Create((object)__instance).Field("opponent").Value; string text = (((Object)(object)LobbySession.Instance != (Object)null && LobbySession.Instance.IsHost) ? "HOST" : "PEER"); float num = (((Object)(object)Singleton.Instance != (Object)null) ? ((float)Singleton.Instance.Balance) : float.NaN); int num2 = (((Object)(object)value != (Object)null) ? value.NumLives : (-1)); bool flag = (Object)(object)DuelSession.Instance != (Object)null && DuelSession.Instance.MatchEnded; bool flag2 = (Object)(object)value != (Object)null && value.Surrendered; Plugin.Log.LogInfo((object)$"DIAG GameIsOver | role={text} result={__result} balance={num} opponent.NumLives={num2} opponent.Surrendered={flag2} PlayerSurrendered={__instance.PlayerSurrendered} MatchEnded={flag}"); } } } [HarmonyPatch(typeof(TurnManager), "PlayerIsWinner")] internal static class PlayerIsWinnerDiagnosticPatch { private static void Postfix(TurnManager __instance, bool __result) { if (PvpRunState.IsDuelActive) { Opponent value = Traverse.Create((object)__instance).Field("opponent").Value; string text = (((Object)(object)LobbySession.Instance != (Object)null && LobbySession.Instance.IsHost) ? "HOST" : "PEER"); int num = (((Object)(object)value != (Object)null) ? value.NumLives : (-1)); bool flag = (Object)(object)value != (Object)null && value.Surrendered; Plugin.Log.LogInfo((object)$"DIAG PlayerIsWinner | role={text} result={__result} opponent.NumLives={num} opponent.Surrendered={flag}"); } } } [HarmonyPatch(typeof(Deck), "Initialize")] [HarmonyPriority(800)] internal static class DuelDeckShufflePatch { private static void Prefix(List cardInfos, ref int randomSeed) { if (DuelDeckShuffleSeed.ShouldOverride(PvpRunState.IsPvpRun, PvpRunState.IsDuelActive)) { int matchId = LobbyMatchState.MatchId; int battleNumber = LobbyMatchState.BattleNumber; int num = (randomSeed = DuelDeckShuffleSeed.Compute(matchId, battleNumber)); string text = (((Object)(object)LobbySession.Instance != (Object)null && LobbySession.Instance.IsHost) ? "HOST" : "PEER"); string text2 = BuildFirstPicksFingerprint(cardInfos, num, 3); Plugin.Log.LogInfo((object)$"Duel (deck-shuffle): role={text} matchId={matchId} battleNumber={battleNumber} seed={num} firstPicks=[{text2}]"); } } private static string BuildFirstPicksFingerprint(List cardInfos, int seed, int count) { List list = new List(cardInfos); List list2 = new List(); for (int i = 0; i < count; i++) { if (list.Count <= 0) { break; } int index = SeededRandom.Range(0, list.Count, seed + i); list2.Add(((Object)list[index]).name); list.RemoveAt(index); } return string.Join(",", list2); } } [HarmonyPatch(typeof(PaperGameMap), "CompleteRegionSequence")] internal static class FirstBossVictoryPatch { private static bool Prefix(ref IEnumerator __result) { if (!PvpRunState.IsPvpRun) { return true; } if (!SaveFile.IsAscension) { return true; } if (RunState.Run.regionTier != 0) { return true; } __result = EmptySequence(); return false; } private static IEnumerator EmptySequence() { yield break; } } [HarmonyPatch(typeof(CardDrawPiles), "ExhaustedSequence")] internal static class SuppressHungerInDuelPatch { private static bool _loggedThisDuel; private static bool Prefix(ref IEnumerator __result) { if (!PvpRunState.IsDuelActive) { _loggedThisDuel = false; return true; } if (!_loggedThisDuel) { _loggedThisDuel = true; Plugin.Log.LogInfo((object)"PvP: Fome (Starvation punisher) disabled during the duel - both piles empty just means no card drawn this turn."); } __result = NoOp(); return false; } private static IEnumerator NoOp() { yield break; } } [HarmonyPatch(typeof(Part1GameFlowManager), "Awake")] internal static class SuppressAwakeDuringInterBattleReloadPatch { private static bool Prefix() { if (!LobbyMatchState.InterBattleSceneReloadActive) { return true; } Plugin.Log.LogInfo((object)$"Duel (interbattle-reload): INTERBATTLE_AWAKE_SUPPRESSED | self={PvpOwnership.LocalAbsoluteSide()} match={LobbyMatchState.MatchId} battle={LobbyMatchState.BattleNumber}"); return false; } } [HarmonyPatch(typeof(GameFlowManager), "TransitionTo")] internal static class SuppressInitialMapTransitionDuringInterBattleReloadPatch { private static bool Prefix(GameState gameState, ref IEnumerator __result) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 if ((int)gameState != 1 || !LobbyMatchState.InterBattleSceneReloadActive) { return true; } Plugin.Log.LogInfo((object)$"Duel (interbattle-reload): INTERBATTLE_INITIAL_MAP_TRANSITION_SUPPRESSED | self={PvpOwnership.LocalAbsoluteSide()} match={LobbyMatchState.MatchId} battle={LobbyMatchState.BattleNumber}"); __result = Empty(); return false; } private static IEnumerator Empty() { yield break; } } [HarmonyPatch(typeof(ConsumableItem), "CanActivate")] internal static class ItemDuelDisablePatch { private static bool _loggedThisDuel; private static bool Prefix(ConsumableItem __instance, ref bool __result) { if (!PvpRunState.IsDuelActive) { _loggedThisDuel = false; return true; } if (__instance is CardBottleItem && (Object)(object)((Item)__instance).Data != (Object)null && ((Object)((Item)__instance).Data).name == "SquirrelBottle") { return true; } __result = false; if (!_loggedThisDuel) { _loggedThisDuel = true; Plugin.Log.LogInfo((object)("PvP: blocked " + ((object)__instance).GetType().Name + "; only one SquirrelBottle is permitted during the duel.")); } return false; } } [HarmonyPatch(typeof(AscensionMenuScreens), "TransitionToGame")] internal static class LobbyGatePatch { private static bool _bypassGate; private static bool _gateInProgress; private static bool Prefix(bool newRun, AscensionMenuScreens __instance) { if (_bypassGate) { return true; } if (!Plugin.MultiplayerEnabled.Value) { return true; } if (!newRun) { return true; } if ((Object)(object)LobbySession.Instance == (Object)null) { return true; } if (_gateInProgress) { return false; } _gateInProgress = true; Plugin.Log.LogInfo((object)"New Ascension run requested - opening PvP lobby before it starts."); ((MonoBehaviour)LobbySession.Instance).StartCoroutine(WaitForLobbyThenProceed(__instance)); return false; } private static IEnumerator WaitForLobbyThenProceed(AscensionMenuScreens instance) { LobbySession.Instance.BeginLobby(); yield return (object)new WaitUntil((Func)(() => LobbySession.Instance.RunStarted || !LobbySession.Instance.IsActive)); if (!LobbySession.Instance.RunStarted) { Plugin.Log.LogInfo((object)"Lobby gate cancelled - returning to Kaycee's Mod configuration."); _gateInProgress = false; yield break; } PvpRunState.IsPvpRun = true; PvpRunState.SharedMapSeed = LobbySession.Instance.SharedMapSeed; PvpRunState.MatchId = LobbySession.Instance.MatchId; PvpRunState.SelectedMapMode = LobbySession.Instance.SelectedMapMode; Plugin.Log.LogInfo((object)$"Lobby ready - starting PvP run with shared map seed {PvpRunState.SharedMapSeed}, matchId {PvpRunState.MatchId}, mapMode {PvpRunState.SelectedMapMode} (host goes first: {PvpRunState.HostGoesFirst})."); _bypassGate = true; instance.TransitionToGame(true); _bypassGate = false; LobbySession.Instance.CloseLobby(); _gateInProgress = false; } } internal static class LocalPeerSteamBypass { internal const string FlagFileName = "kayceepvp_local_peer_no_steam.flag"; internal const string CommandLineArg = "-kayceepvp-no-steam"; private static bool? _enabled; internal static bool IsEnabled { get { if (_enabled.HasValue) { return _enabled.Value; } string text = Directory.GetParent(Application.dataPath)?.FullName; bool flag = text != null && File.Exists(Path.Combine(text, "kayceepvp_local_peer_no_steam.flag")); bool flag2 = Array.IndexOf(Environment.GetCommandLineArgs(), "-kayceepvp-no-steam") >= 0; _enabled = flag || flag2; return _enabled.Value; } } } [HarmonyPatch] internal static class SteamManagerLocalPeerBypassPatch { private static MethodBase TargetMethod() { return AccessTools.Method(AccessTools.TypeByName("SteamManager"), "Awake", (Type[])null, (Type[])null); } private static bool Prefix(MonoBehaviour __instance) { if (!LocalPeerSteamBypass.IsEnabled) { return true; } FieldInfo fieldInfo = AccessTools.Field(AccessTools.TypeByName("SteamManager"), "s_instance"); object obj = fieldInfo?.GetValue(null); if (obj == null) { fieldInfo?.SetValue(null, __instance); Object.DontDestroyOnLoad((Object)(object)((Component)__instance).gameObject); Plugin.Log.LogInfo((object)"KayceePvP local-peer bypass: skipping SteamManager.Awake() (Steamworks left uninitialized) because 'kayceepvp_local_peer_no_steam.flag' or '-kayceepvp-no-steam' is present."); } else if (obj != __instance) { Object.Destroy((Object)(object)((Component)__instance).gameObject); } return false; } } [HarmonyPatch(typeof(Opponent), "SpawnOpponent")] internal static class NetworkOpponentSpawnPatch { private static bool Prefix(EncounterData encounterData, ref Opponent __result) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (!PvpRunState.IsDuelActive) { return true; } NetworkOpponent networkOpponent = new GameObject("NetworkOpponent").AddComponent(); ((Opponent)networkOpponent).TurnPlan = new List>(); ((Opponent)networkOpponent).NumLives = ((Opponent)networkOpponent).StartingLives; ((Opponent)networkOpponent).OpponentType = (Type)1; ((Opponent)networkOpponent).Blueprint = encounterData.Blueprint; ((Opponent)networkOpponent).Difficulty = encounterData.Difficulty; __result = (Opponent)(object)networkOpponent; return false; } } [HarmonyPatch(typeof(TurnManager), "TransitionToNextGameState")] internal static class DuelEndPatch { private static void Postfix() { if (PvpRunState.IsDuelActive && (!PvpRoundState.IsActive || ((Object)(object)DuelSession.Instance != (Object)null && DuelSession.Instance.MatchEnded))) { PvpRunState.IsDuelActive = false; Plugin.Log.LogInfo((object)"Duel: battle ended, IsDuelActive reset."); } } } [HarmonyPatch(typeof(MapNodeManager), "DoMoveToNewNode")] internal static class NoBattleRouteCompletionPatch { private static void Prefix(MapNode newNode) { if (PvpRunState.IsPvpRun && MapModeCatalog.TryGet(PvpRunState.SelectedMapMode, out var descriptor) && descriptor.CompletionPolicy == MapCompletionPolicy.RouteExit && ((newNode != null) ? newNode.Data : null) != null && newNode.Data is NoBattleExitNodeData) { NoBattleRouteCompletionFlow.OnLocalRouteCompleted(); } } } [HarmonyPatch(typeof(SpecialNodeHandler), "StartSpecialNodeSequence")] internal static class OpenDraftDispatchPatch { private static bool Prefix(SpecialNodeHandler __instance, SpecialNodeData nodeData) { if (!(nodeData is OpenDraftNodeData)) { return true; } OpenDraftFlow openDraftFlow = ((Component)__instance).gameObject.GetComponent(); if ((Object)(object)openDraftFlow == (Object)null) { openDraftFlow = ((Component)__instance).gameObject.AddComponent(); } ((MonoBehaviour)__instance).StartCoroutine(openDraftFlow.Run()); return false; } } internal sealed class OpenDraftFlow : MonoBehaviour { private static readonly FieldInfo CardChoiceSequencerField = typeof(SpecialNodeHandler).GetField("cardChoiceSequencer", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly FieldInfo SelectableCardPrefabField = typeof(CardChoicesSequencer).GetField("selectableCardPrefab", BindingFlags.Instance | BindingFlags.NonPublic); private const float CardSpacingX = 1.5f; private const float CardSpacingY = 2.1f; private const int CardsPerRow = 4; private bool _running; private readonly List _spawnedCards = new List(); private string _lastFeedback = PvpLocalization.Get("KayceePvP.OpenDraft.InitialFeedback"); private string _pendingClickedCard; internal static void RegisterLocalizedText() { PvpLocalization.L("KayceePvP.OpenDraft.InitialFeedback", "Choose your entire deck.", "Escolha seu baralho completo."); PvpLocalization.L("KayceePvP.OpenDraft.Returned", "{0} returned.", "{0} devolvida."); PvpLocalization.L("KayceePvP.OpenDraft.PickedFormat", "{0} picked ({1}).", "{0} escolhida ({1})."); PvpLocalization.L("KayceePvP.OpenDraft.NoBudgetLeft", "No {0} pelts left. Pick another tier.", "Sem peles {0} restantes. Escolha outra faixa."); PvpLocalization.L("KayceePvP.OpenDraft.UndoDone", "Last pick undone; the pelt was returned.", "Última escolha desfeita; a pele foi devolvida."); PvpLocalization.L("KayceePvP.OpenDraft.IncompleteBudget", "Complete exactly 8 RABBIT, 4 WOLF and 2 GOLDEN before confirming.", "Complete exatamente 8 COELHO, 4 LOBO e 2 DOURADAS antes de confirmar."); PvpLocalization.L("KayceePvP.OpenDraft.CounterLine", "PELTS: RABBIT {0}/8 WOLF {1}/4 GOLDEN {2}/2 | page {3}/{4} ({5})", "PELES: COELHO {0}/8 LOBO {1}/4 DOURADA {2}/2 | página {3}/{4} ({5})"); PvpLocalization.L("KayceePvP.OpenDraft.ControlsLine", "←/→ pages | click selects/returns | BACKSPACE undoes | ENTER confirms 14 cards", "←/→ páginas | clique seleciona/devolve | BACKSPACE desfaz | ENTER confirma 14 cartas"); PvpLocalization.L("KayceePvP.OpenDraft.TierRabbit", "RABBIT", "COELHO"); PvpLocalization.L("KayceePvP.OpenDraft.TierWolf", "WOLF", "LOBO"); PvpLocalization.L("KayceePvP.OpenDraft.TierGolden", "GOLDEN", "DOURADA"); } public IEnumerator Run() { if (_running) { yield break; } _running = true; List list = BuildFullNaturePool(); Plugin.Log.LogInfo((object)$"Open Draft: flow started; allowed Nature pool has {list.Count} cards."); if (list.Count == 0) { Plugin.Log.LogWarning((object)"Open Draft: Nature PvP pool came back empty - aborting safely, no deck change, returning to map."); _running = false; ReturnToMap(); yield break; } int currentNodeId = ((RunState.Run != null) ? RunState.Run.currentNodeId : 0); Dictionary cardsByName = list.ToDictionary((CardInfo c) => ((Object)c).name, (CardInfo c) => c); List source = OpenDraftCardPool.DeriveOrder(list.Select((CardInfo c) => ((Object)c).name).ToList(), PvpRunState.SharedMapSeed, currentNodeId); List orderedNames = source.OrderBy((string name) => TierFor(cardsByName[name])).ToList(); List selected = new List(); int currentPage = 0; GameObject cardPrefab = ResolveSelectableCardPrefab(); if ((Object)(object)cardPrefab == (Object)null) { Plugin.Log.LogWarning((object)"Open Draft: could not resolve the real selectableCardPrefab via reflection - aborting safely, no deck change, returning to map."); _running = false; ReturnToMap(); yield break; } Singleton.Instance.SwitchToView((View)3, false, true); yield return (object)new WaitForSeconds(0.35f); Singleton.Instance.Controller.SwitchToControlMode((ControlMode)11, false); Singleton.Instance.Controller.LockState = (ViewLockState)0; bool finalizeRequested = false; while (!finalizeRequested) { currentPage = OpenDraftCardPool.ClampPage(currentPage, orderedNames.Count); SpawnPage(cardPrefab, orderedNames, currentPage, selected); ShowStatus(orderedNames, cardsByName, currentPage, selected); string pendingPick = null; int pageDelta = 0; bool undoRequested = false; while (pendingPick == null && pageDelta == 0 && !undoRequested && !finalizeRequested) { if (Input.GetKeyDown((KeyCode)275)) { pageDelta = 1; } else if (Input.GetKeyDown((KeyCode)276)) { pageDelta = -1; } else if (Input.GetKeyDown((KeyCode)8)) { undoRequested = true; } else if (Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)271)) { List selected2 = selected.Select((string name) => TierFor(cardsByName[name])).ToList(); if (OpenDraftSelectionState.CanFinalize(selected) && OpenDraftBudget.IsComplete(selected2)) { finalizeRequested = true; } else { _lastFeedback = PvpLocalization.Get("KayceePvP.OpenDraft.IncompleteBudget"); } } else if (_pendingClickedCard != null) { pendingPick = _pendingClickedCard; _pendingClickedCard = null; } yield return null; } if (pendingPick != null) { if (OpenDraftSelectionState.IsSelected(selected, pendingPick)) { selected = OpenDraftSelectionState.ApplyPick(selected, pendingPick); _lastFeedback = PvpLocalization.Format("KayceePvP.OpenDraft.Returned", cardsByName[pendingPick].DisplayedNameLocalized); } else { OpenDraftTier tier = TierFor(cardsByName[pendingPick]); if (OpenDraftBudget.CanAdd(selected.Select((string name) => TierFor(cardsByName[name])).ToList(), tier)) { selected = OpenDraftSelectionState.ApplyPick(selected, pendingPick); _lastFeedback = PvpLocalization.Format("KayceePvP.OpenDraft.PickedFormat", cardsByName[pendingPick].DisplayedNameLocalized, TierLabel(tier)); } else { _lastFeedback = PvpLocalization.Format("KayceePvP.OpenDraft.NoBudgetLeft", TierLabel(tier)); } } } if (undoRequested) { selected = OpenDraftSelectionState.UndoLast(selected); _lastFeedback = PvpLocalization.Get("KayceePvP.OpenDraft.UndoDone"); } if (pageDelta != 0) { currentPage += pageDelta; } DespawnPage(); } if (OpenDraftBudget.IsComplete(selected.Select((string name) => TierFor(cardsByName[name])).ToList())) { yield return FinalizeSelection(selected); } else { Plugin.Log.LogError((object)"Open Draft: finalize reached without exact 8/4/2 budget; deck unchanged."); } DespawnPage(); Singleton.Instance.Clear(); Singleton.Instance.Controller.SwitchToControlMode((ControlMode)3, false); Singleton.Instance.Controller.LockState = (ViewLockState)1; _running = false; ReturnToMap(); } private void OnCardClicked(SelectableCard card) { _pendingClickedCard = ((Object)((Card)card).Info).name; } private static List BuildFullNaturePool() { return (from c in ScriptableObjectLoader.AllData.FindAll((CardInfo c) => c.metaCategories.Contains((CardMetaCategory)0) && (int)c.temple == 0) where PvpCardPoolRules.IsAllowed(c) && OpenDraftCardPool.IsAllowedName(((Object)c).name) select c).ToList(); } private static GameObject ResolveSelectableCardPrefab() { if (CardChoiceSequencerField == null || SelectableCardPrefabField == null) { return null; } object value = CardChoiceSequencerField.GetValue(Singleton.Instance); if (value == null) { return null; } object? value2 = SelectableCardPrefabField.GetValue(value); return (GameObject)((value2 is GameObject) ? value2 : null); } private void SpawnPage(GameObject cardPrefab, List orderedNames, int pageIndex, List selected) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) List page = OpenDraftCardPool.GetPage(orderedNames, pageIndex); for (int i = 0; i < page.Count; i++) { CardInfo cardByName = CardLoader.GetCardByName(page[i]); GameObject val = Object.Instantiate(cardPrefab); val.transform.SetParent(((Component)this).transform); SelectableCard component = val.GetComponent(); int num = i / 4; int num2 = i % 4; val.transform.localPosition = new Vector3(((float)num2 - 1.5f) * 1.5f, 5.01f, (0.5f - (float)num) * 2.1f); val.SetActive(true); component.Initialize(cardByName, (Action)OnCardClicked, (Action)null, false, (Action)null); ((InteractableBase)component).SetEnabled(true); ((Card)component).SetInteractionEnabled(true); _spawnedCards.Add(val); } Plugin.Log.LogInfo((object)$"Open Draft: spawned page {pageIndex + 1} with {page.Count} visible cards at local Y=5.01."); } private void DespawnPage() { foreach (GameObject spawnedCard in _spawnedCards) { if ((Object)(object)spawnedCard != (Object)null) { Object.Destroy((Object)(object)spawnedCard); } } _spawnedCards.Clear(); } private void ShowStatus(List orderedNames, Dictionary cardsByName, int pageIndex, List selected) { int num = OpenDraftCardPool.PageCount(orderedNames.Count); List source = selected.Select((string name) => TierFor(cardsByName[name])).ToList(); int num2 = source.Count((OpenDraftTier t) => t == OpenDraftTier.Rabbit); int num3 = source.Count((OpenDraftTier t) => t == OpenDraftTier.Wolf); int num4 = source.Count((OpenDraftTier t) => t == OpenDraftTier.Golden); List page = OpenDraftCardPool.GetPage(orderedNames, pageIndex); string text = string.Join("/", page.Select((string name) => TierLabel(TierFor(cardsByName[name]))).Distinct().ToArray()); string text2 = PvpLocalization.Format("KayceePvP.OpenDraft.CounterLine", num2, num3, num4, pageIndex + 1, num, text); string text3 = PvpLocalization.Get("KayceePvP.OpenDraft.ControlsLine"); Singleton.Instance.ShowMessage(text2 + "\n" + _lastFeedback + "\n" + text3, (Emotion)0, (LetterAnimation)0, (Speaker)0, (string[])null); } private static string TierLabel(OpenDraftTier tier) { return tier switch { OpenDraftTier.Wolf => PvpLocalization.Get("KayceePvP.OpenDraft.TierWolf"), OpenDraftTier.Rabbit => PvpLocalization.Get("KayceePvP.OpenDraft.TierRabbit"), _ => PvpLocalization.Get("KayceePvP.OpenDraft.TierGolden"), }; } private static OpenDraftTier TierFor(CardInfo card) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) if (card.metaCategories.Contains((CardMetaCategory)3)) { return OpenDraftTier.Golden; } int num = card.Attack + card.Health; foreach (Ability ability in card.Abilities) { AbilityInfo info = AbilitiesUtil.GetInfo(ability); if ((Object)(object)info != (Object)null) { num += Mathf.Max(0, info.powerLevel); } } if (num < 5) { return OpenDraftTier.Rabbit; } return OpenDraftTier.Wolf; } private static IEnumerator FinalizeSelection(List selected) { DeckInfo val = RunState.Run?.playerDeck; if (val == null) { Plugin.Log.LogWarning((object)"Open Draft: RunState.Run.playerDeck was null at finalize time - aborting safely, no deck change."); yield break; } foreach (string item in selected) { CardInfo cardByName = CardLoader.GetCardByName(item); if ((Object)(object)cardByName == (Object)null || !PvpCardPoolRules.IsAllowed(cardByName) || !OpenDraftCardPool.IsAllowedName(((Object)cardByName).name)) { Plugin.Log.LogError((object)("Open Draft: forbidden/missing final card '" + item + "'; deck unchanged.")); yield break; } } ((CardCollectionInfo)val).Cards.Clear(); foreach (string item2 in selected) { ((CardCollectionInfo)val).AddCard(CardLoader.GetCardByName(item2)); } Plugin.Log.LogInfo((object)$"Open Draft: finalized deck with {((CardCollectionInfo)val).Cards.Count} cards."); yield return (object)new WaitForSeconds(0.1f); } private static void ReturnToMap() { if ((Object)(object)Singleton.Instance != (Object)null) { Singleton.Instance.TransitionToGameState((GameState)1, (NodeData)null); } } } [HarmonyPatch(typeof(RandomConsumable), "RespondsToResolveOnBoard")] internal static class SuppressPackRatConsumableGrantPatch { internal static bool ShouldSuppress(List mods) { return mods?.Exists((CardModificationInfo m) => m.singletonId == PvpCardMutations.PackRatMarker) ?? false; } private static bool Prefix(RandomConsumable __instance, ref bool __result) { PlayableCard component = ((Component)__instance).GetComponent(); CardInfo val = (((Object)(object)component != (Object)null) ? ((Card)component).Info : null); if ((Object)(object)val == (Object)null || !ShouldSuppress(val.Mods)) { return true; } Plugin.Log.LogInfo((object)("PackRat consumable-grant: marker=" + PvpCardMutations.PackRatMarker + " owner=" + PvpOwnership.AbsoluteOwnerOf(component) + " side=" + PvpOwnership.LocalSideOf(component) + " self=" + PvpOwnership.LocalAbsoluteSide() + " card='" + ((Object)val).name + "' decision=suppressed (Rato Curioso never grants a consumable, per product policy).")); __result = false; return false; } } [HarmonyPatch(typeof(AscensionSaveData), "NewRun")] internal static class PeltDraftStartingDeckPatch { [HarmonyPriority(0)] private static void Postfix() { PeltDraftBatchGrant.Reset(); PeltDraftStartingDeck.Reset(); BalancedPathStartingDeck.Reset(); if (!PvpRunState.IsPvpRun) { return; } DeckInfo val = RunState.Run?.playerDeck; if (val != null) { if (PvpRunState.SelectedMapMode == MapMode.PeltDraft) { PeltDraftStartingDeck.Normalize(val); Plugin.Log.LogInfo((object)"Pelt Draft: starting batch granted (3 Rabbit, 2 Wolf, 1 Golden)."); } else if (PvpRunState.SelectedMapMode == MapMode.BalancedRoute) { PeltDraftStartingDeck.Normalize(val); Plugin.Log.LogInfo((object)"Caminho Balanceado: starting batch granted (3 Rabbit, 2 Wolf, 1 Golden)."); } } } } [HarmonyPatch(typeof(MapNodeManager), "DoMoveToNewNode")] internal static class PeltDraftBatchNodePatch { private static void Prefix(MapNode newNode) { if (!PvpRunState.IsPvpRun) { return; } bool flag = PvpRunState.SelectedMapMode == MapMode.PeltDraft; bool flag2 = PvpRunState.SelectedMapMode == MapMode.BalancedRoute; if (!flag && !flag2) { return; } if (((newNode != null) ? newNode.Data : null) is TradePeltsNodeData) { if (flag) { PeltDraftStartingDeck.PrepareTraderVisit(); return; } DeckInfo val = RunState.Run?.playerDeck; if (val != null) { PeltDraftStartingDeck.Normalize(val); Plugin.Log.LogInfo((object)"Caminho Balanceado: deck re-normalized at the Trader boundary (3 Rabbit, 2 Wolf, 1 Golden); third-party starter cards removed."); } } else if (((newNode != null) ? newNode.Data : null) is NoBattleExitNodeData) { PeltDraftBatchGrant.ValidateFinalDeck(); } } } internal static class PeltDraftStartingDeck { private static int _traderVisit; internal static void Reset() { _traderVisit = 0; } internal static void PrepareTraderVisit() { if (_traderVisit == 0) { DeckInfo val = RunState.Run?.playerDeck; if (val == null) { return; } Normalize(val); Plugin.Log.LogInfo((object)"Pelt Draft: deck normalized at first Trader boundary; third-party starter cards removed."); } else if (_traderVisit == 1) { PeltDraftBatchGrant.GrantFullBatch(); } else if (_traderVisit == 2) { PeltDraftBatchGrant.GrantFinalBatch(); } _traderVisit++; } internal static void Normalize(DeckInfo deck) { ((CardCollectionInfo)deck).Cards.Clear(); PeltDraftBatchGrant.Add(deck, "PeltHare", 3); PeltDraftBatchGrant.Add(deck, "PeltWolf", 2); PeltDraftBatchGrant.Add(deck, "PeltGolden", 1); } } internal static class BalancedPathStartingDeck { private static bool _normalizedBeforeFirstEncounter; internal static void Reset() { _normalizedBeforeFirstEncounter = false; } internal static void NormalizeAtFirstEncounter() { if (!_normalizedBeforeFirstEncounter) { DeckInfo val = RunState.Run?.playerDeck; if (val != null) { Normalize(val); _normalizedBeforeFirstEncounter = true; Plugin.Log.LogInfo((object)"Balanced Path: starter normalized at first encounter (1 Rabbit, 1 Wolf, 1 Golden)."); } } } internal static void Normalize(DeckInfo deck) { ((CardCollectionInfo)deck).Cards.Clear(); PeltDraftBatchGrant.Add(deck, "PeltHare", 1); PeltDraftBatchGrant.Add(deck, "PeltWolf", 1); PeltDraftBatchGrant.Add(deck, "PeltGolden", 1); } } internal static class PeltDraftBatchGrant { private static bool _fullBatchGranted; private static bool _finalBatchGranted; internal static void Reset() { _fullBatchGranted = false; _finalBatchGranted = false; } internal static void GrantFullBatch() { if (!_fullBatchGranted) { DeckInfo val = RunState.Run?.playerDeck; if (val != null) { _fullBatchGranted = true; Add(val, "PeltHare", 3); Add(val, "PeltWolf", 2); Add(val, "PeltGolden", 1); Plugin.Log.LogInfo((object)"Pelt Draft: second batch granted (3 Rabbit, 2 Wolf, 1 Golden)."); } } } internal static void GrantFinalBatch() { if (!_finalBatchGranted) { DeckInfo val = RunState.Run?.playerDeck; if (val != null) { _finalBatchGranted = true; Add(val, "PeltHare", 2); Plugin.Log.LogInfo((object)"Pelt Draft: final batch granted (2 Rabbit)."); } } } internal static void Add(DeckInfo deck, string cardName, int count) { for (int i = 0; i < count; i++) { ((CardCollectionInfo)deck).AddCard(CardLoader.GetCardByName(cardName)); } } internal static void ValidateFinalDeck() { DeckInfo val = RunState.Run?.playerDeck; if (val != null) { int count = ((CardCollectionInfo)val).Cards.FindAll((CardInfo c) => (Object)(object)c != (Object)null && (((Object)c).name == "PeltHare" || ((Object)c).name == "PeltWolf" || ((Object)c).name == "PeltGolden")).Count; if (((CardCollectionInfo)val).Cards.Count == 14 && count == 0) { Plugin.Log.LogInfo((object)"Pelt Draft: final deck validated (14 cards, 0 pelts)."); } else { Plugin.Log.LogError((object)$"Pelt Draft: invalid final deck - cards={((CardCollectionInfo)val).Cards.Count}, remainingPelts={count}, expected=14/0."); } } } } [HarmonyPatch] internal static class PeltDraftMergeFilterPatch { [HarmonyPatch(typeof(CardMergeSequencer), "GetValidCardsForHost")] [HarmonyPostfix] private static void HostPostfix(ref List __result) { if (PvpRunState.IsPvpRun && PvpRunState.SelectedMapMode == MapMode.PeltDraft) { __result = PeltDraftAcquiredCards.OnlyMarked(__result); } } [HarmonyPatch(typeof(CardMergeSequencer), "GetValidCardsForSacrifice")] [HarmonyPostfix] private static void SacrificePostfix(ref List __result) { if (PvpRunState.IsPvpRun && PvpRunState.SelectedMapMode == MapMode.PeltDraft) { __result = PeltDraftAcquiredCards.OnlyMarked(__result); } } } [HarmonyPatch] internal static class PeltDraftTraderPatches { [HarmonyPatch(typeof(SaveFile), "GetCurrentRandomSeed")] [HarmonyPrefix] private static bool SeedPrefix(SaveFile __instance, ref int __result) { if (!PvpRunState.IsPvpRun || (PvpRunState.SelectedMapMode != MapMode.PeltDraft && PvpRunState.SelectedMapMode != MapMode.BalancedPath && PvpRunState.SelectedMapMode != MapMode.BalancedRoute)) { return true; } if (!SaveFile.IsAscension || RunState.Run == null) { return true; } __result = PeltDraftTraderSeed.Compute(PvpRunState.SharedMapSeed, RunState.Run.regionTier, RunState.Run.currentNodeId); return false; } } [HarmonyPatch(typeof(AscensionSaveData), "NewRun")] internal static class PeltPenaltyPatch { private static void Prefix(AscensionSaveData __instance) { if (PvpRunState.IsPvpRun) { __instance.numRunsSinceReachedFirstBoss = 0; } } } [HarmonyPatch(typeof(MapNode), "SetActive")] internal static class PreparationBattleNodeVisualPatch { private const string BattleResourceName = "KayceePvP.Assets.battle_node_icon.png"; private const string CampfireResourceName = "KayceePvP.Assets.campfire_node_icon.png"; private const string PvpExitResourceName = "KayceePvP.Assets.final_node_icon.png"; private const string RareRewardResourceName = "KayceePvP.Assets.rare_reward_node_icon.png"; private const string DeathcardResourceName = "KayceePvP.Assets.deathcard_node_icon.png"; private static Texture2D _battleTexture; private static Texture2D _campfireTexture; private static Texture2D _pvpExitTexture; private static Texture2D _rareRewardTexture; private static Texture2D _deathcardTexture; private static Sprite _battleSprite; private static Sprite _campfireSprite; private static Sprite _pvpExitSprite; private static Sprite _rareRewardSprite; private static Sprite _deathcardSprite; private static void Postfix(MapNode __instance) { if (!PvpRunState.IsPvpRun || ((__instance != null) ? __instance.Data : null) == null) { return; } bool flag = __instance.Data is CardBattleNodeData; bool flag2 = __instance.Data is CardStatBoostNodeData; bool flag3 = __instance.Data is NoBattleExitNodeData; bool flag4 = __instance.Data is RareRewardNodeData; bool flag5 = __instance.Data is DeathcardNodeData; if (!flag && !flag2 && !flag3 && !flag4 && !flag5) { return; } AnimatingSprite[] componentsInChildren = ((Component)__instance).GetComponentsInChildren(true); foreach (AnimatingSprite val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } if (val.frames != null && val.frames.Count > 0) { Sprite val2 = ResolveSprite(flag5, flag4, flag, flag2, val.frames[0]); if (!((Object)(object)val2 == (Object)null)) { val.frames = new List { val2 }; val.blinkFrames.Clear(); val.StartFromBeginning(); } return; } if (val.textureFrames != null && val.textureFrames.Count > 0) { Texture2D val3 = ResolveTexture(flag5, flag4, flag, flag2); if (!((Object)(object)val3 == (Object)null)) { val.textureFrames = new List { val3 }; val.StartFromBeginning(); } return; } } SpriteRenderer[] componentsInChildren2 = ((Component)__instance).GetComponentsInChildren(true); foreach (SpriteRenderer val4 in componentsInChildren2) { if (!((Object)(object)val4 == (Object)null) && !((Object)(object)val4.sprite == (Object)null)) { Sprite val5 = ResolveSprite(flag5, flag4, flag, flag2, val4.sprite); if (!((Object)(object)val5 == (Object)null)) { val4.sprite = val5; } return; } } string text = (flag5 ? "Deathcard" : (flag4 ? "rare reward" : (flag ? "battle" : (flag2 ? "campfire" : "PvP exit")))); Plugin.Log.LogWarning((object)("Preparation node visual: vanilla " + text + " glyph animator was not found.")); } private static Sprite ResolveSprite(bool isDeathcard, bool isRareReward, bool isBattle, bool isCampfire, Sprite vanillaFrame) { if (isDeathcard) { return GetNodeSprite(ref _deathcardSprite, ref _deathcardTexture, "KayceePvP.Assets.deathcard_node_icon.png", "KayceePvP_DeathcardNodeIcon", "animated_deathcard_kayceepvp", vanillaFrame); } if (isRareReward) { return GetNodeSprite(ref _rareRewardSprite, ref _rareRewardTexture, "KayceePvP.Assets.rare_reward_node_icon.png", "KayceePvP_RareRewardNodeIcon", "animated_rare_reward_kayceepvp", vanillaFrame); } if (isBattle) { return GetNodeSprite(ref _battleSprite, ref _battleTexture, "KayceePvP.Assets.battle_node_icon.png", "KayceePvP_BattleNodeIcon", "animated_cardbattlenode_kayceepvp", vanillaFrame); } if (isCampfire) { return GetNodeSprite(ref _campfireSprite, ref _campfireTexture, "KayceePvP.Assets.campfire_node_icon.png", "KayceePvP_CampfireNodeIcon", "animated_campfire_kayceepvp", vanillaFrame); } return GetNodeSprite(ref _pvpExitSprite, ref _pvpExitTexture, "KayceePvP.Assets.final_node_icon.png", "KayceePvP_PvpExitNodeIcon", "animated_pvp_exit_kayceepvp", vanillaFrame); } private static Texture2D ResolveTexture(bool isDeathcard, bool isRareReward, bool isBattle, bool isCampfire) { if (isDeathcard) { return GetNodeTexture(ref _deathcardTexture, "KayceePvP.Assets.deathcard_node_icon.png", "KayceePvP_DeathcardNodeIcon"); } if (isRareReward) { return GetNodeTexture(ref _rareRewardTexture, "KayceePvP.Assets.rare_reward_node_icon.png", "KayceePvP_RareRewardNodeIcon"); } if (isBattle) { return GetNodeTexture(ref _battleTexture, "KayceePvP.Assets.battle_node_icon.png", "KayceePvP_BattleNodeIcon"); } if (isCampfire) { return GetNodeTexture(ref _campfireTexture, "KayceePvP.Assets.campfire_node_icon.png", "KayceePvP_CampfireNodeIcon"); } return GetNodeTexture(ref _pvpExitTexture, "KayceePvP.Assets.final_node_icon.png", "KayceePvP_PvpExitNodeIcon"); } private static Sprite GetNodeSprite(ref Sprite cachedSprite, ref Texture2D cachedTexture, string resourceName, string textureName, string spriteName, Sprite vanillaFrame) { //IL_0054: 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) if ((Object)(object)cachedSprite != (Object)null) { return cachedSprite; } Texture2D nodeTexture = GetNodeTexture(ref cachedTexture, resourceName, textureName); if ((Object)(object)nodeTexture == (Object)null) { return null; } float num = (((Object)(object)vanillaFrame != (Object)null) ? vanillaFrame.pixelsPerUnit : 100f); cachedSprite = Sprite.Create(nodeTexture, new Rect(0f, 0f, (float)((Texture)nodeTexture).width, (float)((Texture)nodeTexture).height), new Vector2(0.5f, 0.5f), num); ((Object)cachedSprite).name = spriteName; return cachedSprite; } private static Texture2D GetNodeTexture(ref Texture2D cachedTexture, string resourceName, string textureName) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown if ((Object)(object)cachedTexture != (Object)null) { return cachedTexture; } using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName); if (stream == null) { Plugin.Log.LogError((object)("Preparation node visual: embedded resource '" + resourceName + "' was not found.")); return null; } byte[] array = new byte[stream.Length]; int num; for (int i = 0; i < array.Length; i += num) { num = stream.Read(array, i, array.Length - i); if (num <= 0) { break; } } Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); ((Object)val).name = textureName; ((Texture)val).filterMode = (FilterMode)0; ((Texture)val).wrapMode = (TextureWrapMode)1; if (!ImageConversion.LoadImage(val, array, true)) { Object.Destroy((Object)(object)val); Plugin.Log.LogError((object)("Preparation node visual: embedded PNG '" + resourceName + "' could not be decoded.")); return null; } cachedTexture = val; return cachedTexture; } } [HarmonyPatch(typeof(ProgressionData), "LearnedAbility")] internal static class LearnedAbilityUnlockPatch { private static bool Prefix(ref bool __result) { if (!PvpRunState.IsPvpRun) { return true; } __result = true; return false; } } [HarmonyPatch(typeof(ProgressionData), "LearnedCard")] internal static class LearnedCardUnlockPatch { private static bool Prefix(ref bool __result) { if (!PvpRunState.IsPvpRun) { return true; } __result = true; return false; } } [HarmonyPatch(typeof(ProgressionData), "LearnedMechanic")] internal static class LearnedMechanicUnlockPatch { private static bool Prefix(ref bool __result) { if (!PvpRunState.IsPvpRun) { return true; } __result = true; return false; } } public static class PvpCardPoolRules { public const string GreatKrakenName = "Kraken"; public const string FrozenOpossumName = "FrozenOpossum"; public const string HydraName = "Hydra"; public const string PackRatName = "PackRat"; public const string PoraqueName = "Hrokkall"; public const string IjiraqName = "Ijiraq"; private static readonly string[] TentacleNames = new string[3] { "SquidBell", "SquidCards", "SquidMirror" }; private static readonly SpecialTriggeredAbility[] TentacleAbilities; public static bool IsAllowed(CardInfo card) { if (!((Object)(object)card == (Object)null)) { return !IsBlocked(card); } return true; } public static bool IsBlocked(CardInfo card) { if ((Object)(object)card == (Object)null || ((Object)card).name == "PackRat") { return false; } if (!IsInternalTechnicalCard(((Object)card).name) && !(((Object)card).name == "Hrokkall") && !(((Object)card).name == "FrozenOpossum") && !(((Object)card).name == "Hydra") && !card.HasAbility((Ability)29) && !IsGreatKraken(card) && !IsTentacleCard(card)) { return IsIjiraq(card); } return true; } public static bool IsIjiraq(CardInfo card) { if (!(((Object)card).name == "Ijiraq")) { return card.SpecialAbilities.Contains((SpecialTriggeredAbility)25); } return true; } public static bool IsInternalTechnicalCard(string cardName) { if (!string.IsNullOrEmpty(cardName)) { return cardName.StartsWith("!"); } return false; } public static bool IsGreatKraken(CardInfo card) { if (!(((Object)card).name == "Kraken")) { return card.HasAbility((Ability)89); } return true; } public static bool IsTentacleCard(CardInfo card) { if (!TentacleNames.Contains(((Object)card).name)) { return card.SpecialAbilities.Exists((SpecialTriggeredAbility a) => TentacleAbilities.Contains(a)); } return true; } public unsafe static bool IsAllowedByName(string cardName, IEnumerable abilityNames) { if (string.IsNullOrEmpty(cardName)) { return true; } if (cardName == "PackRat") { return true; } if (IsInternalTechnicalCard(cardName)) { return false; } switch (cardName) { case "Kraken": return false; case "FrozenOpossum": return false; case "Hydra": return false; case "Hrokkall": return false; case "Ijiraq": return false; default: { if (abilityNames != null && abilityNames.Contains("RandomConsumable")) { return false; } if (TentacleNames.Contains(cardName)) { return false; } if (abilityNames == null) { return true; } if (abilityNames.Contains("SubmergeSquid")) { return false; } if (abilityNames.Contains("Shapeshifter")) { return false; } List list = TentacleAbilities.Select((SpecialTriggeredAbility a) => ((object)(*(SpecialTriggeredAbility*)(&a))/*cast due to .constrained prefix*/).ToString()).ToList(); return !abilityNames.Any(list.Contains); } } } public static void FilterFromDeck(List deck, string source) { foreach (CardInfo item in deck.Where(IsBlocked).ToList()) { bool flag = IsGreatKraken(item); bool num = ((Object)item).name == "FrozenOpossum"; bool flag2 = !flag && IsIjiraq(item); string category = (num ? "frozen_opossum_filtered" : (flag ? "great_kraken_filtered" : (flag2 ? "ijiraq_filtered" : "tentacle_card_filtered"))); Plugin.Log.LogWarning((object)("PvP: removed forbidden card '" + ((Object)item).name + "' from the deck (" + source + ") as a safety net.")); Telemetry.Log(category, "source=" + source + " name=" + ((Object)item).name); deck.Remove(item); } } static PvpCardPoolRules() { SpecialTriggeredAbility[] array = new SpecialTriggeredAbility[3]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); TentacleAbilities = (SpecialTriggeredAbility[])(object)array; } } [HarmonyPatch(typeof(CardLoader), "GetUnlockedCards")] internal static class CardPoolFilterPatch { private static void Postfix(ref List __result) { foreach (CardInfo item in __result) { PvpCardMutations.ApplyToCard(item); } if (!PvpRunState.IsPvpRun) { return; } if (PvpRunState.SelectedMapMode == MapMode.PeltDraft || PvpRunState.SelectedMapMode == MapMode.BalancedPath || PvpRunState.SelectedMapMode == MapMode.BalancedRoute) { int count = __result.Count; __result = __result.Where((CardInfo c) => (Object)(object)c != (Object)null && !PeltDraftPlan.IsForbiddenOfferName(((Object)c).name) && PvpCardPoolRules.IsAllowed(c)).ToList(); int num = count - __result.Count; if (num > 0) { Plugin.Log.LogInfo((object)$"PvP pelt map: filtered {num} forbidden/special card occurrence(s), including Lice/Hrokkall, from Trader/choice pool."); } return; } int count2 = __result.Count; List kraken = __result.Where(PvpCardPoolRules.IsGreatKraken).ToList(); List list = __result.Where((CardInfo c) => !kraken.Contains(c) && PvpCardPoolRules.IsTentacleCard(c)).ToList(); List list2 = __result.Where((CardInfo c) => ((Object)c).name == "FrozenOpossum").ToList(); List list3 = __result.Where((CardInfo c) => ((Object)c).name == "Hydra").ToList(); List list4 = __result.Where((CardInfo c) => !kraken.Contains(c) && PvpCardPoolRules.IsIjiraq(c)).ToList(); if (kraken.Count != 0 || list.Count != 0 || list2.Count != 0 || list3.Count != 0 || list4.Count != 0) { __result = __result.Where(PvpCardPoolRules.IsAllowed).ToList(); Plugin.Log.LogInfo((object)$"PvP: filtered {count2 - __result.Count} card occurrence(s) (Kraken/tentacle/Hydra/Ijiraq) out of a card pool ({count2} -> {__result.Count})."); if (kraken.Count > 0) { Telemetry.Log("great_kraken_filtered", $"source=pool count={kraken.Count}"); } if (list.Count > 0) { Telemetry.Log("tentacle_card_filtered", $"source=pool count={list.Count}"); } if (list2.Count > 0) { Telemetry.Log("frozen_opossum_filtered", $"source=pool count={list2.Count}"); } if (list3.Count > 0) { Telemetry.Log("hydra_direct_offer_filtered", $"source=pool count={list3.Count}"); } if (list4.Count > 0) { Telemetry.Log("ijiraq_filtered", $"source=pool count={list4.Count}"); } } } } [HarmonyPatch(typeof(DialogueData), "GetEvent")] internal static class PvpDialogueEventOverridePatch { private static bool Prefix(string id, ref DialogueEvent __result) { if (!PvpRunState.IsPvpRun) { return true; } if (!PvpDialogueOverrides.TryGetEventOverride(id, out var dialogueEvent)) { return true; } __result = dialogueEvent; return false; } } [HarmonyPatch(typeof(TextDisplayer), "ShowMessage")] internal static class PvpDialogueLiteralOverridePatch { private static void Prefix(ref string message) { if (PvpRunState.IsPvpRun && PvpDialogueOverrides.TryGetLiteralOverride(message, out var replacement)) { message = replacement; } } } [HarmonyPatch(typeof(PlayableCard), "SetInfo")] internal static class RemoteCardSetInfoDiagnosticPatch { private static void Postfix(PlayableCard __instance) { if (PvpRunState.IsDuelActive && !((Object)(object)__instance == (Object)null) && !((Object)(object)((Card)__instance).Info == (Object)null) && NetCardInfo.HasCanonicalWireRecord(((Card)__instance).Info)) { Plugin.Log.LogInfo((object)("Duel (card-authority): AFTER SetInfo/CreateCard | card='" + ((Object)((Card)__instance).Info).name + "' abilities=[" + string.Join("+", NetCardInfo.EffectiveAbilityIds(__instance)) + "]")); } } } [HarmonyPatch(typeof(BoardManager), "ResolveCardOnBoard")] internal static class RemoteCardResolveAuthorityPatch { private static void Prefix(PlayableCard card) { if (PvpRunState.IsDuelActive && !((Object)(object)card == (Object)null) && !((Object)(object)((Card)card).Info == (Object)null) && NetCardInfo.HasCanonicalWireRecord(((Card)card).Info)) { Plugin.Log.LogInfo((object)("Duel (card-authority): BEFORE ResolveCardOnBoard | card='" + ((Object)((Card)card).Info).name + "' abilities=[" + string.Join("+", NetCardInfo.EffectiveAbilityIds(card)) + "]")); } } private static void Postfix(PlayableCard card, ref IEnumerator __result) { if (PvpRunState.IsDuelActive && !((Object)(object)card == (Object)null) && !((Object)(object)((Card)card).Info == (Object)null) && NetCardInfo.HasCanonicalWireRecord(((Card)card).Info)) { __result = RunThenEnforce(__result, card); } } private static IEnumerator RunThenEnforce(IEnumerator original, PlayableCard card) { while (original.MoveNext()) { yield return original.Current; } Plugin.Log.LogInfo((object)("Duel (card-authority): AFTER ResolveCardOnBoard triggers | card='" + ((Object)((Card)card).Info).name + "' abilities=[" + string.Join("+", NetCardInfo.EffectiveAbilityIds(card)) + "]")); NetCardInfo.EnforceRemoteCardAbilityAuthority(card); } } [HarmonyPatch(typeof(Part1GameFlowManager), "PlayerLostBattleSequence")] internal static class SuppressRealLifeLossDuringDuelPatch { private static bool Prefix(ref IEnumerator __result) { if (!PvpRunState.IsDuelActive && !LobbyMatchState.InterBattleSceneReloadActive) { return true; } Plugin.Log.LogInfo((object)"PvP: suppressed the real Kaycee's Mod candle/run-life loss for a duel loss (battle or final) - see RoundLifecyclePatch.cs."); __result = Empty(); return false; } private static IEnumerator Empty() { yield break; } } [HarmonyPatch(typeof(MapNodeManager), "DoMoveToNewNode")] internal static class RouteTrackingPatch { private static int _previousGridY = -1; private static float _nodeEnterRealtime; private static void Prefix(MapNode newNode) { if (PvpRunState.IsPvpRun && ((newNode != null) ? newNode.Data : null) != null) { if (newNode.Data.gridY == 0) { _previousGridY = -1; } if (_previousGridY >= 0) { float num = Time.realtimeSinceStartup - _nodeEnterRealtime; Telemetry.Log("node_exit", $"gridY={_previousGridY} durationSeconds={num:F1}"); } _previousGridY = newNode.Data.gridY; _nodeEnterRealtime = Time.realtimeSinceStartup; object[] obj = new object[4] { newNode.Data.gridY, newNode.Data.gridX, ((object)newNode.Data).GetType().Name, null }; RunState run = RunState.Run; int? obj2; if (run == null) { obj2 = null; } else { DeckInfo playerDeck = run.playerDeck; obj2 = ((playerDeck != null) ? new int?(((CardCollectionInfo)playerDeck).Cards.Count) : ((int?)null)); } obj[3] = obj2 ?? (-1); Telemetry.Log("node_enter", string.Format("gridY={0} gridX={1} type={2} deckSize={3}", obj)); if (MapModeCatalog.TryGet(PvpRunState.SelectedMapMode, out var descriptor) && descriptor.ForkGridY.HasValue && newNode.Data.gridY == descriptor.ForkGridY.Value && descriptor.RouteNamesByGridX != null && descriptor.RouteNamesByGridX.TryGetValue(newNode.Data.gridX, out var value)) { PvpRunState.Route = value; Plugin.Log.LogInfo((object)$"PvP: local player took the '{value}' lane (fork gridX={newNode.Data.gridX})."); Telemetry.Log("route_chosen", value); } } } } [HarmonyPatch(typeof(AscensionUnlockSchedule), "StarterDeckIsUnlockedForLevel")] internal static class UnlockAllStarterDecksPatch { private static bool Prefix(ref bool __result) { if (!Plugin.MultiplayerEnabled.Value) { return true; } __result = true; return false; } } [HarmonyPatch(typeof(ConceptProgressionTree), "CardUnlocked")] internal static class UnlockAllCardsPatch { private static bool Prefix(ref bool __result) { if (!PvpRunState.IsPvpRun) { return true; } __result = true; return false; } } internal static class StarterDeckGate { internal const int ForcedCount = 2; internal static bool ShouldOverride(bool multiplayerEnabled) { return multiplayerEnabled; } } [HarmonyPatch(typeof(AscensionUnlockSchedule), "NumStarterDecksUnlocked")] internal static class ForceStarterDeckSelectScreenPatch { private static bool Prefix(ref int __result) { if (!StarterDeckGate.ShouldOverride(Plugin.MultiplayerEnabled.Value)) { return true; } __result = 2; return false; } } [HarmonyPatch(typeof(Part1CardChoiceGenerator), "GenerateChoices")] internal static class SafeRandomChoicePatch { private static bool Prefix(CardChoicesNodeData data, int randomSeed, ref List __result) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (!PvpRunState.IsPvpRun) { return true; } if (data == null || (int)data.choicesType != 0) { return true; } if (data.overrideChoices != null) { return true; } __result = BuildSafeRandomChoices(randomSeed); return false; } private static List BuildSafeRandomChoices(int randomSeed) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown List list = new List(); HashSet hashSet = new HashSet(); int num = randomSeed; int num2 = 0; while (list.Count < 3 && num2++ < 50) { CardInfo randomChoosableCard = CardLoader.GetRandomChoosableCard(num++, (CardTemple)0); if (!((Object)(object)randomChoosableCard == (Object)null) && hashSet.Add(((Object)randomChoosableCard).name)) { list.Add(new CardChoice { CardInfo = randomChoosableCard }); } } return list; } } [HarmonyPatch(typeof(SanctumSceneSequencer), "TakePhoto")] internal static class SanctumTakePhotoPatch { private static bool Prefix(string dialogueId, string animationId, ref IEnumerator __result) { if (!PvpRunState.IsPvpRun) { return true; } __result = PvpCardCreationFlow.TakePhotoAndContinueToPvp(dialogueId, animationId); return false; } } [HarmonyPatch(typeof(TurnManager), "ScalesTippedToOpponent")] internal static class ScalesTippedThresholdPatch { internal static int Threshold { get { if (MapModeCatalog.TryGet(PvpRunState.SelectedMapMode, out var descriptor)) { return descriptor.DuelThreshold; } string text = $"ScalesTippedThresholdPatch: no MapModeCatalog entry for {PvpRunState.SelectedMapMode} while a duel is active - impossible protocol state."; Plugin.Log.LogError((object)text); DuelSession.Instance?.TriggerProtocolDesync(text); return 10; } } private static bool Prefix(ref bool __result) { if (!PvpRunState.IsDuelActive) { return true; } __result = Singleton.Instance.Balance >= Threshold; return false; } } [HarmonyPatch(typeof(TurnManager), "LifeLossConditionsMet")] internal static class LifeLossThresholdPatch { private static bool Prefix(TurnManager __instance, ref bool __result) { if (!PvpRunState.IsDuelActive) { return true; } Opponent value = Traverse.Create((object)__instance).Field("opponent").Value; if (Mathf.Abs(Singleton.Instance.Balance) < ScalesTippedThresholdPatch.Threshold && !value.Surrendered) { __result = __instance.PlayerSurrendered; } else { __result = true; } return false; } } [HarmonyPatch(typeof(RunState), "GenerateMapDataForCurrentRegion")] internal static class SharedMapSeedPatch { private static State _savedState; private const int VanillaGridLength = 14; private static bool Prefix(RunState __instance, ref PredefinedNodes predefinedNodes) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (!PvpRunState.IsPvpRun) { return true; } _savedState = Random.state; Random.InitState(PvpRunState.SharedMapSeed); if (!MapModeCatalog.TryGet(PvpRunState.SelectedMapMode, out var descriptor)) { string text = $"SharedMapSeedPatch: no MapModeCatalog entry for {PvpRunState.SelectedMapMode} - map generation aborted, never falls back to vanilla/a different map."; Plugin.Log.LogError((object)text); DuelSession.Instance?.TriggerProtocolDesync(text); return false; } if (__instance.regionTier == 0 && (Object)(object)predefinedNodes == (Object)null) { predefinedNodes = ScriptableObject.CreateInstance(); predefinedNodes.nodeRows = descriptor.BuildRows(); } if (descriptor.RowCount != 14) { __instance.map = MapGenerator.GenerateMap(RunState.CurrentMapRegion, 3, descriptor.RowCount, predefinedNodes); __instance.currentNodeId = ((MapElementData)__instance.map.RootNode).id; return false; } return true; } private static void Postfix() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) if (PvpRunState.IsPvpRun) { Random.state = _savedState; } } } [HarmonyPatch(typeof(TextDisplayer), "PlayDialogueEvent", new Type[] { typeof(string), typeof(MessageAdvanceMode), typeof(EventIntersectMode), typeof(string[]), typeof(Action) })] internal static class SkipSanctumDialoguePatch { private static bool Prefix(string eventId, ref IEnumerator __result) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (!PvpRunState.IsPvpRun) { return true; } Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).name != "Part1_Sanctum") { return true; } if (eventId == "KayceePvPDeathcardIntro") { return true; } __result = Skip(); return false; } private static IEnumerator Skip() { yield break; } } [HarmonyPatch(typeof(TextDisplayer), "ShowUntilInput")] internal static class SkipSanctumShowUntilInputPatch { private static bool Prefix(ref IEnumerator __result) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (!PvpRunState.IsPvpRun) { return true; } Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).name != "Part1_Sanctum") { return true; } __result = Skip(); return false; } private static IEnumerator Skip() { yield break; } } [HarmonyPatch(typeof(AscensionSaveData), "NewRun")] internal static class TelemetryRunStartPatch { private static void Postfix() { if (PvpRunState.IsPvpRun) { Telemetry.StartRun(); Telemetry.LogStarterDeck(); Telemetry.LogFirstPlayer(); } } } [HarmonyPatch(typeof(DeckInfo), "AddCard")] internal static class TelemetryAddCardPatch { private static void Postfix(CardInfo __result) { if (PvpRunState.IsPvpRun && !((Object)(object)__result == (Object)null)) { Telemetry.Log("card_added", Telemetry.CardSummary(__result)); } } } [HarmonyPatch(typeof(CardCollectionInfo), "RemoveCard")] internal static class TelemetryRemoveCardPatch { private static void Postfix(CardInfo card) { if (PvpRunState.IsPvpRun && !((Object)(object)card == (Object)null)) { Telemetry.Log("card_removed", Telemetry.CardSummary(card)); } } } [HarmonyPatch(typeof(DeckInfo), "ModifyCard")] internal static class TelemetryModifyCardPatch { private static void Postfix(CardInfo card, CardModificationInfo mod) { if (PvpRunState.IsPvpRun) { string text = ((mod.abilities.Count > 0) ? string.Join("+", mod.abilities) : "-"); Telemetry.Log("card_modified", $"{((Object)card).name} atk={mod.attackAdjustment} hp={mod.healthAdjustment} abilities={text} fromMerge={mod.fromCardMerge} fromTotem={mod.fromTotem}"); } } } [HarmonyPatch(typeof(CardMergeSequencer), "ModifyHostCard")] internal static class TelemetryMergePatch { private static void Postfix(CardInfo hostCardInfo, CardInfo sacrificeCardInfo) { if (PvpRunState.IsPvpRun) { Telemetry.Log("merge", "host=" + ((Object)hostCardInfo).name + " sacrifice=" + ((Object)sacrificeCardInfo).name + " transferredAbilities=[" + string.Join("+", sacrificeCardInfo.Abilities) + "]"); } } } [HarmonyPatch(typeof(Part1CardChoiceGenerator), "GenerateChoices")] internal static class TelemetryDraftOfferedPatch { private static void Postfix(CardChoicesNodeData data, List __result) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) if (PvpRunState.IsPvpRun && __result != null) { string arg = string.Join(" | ", __result.Select(DescribeChoice)); Telemetry.Log("draft_offered", $"type={data?.choicesType} options=[{arg}]"); } } private static string DescribeChoice(CardChoice c) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Invalid comparison between Unknown and I4 //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)c.CardInfo != (Object)null) { return ((Object)c.CardInfo).name; } if ((int)c.tribe != 0) { return $"tribe:{c.tribe}"; } if ((int)c.resourceType != 1 || c.resourceAmount != 0) { return $"{c.resourceType}:{c.resourceAmount}"; } return "?"; } } [HarmonyPatch(typeof(CardDrawPiles3D), "DrawOpeningHand")] internal static class TelemetryOpeningHandPatch { private static void Postfix() { if (PvpRunState.IsPvpRun && PvpRunState.IsDuelActive) { List cards = Singleton.Instance.CardsInHand.Select((PlayableCard c) => ((Card)c).Info).ToList(); Telemetry.Log("pvp_opening_hand", Telemetry.DeckSummary(cards)); } } } [HarmonyPatch(typeof(TurnManager), "LifeLossConditionsMet")] internal static class TelemetryDuelEndPatch { private static void Postfix(TurnManager __instance, bool __result) { if (PvpRunState.IsPvpRun && PvpRunState.IsDuelActive && __result && LobbyMatchState.BattleInProgress) { float num = Singleton.Instance.Balance; string arg = ((num > 0f) ? "local_player_won" : ((num < 0f) ? "local_player_lost" : "surrendered")); Telemetry.LogDuelEnd($"{arg} balance={num}"); if ((Object)(object)LobbySession.Instance != (Object)null && (Object)(object)DuelSession.Instance != (Object)null && !DuelSession.Instance.BattleOutcomeAlreadySent(LobbyMatchState.BattleNumber)) { bool flag = num < 0f; bool loserIsHost = (LobbySession.Instance.IsHost ? flag : (!flag)); DuelSession.Instance.ObserveBattleOutcome(loserIsHost, (int)num, "balance"); } } } } [HarmonyPatch(typeof(AscensionSaveData), "NewRun")] internal static class TotemGrantPatch { private static void Postfix() { if (PvpRunState.IsPvpRun) { RunSetup.GrantAllTotemTops(); TotemStateDiagnosticPatch.ResetForNewRun(); } } } [HarmonyPatch(typeof(CardGainAbility), "RespondsToOtherCardAssignedToSlot")] internal static class TotemAppliesOnlyToOwnersOwnCardsPatch { private static bool Prefix(CardGainAbility __instance, PlayableCard otherCard, ref bool __result) { //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_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: 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_0213: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) if (!PvpRunState.IsDuelActive) { return true; } Ability value = Traverse.Create((object)__instance).Property("Data", (object[])null).Field("bottom") .Field("effectParams") .Field("ability") .GetValue(); Tribe value2 = Traverse.Create((object)__instance).Property("Data", (object[])null).Field("top") .Field("prerequisites") .Field("tribe") .GetValue(); if (otherCard.OpponentCard) { if (((Card)otherCard).Info.IsOfTribe(value2) && !otherCard.HasAbility(value)) { Plugin.Log.LogInfo((object)$"Duel (totem): BLOCKED cross-owner grant attempt | CARD='{((Object)((Card)otherCard).Info).name}' CARD_ABSOLUTE_OWNER={PvpOwnership.AbsoluteOwnerOf(otherCard)} TOTEM_OWNER={PvpOwnership.LocalAbsoluteSide()} SELF={PvpOwnership.LocalAbsoluteSide()} TOTEM_TRIBE={value2} TOTEM_ABILITY={value} - this process' own totem does not belong to this card's owner, never applying regardless of tribe match."); } __result = false; return false; } bool flag = ((Card)otherCard).Info.IsOfTribe(value2); bool flag2 = otherCard.HasAbility(value); bool flag3 = ((TriggerReceiver)__instance).RespondsToOtherCardDrawn(otherCard); string text = PvpOwnership.AbsoluteOwnerOf(otherCard); string text2 = PvpOwnership.LocalSideOf(otherCard); string text3 = PvpOwnership.LocalAbsoluteSide(); string text4 = string.Join("+", ((Card)otherCard).Info.tribes); string text5 = string.Join("+", ((Card)otherCard).Info.DefaultAbilities); string text6 = string.Join("+", ((Card)otherCard).Info.ModAbilities); List list = new List(((Card)otherCard).Info.Abilities); foreach (CardModificationInfo temporaryMod in otherCard.TemporaryMods) { list.AddRange(temporaryMod.abilities); } string text7 = string.Join("+", list.Distinct()); string text8 = $"Duel (totem): CARD='{((Object)((Card)otherCard).Info).name}' CARD_ABSOLUTE_OWNER={text} LOCAL_SIDE={text2} TOTEM_OWNER={text3} SELF={text3} TRIBES=[{text4}] BASE_ABILITIES=[{text5}] CARD_MOD_ABILITIES=[{text6}] FINAL_RUNTIME_ABILITIES=[{text7}] TOTEM_TRIBE={value2} TOTEM_ABILITY={value} TOTEM_MATCH={flag} ALREADY_HAS_ABILITY={flag2}"; if (!flag3) { string text9 = ((!flag) ? "tribe mismatch" : "already has ability"); Plugin.Log.LogInfo((object)(text8 + " RESULT=SKIPPED REASON='" + text9 + "'")); __result = false; return false; } __result = !((Card)otherCard).Info.Mods.Exists((CardModificationInfo m) => m.fromEvolve); Plugin.Log.LogInfo((object)(text8 + " RESULT=" + (__result ? "GRANTED" : "SKIPPED") + " REASON='" + (__result ? "-" : "evolve mod") + "'")); return false; } } [HarmonyPatch(typeof(CardGainAbility), "RespondsToOtherCardDrawn")] internal static class TotemDrawnAppliesOnlyToOwnersOwnCardsPatch { private static bool Prefix(CardGainAbility __instance, PlayableCard card, ref bool __result) { //IL_004b: 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_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) if (!PvpRunState.IsDuelActive || (Object)(object)card == (Object)null) { return true; } if (!card.OpponentCard) { return true; } Ability value = Traverse.Create((object)__instance).Property("Data", (object[])null).Field("bottom") .Field("effectParams") .Field("ability") .GetValue(); Tribe value2 = Traverse.Create((object)__instance).Property("Data", (object[])null).Field("top") .Field("prerequisites") .Field("tribe") .GetValue(); Plugin.Log.LogWarning((object)$"Duel (totem): UNEXPECTED - RespondsToOtherCardDrawn fired for a remote card (should never happen - NetworkOpponent never draws into a hand) | CARD='{((Object)((Card)card).Info).name}' CARD_ABSOLUTE_OWNER={PvpOwnership.AbsoluteOwnerOf(card)} TOTEM_OWNER={PvpOwnership.LocalAbsoluteSide()} TOTEM_TRIBE={value2} TOTEM_ABILITY={value} - blocking anyway."); __result = false; return false; } } [HarmonyPatch(typeof(CardGainAbility), "OnOtherCardDrawn")] internal static class TotemDrawnTriggerAppliedDiagnosticPatch { private static void Postfix(PlayableCard card) { if (PvpRunState.IsDuelActive && !((Object)(object)card == (Object)null)) { Plugin.Log.LogInfo((object)("Duel (totem): TRIGGER=DRAWN OnOtherCardDrawn coroutine started for card='" + ((Object)((Card)card).Info).name + "' (CARD_ABSOLUTE_OWNER=" + PvpOwnership.AbsoluteOwnerOf(card) + " SELF=" + PvpOwnership.LocalAbsoluteSide() + ")")); } } } [HarmonyPatch(typeof(Part1ItemsManager), "OnUpdateItems")] internal static class TotemStateDiagnosticPatch { private static int _lastLoggedTotemCount = -1; public static void ResetForNewRun() { _lastLoggedTotemCount = -1; } private static void Postfix() { if (!PvpRunState.IsPvpRun || RunState.Run == null) { return; } int count = RunState.Run.totems.Count; if (count != _lastLoggedTotemCount) { _lastLoggedTotemCount = count; string text = string.Join(" | ", RunState.Run.totems.Select((TotemDefinition t) => $"tribe={t.tribe} ability={t.ability}")); Plugin.Log.LogInfo((object)$"Duel (totem): RunState.Run.totems changed | SELF={PvpOwnership.LocalAbsoluteSide()} count={count} totemTops={RunState.Run.totemTops.Count} totemBottoms={RunState.Run.totemBottoms.Count} assembled=[{text}]"); } } } [HarmonyPatch(typeof(TradePeltsSequencer), "GetTradeCardInfos")] internal static class TraderOfferSafetyPatch { private static void Postfix(ref List __result) { if (PvpRunState.IsPvpRun && __result != null) { int count = __result.Count; __result = __result.Where(PvpCardPoolRules.IsAllowed).ToList(); if (__result.Count != count) { Plugin.Log.LogWarning((object)$"PvP Trader: removed {count - __result.Count} forbidden card(s) at the final offer boundary."); } } } } } namespace KayceePvP.Net { internal class DuelHud : MonoBehaviour { private GameObject _canvasRoot; private Text _balanceText; private Text _seriesText; private Text _turnText; private float _turnNoticeUntil; private bool _wasLocalTurn; private Text _opponentTotemText; private GameObject _opponentTotemRoot; private Image _opponentTotemTribeIcon; private Image _opponentTotemAbilityIcon; private Tribe _displayedTotemTribe; private Ability _displayedTotemAbility; private bool _totemIconsInitialized; private bool _wasActive; internal static void RegisterLocalizedText() { PvpLocalization.L("KayceePvP.Duel.BalanceLine", "{0} DAMAGE x{1} | x{2} DAMAGE {3}", "{0} DANO x{1} | x{2} DANO {3}"); PvpLocalization.L("KayceePvP.Duel.SeriesLine", "SCORE: {0} {1} x {2} {3}", "PLACAR: {0} {1} x {2} {3}"); PvpLocalization.L("KayceePvP.Duel.YourTurn", "YOUR TURN", "SUA VEZ"); PvpLocalization.L("KayceePvP.Duel.OpponentTotem", "OPPONENT'S TOTEM", "TOTEM DO ADVERSÁRIO"); PvpLocalization.L("KayceePvP.Duel.OpponentTotemWithDetail", "OPPONENT'S TOTEM\n{0} + {1}", "TOTEM DO ADVERSÁRIO\n{0} + {1}"); } private void Awake() { BuildUI(); _canvasRoot.SetActive(false); } private void Update() { //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) bool flag = PvpRunState.IsDuelActive && (Object)(object)Singleton.Instance != (Object)null; if (flag != _wasActive) { _canvasRoot.SetActive(flag); _wasActive = flag; if (!flag) { _wasLocalTurn = false; } } if (flag) { bool flag2 = (Object)(object)Singleton.Instance != (Object)null && Singleton.Instance.IsPlayerTurn; if (flag2 && !_wasLocalTurn) { ShowTurnNotice(); } _wasLocalTurn = flag2; if ((Object)(object)_turnText != (Object)null) { ((Component)_turnText).gameObject.SetActive(Time.unscaledTime < _turnNoticeUntil); } float num = Singleton.Instance.Balance; LobbySession instance = LobbySession.Instance; float num2 = (((Object)(object)instance == (Object)null || instance.IsHost) ? num : (0f - num)); int num3 = Mathf.Max(0, Mathf.RoundToInt(num2)); int num4 = Mathf.Max(0, Mathf.RoundToInt(0f - num2)); string text = (((Object)(object)instance != (Object)null) ? instance.HostDisplayName : PvpLocalization.Get("KayceePvP.Lobby.DefaultPlayer1")); string text2 = (((Object)(object)instance != (Object)null) ? instance.PeerDisplayName : PvpLocalization.Get("KayceePvP.Lobby.DefaultPlayer2")); _balanceText.text = PvpLocalization.Format("KayceePvP.Duel.BalanceLine", text, num3, num4, text2); _seriesText.text = PvpLocalization.Format("KayceePvP.Duel.SeriesLine", text, LobbyMatchState.HostWins, LobbyMatchState.PeerWins, text2); bool flag3 = (Object)(object)instance != (Object)null && instance.PeerTotemPresent; _opponentTotemRoot.SetActive(flag3); if (flag3) { RefreshOpponentTotem(instance.PeerTotemTribe, instance.PeerTotemAbility); } } } private unsafe void RefreshOpponentTotem(Tribe tribe, Ability ability) { //IL_001c: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0008: 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_0011: 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) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: 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_0135: Unknown result type (might be due to invalid IL or missing references) if (!_totemIconsInitialized || tribe != _displayedTotemTribe || ability != _displayedTotemAbility) { _displayedTotemTribe = tribe; _displayedTotemAbility = ability; _totemIconsInitialized = true; Sprite val = ResourceBank.Get("Art/Cards/TribeIcons/tribeicon_" + ((object)(*(Tribe*)(&tribe))/*cast due to .constrained prefix*/).ToString().ToLowerInvariant()); AbilityInfo info = AbilitiesUtil.GetInfo(ability); Sprite val2 = (((Object)(object)info != (Object)null) ? info.pixelIcon : null); _opponentTotemTribeIcon.sprite = val; ((Component)_opponentTotemTribeIcon).gameObject.SetActive((Object)(object)val != (Object)null); _opponentTotemAbilityIcon.sprite = val2; ((Component)_opponentTotemAbilityIcon).gameObject.SetActive((Object)(object)val2 != (Object)null); bool flag = (Object)(object)val != (Object)null && (Object)(object)val2 != (Object)null; _opponentTotemText.text = (flag ? PvpLocalization.Get("KayceePvP.Duel.OpponentTotem") : PvpLocalization.Format("KayceePvP.Duel.OpponentTotemWithDetail", tribe, ability)); Plugin.Log.LogInfo((object)string.Format("Duel HUD: opponent Totem icons tribe={0} ({1}) ability={2} ({3}).", tribe, ((Object)(object)val != (Object)null) ? "OK" : "missing", ability, ((Object)(object)val2 != (Object)null) ? "OK" : "missing")); } } private void BuildUI() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: 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) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Expected O, but got Unknown //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Expected O, but got Unknown //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_02ae: Unknown result type (might be due to invalid IL or missing references) //IL_02c2: Unknown result type (might be due to invalid IL or missing references) //IL_02e4: Unknown result type (might be due to invalid IL or missing references) //IL_02ea: Expected O, but got Unknown //IL_0303: Unknown result type (might be due to invalid IL or missing references) //IL_030e: Unknown result type (might be due to invalid IL or missing references) //IL_0319: Unknown result type (might be due to invalid IL or missing references) //IL_0323: Unknown result type (might be due to invalid IL or missing references) //IL_0377: Unknown result type (might be due to invalid IL or missing references) //IL_03bd: Unknown result type (might be due to invalid IL or missing references) //IL_03c4: Expected O, but got Unknown //IL_03ee: Unknown result type (might be due to invalid IL or missing references) //IL_0403: Unknown result type (might be due to invalid IL or missing references) //IL_0418: Unknown result type (might be due to invalid IL or missing references) //IL_042d: Unknown result type (might be due to invalid IL or missing references) //IL_0441: Unknown result type (might be due to invalid IL or missing references) //IL_04a2: Unknown result type (might be due to invalid IL or missing references) //IL_0501: Unknown result type (might be due to invalid IL or missing references) //IL_0515: Unknown result type (might be due to invalid IL or missing references) //IL_053f: Unknown result type (might be due to invalid IL or missing references) //IL_0546: Expected O, but got Unknown //IL_0582: Unknown result type (might be due to invalid IL or missing references) //IL_05a4: Unknown result type (might be due to invalid IL or missing references) //IL_05b9: Unknown result type (might be due to invalid IL or missing references) //IL_05ce: Unknown result type (might be due to invalid IL or missing references) //IL_05e3: Unknown result type (might be due to invalid IL or missing references) //IL_05f7: Unknown result type (might be due to invalid IL or missing references) //IL_0619: Unknown result type (might be due to invalid IL or missing references) //IL_0620: Expected O, but got Unknown //IL_064e: Unknown result type (might be due to invalid IL or missing references) //IL_0655: Unknown result type (might be due to invalid IL or missing references) //IL_0667: Unknown result type (might be due to invalid IL or missing references) //IL_067b: Unknown result type (might be due to invalid IL or missing references) //IL_06b3: Unknown result type (might be due to invalid IL or missing references) //IL_06e1: Unknown result type (might be due to invalid IL or missing references) //IL_06e6: Unknown result type (might be due to invalid IL or missing references) //IL_06f9: Unknown result type (might be due to invalid IL or missing references) //IL_0712: Unknown result type (might be due to invalid IL or missing references) //IL_0719: Unknown result type (might be due to invalid IL or missing references) //IL_072b: Unknown result type (might be due to invalid IL or missing references) //IL_073f: Unknown result type (might be due to invalid IL or missing references) //IL_077f: Unknown result type (might be due to invalid IL or missing references) //IL_07b2: Unknown result type (might be due to invalid IL or missing references) //IL_07b9: Expected O, but got Unknown //IL_07e7: Unknown result type (might be due to invalid IL or missing references) //IL_07ee: Unknown result type (might be due to invalid IL or missing references) //IL_0800: Unknown result type (might be due to invalid IL or missing references) //IL_0814: Unknown result type (might be due to invalid IL or missing references) //IL_084c: Unknown result type (might be due to invalid IL or missing references) //IL_087a: Unknown result type (might be due to invalid IL or missing references) //IL_0881: Expected O, but got Unknown //IL_08a7: Unknown result type (might be due to invalid IL or missing references) //IL_08bc: Unknown result type (might be due to invalid IL or missing references) //IL_08d1: Unknown result type (might be due to invalid IL or missing references) //IL_08e6: Unknown result type (might be due to invalid IL or missing references) //IL_08f0: Unknown result type (might be due to invalid IL or missing references) //IL_0945: Unknown result type (might be due to invalid IL or missing references) _canvasRoot = new GameObject("KayceePvP_DuelHudCanvas"); _canvasRoot.transform.SetParent(((Component)this).transform, false); Canvas obj = _canvasRoot.AddComponent(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 100; CanvasScaler obj2 = _canvasRoot.AddComponent(); obj2.uiScaleMode = (ScaleMode)1; obj2.referenceResolution = new Vector2(1920f, 1080f); GameObject val = new GameObject("Backdrop", new Type[1] { typeof(RectTransform) }); val.transform.SetParent(_canvasRoot.transform, false); Image obj3 = val.AddComponent(); ((Graphic)obj3).color = new Color(0f, 0f, 0f, 0.55f); ((Graphic)obj3).raycastTarget = false; RectTransform component = val.GetComponent(); component.anchorMin = new Vector2(0.5f, 1f); component.anchorMax = new Vector2(0.5f, 1f); component.pivot = new Vector2(0.5f, 1f); component.sizeDelta = new Vector2(1000f, 36f); component.anchoredPosition = new Vector2(0f, -12f); GameObject val2 = new GameObject("BalanceText", new Type[1] { typeof(RectTransform) }); val2.transform.SetParent(val.transform, false); RectTransform component2 = val2.GetComponent(); component2.anchorMin = Vector2.zero; component2.anchorMax = Vector2.one; component2.offsetMin = Vector2.zero; component2.offsetMax = Vector2.zero; _balanceText = val2.AddComponent(); _balanceText.font = LobbyUI.ResolveGameFont(); _balanceText.fontSize = 18; _balanceText.alignment = (TextAnchor)4; ((Graphic)_balanceText).color = Color.white; ((Graphic)_balanceText).raycastTarget = false; _balanceText.horizontalOverflow = (HorizontalWrapMode)1; _balanceText.verticalOverflow = (VerticalWrapMode)1; GameObject val3 = new GameObject("SeriesBackdrop", new Type[1] { typeof(RectTransform) }); val3.transform.SetParent(_canvasRoot.transform, false); Image obj4 = val3.AddComponent(); ((Graphic)obj4).color = new Color(0f, 0f, 0f, 0.55f); ((Graphic)obj4).raycastTarget = false; RectTransform component3 = val3.GetComponent(); component3.anchorMin = new Vector2(0.5f, 1f); component3.anchorMax = new Vector2(0.5f, 1f); component3.pivot = new Vector2(0.5f, 1f); component3.sizeDelta = new Vector2(1000f, 52f); component3.anchoredPosition = new Vector2(0f, -52f); GameObject val4 = new GameObject("SeriesText", new Type[1] { typeof(RectTransform) }); val4.transform.SetParent(val3.transform, false); RectTransform component4 = val4.GetComponent(); component4.anchorMin = Vector2.zero; component4.anchorMax = Vector2.one; component4.offsetMin = Vector2.zero; component4.offsetMax = Vector2.zero; _seriesText = val4.AddComponent(); _seriesText.font = LobbyUI.ResolveGameFont(); _seriesText.fontSize = 14; _seriesText.alignment = (TextAnchor)4; ((Graphic)_seriesText).color = new Color(1f, 0.85f, 0.5f); ((Graphic)_seriesText).raycastTarget = false; _seriesText.horizontalOverflow = (HorizontalWrapMode)1; _seriesText.verticalOverflow = (VerticalWrapMode)1; GameObject val5 = new GameObject("YourTurnNotice", new Type[1] { typeof(RectTransform) }); val5.transform.SetParent(_canvasRoot.transform, false); RectTransform component5 = val5.GetComponent(); component5.anchorMin = new Vector2(0.5f, 0.5f); component5.anchorMax = new Vector2(0.5f, 0.5f); component5.pivot = new Vector2(0.5f, 0.5f); component5.sizeDelta = new Vector2(760f, 120f); component5.anchoredPosition = new Vector2(0f, 185f); _turnText = val5.AddComponent(); _turnText.font = LobbyUI.ResolveGameFont(); _turnText.fontSize = 54; _turnText.fontStyle = (FontStyle)1; _turnText.alignment = (TextAnchor)4; ((Graphic)_turnText).color = new Color(1f, 0.84f, 0.36f); _turnText.text = PvpLocalization.Get("KayceePvP.Duel.YourTurn"); ((Graphic)_turnText).raycastTarget = false; _turnText.horizontalOverflow = (HorizontalWrapMode)1; _turnText.verticalOverflow = (VerticalWrapMode)1; Shadow obj5 = val5.AddComponent(); obj5.effectColor = new Color(0f, 0f, 0f, 0.9f); obj5.effectDistance = new Vector2(4f, -4f); val5.SetActive(false); GameObject val6 = (_opponentTotemRoot = new GameObject("OpponentTotem", new Type[1] { typeof(RectTransform) })); val6.transform.SetParent(_canvasRoot.transform, false); Image obj6 = val6.AddComponent(); ((Graphic)obj6).color = new Color(0.3f, 0.055f, 0.045f, 0.82f); ((Graphic)obj6).raycastTarget = false; RectTransform component6 = val6.GetComponent(); component6.anchorMin = new Vector2(1f, 1f); component6.anchorMax = new Vector2(1f, 1f); component6.pivot = new Vector2(1f, 1f); component6.sizeDelta = new Vector2(300f, 92f); component6.anchoredPosition = new Vector2(-18f, -116f); GameObject val7 = new GameObject("OpponentTotemTribeIcon", new Type[1] { typeof(RectTransform) }); val7.transform.SetParent(val6.transform, false); RectTransform component7 = val7.GetComponent(); Vector2 val8 = default(Vector2); ((Vector2)(ref val8))..ctor(0.5f, 0.5f); component7.anchorMax = val8; component7.anchorMin = val8; component7.sizeDelta = new Vector2(52f, 52f); component7.anchoredPosition = new Vector2(-52f, -9f); _opponentTotemTribeIcon = val7.AddComponent(); _opponentTotemTribeIcon.preserveAspect = true; ((Graphic)_opponentTotemTribeIcon).color = new Color(1f, 0.86f, 0.6f); ((Graphic)_opponentTotemTribeIcon).raycastTarget = false; GameObject val9 = new GameObject("OpponentTotemPlus", new Type[1] { typeof(RectTransform) }); val9.transform.SetParent(val6.transform, false); RectTransform component8 = val9.GetComponent(); ((Vector2)(ref val8))..ctor(0.5f, 0.5f); component8.anchorMax = val8; component8.anchorMin = val8; component8.sizeDelta = new Vector2(28f, 52f); component8.anchoredPosition = new Vector2(0f, -9f); Text obj7 = val9.AddComponent(); obj7.font = LobbyUI.ResolveGameFont(); obj7.fontSize = 22; obj7.fontStyle = (FontStyle)1; obj7.alignment = (TextAnchor)4; ((Graphic)obj7).color = new Color(1f, 0.64f, 0.42f); obj7.text = "+"; ((Graphic)obj7).raycastTarget = false; GameObject val10 = new GameObject("OpponentTotemAbilityIcon", new Type[1] { typeof(RectTransform) }); val10.transform.SetParent(val6.transform, false); RectTransform component9 = val10.GetComponent(); ((Vector2)(ref val8))..ctor(0.5f, 0.5f); component9.anchorMax = val8; component9.anchorMin = val8; component9.sizeDelta = new Vector2(52f, 52f); component9.anchoredPosition = new Vector2(52f, -9f); _opponentTotemAbilityIcon = val10.AddComponent(); _opponentTotemAbilityIcon.preserveAspect = true; ((Graphic)_opponentTotemAbilityIcon).color = new Color(1f, 0.86f, 0.6f); ((Graphic)_opponentTotemAbilityIcon).raycastTarget = false; GameObject val11 = new GameObject("OpponentTotemText", new Type[1] { typeof(RectTransform) }); val11.transform.SetParent(val6.transform, false); RectTransform component10 = val11.GetComponent(); component10.anchorMin = new Vector2(0f, 1f); component10.anchorMax = new Vector2(1f, 1f); component10.pivot = new Vector2(0.5f, 1f); component10.sizeDelta = new Vector2(0f, 26f); component10.anchoredPosition = Vector2.zero; _opponentTotemText = val11.AddComponent(); _opponentTotemText.font = LobbyUI.ResolveGameFont(); _opponentTotemText.fontSize = 13; _opponentTotemText.alignment = (TextAnchor)1; ((Graphic)_opponentTotemText).color = new Color(1f, 0.82f, 0.54f); ((Graphic)_opponentTotemText).raycastTarget = false; _opponentTotemText.horizontalOverflow = (HorizontalWrapMode)1; _opponentTotemText.verticalOverflow = (VerticalWrapMode)1; _opponentTotemRoot.SetActive(false); } private void ShowTurnNotice() { _turnNoticeUntil = Time.unscaledTime + 1.35f; _turnText.text = PvpLocalization.Get("KayceePvP.Duel.YourTurn"); ((Component)_turnText).gameObject.SetActive(true); if ((Object)(object)AudioController.Instance != (Object)null) { AudioController.Instance.PlaySound2D("bell", (MixerGroup)0, 0.85f, 0f, (Pitch)null, (Repetition)null, (Randomization)null, (Distortion)null, false); } } } [Serializable] internal class DuelMessage { public string type; public int matchId; public int sequence; public int turnNumber; public int battleNumber; public int slotIndex; public string cardBaseName; public string cardNameReplacement; public int cardAttackAdjustment; public int cardHealthAdjustment; public int cardBloodCostAdjustment; public int cardBonesCostAdjustment; public int cardEnergyCostAdjustment; public int[] cardAbilities; public bool cardHasTotemAbility; public int[] sacrificedSlotIndices; public int cardsPlayedThisTurn; public int basedOnSequence; public bool basedOnActionFromHost; public string stateHash; public int finalBalance; public string reason; public string loserAbsoluteSide; public int hostWins; public int peerWins; public bool isMatchFinal; public string observerAbsoluteSide; public long sentUtcTicks; [NonSerialized] public long localReceivedUtcTicks; [NonSerialized] public long localHandleMessageUtcTicks; [NonSerialized] public long localEnqueueUtcTicks; [NonSerialized] public long localDequeueUtcTicks; public void SetCard(NetCardInfo net) { cardBaseName = net.baseName; cardNameReplacement = net.nameReplacement; cardAttackAdjustment = net.attackAdjustment; cardHealthAdjustment = net.healthAdjustment; cardBloodCostAdjustment = net.bloodCostAdjustment; cardBonesCostAdjustment = net.bonesCostAdjustment; cardEnergyCostAdjustment = net.energyCostAdjustment; cardAbilities = net.abilities; cardHasTotemAbility = net.hasTotemAbility; } private NetCardInfo ToNetCardInfo() { return new NetCardInfo { baseName = cardBaseName, nameReplacement = cardNameReplacement, attackAdjustment = cardAttackAdjustment, healthAdjustment = cardHealthAdjustment, bloodCostAdjustment = cardBloodCostAdjustment, bonesCostAdjustment = cardBonesCostAdjustment, energyCostAdjustment = cardEnergyCostAdjustment, abilities = cardAbilities, hasTotemAbility = cardHasTotemAbility }; } public CardInfo ToCardInfo() { return ToNetCardInfo().ToCardInfo(); } public bool TryToCardInfo(out CardInfo card) { return ToNetCardInfo().TryToCardInfo(out card); } } internal class DuelSession : MonoBehaviour { private const int MaxQueuedGameplayMessages = 256; public readonly Queue Incoming = new Queue(); private int _localReadyBattle; private int _peerReadyBattle; private int _lastSentBattleNumber; private int _lastRequestedBattleNumber; private int _outgoingSequence; private int _expectedIncomingSequence; private readonly Dictionary<(bool ActionFromHost, int Sequence), (string Hash, string Dump)> _localStateBySequence = new Dictionary<(bool, int), (string, string)>(); private readonly Dictionary<(bool ActionFromHost, int Sequence), string> _peerHashesBySequence = new Dictionary<(bool, int), string>(); private int _cardsSentThisTurn; private int _lastClosedTurnNumber = -1; private int _expectedOutgoingTurnNumber; private int _cardsAppliedForRemoteTurn; private int _expectedIncomingTurnNumber; public static DuelSession Instance { get; private set; } public int MatchId { get; private set; } public int CurrentTurnNumber { get; private set; } public bool MatchEnded { get; private set; } public bool BattleConcluded { get; private set; } public bool BattleBeginReceived { get; private set; } public int BattleBeginNumber { get; private set; } public bool ProtocolDesync { get; private set; } public string ProtocolDesyncReason { get; private set; } public bool ConnectionLost { get; private set; } public string ConnectionLostReason { get; private set; } public int CardsAppliedForRemoteTurn => _cardsAppliedForRemoteTurn; public bool BattleOutcomeAlreadySent(int battleNumber) { if (!MatchEnded) { return battleNumber <= _lastSentBattleNumber; } return true; } public void TriggerProtocolDesync(string reason) { string text = (((Object)(object)LobbySession.Instance != (Object)null && LobbySession.Instance.IsHost) ? "HOST" : "PEER"); if (ProtocolDesync) { Plugin.Log.LogWarning((object)$"Duel: PROTOCOL_DESYNC_ADDITIONAL_REASON | self={text} match={MatchId} battle={LobbyMatchState.BattleNumber} firstReason={ProtocolDesyncReason} newReason={reason}"); } else { ProtocolDesync = true; ProtocolDesyncReason = reason; Plugin.Log.LogError((object)$"Duel: PROTOCOL_DESYNC_FATAL | self={text} match={MatchId} battle={LobbyMatchState.BattleNumber} turn={CurrentTurnNumber} reason={reason}"); } } public void TriggerConnectionLost(string reason) { if (!ConnectionLost) { ConnectionLost = true; ConnectionLostReason = reason; string text = (((Object)(object)LobbySession.Instance != (Object)null && LobbySession.Instance.IsHost) ? "HOST" : "PEER"); Plugin.Log.LogError((object)$"Duel: CONNECTION_LOST_FATAL | self={text} match={MatchId} battle={LobbyMatchState.BattleNumber} turn={CurrentTurnNumber} reason={reason}"); } } private void Awake() { Instance = this; } public void PrepareForMatch(int matchId) { Incoming.Clear(); MatchId = matchId; CurrentTurnNumber = 0; _outgoingSequence = 0; _expectedIncomingSequence = 0; _localStateBySequence.Clear(); _peerHashesBySequence.Clear(); MatchEnded = false; BattleConcluded = false; _localReadyBattle = 0; _peerReadyBattle = 0; BattleBeginReceived = false; BattleBeginNumber = 0; _lastSentBattleNumber = 0; _lastRequestedBattleNumber = 0; _cardsSentThisTurn = 0; _lastClosedTurnNumber = -1; _cardsAppliedForRemoteTurn = 0; _expectedOutgoingTurnNumber = 0; _expectedIncomingTurnNumber = 0; ProtocolDesync = false; ProtocolDesyncReason = null; ConnectionLost = false; ConnectionLostReason = null; CombatResolutionSignalPatch.ResetForMatch($"PrepareForMatch({matchId})"); Plugin.Log.LogInfo((object)$"Duel: prepared for match {matchId} - sequence/turn counters reset."); } public void ArmTurnOwnershipForBattle() { bool flag = (Object)(object)LobbySession.Instance != (Object)null && LobbySession.Instance.IsHost; bool flag2 = (Object)(object)LobbySession.Instance == (Object)null || LobbySession.Instance.Transport == null; int num = (_expectedOutgoingTurnNumber = ((!flag2) ? TurnBatchGuard.FirstTurnNumberForRole(LobbyMatchState.HostGoesFirstThisBattle, flag) : 0)); _expectedIncomingTurnNumber = 1 - num; Plugin.Log.LogInfo((object)string.Format("Duel: ARMED_TURN_OWNERSHIP | self={0} match={1} battle={2} hostGoesFirstThisBattle={3} soloDebugMode={4} localFirstTurn={5} expectedOutgoing={6} expectedIncoming={7}", flag ? "HOST" : "PEER", MatchId, LobbyMatchState.BattleNumber, LobbyMatchState.HostGoesFirstThisBattle, flag2, num, _expectedOutgoingTurnNumber, _expectedIncomingTurnNumber)); } public void ResetHandshakeForNewBattle() { BattleBeginReceived = false; BattleBeginNumber = 0; } public void ResetForNewBattle() { Incoming.Clear(); CurrentTurnNumber = 0; BattleConcluded = false; _cardsSentThisTurn = 0; _lastClosedTurnNumber = -1; _cardsAppliedForRemoteTurn = 0; ArmTurnOwnershipForBattle(); CombatResolutionSignalPatch.ResetForMatch("ResetForNewBattle"); } public void SendBattleReturnReady(int battleNumber) { if (!ConnectionLost) { _localReadyBattle = battleNumber; DuelMessage msg = CreateOutgoingMessage("battle_return_ready"); bool flag = LobbySession.Instance?.Transport != null; if (Send(msg) && !flag) { _peerReadyBattle = Mathf.Max(_peerReadyBattle, battleNumber); } } } public bool BothReadyForBattle(int battleNumber) { if (_localReadyBattle >= battleNumber) { return _peerReadyBattle >= battleNumber; } return false; } public void SendBattleBegin(int battleNumber) { if (!ConnectionLost) { DuelMessage msg = CreateOutgoingMessage("battle_begin"); Plugin.Log.LogInfo((object)$"Duel: SENDING battle_begin | match={MatchId} battle={battleNumber}"); if (Send(msg)) { BattleBeginReceived = true; BattleBeginNumber = battleNumber; } } } public void AdvanceTurn() { CurrentTurnNumber++; } public void NoteRemoteCardApplied() { _cardsAppliedForRemoteTurn++; } public void CloseRemoteTurnBatch() { _cardsAppliedForRemoteTurn = 0; } public void HandleMessage(byte[] payload) { if (payload == null || payload.Length == 0) { throw new InvalidOperationException("Empty duel payload."); } DuelMessage duelMessage = JsonUtility.FromJson(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetString(payload)); if (duelMessage == null || !WireMessagePolicy.CanReceiveDuelType((Object)(object)LobbySession.Instance != (Object)null && LobbySession.Instance.IsHost, duelMessage.type)) { throw new InvalidOperationException("Unknown duel message type or invalid sender role."); } string text = (((Object)(object)LobbySession.Instance != (Object)null && LobbySession.Instance.IsHost) ? "PEER" : "HOST"); long num = (duelMessage.localHandleMessageUtcTicks = (duelMessage.localReceivedUtcTicks = DateTime.UtcNow.Ticks)); double num2 = ((duelMessage.sentUtcTicks > 0) ? ((double)(num - duelMessage.sentUtcTicks) / 10000.0) : double.NaN); TurnManager instance = Singleton.Instance; string text2 = (((Object)(object)LobbySession.Instance != (Object)null && LobbySession.Instance.IsHost) ? "HOST" : "PEER"); Plugin.Log.LogInfo((object)string.Format("Duel: TRACE_HANDLE_MESSAGE | self={0} sender={1} type={2} match={3} battle={4} turn={5} seq={6} currentTurn={7} isPlayerTurn={8} netTransitMsApprox={9:F1}", text2, text, duelMessage.type, duelMessage.matchId, duelMessage.battleNumber, duelMessage.turnNumber, duelMessage.sequence, CurrentTurnNumber, ((Object)(object)instance != (Object)null) ? instance.IsPlayerTurn.ToString() : "n/a", num2)); if (ProtocolDesync) { Plugin.Log.LogWarning((object)("Duel: message IGNORED - ProtocolDesync already latched (" + ProtocolDesyncReason + "). self=" + text2 + " sender=" + text + " type=" + duelMessage.type)); return; } if (ConnectionLost) { Plugin.Log.LogWarning((object)("Duel: message IGNORED - ConnectionLost already latched (" + ConnectionLostReason + "). self=" + text2 + " sender=" + text + " type=" + duelMessage.type)); return; } if (duelMessage.matchId != MatchId) { Plugin.Log.LogWarning((object)$"Duel: IGNORED stale match | sender={text} type={duelMessage.type} msgMatch={duelMessage.matchId} currentMatch={MatchId} seq={duelMessage.sequence} turn={duelMessage.turnNumber}"); return; } switch (SequenceGuard.Classify(duelMessage.sequence, _expectedIncomingSequence)) { case IncomingSequenceClassification.Duplicate: Plugin.Log.LogWarning((object)$"Duel: IGNORED duplicate/stale seq | sender={text} type={duelMessage.type} match={duelMessage.matchId} seq={duelMessage.sequence} expected>={_expectedIncomingSequence} turn={duelMessage.turnNumber}"); return; case IncomingSequenceClassification.Gap: TriggerProtocolDesync($"sequence gap from {text}: expected={_expectedIncomingSequence} got={duelMessage.sequence} type={duelMessage.type} turn={duelMessage.turnNumber}"); return; } _expectedIncomingSequence = duelMessage.sequence + 1; if ((duelMessage.type == "card_played" || duelMessage.type == "end_turn" || duelMessage.type == "duel_state_hash" || duelMessage.type == "battle_outcome_observed") && duelMessage.battleNumber != LobbyMatchState.BattleNumber) { Plugin.Log.LogWarning((object)$"Duel: IGNORED stale battle | sender={text} type={duelMessage.type} msgBattle={duelMessage.battleNumber} currentBattle={LobbyMatchState.BattleNumber} seq={duelMessage.sequence} turn={duelMessage.turnNumber}"); return; } if (duelMessage.type == "card_played" || duelMessage.type == "end_turn") { switch (TurnBatchGuard.ClassifyIncomingTurn(duelMessage.turnNumber, _expectedIncomingTurnNumber)) { case IncomingTurnClassification.AlreadyClosed: TriggerProtocolDesync($"{duelMessage.type} arrived for already-closed remote turn {duelMessage.turnNumber} (expectedIngress={_expectedIncomingTurnNumber}) sender={text} seq={duelMessage.sequence}"); return; case IncomingTurnClassification.Future: TriggerProtocolDesync($"{duelMessage.type} arrived for future remote turn {duelMessage.turnNumber} (expectedIngress={_expectedIncomingTurnNumber}) sender={text} seq={duelMessage.sequence} - never legitimate under strict alternation."); return; } if (duelMessage.type == "end_turn") { _expectedIncomingTurnNumber += 2; } } if (duelMessage.type == "battle_outcome_observed") { string text3 = (((Object)(object)LobbySession.Instance != (Object)null && LobbySession.Instance.IsHost) ? "HOST" : "PEER"); Plugin.Log.LogInfo((object)$"Duel: OUTCOME_REQUEST_RECEIVED | self={text3} match={duelMessage.matchId} battle={duelMessage.battleNumber} observer={duelMessage.observerAbsoluteSide} loserAbsoluteSide={duelMessage.loserAbsoluteSide} balance={duelMessage.finalBalance} reason={duelMessage.reason}"); if (!LobbySession.Instance.IsHost) { Plugin.Log.LogWarning((object)("Duel: battle_outcome_observed received on a non-host process - ignoring (only HOST validates/decides battle outcomes). self=" + text3)); } else { DecideAndSendBattleResult(duelMessage.loserAbsoluteSide == "HOST", duelMessage.finalBalance, duelMessage.reason, duelMessage.observerAbsoluteSide, duelMessage.battleNumber, duelMessage.matchId); } } else if (duelMessage.type == "battle_result") { string text4 = (((Object)(object)LobbySession.Instance != (Object)null && LobbySession.Instance.IsHost) ? "HOST" : "PEER"); if (duelMessage.battleNumber < LobbyMatchState.BattleNumber) { Plugin.Log.LogInfo((object)$"Duel: duplicate/stale battle_result ignored | self={text4} match={duelMessage.matchId} battle={duelMessage.battleNumber} currentBattle={LobbyMatchState.BattleNumber}"); return; } bool flag = duelMessage.loserAbsoluteSide == "HOST"; LobbyMatchState.ApplyAuthoritativeWins(duelMessage.hostWins, duelMessage.peerWins); if (duelMessage.isMatchFinal) { MatchEnded = true; } BattleConcluded = true; Plugin.Log.LogInfo((object)$"Duel: RECEIVED battle_result | self={text4} match={duelMessage.matchId} battle={duelMessage.battleNumber} loser={duelMessage.loserAbsoluteSide} hostWins={duelMessage.hostWins} peerWins={duelMessage.peerWins} balance={duelMessage.finalBalance} reason={duelMessage.reason} isMatchFinal={duelMessage.isMatchFinal}"); Telemetry.LogBattleResult(duelMessage.battleNumber, duelMessage.loserAbsoluteSide, duelMessage.hostWins, duelMessage.peerWins, duelMessage.finalBalance, duelMessage.isMatchFinal); Plugin.Log.LogInfo((object)$"Duel: BATTLE_RESULT_CONFIRMED | self={text4} match={duelMessage.matchId} battle={duelMessage.battleNumber} loser={duelMessage.loserAbsoluteSide} hostWins={duelMessage.hostWins} peerWins={duelMessage.peerWins} isMatchFinal={duelMessage.isMatchFinal}"); RequestAuthoritativeBattleConclusion(!flag, duelMessage.battleNumber, duelMessage.isMatchFinal); LobbySession.Instance.OnBattleConcluded(duelMessage.battleNumber, duelMessage.isMatchFinal); } else if (duelMessage.type == "battle_return_ready") { _peerReadyBattle = Mathf.Max(_peerReadyBattle, duelMessage.battleNumber); Plugin.Log.LogInfo((object)$"Duel: RECEIVED battle_return_ready | match={duelMessage.matchId} battle={duelMessage.battleNumber} (peerReadyBattle now {_peerReadyBattle})"); } else if (duelMessage.type == "battle_begin") { BattleBeginReceived = true; BattleBeginNumber = duelMessage.battleNumber; Plugin.Log.LogInfo((object)$"Duel: RECEIVED battle_begin | match={duelMessage.matchId} battle={duelMessage.battleNumber}"); } else if (duelMessage.type == "duel_state_hash") { (bool, int) key = (duelMessage.basedOnActionFromHost, duelMessage.basedOnSequence); Plugin.Log.LogInfo((object)$"Duel: RECEIVED PEER HASH | match={duelMessage.matchId} sender={text} forSeq={duelMessage.basedOnSequence} basedOnActionFromHost={duelMessage.basedOnActionFromHost} hash={duelMessage.stateHash}"); _peerHashesBySequence[key] = duelMessage.stateHash; CompareIfBothPresent(duelMessage.basedOnActionFromHost, duelMessage.basedOnSequence); } else { int count = Incoming.Count; if (count >= 256) { throw new InvalidOperationException($"Gameplay queue exceeded {256} messages."); } duelMessage.localEnqueueUtcTicks = DateTime.UtcNow.Ticks; Incoming.Enqueue(duelMessage); int count2 = Incoming.Count; string text5 = ((duelMessage.type == "card_played") ? $" card={duelMessage.cardBaseName} slot={duelMessage.slotIndex}" : ""); Plugin.Log.LogInfo((object)$"Duel: RECEIVED | match={duelMessage.matchId} battle={duelMessage.battleNumber} turn={duelMessage.turnNumber} seq={duelMessage.sequence} sender={text} type={duelMessage.type}{text5} queueBefore={count} queueAfter={count2} currentTurn={CurrentTurnNumber}"); } } public void RecordLocalStateHash(int forSequence, bool actionFromHost) { if (!ConnectionLost) { string readableDump; string text = DuelStateHash.ComputeCanonicalHash(out readableDump); (bool, int) key = (actionFromHost, forSequence); _localStateBySequence[key] = (text, readableDump); Plugin.Log.LogInfo((object)$"Duel: LOCAL STATE HASH | match={MatchId} forSeq={forSequence} basedOnActionFromHost={actionFromHost} hash={text} state=[{readableDump}]"); DuelMessage duelMessage = CreateOutgoingMessage("duel_state_hash"); duelMessage.basedOnSequence = forSequence; duelMessage.basedOnActionFromHost = actionFromHost; duelMessage.stateHash = text; Send(duelMessage); CompareIfBothPresent(actionFromHost, forSequence); } } private IEnumerator RecordLocalStateHashAfterResolved(int forSequence, bool actionFromHost, PlayableCard card, int slotIndex) { List playerSlotsCopy = Singleton.Instance.PlayerSlotsCopy; CardSlot targetSlot = ((slotIndex >= 0 && slotIndex < playerSlotsCopy.Count) ? playerSlotsCopy[slotIndex] : null); if ((Object)(object)targetSlot != (Object)null) { float deadline = Time.realtimeSinceStartup + 5f; yield return (object)new WaitUntil((Func)(() => (Object)(object)targetSlot.Card == (Object)(object)card || Time.realtimeSinceStartup > deadline)); if ((Object)(object)targetSlot.Card != (Object)(object)card) { Plugin.Log.LogWarning((object)$"Duel: RecordLocalStateHashAfterResolved timed out waiting for '{((Object)((Card)card).Info).name}' to resolve onto slot {slotIndex} (forSeq={forSequence}) - recording the hash anyway."); } } RecordLocalStateHash(forSequence, actionFromHost); } private void CompareIfBothPresent(bool actionFromHost, int forSequence) { (bool, int) key = (actionFromHost, forSequence); if (_localStateBySequence.TryGetValue(key, out (string, string) value) && _peerHashesBySequence.TryGetValue(key, out var value2)) { if (value.Item1 == value2) { Plugin.Log.LogInfo((object)$"Duel: STATE HASH MATCH | match={MatchId} forSeq={forSequence} basedOnActionFromHost={actionFromHost} hash={value.Item1}"); } else { Plugin.Log.LogError((object)$"Duel: STATE HASH MISMATCH | match={MatchId} forSeq={forSequence} basedOnActionFromHost={actionFromHost} localHash={value.Item1} peerHash={value2}"); Plugin.Log.LogError((object)$"Duel: local state dump (forSeq={forSequence}) | {value.Item2}"); } } } public void SendCardPlayed(PlayableCard card, int slotIndex, int[] sacrificedSlotIndices = null) { if (ProtocolDesync || ConnectionLost) { return; } int currentTurnNumber = CurrentTurnNumber; if (!TurnBatchGuard.CanSendForTurn(currentTurnNumber, _expectedOutgoingTurnNumber)) { TriggerProtocolDesync($"SendCardPlayed for turn {currentTurnNumber} not locally owned (expectedOutgoing={_expectedOutgoingTurnNumber}, lastClosed={_lastClosedTurnNumber}, card={((Object)((Card)card).Info).name})"); return; } _cardsSentThisTurn++; DuelMessage duelMessage = CreateOutgoingMessage("card_played"); duelMessage.slotIndex = slotIndex; duelMessage.SetCard(NetCardInfo.FromCardInfo(((Card)card).Info, card.TemporaryMods)); duelMessage.sacrificedSlotIndices = sacrificedSlotIndices ?? Array.Empty(); bool flag = (Object)(object)LobbySession.Instance != (Object)null && LobbySession.Instance.IsHost; string text = (flag ? "HOST" : "PEER"); string text2 = ((duelMessage.sacrificedSlotIndices.Length != 0) ? (" sacrificedSlots=[" + string.Join(",", duelMessage.sacrificedSlotIndices) + "]") : ""); Plugin.Log.LogInfo((object)$"Duel: SENDING | match={duelMessage.matchId} turn={duelMessage.turnNumber} seq={duelMessage.sequence} sender={text} type={duelMessage.type} card={((Object)((Card)card).Info).name} slot={slotIndex}{text2}"); if (Send(duelMessage)) { ((MonoBehaviour)this).StartCoroutine(RecordLocalStateHashAfterResolved(duelMessage.sequence, PvpOwnership.CardIsHost(flag, cardIsLocal: true), card, slotIndex)); } } public void SendEndTurn() { if (ProtocolDesync || ConnectionLost) { return; } int currentTurnNumber = CurrentTurnNumber; if (!TurnBatchGuard.CanSendForTurn(currentTurnNumber, _expectedOutgoingTurnNumber)) { TriggerProtocolDesync($"Duplicate/out-of-turn SendEndTurn for turn {currentTurnNumber} not locally owned (expectedOutgoing={_expectedOutgoingTurnNumber}, lastClosed={_lastClosedTurnNumber})"); return; } DuelMessage duelMessage = CreateOutgoingMessage("end_turn"); duelMessage.cardsPlayedThisTurn = _cardsSentThisTurn; bool flag = (Object)(object)LobbySession.Instance != (Object)null && LobbySession.Instance.IsHost; string text = (flag ? "HOST" : "PEER"); Plugin.Log.LogInfo((object)$"Duel: SENDING | match={duelMessage.matchId} turn={duelMessage.turnNumber} seq={duelMessage.sequence} sender={text} type={duelMessage.type} cardsPlayedThisTurn={duelMessage.cardsPlayedThisTurn}"); int resolvedGeneration = CombatResolutionSignalPatch.ResolvedGeneration; if (Send(duelMessage)) { _lastClosedTurnNumber = currentTurnNumber; _cardsSentThisTurn = 0; _expectedOutgoingTurnNumber += 2; ((MonoBehaviour)this).StartCoroutine(AdvanceTurnAfterCombatResolved(duelMessage.sequence, PvpOwnership.CardIsHost(flag, cardIsLocal: true), resolvedGeneration)); } } internal IEnumerator AdvanceTurnAfterCombatResolved(int forSequence, bool actionFromHost, int generationBefore) { int battleAtStart = LobbyMatchState.BattleNumber; float deadline = Time.realtimeSinceStartup + 8f; while (true) { bool battleEnded = BattleConcluded || !LobbyMatchState.BattleInProgress || LobbyMatchState.InterBattleSceneReloadActive || LobbyMatchState.BattleNumber != battleAtStart; switch (CombatResolutionSignalPatch.EvaluateCombatGate(CombatResolutionSignalPatch.ResolvedGeneration, generationBefore, ProtocolDesync, battleEnded, Time.realtimeSinceStartup > deadline)) { case CombatResolutionSignalPatch.CombatGateOutcome.StillWaiting: yield return null; break; case CombatResolutionSignalPatch.CombatGateOutcome.AlreadyDesynced: yield break; case CombatResolutionSignalPatch.CombatGateOutcome.BattleEnded: Plugin.Log.LogInfo((object)$"Duel: COMBAT_GATE_CANCELLED_BATTLE_ENDED | match={MatchId} battleAtStart={battleAtStart} currentBattle={LobbyMatchState.BattleNumber} forSeq={forSequence} generationBefore={generationBefore} resolvedGeneration={CombatResolutionSignalPatch.ResolvedGeneration}"); yield break; case CombatResolutionSignalPatch.CombatGateOutcome.TimedOut: TriggerProtocolDesync($"combat phase for turn-close seq={forSequence} never signaled COMBAT_PHASE_RESOLVED within 8s (generationBefore={generationBefore}) - refusing to release the next turn in an uncertain state."); yield break; default: AdvanceTurn(); RecordLocalStateHash(forSequence, actionFromHost); yield break; } } } public void ObserveBattleOutcome(bool loserIsHost, int observedBalanceLocal, string reason) { if (!LobbyMatchState.BattleInProgress) { string text = (((Object)(object)LobbySession.Instance != (Object)null && LobbySession.Instance.IsHost) ? "HOST" : "PEER"); Plugin.Log.LogWarning((object)string.Format("Duel: OUTCOME_IGNORED_NO_ACTIVE_BATTLE | self={0} match={1} battle={2} loserAbsoluteSide={3} balance={4} reason={5} reloadActive={6} isDuelActive={7}", text, MatchId, LobbyMatchState.BattleNumber, loserIsHost ? "HOST" : "PEER", observedBalanceLocal, reason, LobbyMatchState.InterBattleSceneReloadActive, PvpRunState.IsDuelActive)); } else if (!BattleOutcomeAlreadySent(LobbyMatchState.BattleNumber)) { bool flag = (Object)(object)LobbySession.Instance != (Object)null && LobbySession.Instance.IsHost; string text2 = (flag ? "HOST" : "PEER"); int num = (flag ? observedBalanceLocal : (-observedBalanceLocal)); Plugin.Log.LogInfo((object)string.Format("Duel: OUTCOME_OBSERVED_LOCAL | self={0} match={1} battle={2} loserAbsoluteSide={3} localBalance={4} hostRelativeBalance={5} reason={6}", text2, MatchId, LobbyMatchState.BattleNumber, loserIsHost ? "HOST" : "PEER", observedBalanceLocal, num, reason)); if (flag) { DecideAndSendBattleResult(loserIsHost, num, reason, "HOST", LobbyMatchState.BattleNumber, MatchId); } else if (LobbyMatchState.BattleNumber > _lastRequestedBattleNumber) { _lastRequestedBattleNumber = LobbyMatchState.BattleNumber; DuelMessage duelMessage = CreateOutgoingMessage("battle_outcome_observed"); duelMessage.loserAbsoluteSide = (loserIsHost ? "HOST" : "PEER"); duelMessage.finalBalance = num; duelMessage.reason = reason; duelMessage.observerAbsoluteSide = "PEER"; Plugin.Log.LogInfo((object)$"Duel: OUTCOME_REQUEST_SENT | self=PEER match={MatchId} battle={duelMessage.battleNumber} loserAbsoluteSide={duelMessage.loserAbsoluteSide} hostRelativeBalance={num}"); Send(duelMessage); } } } private void DecideAndSendBattleResult(bool loserIsHost, int hostRelativeBalance, string reason, string observerAbsoluteSide, int requestBattleNumber, int requestMatchId) { if (!LobbySession.Instance.IsHost) { Plugin.Log.LogWarning((object)("Duel: DecideAndSendBattleResult called on a non-host process - ignoring. observer=" + observerAbsoluteSide)); } else if (requestMatchId != MatchId) { Plugin.Log.LogInfo((object)$"Duel: OUTCOME_REJECTED (stale match) | observer={observerAbsoluteSide} requestMatch={requestMatchId} currentMatch={MatchId} battle={requestBattleNumber}"); } else if (!LobbyMatchState.BattleInProgress) { Plugin.Log.LogWarning((object)$"Duel: OUTCOME_IGNORED_NO_ACTIVE_BATTLE | observer={observerAbsoluteSide} match={MatchId} battle={requestBattleNumber} currentBattle={LobbyMatchState.BattleNumber} reloadActive={LobbyMatchState.InterBattleSceneReloadActive}"); } else if (requestBattleNumber < LobbyMatchState.BattleNumber) { Plugin.Log.LogInfo((object)$"Duel: OUTCOME_REJECTED (stale battle) | observer={observerAbsoluteSide} match={MatchId} requestBattle={requestBattleNumber} currentBattle={LobbyMatchState.BattleNumber}"); } else if (BattleOutcomeAlreadySent(requestBattleNumber)) { Plugin.Log.LogInfo((object)$"Duel: OUTCOME_REJECTED (already sent/latched) | observer={observerAbsoluteSide} match={MatchId} battle={requestBattleNumber} MatchEnded={MatchEnded} lastSentBattle={_lastSentBattleNumber}"); } else { Plugin.Log.LogInfo((object)string.Format("Duel: OUTCOME_CONFIRMED_HOST | observer={0} match={1} battle={2} loserAbsoluteSide={3} balance={4}", observerAbsoluteSide, MatchId, requestBattleNumber, loserIsHost ? "HOST" : "PEER", hostRelativeBalance)); SendBattleResult(loserIsHost, hostRelativeBalance, reason); } } private void SendBattleResult(bool loserIsHost, int finalBalance, string reason) { if (!LobbySession.Instance.IsHost) { Plugin.Log.LogWarning((object)"Duel: SendBattleResult called on a non-host process - ignoring (only the host confirms battle results)."); } else if (!ConnectionLost && LobbyMatchState.BattleNumber > _lastSentBattleNumber) { _lastSentBattleNumber = LobbyMatchState.BattleNumber; int hostWins = LobbyMatchState.HostWins; int peerWins = LobbyMatchState.PeerWins; bool flag = LobbyMatchState.ApplyBattleResult(loserIsHost); if (flag) { MatchEnded = true; } BattleConcluded = true; DuelMessage duelMessage = CreateOutgoingMessage("battle_result"); duelMessage.loserAbsoluteSide = (loserIsHost ? "HOST" : "PEER"); duelMessage.hostWins = LobbyMatchState.HostWins; duelMessage.peerWins = LobbyMatchState.PeerWins; duelMessage.finalBalance = finalBalance; duelMessage.reason = reason; duelMessage.isMatchFinal = flag; Plugin.Log.LogInfo((object)$"Duel: SENDING battle_result | match={MatchId} battle={duelMessage.battleNumber} loser={duelMessage.loserAbsoluteSide} hostWinsBefore={hostWins} peerWinsBefore={peerWins} hostWinsAfter={LobbyMatchState.HostWins} peerWinsAfter={LobbyMatchState.PeerWins} balance={finalBalance} isMatchFinal={flag}"); if (Send(duelMessage)) { Telemetry.LogBattleResult(duelMessage.battleNumber, duelMessage.loserAbsoluteSide, LobbyMatchState.HostWins, LobbyMatchState.PeerWins, finalBalance, flag); Plugin.Log.LogInfo((object)$"Duel: BATTLE_RESULT_CONFIRMED | self=HOST match={MatchId} battle={duelMessage.battleNumber} loser={duelMessage.loserAbsoluteSide} hostWins={LobbyMatchState.HostWins} peerWins={LobbyMatchState.PeerWins} isMatchFinal={flag}"); RequestAuthoritativeBattleConclusion(!loserIsHost, duelMessage.battleNumber, flag); LobbySession.Instance.OnBattleConcluded(duelMessage.battleNumber, flag); } } } private void RequestAuthoritativeBattleConclusion(bool winnerIsHost, int battleNumber, bool isMatchFinal) { string text = (((Object)(object)LobbySession.Instance != (Object)null && LobbySession.Instance.IsHost) ? "HOST" : "PEER"); bool flag = winnerIsHost == ((Object)(object)LobbySession.Instance != (Object)null && LobbySession.Instance.IsHost); TurnManager instance = Singleton.Instance; float num = (((Object)(object)Singleton.Instance != (Object)null) ? ((float)Singleton.Instance.Balance) : float.NaN); if ((Object)(object)instance == (Object)null) { Plugin.Log.LogWarning((object)$"Duel: RequestAuthoritativeBattleConclusion called with no TurnManager instance - cannot conclude. | self={text} match={MatchId} battle={battleNumber} localPlayerWon={flag} isMatchFinal={isMatchFinal}"); return; } Opponent opponent = instance.Opponent; bool playerSurrendered = instance.PlayerSurrendered; bool flag2 = (Object)(object)opponent != (Object)null && opponent.Surrendered; if (flag) { if ((Object)(object)opponent == (Object)null) { Plugin.Log.LogWarning((object)$"Duel: local player won, but no TurnManager.Opponent found to conclude the encounter through. | self={text} match={MatchId} battle={battleNumber}"); } else { Traverse.Create((object)opponent).Property("Surrendered", (object[])null).SetValue((object)true); } } else { instance.PlayerSurrendered = true; } Plugin.Log.LogInfo((object)$"Duel: ROUND_CONCLUSION_REQUESTED | self={text} match={MatchId} battle={battleNumber} winnerIsHost={winnerIsHost} localPlayerWon={flag} isMatchFinal={isMatchFinal} balance={num} incomingCount={Incoming.Count} playerSurrendered={playerSurrendered}->{instance.PlayerSurrendered} opponentSurrendered={flag2}->{(Object)(object)opponent != (Object)null && opponent.Surrendered} numLives={(((Object)(object)opponent != (Object)null) ? opponent.NumLives : (-1))}"); } private DuelMessage CreateOutgoingMessage(string type) { return new DuelMessage { type = type, matchId = MatchId, sequence = _outgoingSequence++, turnNumber = CurrentTurnNumber, battleNumber = LobbyMatchState.BattleNumber, sentUtcTicks = DateTime.UtcNow.Ticks }; } private bool Send(DuelMessage msg) { if (ConnectionLost) { return false; } ITransport transport = LobbySession.Instance?.Transport; if (transport == null) { int count = Incoming.Count; Incoming.Enqueue(msg); Plugin.Log.LogInfo((object)$"Duel: no live transport - mirroring '{msg.type}' back to self (queueBefore={count} queueAfter={Incoming.Count})."); return true; } if (!transport.IsConnected) { TriggerConnectionLost("transport not connected before send | type=" + msg.type + " LastError='" + transport.LastError + "'"); return false; } string s = JsonUtility.ToJson((object)msg); bool flag = transport.Send(Encoding.UTF8.GetBytes(s)); if (!flag) { Plugin.Log.LogError((object)$"Duel: SEND FAILED | match={msg.matchId} battle={msg.battleNumber} turn={msg.turnNumber} seq={msg.sequence} type={msg.type} LastError='{transport.LastError}'"); TriggerConnectionLost("Send() returned false | type=" + msg.type + " LastError='" + transport.LastError + "'"); } return flag; } } internal static class DuelStateHash { public static string ComputeCanonicalHash(out string readableDump) { bool num = (Object)(object)LobbySession.Instance != (Object)null && LobbySession.Instance.IsHost; List playerSlotsCopy = Singleton.Instance.PlayerSlotsCopy; List opponentSlotsCopy = Singleton.Instance.OpponentSlotsCopy; string text = DescribeSlots(num ? playerSlotsCopy : opponentSlotsCopy); string text2 = DescribeSlots(num ? opponentSlotsCopy : playerSlotsCopy); int balance = Singleton.Instance.Balance; int val = (num ? balance : (-balance)); int num2 = Math.Max(-5, Math.Min(5, val)); int num3 = (((Object)(object)DuelSession.Instance != (Object)null) ? DuelSession.Instance.CurrentTurnNumber : (-1)); string s = (readableDump = $"HOST=[{text}] PEER=[{text2}] BAL={num2} TURN={num3} BATTLE={LobbyMatchState.BattleNumber} HW={LobbyMatchState.HostWins} PW={LobbyMatchState.PeerWins}"); using SHA256 sHA = SHA256.Create(); return Convert.ToBase64String(sHA.ComputeHash(Encoding.UTF8.GetBytes(s))).Substring(0, 16); } private static string DescribeSlots(List slots) { return string.Join(";", slots.Select(DescribeSlot)); } private static string DescribeSlot(CardSlot slot) { if ((Object)(object)slot == (Object)null || (Object)(object)slot.Card == (Object)null || slot.Card.Dead) { return "EMPTY"; } PlayableCard card = slot.Card; CardInfo info = ((Card)card).Info; List list = new List(info.Abilities); foreach (CardModificationInfo temporaryMod in card.TemporaryMods) { list.AddRange(temporaryMod.abilities); } string text = string.Join(",", from a in list.Distinct() select (int)a into a orderby a select a); return $"{((Object)info).name}/{card.Attack}/{card.Health}/[{text}]"; } } internal static class FrameProtocol { internal static int DecodeLength(byte b0, byte b1, byte b2, byte b3) { return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; } internal static bool IsValidLength(int length, int maxFrameBytes) { if (maxFrameBytes > 0 && length > 0) { return length <= maxFrameBytes; } return false; } } internal interface ITransport : IDisposable { ConcurrentQueue IncomingMessages { get; } bool IsConnected { get; } string LastError { get; } bool Send(byte[] payload); } internal sealed class LanTransport : ITransport, IDisposable { public const int DefaultPort = 40125; internal const int MaxFrameBytes = 65536; internal const int MaxPendingMessages = 256; private const int FrameReadTimeoutMs = 10000; private TcpListener _listener; private TcpClient _client; private NetworkStream _stream; private volatile bool _stopping; private int _generation; private readonly object _pendingClientLock = new object(); private TcpClient _pendingClient; private Thread _hostThread; private Thread _connectThread; private Thread _receiveThread; public ConcurrentQueue IncomingMessages { get; } = new ConcurrentQueue(); public bool IsConnected { get; private set; } public string LastError { get; private set; } public void Host(int port = 40125) { int generation = Interlocked.Increment(ref _generation); _listener = new TcpListener(IPAddress.Any, port); _listener.Start(); Plugin.Log.LogInfo((object)$"Lobby: listening on port {port}, waiting for a connection..."); _hostThread = new Thread((ThreadStart)delegate { try { TcpClient tcpClient = _listener.AcceptTcpClient(); if (Volatile.Read(in _generation) != generation) { tcpClient.Close(); } else { OnConnected(tcpClient); Plugin.Log.LogInfo((object)"Lobby: opponent connected."); } } catch (Exception ex) { if (Volatile.Read(in _generation) == generation) { LastError = ex.GetType().Name + ": " + ex.Message; Plugin.Log.LogError((object)$"Lobby: hosting on port {port} failed - {LastError}"); } } }) { IsBackground = true }; _hostThread.Start(); } public void Connect(string host, int port = 40125, int timeoutMs = 8000) { host = (host ?? string.Empty).Trim(); int generation = Interlocked.Increment(ref _generation); Plugin.Log.LogInfo((object)$"Lobby: attempting to connect to '{host}':{port}..."); _connectThread = new Thread((ThreadStart)delegate { TcpClient tcpClient = new TcpClient(); lock (_pendingClientLock) { _pendingClient = tcpClient; } try { IAsyncResult asyncResult = tcpClient.BeginConnect(host, port, null, null); bool flag = asyncResult.AsyncWaitHandle.WaitOne(timeoutMs); if (Volatile.Read(in _generation) != generation) { tcpClient.Close(); } else if (!flag) { tcpClient.Close(); LastError = "Tempo esgotado ao conectar."; Plugin.Log.LogWarning((object)$"Lobby: connect to '{host}':{port} timed out after {timeoutMs}ms."); } else { tcpClient.EndConnect(asyncResult); if (Volatile.Read(in _generation) != generation) { tcpClient.Close(); } else { OnConnected(tcpClient); Plugin.Log.LogInfo((object)"Lobby: connected."); } } } catch (Exception ex) { if (Volatile.Read(in _generation) == generation) { LastError = ex.GetType().Name + ": " + ex.Message; Plugin.Log.LogError((object)$"Lobby: connect to '{host}':{port} failed - {LastError}"); } } finally { lock (_pendingClientLock) { if (_pendingClient == tcpClient) { _pendingClient = null; } } } }) { IsBackground = true }; _connectThread.Start(); } private void OnConnected(TcpClient client) { client.NoDelay = true; _client = client; _stream = client.GetStream(); IsConnected = true; _receiveThread = new Thread(ReceiveLoop) { IsBackground = true }; _receiveThread.Start(); } private void ReceiveLoop() { try { while (!_stopping) { _stream.ReadTimeout = -1; byte[] array = ReadExact(1); if (array != null) { _stream.ReadTimeout = 10000; byte[] array2 = ReadExact(3); if (array2 != null) { int num = FrameProtocol.DecodeLength(array[0], array2[0], array2[1], array2[2]); if (!FrameProtocol.IsValidLength(num, 65536)) { throw new InvalidDataException($"Invalid frame length {num}; allowed range is 1..{65536} bytes."); } if (IncomingMessages.Count >= 256) { throw new InvalidDataException($"Incoming message queue exceeded {256} frames."); } byte[] array3 = ReadExact(num); if (array3 != null) { IncomingMessages.Enqueue(array3); _stream.ReadTimeout = -1; continue; } break; } break; } break; } } catch (Exception ex) { if (!_stopping) { LastError = ex.Message; Plugin.Log.LogError((object)("Lobby: receive loop ended - " + ex.GetType().Name + ": " + ex.Message)); } } finally { IsConnected = false; } } private byte[] ReadExact(int count) { byte[] array = new byte[count]; int num; for (int i = 0; i < count; i += num) { num = _stream.Read(array, i, count - i); if (num <= 0) { return null; } } return array; } public bool Send(byte[] payload) { if (!IsConnected) { return false; } if (payload == null || !FrameProtocol.IsValidLength(payload.Length, 65536)) { LastError = $"Invalid outgoing frame length {((payload != null) ? payload.Length : 0)}; allowed range is 1..{65536} bytes."; IsConnected = false; return false; } byte[] buffer = new byte[4] { (byte)(payload.Length >> 24), (byte)(payload.Length >> 16), (byte)(payload.Length >> 8), (byte)payload.Length }; try { lock (_stream) { _stream.Write(buffer, 0, 4); _stream.Write(payload, 0, payload.Length); } return true; } catch (Exception ex) { LastError = ex.Message; IsConnected = false; return false; } } public void Dispose() { _stopping = true; Interlocked.Increment(ref _generation); IsConnected = false; TcpClient pendingClient; lock (_pendingClientLock) { pendingClient = _pendingClient; _pendingClient = null; } try { pendingClient?.Close(); } catch { } try { _stream?.Dispose(); } catch { } try { _client?.Close(); } catch { } try { _listener?.Stop(); } catch { } Thread currentThread = Thread.CurrentThread; Thread[] array = new Thread[3] { _hostThread, _connectThread, _receiveThread }; foreach (Thread thread in array) { if (thread != null && thread.IsAlive && thread != currentThread) { thread.Join(500); } } } } [Serializable] internal class LobbyMessage { public const int CurrentProtocolVersion = 10; public string type; public bool ready; public int mapSeed; public int matchId; public string deckCardsPacked; public int mapMode; public string riskHitsPacked; public string route; public bool deathCardPresent; public string deathCardNameReplacement; public int deathCardAttack; public int deathCardHealth; public int[] deathCardAbilities; public string starterDeckId; public bool totemPresent; public int totemTribe; public int totemAbility; public string playerName; public string hostPlayerName; public string peerPlayerName; public int protocolVersion; public string buildVersion; } internal class LobbySession : MonoBehaviour { private string _confirmedHostName; private string _confirmedPeerName; private bool _backspaceResolving; public const string DefaultJoinIp = "127.0.0.1"; private ITransport _transport; private bool _wasConnected; private bool _quitAfterMatchInProgress; private const int JoinConnectTimeoutMs = 8000; private int _handledBattle; private int _battleFlowMatchId; private Coroutine _activeBattleTransitionCoroutine; public static LobbySession Instance { get; private set; } public bool IsActive { get; private set; } public bool IsHost { get; private set; } public bool LocalReady { get; private set; } public bool PeerReady { get; private set; } public int SharedMapSeed { get; private set; } public int MatchId { get; private set; } public bool RunStarted { get; private set; } public MapMode SelectedMapMode { get; private set; } public NetCardInfo[] PeerDeck { get; private set; } public bool LocalDeckSent { get; private set; } public string[] PeerRiskHits { get; private set; } = Array.Empty(); public string PeerRoute { get; private set; } public bool PeerDeathCardPresent { get; private set; } public string PeerDeathCardNameReplacement { get; private set; } public int PeerDeathCardAttack { get; private set; } public int PeerDeathCardHealth { get; private set; } public int[] PeerDeathCardAbilities { get; private set; } = Array.Empty(); public string PeerStarterDeckId { get; private set; } public bool PeerTotemPresent { get; private set; } public Tribe PeerTotemTribe { get; private set; } public Ability PeerTotemAbility { get; private set; } public string LocalPlayerName { get; private set; } = ""; public string PeerPlayerName { get; private set; } = ""; public int PeerProtocolVersion { get; private set; } public string PeerBuildVersion { get; private set; } public string HostDisplayName { get { if (string.IsNullOrEmpty(_confirmedHostName)) { return SanitizePlayerName(IsHost ? LocalPlayerName : PeerPlayerName, PvpLocalization.Get("KayceePvP.Lobby.DefaultPlayer1")); } return _confirmedHostName; } } public string PeerDisplayName { get { if (string.IsNullOrEmpty(_confirmedPeerName)) { return SanitizePlayerName(IsHost ? PeerPlayerName : LocalPlayerName, PvpLocalization.Get("KayceePvP.Lobby.DefaultPlayer2")); } return _confirmedPeerName; } } public bool LocalDuelReady { get; private set; } public bool PeerDuelReady { get; private set; } public bool CountdownStarted { get; private set; } public bool LocalCountdownDone { get; private set; } public bool PeerCountdownDone { get; private set; } public bool DuelBegun { get; private set; } public bool LocalRematchRequested { get; private set; } public bool PeerRematchRequested { get; private set; } public bool RematchAccepted { get; private set; } public bool DuelReadyPhaseActive { get; private set; } public string DisplayError { get; private set; } public ITransport Transport => _transport; public bool RematchAvailable { get { if (_transport != null) { return _transport.IsConnected; } return true; } } internal static void RegisterLocalizedText() { PvpLocalization.L("KayceePvP.Error.InvalidPort", "Invalid port (use 1-65535).", "Porta inválida (use 1-65535)."); PvpLocalization.L("KayceePvP.Error.MissingIp", "Enter an IP or host to connect to.", "Informe um IP ou host para conectar."); PvpLocalization.L("KayceePvP.Error.MalformedNetworkMessage", "Invalid network message; connection closed.", "Mensagem de rede inválida; conexão encerrada."); PvpLocalization.L("KayceePvP.Error.UnsupportedMapModeFromHost", "Unknown map mode received from the Host - update the mod and try again.", "Modo de mapa desconhecido recebido do Host - atualize o mod e tente de novo."); PvpLocalization.L("KayceePvP.Error.MapModeDivergence", "Map mode mismatch with the Host - run not started.", "Divergência de modo de mapa com o Host - run não iniciada."); PvpLocalization.L("KayceePvP.Error.UnsupportedMapModeLocal", "Unknown map mode - selection refused.", "Modo de mapa desconhecido - seleção recusada."); PvpLocalization.L("KayceePvP.Error.InvalidMapModeLocal", "Invalid local map mode - run not started.", "Modo de mapa local inválido - run não iniciada."); PvpLocalization.L("KayceePvP.Error.InvalidInviteCode", "Invalid online invite code.", "Código de convite online inválido."); PvpLocalization.L("KayceePvP.Error.InviteCodeFormatInvalid", "Invalid invite code.", "Código de convite inválido."); PvpLocalization.L("KayceePvP.Error.RelayNotConfigured", "Online relay not configured yet (Online.RelayHost).", "Relay online ainda não configurado (Online.RelayHost)."); PvpLocalization.L("KayceePvP.Error.InvalidRelayPort", "Invalid relay port (use 1-65535).", "Porta do relay inválida (use 1-65535)."); PvpLocalization.L("KayceePvP.Error.ProtocolVersionMismatch", "Incompatible mod version with the opponent (local={0} peer={1}) - update both sides before trying again.", "Versão do mod incompatível com o oponente (local={0} peer={1}) - atualize os dois lados antes de tentar de novo."); PvpLocalization.L("KayceePvP.Error.BuildVersionMismatch", "Incompatible package version (local={0} peer={1}) - install the same version on both sides.", "Versão do pacote incompatível (local={0} peer={1}) - instale a mesma versão nos dois lados."); PvpLocalization.L("KayceePvP.Error.OpponentDisconnected", "Opponent disconnected.", "Oponente desconectado."); PvpLocalization.L("KayceePvP.Route.Explanation1", "Along the way, you may find battles to test your deck.", "Durante o caminho, você pode encontrar batalhas para testar seu baralho."); PvpLocalization.L("KayceePvP.Route.Explanation2", "These battles are optional. After all, even a bad plan deserves to be tested.", "Essas batalhas são opcionais. Afinal, até um plano ruim merece ser testado."); PvpLocalization.L("KayceePvP.Route.Explanation3", "If you just want to move on, trick Leshy by pressing BACKSPACE. He probably deserves it.", "Se quiser apenas seguir viagem, engane Leshy apertando BACKSPACE. Ele provavelmente merece."); PvpLocalization.L("KayceePvP.Lobby.DefaultPlayer1", "PLAYER 1", "JOGADOR 1"); PvpLocalization.L("KayceePvP.Lobby.DefaultPlayer2", "PLAYER 2", "JOGADOR 2"); } public void SetLocalPlayerName(string raw) { LocalPlayerName = SanitizePlayerNameRaw(raw); } private static string SanitizePlayerNameRaw(string raw) { if (string.IsNullOrEmpty(raw)) { return ""; } StringBuilder stringBuilder = new StringBuilder(raw.Length); foreach (char c in raw) { if (!char.IsControl(c) && c != '<' && c != '>') { stringBuilder.Append(c); } } string text = stringBuilder.ToString().Trim(); if (text.Length <= 16) { return text; } return text.Substring(0, 16); } private static string SanitizePlayerName(string raw, string fallback) { string text = SanitizePlayerNameRaw(raw); if (text.Length <= 0) { return fallback; } return text; } private void Awake() { Instance = this; } private void OnDestroy() { _transport?.Dispose(); } public void CloseLobby() { IsActive = false; } public void BeginLobby() { IsActive = true; RunStarted = false; LocalReady = false; PeerReady = false; SharedMapSeed = 0; MatchId = 0; SelectedMapMode = MapMode.Battles; DisplayError = null; _wasConnected = false; PeerDeck = null; PeerRiskHits = Array.Empty(); PeerRoute = null; PeerDeathCardPresent = false; PeerDeathCardNameReplacement = null; PeerDeathCardAttack = 0; PeerDeathCardHealth = 0; PeerDeathCardAbilities = Array.Empty(); PeerStarterDeckId = null; PeerTotemPresent = false; PeerTotemTribe = (Tribe)0; PeerTotemAbility = (Ability)0; PeerPlayerName = ""; _confirmedHostName = null; _confirmedPeerName = null; PeerProtocolVersion = 0; PeerBuildVersion = null; LocalDeckSent = false; LocalDuelReady = false; PeerDuelReady = false; CountdownStarted = false; LocalCountdownDone = false; PeerCountdownDone = false; DuelBegun = false; DuelReadyPhaseActive = false; } private void Update() { if (Input.GetKeyDown((KeyCode)289)) { MapDebug.DumpNewMap(); } if (Input.GetKeyDown((KeyCode)288)) { ResearchDump.DumpAll(); } Telemetry.Poll(); if (!Plugin.MultiplayerEnabled.Value) { return; } if (!IsActive && !PvpRunState.IsPvpRun && Input.GetKeyDown((KeyCode)290)) { BeginLobby(); } bool flag = PvpRunState.IsPvpRun && (Object)(object)Singleton.Instance != (Object)null && (Object)(object)Singleton.Instance != (Object)null && !PvpRunState.IsDuelActive; if (!flag) { _backspaceResolving = false; } if (Input.GetKeyDown((KeyCode)8) && flag && !_backspaceResolving) { _backspaceResolving = true; ((MonoBehaviour)this).StartCoroutine(ResolvePreparationBattleWithBackspace()); Plugin.Log.LogInfo((object)"PvP: Backspace pressed - skipping the current preparation battle phase."); } if (flag && !PvpRunState.BackspaceTutorialShown) { PvpRunState.BackspaceTutorialShown = true; ((MonoBehaviour)this).StartCoroutine(PlayBackspaceTutorial()); } if (!IsActive && Input.GetKeyDown((KeyCode)287)) { PvpCardCreationFlow.StartFixedDeckDuelDebug(); } if (!IsActive && Input.GetKeyDown((KeyCode)291)) { DuelDebug.StartRandomDuel(); } if (!IsActive && DuelReadyPhaseActive && !LocalDuelReady && Input.GetKeyDown((KeyCode)32)) { MarkLocalDuelReady(); } UpdateDisplayError(); if (_transport == null) { return; } byte[] result; while (_transport.IncomingMessages.TryDequeue(out result)) { try { HandleMessage(result); } catch (Exception ex) { DisplayError = PvpLocalization.Get("KayceePvP.Error.MalformedNetworkMessage"); Plugin.Log.LogError((object)("Lobby: rejected malformed network message (" + ex.GetType().Name + ").")); _transport.Dispose(); _transport = null; _wasConnected = false; break; } } } private IEnumerator ResolvePreparationBattleWithBackspace() { LifeManager instance = Singleton.Instance; if ((Object)(object)instance != (Object)null) { yield return instance.ShowDamageSequence(5, 5, false, 0.125f, (GameObject)null, 0f, true); } _backspaceResolving = false; } private void UpdateDisplayError() { if (_transport == null) { _wasConnected = false; return; } if (_transport.IsConnected) { _wasConnected = true; } else if (_wasConnected) { DisplayError = (string.IsNullOrEmpty(_transport.LastError) ? PvpLocalization.Get("KayceePvP.Error.OpponentDisconnected") : _transport.LastError); _wasConnected = false; } if (!string.IsNullOrEmpty(_transport.LastError) && DisplayError != _transport.LastError) { DisplayError = _transport.LastError; } } private static IEnumerator PlayBackspaceTutorial() { if (!((Object)(object)Singleton.Instance == (Object)null)) { TextDisplayer textDisplayer = Singleton.Instance; textDisplayer.ShowMessage(PvpLocalization.English("KayceePvP.Route.Explanation1"), (Emotion)0, (LetterAnimation)0, (Speaker)0, (string[])null); yield return (object)new WaitForSeconds(2.5f); textDisplayer.ShowMessage(PvpLocalization.English("KayceePvP.Route.Explanation2"), (Emotion)0, (LetterAnimation)0, (Speaker)0, (string[])null); yield return (object)new WaitForSeconds(2.5f); textDisplayer.ShowMessage(PvpLocalization.English("KayceePvP.Route.Explanation3"), (Emotion)0, (LetterAnimation)0, (Speaker)0, (string[])null); yield return (object)new WaitForSeconds(5f); textDisplayer.Clear(); } } private void HandleMessage(byte[] payload) { if (payload == null || payload.Length == 0) { throw new InvalidOperationException("Empty network payload."); } LobbyMessage lobbyMessage = JsonUtility.FromJson(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetString(payload)); if (lobbyMessage == null || string.IsNullOrEmpty(lobbyMessage.type)) { throw new InvalidOperationException("Missing network message type."); } if (WireMessagePolicy.IsKnownLobbyType(lobbyMessage.type) && !WireMessagePolicy.CanReceiveLobbyType(IsHost, lobbyMessage.type)) { throw new InvalidOperationException("Network message is invalid for the sender role."); } switch (lobbyMessage.type) { case "ready": PeerReady = lobbyMessage.ready; if (!string.IsNullOrEmpty(lobbyMessage.playerName)) { PeerPlayerName = SanitizePlayerName(lobbyMessage.playerName, ""); } PeerProtocolVersion = lobbyMessage.protocolVersion; PeerBuildVersion = lobbyMessage.buildVersion; TryStartIfBothReady(); break; case "start": if (!MapModeCatalog.IsSupported(lobbyMessage.mapMode)) { DisplayError = PvpLocalization.Get("KayceePvP.Error.UnsupportedMapModeFromHost"); Plugin.Log.LogError((object)$"Lobby: REFUSED to start - unsupported mapMode value {lobbyMessage.mapMode} from Host."); break; } if (!IsHost && SelectedMapMode != (MapMode)lobbyMessage.mapMode) { DisplayError = PvpLocalization.Get("KayceePvP.Error.MapModeDivergence"); Plugin.Log.LogError((object)$"Lobby: REFUSED to start - map mode mismatch (local={SelectedMapMode}, host={(MapMode)lobbyMessage.mapMode})."); break; } SelectedMapMode = (MapMode)lobbyMessage.mapMode; SharedMapSeed = lobbyMessage.mapSeed; MatchId = lobbyMessage.matchId; RunStarted = true; _confirmedHostName = SanitizePlayerName(lobbyMessage.hostPlayerName, PvpLocalization.Get("KayceePvP.Lobby.DefaultPlayer1")); _confirmedPeerName = SanitizePlayerName(lobbyMessage.peerPlayerName, PvpLocalization.Get("KayceePvP.Lobby.DefaultPlayer2")); Plugin.Log.LogInfo((object)$"Lobby: run starting, shared map seed = {SharedMapSeed}, matchId = {MatchId}, mapMode = {SelectedMapMode}, names=[{_confirmedHostName}]/[{_confirmedPeerName}]"); break; case "map_mode": if (!IsHost) { if (!MapModeCatalog.IsSupported(lobbyMessage.mapMode)) { Plugin.Log.LogError((object)$"Lobby: IGNORED map_mode with unsupported value {lobbyMessage.mapMode} from Host."); break; } SelectedMapMode = (MapMode)lobbyMessage.mapMode; Plugin.Log.LogInfo((object)$"Lobby: host selected map mode = {SelectedMapMode}"); } break; case "deck_ready": { NetCardInfo[] array = NetCardInfo.UnpackDeck(lobbyMessage.deckCardsPacked); NetCardInfo[] array2 = array; foreach (NetCardInfo netCardInfo in array2) { if (!netCardInfo.TryToCardInfo(out var _)) { throw new InvalidDataException("deck_ready contains an unresolvable card '" + netCardInfo.baseName + "'."); } } PeerDeck = array; PeerRiskHits = (string.IsNullOrEmpty(lobbyMessage.riskHitsPacked) ? Array.Empty() : lobbyMessage.riskHitsPacked.Split(new char[1] { '|' })); PeerRoute = lobbyMessage.route; PeerDeathCardPresent = lobbyMessage.deathCardPresent; PeerDeathCardNameReplacement = lobbyMessage.deathCardNameReplacement; PeerDeathCardAttack = lobbyMessage.deathCardAttack; PeerDeathCardHealth = lobbyMessage.deathCardHealth; PeerDeathCardAbilities = lobbyMessage.deathCardAbilities ?? Array.Empty(); PeerStarterDeckId = lobbyMessage.starterDeckId; PeerTotemPresent = lobbyMessage.totemPresent; PeerTotemTribe = (Tribe)lobbyMessage.totemTribe; PeerTotemAbility = (Ability)lobbyMessage.totemAbility; Plugin.Log.LogInfo((object)string.Format("Lobby: opponent finished their boss - received their deck ({0} cards, route={1}, risk={2}).", PeerDeck.Length, PeerRoute, (PeerRiskHits.Length == 0) ? "clean" : string.Join("+", PeerRiskHits))); break; } case "duel_ready": if (IsCurrentMatch(lobbyMessage) && lobbyMessage.ready) { PeerDuelReady = true; TryStartDuelCountdown(); } break; case "duel_countdown_start": if (IsCurrentMatch(lobbyMessage) && !CountdownStarted) { CountdownStarted = true; } break; case "duel_countdown_done": if (IsCurrentMatch(lobbyMessage) && !PeerCountdownDone) { PeerCountdownDone = true; TryStartDuelBegin(); } break; case "duel_begin": if (IsCurrentMatch(lobbyMessage) && !DuelBegun) { DuelBegun = true; } break; case "rematch_request": if (!RematchProtocol.IsMatchingSeries(lobbyMessage.matchId, LobbyMatchState.MatchId)) { Plugin.Log.LogInfo((object)$"Duel (rematch): IGNORED rematch_request for a different series (msgMatch={lobbyMessage.matchId} finishedMatch={LobbyMatchState.MatchId})."); break; } PeerRematchRequested = true; Plugin.Log.LogInfo((object)$"Duel (rematch): peer requested a rematch | localRequested={LocalRematchRequested} peerRequested={PeerRematchRequested}"); TryStartRematch(); break; case "rematch_accept": BeginRematchSeries(lobbyMessage.matchId); break; default: if ((Object)(object)DuelSession.Instance == (Object)null || !WireMessagePolicy.IsKnownDuelType(lobbyMessage.type)) { throw new InvalidOperationException("Unknown network message type."); } DuelSession.Instance.HandleMessage(payload); break; } } private bool SendLobbyMessage(LobbyMessage msg) { string s = JsonUtility.ToJson((object)msg); return _transport.Send(Encoding.UTF8.GetBytes(s)); } public void SendLocalDeck(List cards) { //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) PvpCardPoolRules.FilterFromDeck(cards, "deck_final"); NetCardInfo[] array = cards.Select((CardInfo c) => NetCardInfo.FromCardInfo(c)).ToArray(); string text = string.Join("|", Telemetry.GetLocalRiskHits()); LocalDeckSent = true; CardInfo val = cards.Find((CardInfo c) => ((Object)c).name == "!DEATHCARD_BASE"); CardModificationInfo val2 = ((val != null && val.Mods?.Count > 0) ? val.Mods[val.Mods.Count - 1] : null); TotemDefinition val3 = RunState.Run?.totems?.LastOrDefault(); LobbyMessage lobbyMessage = new LobbyMessage { type = "deck_ready", deckCardsPacked = NetCardInfo.PackDeck(array), riskHitsPacked = text, route = PvpRunState.Route, deathCardPresent = ((Object)(object)val != (Object)null), deathCardNameReplacement = val2?.nameReplacement, deathCardAttack = (val2?.attackAdjustment ?? 0), deathCardHealth = (val2?.healthAdjustment ?? 0), deathCardAbilities = ((val2 != null) ? val2.abilities.ConvertAll((Ability a) => (int)a).ToArray() : Array.Empty()), starterDeckId = (SaveManager.SaveFile?.ascensionData?.currentStarterDeck ?? ""), totemPresent = (val3 != null), totemTribe = ((val3 != null) ? ((int)val3.tribe) : 0), totemAbility = ((val3 != null) ? ((int)val3.ability) : 0) }; if (_transport == null) { PeerDeck = array; PeerRiskHits = (string.IsNullOrEmpty(text) ? Array.Empty() : text.Split(new char[1] { '|' })); PeerRoute = lobbyMessage.route; PeerDeathCardPresent = lobbyMessage.deathCardPresent; PeerDeathCardNameReplacement = lobbyMessage.deathCardNameReplacement; PeerDeathCardAttack = lobbyMessage.deathCardAttack; PeerDeathCardHealth = lobbyMessage.deathCardHealth; PeerDeathCardAbilities = lobbyMessage.deathCardAbilities; PeerStarterDeckId = lobbyMessage.starterDeckId; PeerTotemPresent = lobbyMessage.totemPresent; PeerTotemTribe = (Tribe)lobbyMessage.totemTribe; PeerTotemAbility = (Ability)lobbyMessage.totemAbility; Plugin.Log.LogInfo((object)$"Lobby: solo debug mode - mirroring local deck ({array.Length} cards) as the opponent's."); } else { bool flag = SendLobbyMessage(lobbyMessage); Plugin.Log.LogInfo((object)(flag ? $"Lobby: sent our deck ({array.Length} cards, route={lobbyMessage.route}) to the opponent, waiting for theirs..." : $"Lobby: FAILED to send our deck ({array.Length} cards) - transport not connected (LastError='{_transport.LastError}').")); } } public void Host(int port = 40125) { if (port < 1 || port > 65535) { DisplayError = PvpLocalization.Get("KayceePvP.Error.InvalidPort"); return; } _transport?.Dispose(); DisplayError = null; _wasConnected = false; IsHost = true; ((LanTransport)(_transport = new LanTransport())).Host(port); } public void Join(string ip, int port = 40125) { if (string.IsNullOrWhiteSpace(ip)) { DisplayError = PvpLocalization.Get("KayceePvP.Error.MissingIp"); return; } if (port < 1 || port > 65535) { DisplayError = PvpLocalization.Get("KayceePvP.Error.InvalidPort"); return; } _transport?.Dispose(); DisplayError = null; _wasConnected = false; IsHost = false; ((LanTransport)(_transport = new LanTransport())).Connect(ip, port, 8000); } public void CreateOnlineRoom() { string relayHost = Plugin.RelayHost?.Value?.Trim(); int relayPort = Plugin.RelayPort?.Value ?? 443; if (ValidateRelayEndpoint(relayHost, relayPort)) { _transport?.Dispose(); DisplayError = null; _wasConnected = false; IsHost = true; ((RelayTransport)(_transport = new RelayTransport())).CreateRoom(relayHost, relayPort); } } public void JoinOnlineRoom(string inviteCode) { string relayHost = Plugin.RelayHost?.Value?.Trim(); int relayPort = Plugin.RelayPort?.Value ?? 443; if (ValidateRelayEndpoint(relayHost, relayPort)) { if (!RelayInvite.TryParse(inviteCode, out var _, out var _)) { DisplayError = PvpLocalization.Get("KayceePvP.Error.InvalidInviteCode"); return; } _transport?.Dispose(); DisplayError = null; _wasConnected = false; IsHost = false; ((RelayTransport)(_transport = new RelayTransport())).JoinRoom(relayHost, inviteCode, relayPort); } } private bool ValidateRelayEndpoint(string relayHost, int relayPort) { if (string.IsNullOrEmpty(relayHost)) { DisplayError = PvpLocalization.Get("KayceePvP.Error.RelayNotConfigured"); return false; } if (relayPort < 1 || relayPort > 65535) { DisplayError = PvpLocalization.Get("KayceePvP.Error.InvalidRelayPort"); return false; } return true; } public void CancelConnection() { _transport?.Dispose(); _transport = null; IsHost = false; LocalReady = false; PeerReady = false; RunStarted = false; SharedMapSeed = 0; MatchId = 0; DisplayError = null; _wasConnected = false; } public void ExitLobby() { if (!RunStarted) { Plugin.Log.LogInfo((object)"Lobby: player closed the pre-run lobby."); CancelConnection(); CloseLobby(); } } public void QuitGameAfterMatch() { if (!_quitAfterMatchInProgress) { _quitAfterMatchInProgress = true; string text = (IsHost ? "HOST" : "PEER"); Plugin.Log.LogInfo((object)$"Duel (match-result): SESSION_QUIT_BEGIN | self={text} match={LobbyMatchState.MatchId}"); PvpDuelItems.RestorePreparationInventory(); CancelConnection(); BeginLobby(); CloseLobby(); DuelSession.Instance?.PrepareForMatch(0); PvpRunState.Reset(); NoBattleWaitingScreen.Hide(); FinalMatchScreen.Hide(); LobbyMatchState.EndInterBattleSceneReload(); Plugin.Log.LogInfo((object)("Duel (match-result): SESSION_QUIT_APPLICATION_QUIT | self=" + text)); Application.Quit(); } } public void ReturnToAscensionMenuAfterMatch() { if (!_quitAfterMatchInProgress) { _quitAfterMatchInProgress = true; string text = (IsHost ? "HOST" : "PEER"); Plugin.Log.LogInfo((object)$"Duel (match-result): NEW_PREPARATION_BEGIN | self={text} match={LobbyMatchState.MatchId}"); PvpDuelItems.RestorePreparationInventory(); CancelConnection(); BeginLobby(); CloseLobby(); DuelSession.Instance?.PrepareForMatch(0); PvpRunState.Reset(); NoBattleWaitingScreen.Hide(); FinalMatchScreen.Hide(); LobbyMatchState.EndInterBattleSceneReload(); Time.timeScale = 1f; FrameLoopManager instance = FrameLoopManager.Instance; if (instance != null) { instance.SetIterationDisabled(false); } SaveManager.savingDisabled = false; SaveFile.IsAscension = true; Plugin.Log.LogInfo((object)("Duel (match-result): LOADING_ASCENSION_CONFIGURE | self=" + text)); SceneLoader.Load("Ascension_Configure"); _quitAfterMatchInProgress = false; } } public void SetMapMode(MapMode mode) { if (RunStarted || (_transport != null && !IsHost)) { return; } if (!MapModeCatalog.IsSupported((int)mode)) { DisplayError = PvpLocalization.Get("KayceePvP.Error.UnsupportedMapModeLocal"); Plugin.Log.LogError((object)$"Lobby: REFUSED SetMapMode - unsupported mode {mode} not in MapModeCatalog."); } else if (SelectedMapMode != mode) { SelectedMapMode = mode; Plugin.Log.LogInfo((object)$"Lobby: host set map mode = {mode}"); if (_transport != null) { SendLobbyMessage(new LobbyMessage { type = "map_mode", mapMode = (int)mode }); } } } public void SetLocalReady(bool ready) { LocalReady = ready; SendLobbyMessage(new LobbyMessage { type = "ready", ready = ready, playerName = LocalPlayerName, protocolVersion = 10, buildVersion = "0.1.0" }); TryStartIfBothReady(); } private void TryStartIfBothReady() { if (IsHost && !RunStarted && LocalReady && PeerReady) { if (PeerProtocolVersion != 10) { DisplayError = PvpLocalization.Format("KayceePvP.Error.ProtocolVersionMismatch", 10, PeerProtocolVersion); Plugin.Log.LogError((object)$"Lobby: REFUSED to start - protocol version mismatch (local={10}, peer={PeerProtocolVersion})."); return; } if (!string.Equals(PeerBuildVersion, "0.1.0", StringComparison.Ordinal)) { DisplayError = PvpLocalization.Format("KayceePvP.Error.BuildVersionMismatch", "0.1.0", PeerBuildVersion ?? "?"); Plugin.Log.LogError((object)("Lobby: REFUSED to start - build version mismatch (local=0.1.0, peer=" + (PeerBuildVersion ?? "missing") + ").")); return; } if (!MapModeCatalog.IsSupported((int)SelectedMapMode)) { DisplayError = PvpLocalization.Get("KayceePvP.Error.InvalidMapModeLocal"); Plugin.Log.LogError((object)$"Lobby: REFUSED to start - local SelectedMapMode {SelectedMapMode} not in MapModeCatalog."); return; } SharedMapSeed = 20260727; MatchId = Random.Range(int.MinValue, int.MaxValue); RunStarted = true; _confirmedHostName = SanitizePlayerName(LocalPlayerName, PvpLocalization.Get("KayceePvP.Lobby.DefaultPlayer1")); _confirmedPeerName = SanitizePlayerName(PeerPlayerName, PvpLocalization.Get("KayceePvP.Lobby.DefaultPlayer2")); Plugin.Log.LogInfo((object)$"Lobby: both ready, host picked shared map seed = {SharedMapSeed}, matchId = {MatchId}, mapMode = {SelectedMapMode}, names=[{_confirmedHostName}]/[{_confirmedPeerName}]"); SendLobbyMessage(new LobbyMessage { type = "start", mapSeed = SharedMapSeed, matchId = MatchId, mapMode = (int)SelectedMapMode, hostPlayerName = _confirmedHostName, peerPlayerName = _confirmedPeerName }); } } private static bool IsCurrentMatch(LobbyMessage msg) { if (msg.matchId != PvpRunState.MatchId) { Plugin.Log.LogInfo((object)$"PvP: ignoring stale duel-phase message (matchId {msg.matchId} != current {PvpRunState.MatchId})."); return false; } return true; } public void BeginDuelReadyPhase() { DuelReadyPhaseActive = true; } public void EndDuelReadyPhase() { DuelReadyPhaseActive = false; } public void MarkLocalDuelReady() { if (!LocalDuelReady) { LocalDuelReady = true; if (_transport == null) { PeerDuelReady = true; } else { SendLobbyMessage(new LobbyMessage { type = "duel_ready", ready = true, matchId = PvpRunState.MatchId }); } TryStartDuelCountdown(); } } private void TryStartDuelCountdown() { if (IsHost && !CountdownStarted && LocalDuelReady && PeerDuelReady) { CountdownStarted = true; if (_transport != null) { SendLobbyMessage(new LobbyMessage { type = "duel_countdown_start", matchId = PvpRunState.MatchId }); } } } public void MarkLocalCountdownDone() { if (!LocalCountdownDone) { LocalCountdownDone = true; if (_transport == null) { PeerCountdownDone = true; } if (IsHost) { TryStartDuelBegin(); return; } SendLobbyMessage(new LobbyMessage { type = "duel_countdown_done", matchId = PvpRunState.MatchId }); } } private void TryStartDuelBegin() { if (IsHost && !DuelBegun && LocalCountdownDone && PeerCountdownDone) { DuelBegun = true; if (_transport != null) { SendLobbyMessage(new LobbyMessage { type = "duel_begin", matchId = PvpRunState.MatchId }); } } } public void RequestRematch() { if (LocalRematchRequested) { return; } if (!RematchAvailable) { Plugin.Log.LogInfo((object)"Duel (rematch): request ignored - no live connection."); return; } LocalRematchRequested = true; Plugin.Log.LogInfo((object)$"Duel (rematch): local player requested a rematch | match={LobbyMatchState.MatchId}"); if (_transport == null) { PeerRematchRequested = true; } else { SendLobbyMessage(new LobbyMessage { type = "rematch_request", matchId = LobbyMatchState.MatchId }); } TryStartRematch(); } private void TryStartRematch() { if (RematchProtocol.ShouldBroadcastAccept(IsHost, RematchAccepted, LocalRematchRequested, PeerRematchRequested, RematchAvailable)) { RematchAccepted = true; int num = Random.Range(int.MinValue, int.MaxValue); Plugin.Log.LogInfo((object)$"Duel (rematch): both sides requested - starting new series | previousMatch={LobbyMatchState.MatchId} newMatch={num}"); if (_transport != null) { SendLobbyMessage(new LobbyMessage { type = "rematch_accept", matchId = num }); } BeginRematchSeries(num); } } internal void BeginRematchSeries(int newMatchId) { Plugin.Log.LogInfo((object)$"Duel (rematch): BEGIN | previousMatch={LobbyMatchState.MatchId} newMatch={newMatchId}"); FinalMatchScreen.Hide(); PvpRunState.MatchId = newMatchId; LobbyMatchState.RestoreLocalDeckForNextBattle(); ResetDuelReadyFlagsForRematch(); ((MonoBehaviour)this).StartCoroutine(PvpCardCreationFlow.ShowReadyScreenAndCountdown()); } private void ResetDuelReadyFlagsForRematch() { LocalDuelReady = false; PeerDuelReady = false; CountdownStarted = false; LocalCountdownDone = false; PeerCountdownDone = false; DuelBegun = false; LocalRematchRequested = false; PeerRematchRequested = false; RematchAccepted = false; } public void StartSoloDebugBypass() { _transport?.Dispose(); _transport = null; DisplayError = null; _wasConnected = false; IsHost = true; SharedMapSeed = 20260727; MatchId = Random.Range(int.MinValue, int.MaxValue); LocalReady = true; PeerReady = true; RunStarted = true; Plugin.Log.LogInfo((object)$"Lobby: solo debug bypass, seed = {SharedMapSeed}, matchId = {MatchId}"); } public void ResetBattleFlowForMatch(int matchId, string reason) { int battleFlowMatchId = _battleFlowMatchId; int handledBattle = _handledBattle; if (_activeBattleTransitionCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_activeBattleTransitionCoroutine); } _activeBattleTransitionCoroutine = null; _handledBattle = 0; _battleFlowMatchId = matchId; LobbyMatchState.EndInterBattleSceneReload(); Plugin.Log.LogInfo((object)$"Duel (interbattle): ResetBattleFlowForMatch - reason='{reason}', matchId {battleFlowMatchId} -> {matchId}, _handledBattle {handledBattle} -> {_handledBattle}."); } public void OnBattleConcluded(int battleNumber, bool isMatchFinal) { if (battleNumber > _handledBattle) { if (_activeBattleTransitionCoroutine != null) { string text = (IsHost ? "HOST" : "PEER"); Plugin.Log.LogWarning((object)$"Duel (interbattle): BATTLE_CONCLUSION_IGNORED_TRANSITION_ACTIVE | self={text} concludedBattle={battleNumber} handledBattle={_handledBattle} currentBattle={LobbyMatchState.BattleNumber} isMatchFinal={isMatchFinal}"); } else { _handledBattle = battleNumber; _activeBattleTransitionCoroutine = ((MonoBehaviour)this).StartCoroutine(InterBattleTransitionCoroutine(battleNumber, isMatchFinal)); } } } private IEnumerator InterBattleTransitionCoroutine(int concludedBattle, bool isMatchFinal) { string self = (IsHost ? "HOST" : "PEER"); Plugin.Log.LogInfo((object)$"Duel (interbattle): LEAVING_BATTLE | self={self} match={LobbyMatchState.MatchId} concludedBattle={concludedBattle} isMatchFinal={isMatchFinal}"); if (isMatchFinal) { DuelStart.LogTotemSnapshot($"battle end (final battle {concludedBattle}, match concluded)"); bool reachedFinalPresentation = false; LobbyMatchState.BeginInterBattleSceneReload(); try { DuelSession.Instance.ResetHandshakeForNewBattle(); DuelSession.Instance.SendBattleReturnReady(concludedBattle); if (IsHost) { yield return (object)new WaitUntil((Func)(() => DuelSession.Instance.BothReadyForBattle(concludedBattle))); DuelSession.Instance.SendBattleBegin(concludedBattle); } else { yield return (object)new WaitUntil((Func)(() => DuelSession.Instance.BattleBeginReceived && DuelSession.Instance.BattleBeginNumber >= concludedBattle)); } Plugin.Log.LogInfo((object)$"Duel (interbattle): FINAL_BATTLE_ACK | self={self} match={LobbyMatchState.MatchId} battle={concludedBattle} hostWins={LobbyMatchState.HostWins} peerWins={LobbyMatchState.PeerWins}"); Plugin.Log.LogInfo((object)$"Duel (interbattle): FINAL_SCENE_EXIT_BEGIN | self={self} match={LobbyMatchState.MatchId} battle={concludedBattle}"); AsyncOperation asyncOp = SceneLoader.StartAsyncLoad("Part1_Cabin"); asyncOp.allowSceneActivation = false; yield return null; SceneLoader.CompleteAsyncLoad(asyncOp); float sceneDeadline = Time.realtimeSinceStartup + 20f; yield return (object)new WaitUntil((Func)(() => asyncOp.isDone || Time.realtimeSinceStartup > sceneDeadline)); if (!asyncOp.isDone) { Plugin.Log.LogError((object)$"Duel (interbattle): FINAL_SCENE_EXIT timed out waiting for asyncOp.isDone | self={self} battle={concludedBattle}"); yield break; } float turnManagerDeadline = Time.realtimeSinceStartup + 20f; yield return (object)new WaitUntil((Func)(() => (Object)(object)Singleton.Instance != (Object)null || Time.realtimeSinceStartup > turnManagerDeadline)); if ((Object)(object)Singleton.Instance == (Object)null) { Plugin.Log.LogError((object)$"Duel (interbattle): FINAL_SCENE_EXIT new TurnManager timed out | self={self} battle={concludedBattle}"); yield break; } DuelSession.Instance.ResetForNewBattle(); PvpRunState.IsDuelActive = false; Plugin.Log.LogInfo((object)$"Duel (interbattle): FINAL_SCENE_EXIT_DONE | self={self} match={LobbyMatchState.MatchId} battle={concludedBattle} IsDuelActive={PvpRunState.IsDuelActive}"); string text = ((LobbyMatchState.HostWins > LobbyMatchState.PeerWins) ? "HOST" : "PEER"); Plugin.Log.LogInfo((object)$"Duel (interbattle): MATCH_RESULT_PRESENTED | self={self} match={LobbyMatchState.MatchId} battle={concludedBattle} winner={text} hostWins={LobbyMatchState.HostWins} peerWins={LobbyMatchState.PeerWins}"); FinalMatchScreen.Show(text, LobbyMatchState.HostWins, LobbyMatchState.PeerWins); reachedFinalPresentation = true; } finally { LobbyMatchState.EndInterBattleSceneReload(); if (!reachedFinalPresentation) { Plugin.Log.LogError((object)$"Duel (interbattle): final exit sequence did not reach MATCH_RESULT_PRESENTED for battle {concludedBattle} - self={self}. Match is likely stuck; preserve both logs."); } } LobbyMatchState.EndMatch(); PvpRoundState.EndMatch(); _activeBattleTransitionCoroutine = null; ResetBattleFlowForMatch(PvpRunState.MatchId, "match concluded (final battle)"); Plugin.Log.LogInfo((object)$"Duel (interbattle): FINALIZE_MATCH_DONE | self={self} match={LobbyMatchState.MatchId}"); yield break; } bool reachedNextBattle = false; LobbyMatchState.BeginInterBattleSceneReload(); try { DuelSession.Instance.ResetHandshakeForNewBattle(); LobbyMatchState.AdvanceToNextBattle(); LobbyMatchState.RestoreLocalDeckForNextBattle(); int nextBattle = LobbyMatchState.BattleNumber; DuelSession.Instance.SendBattleReturnReady(nextBattle); if (IsHost) { yield return (object)new WaitUntil((Func)(() => DuelSession.Instance.BothReadyForBattle(nextBattle))); DuelSession.Instance.SendBattleBegin(nextBattle); } else { yield return (object)new WaitUntil((Func)(() => DuelSession.Instance.BattleBeginReceived && DuelSession.Instance.BattleBeginNumber >= nextBattle)); } Plugin.Log.LogInfo((object)$"Duel (interbattle): NEXT_BATTLE_READY | self={self} match={LobbyMatchState.MatchId} battle={nextBattle}"); Plugin.Log.LogInfo((object)$"Duel (interbattle): SCENE_RELOAD_BEGIN | self={self} match={LobbyMatchState.MatchId} battle={nextBattle}"); AsyncOperation asyncOp2 = SceneLoader.StartAsyncLoad("Part1_Cabin"); asyncOp2.allowSceneActivation = false; yield return null; SceneLoader.CompleteAsyncLoad(asyncOp2); float sceneDeadline2 = Time.realtimeSinceStartup + 20f; yield return (object)new WaitUntil((Func)(() => asyncOp2.isDone || Time.realtimeSinceStartup > sceneDeadline2)); if (!asyncOp2.isDone) { Plugin.Log.LogError((object)$"Duel (interbattle): SCENE_RELOAD timed out waiting for asyncOp.isDone | self={self} battle={nextBattle}"); yield break; } Plugin.Log.LogInfo((object)$"Duel (interbattle): SCENE_RELOAD_DONE | self={self} match={LobbyMatchState.MatchId} battle={nextBattle}"); float turnManagerDeadline2 = Time.realtimeSinceStartup + 20f; yield return (object)new WaitUntil((Func)(() => (Object)(object)Singleton.Instance != (Object)null || Time.realtimeSinceStartup > turnManagerDeadline2)); if ((Object)(object)Singleton.Instance == (Object)null) { Plugin.Log.LogError((object)$"Duel (interbattle): NEW_TURN_MANAGER_READY timed out | self={self} battle={nextBattle}"); yield break; } Plugin.Log.LogInfo((object)$"Duel (interbattle): NEW_TURN_MANAGER_READY | self={self} match={LobbyMatchState.MatchId} battle={nextBattle}"); DuelSession.Instance.ResetForNewBattle(); DuelStart.BeginNextBattle(); Plugin.Log.LogInfo((object)$"Duel (interbattle): NEXT_BATTLE_BEGIN | self={self} match={LobbyMatchState.MatchId} battle={nextBattle}"); reachedNextBattle = true; } finally { LobbyMatchState.EndInterBattleSceneReload(); if (!reachedNextBattle) { Plugin.Log.LogError((object)$"Duel (interbattle): reload sequence did not reach NEXT_BATTLE_BEGIN for battle {LobbyMatchState.BattleNumber} - self={self}. Match is likely stuck; preserve both logs."); } } _activeBattleTransitionCoroutine = null; } } internal class LobbyUI : MonoBehaviour { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static UnityAction <>9__74_2; public static UnityAction <>9__74_4; public static UnityAction <>9__74_5; public static UnityAction <>9__74_6; public static UnityAction <>9__74_7; public static UnityAction <>9__74_8; public static UnityAction <>9__74_9; public static UnityAction <>9__74_10; public static UnityAction <>9__74_11; public static UnityAction <>9__74_12; public static UnityAction <>9__74_13; public static UnityAction <>9__74_14; public static UnityAction <>9__74_15; public static UnityAction <>9__74_16; public static UnityAction <>9__74_17; public static UnityAction <>9__74_18; public static UnityAction <>9__74_19; public static UnityAction <>9__74_20; public static UnityAction <>9__74_21; public static Predicate <>9__82_0; internal void b__74_2() { LobbySession.Instance.CreateOnlineRoom(); } internal void b__74_4() { LobbySession.Instance.StartSoloDebugBypass(); } internal void b__74_5() { LobbySession.Instance.SetLocalReady(ready: true); } internal void b__74_6() { LobbySession.Instance.SetMapMode(MapMode.Battles); } internal void b__74_7() { LobbySession.Instance.SetMapMode(MapMode.NoBattles); } internal void b__74_8() { LobbySession.Instance.SetMapMode(MapMode.SymmetricForge); } internal void b__74_9() { LobbySession.Instance.SetMapMode(MapMode.PeltDraft); } internal void b__74_10() { LobbySession.Instance.SetMapMode(MapMode.BalancedPath); } internal void b__74_11() { LobbySession.Instance.SetMapMode(MapMode.OpenDraftPath); } internal void b__74_12() { LobbySession.Instance.SetMapMode(MapMode.BalancedRoute); } internal void b__74_13() { LobbySession.Instance.SetMapMode(MapMode.Battles); } internal void b__74_14() { LobbySession.Instance.SetMapMode(MapMode.NoBattles); } internal void b__74_15() { LobbySession.Instance.SetMapMode(MapMode.SymmetricForge); } internal void b__74_16() { LobbySession.Instance.SetMapMode(MapMode.PeltDraft); } internal void b__74_17() { LobbySession.Instance.SetMapMode(MapMode.BalancedPath); } internal void b__74_18() { LobbySession.Instance.SetMapMode(MapMode.OpenDraftPath); } internal void b__74_19() { LobbySession.Instance.SetMapMode(MapMode.BalancedRoute); } internal void b__74_20() { LobbySession.Instance.CancelConnection(); } internal void b__74_21() { LobbySession.Instance.ExitLobby(); } internal bool b__82_0(Font f) { return (Object)(object)f != (Object)null; } } private const string BackgroundResourceName = "KayceePvP.Assets.lobby_ritual_background.png"; private static Font _resolvedFontCache; private static readonly Color BackdropColor = new Color(0.025f, 0.018f, 0.012f, 0.88f); private static readonly Color FrameColor = new Color(0.055f, 0.035f, 0.018f, 0.99f); private static readonly Color BrassColor = new Color(0.48f, 0.36f, 0.18f, 1f); private static readonly Color PanelColor = new Color(0.075f, 0.047f, 0.025f, 0.94f); private static readonly Color PaperColor = new Color(0.78f, 0.7f, 0.52f, 1f); private static readonly Color InkColor = new Color(0.1f, 0.075f, 0.045f, 1f); private static readonly Color ButtonColor = new Color(0.2f, 0.15f, 0.09f, 1f); private static readonly Color SelectedColor = new Color(0.22f, 0.38f, 0.2f, 1f); private static readonly Color ErrorColor = new Color(0.92f, 0.35f, 0.25f, 1f); private GameObject _canvasRoot; private GameObject _preConnectGroup; private GameObject _connectedGroup; private GameObject _readyGroup; private Text _statusText; private Text _errorText; private Text _preConnectErrorText; private Text _starterDeckText; private Text _hostPeerTagText; private Text _readyStatusText; private Text _seedMatchText; private InputField _ipField; private InputField _portField; private InputField _nameField; private InputField _onlineInviteField; private Button _readyButton; private string _copiedInvite; private Text _nameLabelText; private Text _inviteLabelText; private Text _ipLabelText; private Text _portLabelText; private Text _onlineBetaNoticeText; private Button _createOnlineButton; private Button _joinOnlineButton; private Button _hostButton; private Button _joinButton; private Button _soloButton; private Button _cancelButton; private Button _closeButton; private Text _mapModeLabel; private Button _battlesButton; private Button _noBattlesButton; private Button _symmetricForgeButton; private Button _peltDraftButton; private Button _balancedPathButton; private Button _openDraftPathButton; private Button _balancedRouteButton; private Text _mapModeLabelPre; private Button _battlesButtonPre; private Button _noBattlesButtonPre; private Button _symmetricForgeButtonPre; private Button _peltDraftButtonPre; private Button _balancedPathButtonPre; private Button _openDraftPathButtonPre; private Button _balancedRouteButtonPre; private EventSystem _ownedEventSystem; private bool _wasActive; private RectTransform _cursorRect; private bool _prevCursorVisible; private CursorLockMode _prevCursorLockState; private bool _prevInteractionDisabled; private bool _prevInteractionCursorEnabled; internal static void RegisterLocalizedText() { PvpLocalization.L("KayceePvP.Lobby.NameLabel", "Name:", "Nome:"); PvpLocalization.L("KayceePvP.Lobby.CreateOnline", "CREATE ONLINE ROOM", "CRIAR SALA ONLINE"); PvpLocalization.L("KayceePvP.Lobby.InviteLabel", "Invite:", "Convite:"); PvpLocalization.L("KayceePvP.Lobby.JoinOnline", "JOIN ONLINE", "ENTRAR ONLINE"); PvpLocalization.L("KayceePvP.Lobby.HostLan", "Host (LAN)", "Hospedar (LAN)"); PvpLocalization.L("KayceePvP.Lobby.IpLabel", "IP:", "IP:"); PvpLocalization.L("KayceePvP.Lobby.PortLabel", "Port:", "Porta:"); PvpLocalization.L("KayceePvP.Lobby.JoinLan", "Join LAN", "Entrar LAN"); PvpLocalization.L("KayceePvP.Lobby.SoloDebug", "Test alone (skips the lobby)", "Testar sozinho (pula o lobby)"); PvpLocalization.L("KayceePvP.Lobby.OnlineBetaNotice", "ONLINE BETA: Create a room and send its invite code directly to the other player. They must paste the code to join.", "BETA ONLINE: Crie uma sala e envie o código de convite diretamente ao outro jogador. Ele deve colar o código para entrar."); PvpLocalization.L("KayceePvP.Lobby.CancelAttempt", "Cancel attempt", "Cancelar tentativa"); PvpLocalization.L("KayceePvP.Lobby.CloseEsc", "Close lobby [ESC]", "Fechar lobby [ESC]"); PvpLocalization.L("KayceePvP.Lobby.Ready", "Ready", "Pronto"); PvpLocalization.L("KayceePvP.Lobby.Waiting", "Waiting", "Aguardando"); PvpLocalization.L("KayceePvP.Lobby.Disconnected", "Disconnected.", "Desconectado."); PvpLocalization.L("KayceePvP.Lobby.ConnectedRunStarting", "Run starting!", "Run iniciando!"); PvpLocalization.L("KayceePvP.Lobby.Connected", "Connected!", "Conectado!"); PvpLocalization.L("KayceePvP.Lobby.OnlineRoomCreated", "Online room created. Invite copied:\n{0}", "Sala online criada. Convite copiado:\n{0}"); PvpLocalization.L("KayceePvP.Lobby.HostingWaiting", "Hosting, waiting for opponent... (Cancel to give up)", "Hospedando, aguardando oponente... (Cancelar para desistir)"); PvpLocalization.L("KayceePvP.Lobby.Connecting", "Connecting...", "Conectando..."); PvpLocalization.L("KayceePvP.Lobby.ErrorPrefix", "Error: {0}", "Erro: {0}"); PvpLocalization.L("KayceePvP.Lobby.StarterDeck", "Starter deck: {0}", "Baralho inicial: {0}"); PvpLocalization.L("KayceePvP.Lobby.MapPrefix", "Map: {0}", "Mapa: {0}"); PvpLocalization.L("KayceePvP.Lobby.MapYouChoose", " (you choose)", " (você escolhe)"); PvpLocalization.L("KayceePvP.Lobby.MapHostChoice", " (Host's choice)", " (escolha do Host)"); PvpLocalization.L("KayceePvP.Lobby.MapUnknown", "UNKNOWN MODE", "MODO DESCONHECIDO"); PvpLocalization.L("KayceePvP.Lobby.ReadyStatusLine", "You: {0} Opponent: {1}\n{2} x {3}", "Você: {0} Oponente: {1}\n{2} x {3}"); PvpLocalization.L("KayceePvP.Lobby.SeedMatchLine", "Map seed: {0} Match ID: {1}", "Seed do mapa: {0} Match ID: {1}"); PvpLocalization.L("KayceePvP.Lobby.SeedMatchEmpty", "Map seed: — Match ID: —", "Seed do mapa: — Match ID: —"); PvpLocalization.L("KayceePvP.Lobby.HostTag", "HOST", "HOST"); PvpLocalization.L("KayceePvP.Lobby.PeerTag", "PEER", "PEER"); } private void Awake() { BuildUI(); _canvasRoot.SetActive(false); } private void OnDestroy() { if ((Object)(object)_ownedEventSystem != (Object)null) { Object.Destroy((Object)(object)((Component)_ownedEventSystem).gameObject); } } private void Update() { //IL_007a: Unknown result type (might be due to invalid IL or missing references) LobbySession instance = LobbySession.Instance; bool flag = (Object)(object)instance != (Object)null && instance.IsActive; if (flag != _wasActive) { if (flag) { OnLobbyOpened(); } else { OnLobbyClosed(); } _wasActive = flag; } if (flag) { MaintainEventSystem(); if (Input.GetKeyDown((KeyCode)27) && !instance.RunStarted) { instance.ExitLobby(); return; } instance.SetLocalPlayerName(_nameField.text); RefreshLabels(instance); ((Transform)_cursorRect).position = Input.mousePosition; } } private void OnLobbyOpened() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) _prevCursorVisible = Cursor.visible; _prevCursorLockState = Cursor.lockState; Cursor.lockState = (CursorLockMode)0; Cursor.visible = false; InteractionCursor instance = Singleton.Instance; _prevInteractionDisabled = (Object)(object)instance != (Object)null && instance.InteractionDisabled; _prevInteractionCursorEnabled = (Object)(object)instance == (Object)null || ((Behaviour)instance).enabled; if ((Object)(object)instance != (Object)null) { instance.InteractionDisabled = true; instance.SetEnabled(false); } _canvasRoot.SetActive(true); } private void OnLobbyClosed() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) Cursor.visible = _prevCursorVisible; Cursor.lockState = _prevCursorLockState; InteractionCursor instance = Singleton.Instance; if ((Object)(object)instance != (Object)null) { instance.SetEnabled(_prevInteractionCursorEnabled); instance.InteractionDisabled = _prevInteractionDisabled; } if ((Object)(object)_ownedEventSystem != (Object)null) { Object.Destroy((Object)(object)((Component)_ownedEventSystem).gameObject); _ownedEventSystem = null; } _canvasRoot.SetActive(false); } private void MaintainEventSystem() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown EventSystem[] array = Object.FindObjectsOfType(); if (array.Length == 0) { if ((Object)(object)_ownedEventSystem == (Object)null) { GameObject val = new GameObject("KayceePvP_LobbyEventSystem"); val.transform.SetParent(((Component)this).transform, false); _ownedEventSystem = val.AddComponent(); val.AddComponent(); } else { ((Component)_ownedEventSystem).gameObject.SetActive(true); } } else if (array.Length > 1 && (Object)(object)_ownedEventSystem != (Object)null && ((Component)_ownedEventSystem).gameObject.activeSelf) { ((Component)_ownedEventSystem).gameObject.SetActive(false); } } private void RefreshLabels(LobbySession session) { ITransport transport = session.Transport; bool flag = transport == null; _preConnectGroup.SetActive(flag); _connectedGroup.SetActive(!flag); ((Component)_hostPeerTagText).gameObject.SetActive(!flag); string text = PvpLocalization.Get("KayceePvP.Lobby.Ready"); string text2 = PvpLocalization.Get("KayceePvP.Lobby.Waiting"); string text3 = PvpLocalization.Get("KayceePvP.Lobby.HostTag"); string text4 = PvpLocalization.Get("KayceePvP.Lobby.PeerTag"); if (flag) { _statusText.text = PvpLocalization.Get("KayceePvP.Lobby.Disconnected"); } else if (transport.IsConnected) { _statusText.text = (session.RunStarted ? PvpLocalization.Get("KayceePvP.Lobby.ConnectedRunStarting") : PvpLocalization.Get("KayceePvP.Lobby.Connected")); _hostPeerTagText.text = (session.IsHost ? text3 : text4); } else { if (transport is RelayTransport { IsWaitingForPeer: not false } relayTransport && !string.IsNullOrEmpty(relayTransport.InviteCode)) { _statusText.text = PvpLocalization.Format("KayceePvP.Lobby.OnlineRoomCreated", relayTransport.InviteCode); if (_copiedInvite != relayTransport.InviteCode) { GUIUtility.systemCopyBuffer = relayTransport.InviteCode; _copiedInvite = relayTransport.InviteCode; } } else { _statusText.text = (session.IsHost ? PvpLocalization.Get("KayceePvP.Lobby.HostingWaiting") : PvpLocalization.Get("KayceePvP.Lobby.Connecting")); } _hostPeerTagText.text = (session.IsHost ? text3 : text4); } bool flag2 = !string.IsNullOrEmpty(session.DisplayError); ((Component)_errorText).gameObject.SetActive(flag2); _errorText.text = (flag2 ? PvpLocalization.Format("KayceePvP.Lobby.ErrorPrefix", session.DisplayError) : ""); ((Component)_preConnectErrorText).gameObject.SetActive(flag && flag2); _preConnectErrorText.text = (flag2 ? PvpLocalization.Format("KayceePvP.Lobby.ErrorPrefix", session.DisplayError) : ""); _starterDeckText.text = PvpLocalization.Format("KayceePvP.Lobby.StarterDeck", SaveManager.SaveFile?.ascensionData?.currentStarterDeck ?? "?"); _onlineBetaNoticeText.text = PvpLocalization.Get("KayceePvP.Lobby.OnlineBetaNotice"); _nameLabelText.text = PvpLocalization.Get("KayceePvP.Lobby.NameLabel"); ((Component)_createOnlineButton).GetComponentInChildren().text = PvpLocalization.Get("KayceePvP.Lobby.CreateOnline"); _inviteLabelText.text = PvpLocalization.Get("KayceePvP.Lobby.InviteLabel"); ((Component)_joinOnlineButton).GetComponentInChildren().text = PvpLocalization.Get("KayceePvP.Lobby.JoinOnline"); ((Component)_hostButton).GetComponentInChildren().text = PvpLocalization.Get("KayceePvP.Lobby.HostLan"); _ipLabelText.text = PvpLocalization.Get("KayceePvP.Lobby.IpLabel"); _portLabelText.text = PvpLocalization.Get("KayceePvP.Lobby.PortLabel"); ((Component)_joinButton).GetComponentInChildren().text = PvpLocalization.Get("KayceePvP.Lobby.JoinLan"); ((Component)_soloButton).GetComponentInChildren().text = PvpLocalization.Get("KayceePvP.Lobby.SoloDebug"); ((Component)_cancelButton).GetComponentInChildren().text = PvpLocalization.Get("KayceePvP.Lobby.CancelAttempt"); ((Component)_closeButton).GetComponentInChildren().text = PvpLocalization.Get("KayceePvP.Lobby.CloseEsc"); ((Component)_readyButton).GetComponentInChildren().text = text; RefreshMapModeButtonLabel(_battlesButton, MapMode.Battles); RefreshMapModeButtonLabel(_noBattlesButton, MapMode.NoBattles); RefreshMapModeButtonLabel(_symmetricForgeButton, MapMode.SymmetricForge); RefreshMapModeButtonLabel(_peltDraftButton, MapMode.PeltDraft); RefreshMapModeButtonLabel(_balancedPathButton, MapMode.BalancedPath); RefreshMapModeButtonLabel(_openDraftPathButton, MapMode.OpenDraftPath); RefreshMapModeButtonLabel(_balancedRouteButton, MapMode.BalancedRoute); RefreshMapModeButtonLabel(_battlesButtonPre, MapMode.Battles); RefreshMapModeButtonLabel(_noBattlesButtonPre, MapMode.NoBattles); RefreshMapModeButtonLabel(_symmetricForgeButtonPre, MapMode.SymmetricForge); RefreshMapModeButtonLabel(_peltDraftButtonPre, MapMode.PeltDraft); RefreshMapModeButtonLabel(_balancedPathButtonPre, MapMode.BalancedPath); RefreshMapModeButtonLabel(_openDraftPathButtonPre, MapMode.OpenDraftPath); RefreshMapModeButtonLabel(_balancedRouteButtonPre, MapMode.BalancedRoute); bool interactable = !session.RunStarted && (flag || session.IsHost); MapModeDescriptor descriptor; string text5 = (MapModeCatalog.TryGet(session.SelectedMapMode, out descriptor) ? descriptor.UiLabel : PvpLocalization.Get("KayceePvP.Lobby.MapUnknown")); string text6 = (flag ? "" : (session.IsHost ? PvpLocalization.Get("KayceePvP.Lobby.MapYouChoose") : PvpLocalization.Get("KayceePvP.Lobby.MapHostChoice"))); string text7 = PvpLocalization.Format("KayceePvP.Lobby.MapPrefix", text5) + text6; ((Selectable)_battlesButton).interactable = interactable; ((Selectable)_noBattlesButton).interactable = interactable; ((Selectable)_symmetricForgeButton).interactable = interactable; ((Selectable)_peltDraftButton).interactable = interactable; ((Selectable)_balancedPathButton).interactable = interactable; ((Selectable)_openDraftPathButton).interactable = interactable; ((Selectable)_balancedRouteButton).interactable = interactable; TintSelected(_battlesButton, session.SelectedMapMode == MapMode.Battles); TintSelected(_noBattlesButton, session.SelectedMapMode == MapMode.NoBattles); TintSelected(_symmetricForgeButton, session.SelectedMapMode == MapMode.SymmetricForge); TintSelected(_peltDraftButton, session.SelectedMapMode == MapMode.PeltDraft); TintSelected(_balancedPathButton, session.SelectedMapMode == MapMode.BalancedPath); TintSelected(_openDraftPathButton, session.SelectedMapMode == MapMode.OpenDraftPath); TintSelected(_balancedRouteButton, session.SelectedMapMode == MapMode.BalancedRoute); _mapModeLabel.text = text7; ((Selectable)_battlesButtonPre).interactable = interactable; ((Selectable)_noBattlesButtonPre).interactable = interactable; ((Selectable)_symmetricForgeButtonPre).interactable = interactable; ((Selectable)_peltDraftButtonPre).interactable = interactable; ((Selectable)_balancedPathButtonPre).interactable = interactable; ((Selectable)_openDraftPathButtonPre).interactable = interactable; ((Selectable)_balancedRouteButtonPre).interactable = interactable; TintSelected(_battlesButtonPre, session.SelectedMapMode == MapMode.Battles); TintSelected(_noBattlesButtonPre, session.SelectedMapMode == MapMode.NoBattles); TintSelected(_symmetricForgeButtonPre, session.SelectedMapMode == MapMode.SymmetricForge); TintSelected(_peltDraftButtonPre, session.SelectedMapMode == MapMode.PeltDraft); TintSelected(_balancedPathButtonPre, session.SelectedMapMode == MapMode.BalancedPath); TintSelected(_openDraftPathButtonPre, session.SelectedMapMode == MapMode.OpenDraftPath); TintSelected(_balancedRouteButtonPre, session.SelectedMapMode == MapMode.BalancedRoute); _mapModeLabelPre.text = text7; bool flag3 = !flag && transport.IsConnected && !session.RunStarted; _readyGroup.SetActive(flag3); if (flag3) { _readyStatusText.text = PvpLocalization.Format("KayceePvP.Lobby.ReadyStatusLine", session.LocalReady ? text : text2, session.PeerReady ? text : text2, session.HostDisplayName, session.PeerDisplayName); ((Selectable)_readyButton).interactable = !session.LocalReady; } _seedMatchText.text = (session.RunStarted ? PvpLocalization.Format("KayceePvP.Lobby.SeedMatchLine", session.SharedMapSeed, session.MatchId) : PvpLocalization.Get("KayceePvP.Lobby.SeedMatchEmpty")); } private static void RefreshMapModeButtonLabel(Button button, MapMode mode) { Text componentInChildren = ((Component)button).GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null && MapModeCatalog.TryGet(mode, out var descriptor)) { componentInChildren.text = descriptor.UiLabel; } } private static void TintSelected(Button button, bool selected) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) Image component = ((Component)button).GetComponent(); if ((Object)(object)component != (Object)null) { ((Graphic)component).color = (selected ? SelectedColor : ButtonColor); } } private static int ParsePortField(string text) { if (!int.TryParse(text, out var result)) { return 0; } return result; } private void BuildUI() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0085: 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_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0100: 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) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_0255: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_02a4: Unknown result type (might be due to invalid IL or missing references) //IL_02b3: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02e1: Unknown result type (might be due to invalid IL or missing references) //IL_02f0: Unknown result type (might be due to invalid IL or missing references) //IL_0319: Unknown result type (might be due to invalid IL or missing references) //IL_0328: Unknown result type (might be due to invalid IL or missing references) //IL_0351: Unknown result type (might be due to invalid IL or missing references) //IL_0360: Unknown result type (might be due to invalid IL or missing references) //IL_0394: Unknown result type (might be due to invalid IL or missing references) //IL_039e: Expected O, but got Unknown //IL_03ca: Unknown result type (might be due to invalid IL or missing references) //IL_03f4: Unknown result type (might be due to invalid IL or missing references) //IL_0403: Unknown result type (might be due to invalid IL or missing references) //IL_042a: Unknown result type (might be due to invalid IL or missing references) //IL_0439: Unknown result type (might be due to invalid IL or missing references) //IL_0463: Unknown result type (might be due to invalid IL or missing references) //IL_0472: Unknown result type (might be due to invalid IL or missing references) //IL_04a6: Unknown result type (might be due to invalid IL or missing references) //IL_04b5: Unknown result type (might be due to invalid IL or missing references) //IL_04dc: Unknown result type (might be due to invalid IL or missing references) //IL_04eb: Unknown result type (might be due to invalid IL or missing references) //IL_0512: Unknown result type (might be due to invalid IL or missing references) //IL_0521: Unknown result type (might be due to invalid IL or missing references) //IL_054b: Unknown result type (might be due to invalid IL or missing references) //IL_055a: Unknown result type (might be due to invalid IL or missing references) //IL_0582: Unknown result type (might be due to invalid IL or missing references) //IL_0591: Unknown result type (might be due to invalid IL or missing references) //IL_05bb: Unknown result type (might be due to invalid IL or missing references) //IL_05ca: Unknown result type (might be due to invalid IL or missing references) //IL_05fb: Unknown result type (might be due to invalid IL or missing references) //IL_060a: Unknown result type (might be due to invalid IL or missing references) //IL_0631: Unknown result type (might be due to invalid IL or missing references) //IL_0640: Unknown result type (might be due to invalid IL or missing references) //IL_0667: Unknown result type (might be due to invalid IL or missing references) //IL_0676: Unknown result type (might be due to invalid IL or missing references) //IL_06a7: Unknown result type (might be due to invalid IL or missing references) //IL_06c4: Unknown result type (might be due to invalid IL or missing references) //IL_06d3: Unknown result type (might be due to invalid IL or missing references) //IL_06fd: Unknown result type (might be due to invalid IL or missing references) //IL_070c: Unknown result type (might be due to invalid IL or missing references) //IL_0739: Unknown result type (might be due to invalid IL or missing references) //IL_0748: Unknown result type (might be due to invalid IL or missing references) //IL_0775: Unknown result type (might be due to invalid IL or missing references) //IL_0784: Unknown result type (might be due to invalid IL or missing references) //IL_07b1: Unknown result type (might be due to invalid IL or missing references) //IL_07c0: Unknown result type (might be due to invalid IL or missing references) //IL_07ed: Unknown result type (might be due to invalid IL or missing references) //IL_07fc: Unknown result type (might be due to invalid IL or missing references) //IL_0829: Unknown result type (might be due to invalid IL or missing references) //IL_0838: Unknown result type (might be due to invalid IL or missing references) //IL_0876: Unknown result type (might be due to invalid IL or missing references) //IL_0885: Unknown result type (might be due to invalid IL or missing references) //IL_08b2: Unknown result type (might be due to invalid IL or missing references) //IL_08c1: Unknown result type (might be due to invalid IL or missing references) //IL_08ed: Unknown result type (might be due to invalid IL or missing references) //IL_093e: Unknown result type (might be due to invalid IL or missing references) //IL_0948: Expected O, but got Unknown //IL_0974: Unknown result type (might be due to invalid IL or missing references) //IL_099e: Unknown result type (might be due to invalid IL or missing references) //IL_09ad: Unknown result type (might be due to invalid IL or missing references) //IL_09d7: Unknown result type (might be due to invalid IL or missing references) //IL_09e6: Unknown result type (might be due to invalid IL or missing references) //IL_0a12: Unknown result type (might be due to invalid IL or missing references) //IL_0a2f: Unknown result type (might be due to invalid IL or missing references) //IL_0a3e: Unknown result type (might be due to invalid IL or missing references) //IL_0a68: Unknown result type (might be due to invalid IL or missing references) //IL_0a77: Unknown result type (might be due to invalid IL or missing references) //IL_0aa1: Unknown result type (might be due to invalid IL or missing references) //IL_0ab0: Unknown result type (might be due to invalid IL or missing references) //IL_0add: Unknown result type (might be due to invalid IL or missing references) //IL_0aec: Unknown result type (might be due to invalid IL or missing references) //IL_0b19: Unknown result type (might be due to invalid IL or missing references) //IL_0b28: Unknown result type (might be due to invalid IL or missing references) //IL_0b55: Unknown result type (might be due to invalid IL or missing references) //IL_0b64: Unknown result type (might be due to invalid IL or missing references) //IL_0b91: Unknown result type (might be due to invalid IL or missing references) //IL_0ba0: Unknown result type (might be due to invalid IL or missing references) //IL_0bcd: Unknown result type (might be due to invalid IL or missing references) //IL_0bdc: Unknown result type (might be due to invalid IL or missing references) //IL_0c1a: Unknown result type (might be due to invalid IL or missing references) //IL_0c29: Unknown result type (might be due to invalid IL or missing references) //IL_0c8a: Unknown result type (might be due to invalid IL or missing references) //IL_0c94: Expected O, but got Unknown //IL_0cc1: Unknown result type (might be due to invalid IL or missing references) //IL_0ce7: Unknown result type (might be due to invalid IL or missing references) //IL_0cf6: Unknown result type (might be due to invalid IL or missing references) //IL_0d35: Unknown result type (might be due to invalid IL or missing references) //IL_0d44: Unknown result type (might be due to invalid IL or missing references) //IL_0d6b: Unknown result type (might be due to invalid IL or missing references) //IL_0d7a: Unknown result type (might be due to invalid IL or missing references) //IL_0da3: Unknown result type (might be due to invalid IL or missing references) //IL_0db2: Unknown result type (might be due to invalid IL or missing references) //IL_0dd8: Unknown result type (might be due to invalid IL or missing references) //IL_0de7: Unknown result type (might be due to invalid IL or missing references) //IL_0e0d: Unknown result type (might be due to invalid IL or missing references) //IL_0e17: Expected O, but got Unknown //IL_0e29: Unknown result type (might be due to invalid IL or missing references) //IL_0e33: Expected O, but got Unknown //IL_0e74: Unknown result type (might be due to invalid IL or missing references) //IL_0e7e: Expected O, but got Unknown //IL_0e52: Unknown result type (might be due to invalid IL or missing references) //IL_0e57: Unknown result type (might be due to invalid IL or missing references) //IL_0e5d: Expected O, but got Unknown //IL_0e9d: Unknown result type (might be due to invalid IL or missing references) //IL_0ea2: Unknown result type (might be due to invalid IL or missing references) //IL_0ea8: Expected O, but got Unknown //IL_0ecc: Unknown result type (might be due to invalid IL or missing references) //IL_0ed1: Unknown result type (might be due to invalid IL or missing references) //IL_0ed7: Expected O, but got Unknown //IL_0efb: Unknown result type (might be due to invalid IL or missing references) //IL_0f00: Unknown result type (might be due to invalid IL or missing references) //IL_0f06: Expected O, but got Unknown //IL_0f2a: Unknown result type (might be due to invalid IL or missing references) //IL_0f2f: Unknown result type (might be due to invalid IL or missing references) //IL_0f35: Expected O, but got Unknown //IL_0f59: Unknown result type (might be due to invalid IL or missing references) //IL_0f5e: Unknown result type (might be due to invalid IL or missing references) //IL_0f64: Expected O, but got Unknown //IL_0f88: Unknown result type (might be due to invalid IL or missing references) //IL_0f8d: Unknown result type (might be due to invalid IL or missing references) //IL_0f93: Expected O, but got Unknown //IL_0fb7: Unknown result type (might be due to invalid IL or missing references) //IL_0fbc: Unknown result type (might be due to invalid IL or missing references) //IL_0fc2: Expected O, but got Unknown //IL_0fe6: Unknown result type (might be due to invalid IL or missing references) //IL_0feb: Unknown result type (might be due to invalid IL or missing references) //IL_0ff1: Expected O, but got Unknown //IL_1015: Unknown result type (might be due to invalid IL or missing references) //IL_101a: Unknown result type (might be due to invalid IL or missing references) //IL_1020: Expected O, but got Unknown //IL_1044: Unknown result type (might be due to invalid IL or missing references) //IL_1049: Unknown result type (might be due to invalid IL or missing references) //IL_104f: Expected O, but got Unknown //IL_1073: Unknown result type (might be due to invalid IL or missing references) //IL_1078: Unknown result type (might be due to invalid IL or missing references) //IL_107e: Expected O, but got Unknown //IL_10a2: Unknown result type (might be due to invalid IL or missing references) //IL_10a7: Unknown result type (might be due to invalid IL or missing references) //IL_10ad: Expected O, but got Unknown //IL_10d1: Unknown result type (might be due to invalid IL or missing references) //IL_10d6: Unknown result type (might be due to invalid IL or missing references) //IL_10dc: Expected O, but got Unknown //IL_1100: Unknown result type (might be due to invalid IL or missing references) //IL_1105: Unknown result type (might be due to invalid IL or missing references) //IL_110b: Expected O, but got Unknown //IL_112f: Unknown result type (might be due to invalid IL or missing references) //IL_1134: Unknown result type (might be due to invalid IL or missing references) //IL_113a: Expected O, but got Unknown //IL_115e: Unknown result type (might be due to invalid IL or missing references) //IL_1163: Unknown result type (might be due to invalid IL or missing references) //IL_1169: Expected O, but got Unknown //IL_118d: Unknown result type (might be due to invalid IL or missing references) //IL_1192: Unknown result type (might be due to invalid IL or missing references) //IL_1198: Expected O, but got Unknown //IL_11f0: Unknown result type (might be due to invalid IL or missing references) //IL_1226: Unknown result type (might be due to invalid IL or missing references) //IL_1240: Unknown result type (might be due to invalid IL or missing references) //IL_11bc: Unknown result type (might be due to invalid IL or missing references) //IL_11c1: Unknown result type (might be due to invalid IL or missing references) //IL_11c7: Expected O, but got Unknown _canvasRoot = new GameObject("KayceePvP_LobbyCanvas"); _canvasRoot.transform.SetParent(((Component)this).transform, false); Canvas obj = _canvasRoot.AddComponent(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 32000; CanvasScaler obj2 = _canvasRoot.AddComponent(); obj2.uiScaleMode = (ScaleMode)1; obj2.referenceResolution = new Vector2(1920f, 1080f); _canvasRoot.AddComponent(); RectTransform component = CreateImage("Backdrop", _canvasRoot.transform, BackdropColor).GetComponent(); component.anchorMin = Vector2.zero; component.anchorMax = Vector2.one; component.offsetMin = Vector2.zero; component.offsetMax = Vector2.zero; GameObject obj3 = CreateImage("RitualArtwork", _canvasRoot.transform, Color.white); RectTransform component2 = obj3.GetComponent(); component2.anchorMin = Vector2.zero; component2.anchorMax = Vector2.one; component2.offsetMin = Vector2.zero; component2.offsetMax = Vector2.zero; Image component3 = obj3.GetComponent(); component3.sprite = LoadEmbeddedBackground(); component3.preserveAspect = false; GameObject val = CreateImage("WoodFrame", _canvasRoot.transform, FrameColor); RectTransform component4 = val.GetComponent(); component4.anchorMin = new Vector2(0.5f, 0.5f); component4.anchorMax = new Vector2(0.5f, 0.5f); component4.pivot = new Vector2(0.5f, 0.5f); component4.sizeDelta = new Vector2(820f, 900f); component4.anchoredPosition = Vector2.zero; GameObject val2 = CreateImage("BrassInset", val.transform, BrassColor); StretchToParent(val2.GetComponent(), 7f); GameObject val3 = CreateImage("Panel", val2.transform, PanelColor); StretchToParent(val3.GetComponent(), 4f); GameObject val4 = new GameObject("LobbyContent", new Type[1] { typeof(RectTransform) }); val4.transform.SetParent(val3.transform, false); RectTransform component5 = val4.GetComponent(); component5.anchorMin = new Vector2(0.5f, 0.5f); component5.anchorMax = new Vector2(0.5f, 0.5f); component5.pivot = new Vector2(0.5f, 0.5f); component5.sizeDelta = new Vector2(492f, 552f); ((Transform)component5).localScale = new Vector3(1.42f, 1.42f, 1f); Transform transform = val4.transform; ((Graphic)CreateText("Title", transform, new Vector2(0f, 220f), new Vector2(420f, 30f), "KAYCEE · PVP", 24, (TextAnchor)4)).color = PaperColor; _hostPeerTagText = CreateText("HostPeerTag", transform, new Vector2(0f, 190f), new Vector2(420f, 24f), "", 18, (TextAnchor)4); _nameLabelText = CreateText("NameLabel", transform, new Vector2(-150f, 160f), new Vector2(80f, 24f), "Nome:", 14, (TextAnchor)5); _nameField = CreateInputField("NameField", transform, new Vector2(50f, 160f), new Vector2(220f, 30f), "", (ContentType)0); _nameField.characterLimit = 16; _nameField.lineType = (LineType)0; _preConnectGroup = new GameObject("PreConnectGroup"); _preConnectGroup.transform.SetParent(transform, false); _preConnectGroup.transform.localPosition = new Vector3(0f, -20f, 0f); Transform transform2 = _preConnectGroup.transform; _createOnlineButton = CreateButton("CreateOnlineButton", transform2, new Vector2(0f, 120f), new Vector2(400f, 34f), "CRIAR SALA ONLINE"); _inviteLabelText = CreateText("InviteLabel", transform2, new Vector2(-165f, 82f), new Vector2(90f, 20f), "Convite:", 13, (TextAnchor)5); _onlineInviteField = CreateInputField("OnlineInviteField", transform2, new Vector2(-20f, 80f), new Vector2(200f, 30f), "", (ContentType)0); _onlineInviteField.lineType = (LineType)0; _joinOnlineButton = CreateButton("JoinOnlineButton", transform2, new Vector2(145f, 80f), new Vector2(120f, 32f), "ENTRAR ONLINE"); _hostButton = CreateButton("HostButton", transform2, new Vector2(-110f, 30f), new Vector2(180f, 32f), "Hospedar (LAN)"); _ipLabelText = CreateText("IpLabel", transform2, new Vector2(50f, 48f), new Vector2(40f, 20f), "IP:", 13, (TextAnchor)3); _ipField = CreateInputField("IpField", transform2, new Vector2(120f, 30f), new Vector2(160f, 28f), "127.0.0.1", (ContentType)0); _portLabelText = CreateText("PortLabel", transform2, new Vector2(35f, 5f), new Vector2(55f, 20f), "Porta:", 13, (TextAnchor)3); _portField = CreateInputField("PortField", transform2, new Vector2(90f, -10f), new Vector2(90f, 28f), 40125.ToString(), (ContentType)2); _joinButton = CreateButton("JoinButton", transform2, new Vector2(165f, -10f), new Vector2(80f, 30f), "Entrar LAN"); _soloButton = CreateButton("SoloButton", transform2, new Vector2(0f, -50f), new Vector2(400f, 28f), "Testar sozinho (pula o lobby)"); _onlineBetaNoticeText = CreateText("OnlineBetaNotice", transform2, new Vector2(0f, 152f), new Vector2(440f, 40f), "", 11, (TextAnchor)4); ((Graphic)_onlineBetaNoticeText).color = new Color(0.8f, 0.66f, 0.32f, 1f); _mapModeLabelPre = CreateText("MapModeLabelPre", transform2, new Vector2(0f, -82f), new Vector2(420f, 20f), "", 13, (TextAnchor)4); _battlesButtonPre = CreateButton("BattlesButtonPre", transform2, new Vector2(-174f, -112f), new Vector2(110f, 28f), MapModeCatalog.Get(MapMode.Battles).UiLabel); _noBattlesButtonPre = CreateButton("NoBattlesButtonPre", transform2, new Vector2(-58f, -112f), new Vector2(110f, 28f), MapModeCatalog.Get(MapMode.NoBattles).UiLabel); _symmetricForgeButtonPre = CreateButton("SymmetricForgeButtonPre", transform2, new Vector2(58f, -112f), new Vector2(110f, 28f), MapModeCatalog.Get(MapMode.SymmetricForge).UiLabel); _peltDraftButtonPre = CreateButton("PeltDraftButtonPre", transform2, new Vector2(174f, -112f), new Vector2(110f, 28f), MapModeCatalog.Get(MapMode.PeltDraft).UiLabel); _balancedPathButtonPre = CreateButton("BalancedPathButtonPre", transform2, new Vector2(-58f, -144f), new Vector2(110f, 28f), MapModeCatalog.Get(MapMode.BalancedPath).UiLabel); _openDraftPathButtonPre = CreateButton("OpenDraftPathButtonPre", transform2, new Vector2(58f, -144f), new Vector2(110f, 28f), MapModeCatalog.Get(MapMode.OpenDraftPath).UiLabel); ((Component)_openDraftPathButtonPre).gameObject.SetActive(false); _balancedRouteButtonPre = CreateButton("BalancedRouteButtonPre", transform2, new Vector2(174f, -144f), new Vector2(110f, 28f), MapModeCatalog.Get(MapMode.BalancedRoute).UiLabel); _preConnectErrorText = CreateText("PreConnectErrorText", transform2, new Vector2(0f, -176f), new Vector2(420f, 30f), "", 12, (TextAnchor)4); ((Graphic)_preConnectErrorText).color = new Color(1f, 0.4f, 0.4f); ShrinkButtonLabelFont(_symmetricForgeButtonPre, 11); ShrinkButtonLabelFont(_peltDraftButtonPre, 10); ShrinkButtonLabelFont(_balancedPathButtonPre, 11); ShrinkButtonLabelFont(_openDraftPathButtonPre, 10); ShrinkButtonLabelFont(_balancedRouteButtonPre, 9); _connectedGroup = new GameObject("ConnectedGroup"); _connectedGroup.transform.SetParent(transform, false); _connectedGroup.transform.localPosition = new Vector3(0f, -20f, 0f); Transform transform3 = _connectedGroup.transform; _statusText = CreateText("StatusText", transform3, new Vector2(0f, 120f), new Vector2(420f, 40f), "", 16, (TextAnchor)4); _errorText = CreateText("ErrorText", transform3, new Vector2(0f, 80f), new Vector2(420f, 40f), "", 14, (TextAnchor)4); ((Graphic)_errorText).color = new Color(1f, 0.4f, 0.4f); _starterDeckText = CreateText("StarterDeckText", transform3, new Vector2(0f, 40f), new Vector2(420f, 24f), "", 14, (TextAnchor)4); _mapModeLabel = CreateText("MapModeLabel", transform3, new Vector2(0f, 12f), new Vector2(420f, 20f), "", 13, (TextAnchor)4); _battlesButton = CreateButton("BattlesButton", transform3, new Vector2(-174f, -16f), new Vector2(110f, 28f), MapModeCatalog.Get(MapMode.Battles).UiLabel); _noBattlesButton = CreateButton("NoBattlesButton", transform3, new Vector2(-58f, -16f), new Vector2(110f, 28f), MapModeCatalog.Get(MapMode.NoBattles).UiLabel); _symmetricForgeButton = CreateButton("SymmetricForgeButton", transform3, new Vector2(58f, -16f), new Vector2(110f, 28f), MapModeCatalog.Get(MapMode.SymmetricForge).UiLabel); _peltDraftButton = CreateButton("PeltDraftButton", transform3, new Vector2(174f, -16f), new Vector2(110f, 28f), MapModeCatalog.Get(MapMode.PeltDraft).UiLabel); _balancedPathButton = CreateButton("BalancedPathButton", transform3, new Vector2(-58f, -48f), new Vector2(110f, 28f), MapModeCatalog.Get(MapMode.BalancedPath).UiLabel); _openDraftPathButton = CreateButton("OpenDraftPathButton", transform3, new Vector2(58f, -48f), new Vector2(110f, 28f), MapModeCatalog.Get(MapMode.OpenDraftPath).UiLabel); ((Component)_openDraftPathButton).gameObject.SetActive(false); _balancedRouteButton = CreateButton("BalancedRouteButton", transform3, new Vector2(174f, -48f), new Vector2(110f, 28f), MapModeCatalog.Get(MapMode.BalancedRoute).UiLabel); ShrinkButtonLabelFont(_symmetricForgeButton, 11); ShrinkButtonLabelFont(_peltDraftButton, 10); ShrinkButtonLabelFont(_balancedPathButton, 11); ShrinkButtonLabelFont(_openDraftPathButton, 10); ShrinkButtonLabelFont(_balancedRouteButton, 9); _readyGroup = new GameObject("ReadyGroup"); _readyGroup.transform.SetParent(transform3, false); _readyGroup.transform.localPosition = new Vector3(0f, -82f, 0f); _readyStatusText = CreateText("ReadyStatusText", _readyGroup.transform, new Vector2(0f, 0f), new Vector2(420f, 40f), "", 13, (TextAnchor)4); _readyStatusText.verticalOverflow = (VerticalWrapMode)1; _readyButton = CreateButton("ReadyButton", _readyGroup.transform, new Vector2(0f, -38f), new Vector2(160f, 34f), "Pronto"); _seedMatchText = CreateText("SeedMatchText", transform3, new Vector2(0f, -150f), new Vector2(420f, 24f), "", 14, (TextAnchor)4); _cancelButton = CreateButton("CancelButton", transform, new Vector2(-108f, -215f), new Vector2(200f, 34f), "Cancelar tentativa"); _closeButton = CreateButton("CloseButton", transform, new Vector2(108f, -215f), new Vector2(200f, 34f), "Fechar lobby [ESC]"); ((UnityEvent)_hostButton.onClick).AddListener((UnityAction)delegate { LobbySession.Instance.Host(ParsePortField(_portField.text)); }); ((UnityEvent)_joinButton.onClick).AddListener((UnityAction)delegate { LobbySession.Instance.Join(_ipField.text, ParsePortField(_portField.text)); }); ButtonClickedEvent onClick = _createOnlineButton.onClick; object obj4 = <>c.<>9__74_2; if (obj4 == null) { UnityAction val5 = delegate { LobbySession.Instance.CreateOnlineRoom(); }; <>c.<>9__74_2 = val5; obj4 = (object)val5; } ((UnityEvent)onClick).AddListener((UnityAction)obj4); ((UnityEvent)_joinOnlineButton.onClick).AddListener((UnityAction)delegate { LobbySession.Instance.JoinOnlineRoom(_onlineInviteField.text); }); ButtonClickedEvent onClick2 = _soloButton.onClick; object obj5 = <>c.<>9__74_4; if (obj5 == null) { UnityAction val6 = delegate { LobbySession.Instance.StartSoloDebugBypass(); }; <>c.<>9__74_4 = val6; obj5 = (object)val6; } ((UnityEvent)onClick2).AddListener((UnityAction)obj5); ButtonClickedEvent onClick3 = _readyButton.onClick; object obj6 = <>c.<>9__74_5; if (obj6 == null) { UnityAction val7 = delegate { LobbySession.Instance.SetLocalReady(ready: true); }; <>c.<>9__74_5 = val7; obj6 = (object)val7; } ((UnityEvent)onClick3).AddListener((UnityAction)obj6); ButtonClickedEvent onClick4 = _battlesButton.onClick; object obj7 = <>c.<>9__74_6; if (obj7 == null) { UnityAction val8 = delegate { LobbySession.Instance.SetMapMode(MapMode.Battles); }; <>c.<>9__74_6 = val8; obj7 = (object)val8; } ((UnityEvent)onClick4).AddListener((UnityAction)obj7); ButtonClickedEvent onClick5 = _noBattlesButton.onClick; object obj8 = <>c.<>9__74_7; if (obj8 == null) { UnityAction val9 = delegate { LobbySession.Instance.SetMapMode(MapMode.NoBattles); }; <>c.<>9__74_7 = val9; obj8 = (object)val9; } ((UnityEvent)onClick5).AddListener((UnityAction)obj8); ButtonClickedEvent onClick6 = _symmetricForgeButton.onClick; object obj9 = <>c.<>9__74_8; if (obj9 == null) { UnityAction val10 = delegate { LobbySession.Instance.SetMapMode(MapMode.SymmetricForge); }; <>c.<>9__74_8 = val10; obj9 = (object)val10; } ((UnityEvent)onClick6).AddListener((UnityAction)obj9); ButtonClickedEvent onClick7 = _peltDraftButton.onClick; object obj10 = <>c.<>9__74_9; if (obj10 == null) { UnityAction val11 = delegate { LobbySession.Instance.SetMapMode(MapMode.PeltDraft); }; <>c.<>9__74_9 = val11; obj10 = (object)val11; } ((UnityEvent)onClick7).AddListener((UnityAction)obj10); ButtonClickedEvent onClick8 = _balancedPathButton.onClick; object obj11 = <>c.<>9__74_10; if (obj11 == null) { UnityAction val12 = delegate { LobbySession.Instance.SetMapMode(MapMode.BalancedPath); }; <>c.<>9__74_10 = val12; obj11 = (object)val12; } ((UnityEvent)onClick8).AddListener((UnityAction)obj11); ButtonClickedEvent onClick9 = _openDraftPathButton.onClick; object obj12 = <>c.<>9__74_11; if (obj12 == null) { UnityAction val13 = delegate { LobbySession.Instance.SetMapMode(MapMode.OpenDraftPath); }; <>c.<>9__74_11 = val13; obj12 = (object)val13; } ((UnityEvent)onClick9).AddListener((UnityAction)obj12); ButtonClickedEvent onClick10 = _balancedRouteButton.onClick; object obj13 = <>c.<>9__74_12; if (obj13 == null) { UnityAction val14 = delegate { LobbySession.Instance.SetMapMode(MapMode.BalancedRoute); }; <>c.<>9__74_12 = val14; obj13 = (object)val14; } ((UnityEvent)onClick10).AddListener((UnityAction)obj13); ButtonClickedEvent onClick11 = _battlesButtonPre.onClick; object obj14 = <>c.<>9__74_13; if (obj14 == null) { UnityAction val15 = delegate { LobbySession.Instance.SetMapMode(MapMode.Battles); }; <>c.<>9__74_13 = val15; obj14 = (object)val15; } ((UnityEvent)onClick11).AddListener((UnityAction)obj14); ButtonClickedEvent onClick12 = _noBattlesButtonPre.onClick; object obj15 = <>c.<>9__74_14; if (obj15 == null) { UnityAction val16 = delegate { LobbySession.Instance.SetMapMode(MapMode.NoBattles); }; <>c.<>9__74_14 = val16; obj15 = (object)val16; } ((UnityEvent)onClick12).AddListener((UnityAction)obj15); ButtonClickedEvent onClick13 = _symmetricForgeButtonPre.onClick; object obj16 = <>c.<>9__74_15; if (obj16 == null) { UnityAction val17 = delegate { LobbySession.Instance.SetMapMode(MapMode.SymmetricForge); }; <>c.<>9__74_15 = val17; obj16 = (object)val17; } ((UnityEvent)onClick13).AddListener((UnityAction)obj16); ButtonClickedEvent onClick14 = _peltDraftButtonPre.onClick; object obj17 = <>c.<>9__74_16; if (obj17 == null) { UnityAction val18 = delegate { LobbySession.Instance.SetMapMode(MapMode.PeltDraft); }; <>c.<>9__74_16 = val18; obj17 = (object)val18; } ((UnityEvent)onClick14).AddListener((UnityAction)obj17); ButtonClickedEvent onClick15 = _balancedPathButtonPre.onClick; object obj18 = <>c.<>9__74_17; if (obj18 == null) { UnityAction val19 = delegate { LobbySession.Instance.SetMapMode(MapMode.BalancedPath); }; <>c.<>9__74_17 = val19; obj18 = (object)val19; } ((UnityEvent)onClick15).AddListener((UnityAction)obj18); ButtonClickedEvent onClick16 = _openDraftPathButtonPre.onClick; object obj19 = <>c.<>9__74_18; if (obj19 == null) { UnityAction val20 = delegate { LobbySession.Instance.SetMapMode(MapMode.OpenDraftPath); }; <>c.<>9__74_18 = val20; obj19 = (object)val20; } ((UnityEvent)onClick16).AddListener((UnityAction)obj19); ButtonClickedEvent onClick17 = _balancedRouteButtonPre.onClick; object obj20 = <>c.<>9__74_19; if (obj20 == null) { UnityAction val21 = delegate { LobbySession.Instance.SetMapMode(MapMode.BalancedRoute); }; <>c.<>9__74_19 = val21; obj20 = (object)val21; } ((UnityEvent)onClick17).AddListener((UnityAction)obj20); ButtonClickedEvent onClick18 = _cancelButton.onClick; object obj21 = <>c.<>9__74_20; if (obj21 == null) { UnityAction val22 = delegate { LobbySession.Instance.CancelConnection(); }; <>c.<>9__74_20 = val22; obj21 = (object)val22; } ((UnityEvent)onClick18).AddListener((UnityAction)obj21); ButtonClickedEvent onClick19 = _closeButton.onClick; object obj22 = <>c.<>9__74_21; if (obj22 == null) { UnityAction val23 = delegate { LobbySession.Instance.ExitLobby(); }; <>c.<>9__74_21 = val23; obj22 = (object)val23; } ((UnityEvent)onClick19).AddListener((UnityAction)obj22); GameObject val24 = CreateImage("CursorPointer", _canvasRoot.transform, new Color(1f, 1f, 0f, 0.9f)); ((Graphic)val24.GetComponent()).raycastTarget = false; _cursorRect = val24.GetComponent(); _cursorRect.sizeDelta = new Vector2(16f, 16f); _cursorRect.pivot = new Vector2(0.15f, 0.85f); } private static GameObject CreateImage(string name, Transform parent, Color color) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) }); val.transform.SetParent(parent, false); Image obj = val.AddComponent(); ((Graphic)obj).color = color; ((Graphic)obj).raycastTarget = true; return val; } private static Sprite LoadEmbeddedBackground() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("KayceePvP.Assets.lobby_ritual_background.png"); if (stream == null) { Plugin.Log.LogError((object)"Lobby: embedded background not found: KayceePvP.Assets.lobby_ritual_background.png"); return null; } byte[] array = new byte[stream.Length]; int num; for (int i = 0; i < array.Length; i += num) { num = stream.Read(array, i, array.Length - i); if (num <= 0) { break; } } Texture2D val = new Texture2D(2, 2, (TextureFormat)3, false); if (!ImageConversion.LoadImage(val, array, true)) { Plugin.Log.LogError((object)"Lobby: failed to decode embedded ritual background PNG."); Object.Destroy((Object)(object)val); return null; } ((Object)val).name = "KayceePvP_LobbyRitualBackground"; ((Texture)val).wrapMode = (TextureWrapMode)1; return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f); } private static void StretchToParent(RectTransform rect, float inset) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = new Vector2(inset, inset); rect.offsetMax = new Vector2(0f - inset, 0f - inset); } private Text CreateText(string name, Transform parent, Vector2 anchoredPos, Vector2 size, string content, int fontSize, TextAnchor alignment) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) }); val.transform.SetParent(parent, false); RectTransform component = val.GetComponent(); component.anchorMin = new Vector2(0.5f, 0.5f); component.anchorMax = new Vector2(0.5f, 0.5f); component.pivot = new Vector2(0.5f, 0.5f); component.sizeDelta = size; component.anchoredPosition = anchoredPos; Text obj = val.AddComponent(); obj.text = content; obj.font = ResolveGameFont(); obj.fontSize = fontSize; obj.alignment = alignment; ((Graphic)obj).color = Color.white; obj.horizontalOverflow = (HorizontalWrapMode)0; return obj; } private Button CreateButton(string name, Transform parent, Vector2 anchoredPos, Vector2 size, string label) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_0038: 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_0062: 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_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: 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_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) }); val.transform.SetParent(parent, false); RectTransform component = val.GetComponent(); component.anchorMin = new Vector2(0.5f, 0.5f); component.anchorMax = new Vector2(0.5f, 0.5f); component.pivot = new Vector2(0.5f, 0.5f); component.sizeDelta = size; component.anchoredPosition = anchoredPos; ((Graphic)val.AddComponent()).color = ButtonColor; Button obj = val.AddComponent