using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using ModdingUtils.Utils; using Photon.Pun; using Photon.Realtime; using UnityEngine; using UnityEngine.UI; using UpgradeDraft.Models; using UpgradeDraft.Services; using UpgradeDraft.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 UpgradeDraft { [BepInPlugin("com.damian.rounds.upgradedraft", "Upgrade Draft", "1.0.13")] [BepInProcess("Rounds.exe")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class UpgradeDraftPlugin : BaseUnityPlugin { public const string PluginGuid = "com.damian.rounds.upgradedraft"; public const string PluginName = "Upgrade Draft"; public const string PluginVersion = "1.0.13"; private static readonly int[] DefaultChances = new int[4] { 25, 50, 20, 5 }; private Harmony _harmony; private int _chanceZero; private int _chanceOne; private int _chanceTwo; private int _chanceThree; internal static UpgradeDraftPlugin Instance { get; private set; } internal static ManualLogSource Log => ((BaseUnityPlugin)Instance).Logger; internal static PlayerDeckService DeckService { get; private set; } internal static DraftRollService RollService { get; private set; } internal static CardSelectionService SelectionService { get; private set; } internal static UpgradePreviewService PreviewService { get; private set; } internal static UpgradeCardVisualService VisualService { get; private set; } internal static DraftCardNetworkService NetworkService { get; private set; } internal static UpgradeDraftState State { get; private set; } internal static SkipRerollService SkipRerollService { get; private set; } internal ConfigEntry NormalCardCount { get; private set; } internal ConfigEntry ChanceZeroUpgrades { get; private set; } internal ConfigEntry ChanceOneUpgrade { get; private set; } internal ConfigEntry ChanceTwoUpgrades { get; private set; } internal ConfigEntry ChanceThreeUpgrades { get; private set; } internal ConfigEntry EnableUpgradeVisuals { get; private set; } internal ConfigEntry EnableStatPreview { get; private set; } internal ConfigEntry UpgradeBlacklist { get; private set; } internal ConfigEntry VerboseLogging { get; private set; } internal ConfigEntry EnableSkipForPoint { get; private set; } internal ConfigEntry SkipPointsForReroll { get; private set; } internal ConfigEntry EnableSkipRerollOverlay { get; private set; } internal ConfigEntry AllowMultipleRerollsPerPick { get; private set; } private void Awake() { //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Expected O, but got Unknown Instance = this; BindConfig(); ValidateConfig(); State = new UpgradeDraftState(((BaseUnityPlugin)this).Logger); DeckService = new PlayerDeckService(((BaseUnityPlugin)this).Logger, this); RollService = new DraftRollService(((BaseUnityPlugin)this).Logger, this); SelectionService = new CardSelectionService(((BaseUnityPlugin)this).Logger, this, DeckService); PreviewService = new UpgradePreviewService(((BaseUnityPlugin)this).Logger, this); VisualService = new UpgradeCardVisualService(((BaseUnityPlugin)this).Logger, this, PreviewService); NetworkService = new DraftCardNetworkService(((BaseUnityPlugin)this).Logger, this, VisualService); SkipRerollService = new SkipRerollService(((BaseUnityPlugin)this).Logger, this); if ((Object)(object)((Component)this).GetComponent() == (Object)null) { ((Component)this).gameObject.AddComponent(); } _harmony = new Harmony("com.damian.rounds.upgradedraft"); _harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Upgrade Draft 1.0.13 loaded."); } private void OnDestroy() { NetworkService?.Dispose(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } internal int GetCardCount() { int num = Math.Max(1, NormalCardCount.Value); if (num != 4) { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"NormalCardCount is set to {num}. Values other than 4 are experimental."); } return num; } internal (int Zero, int One, int Two, int Three) GetWeightedChances() { return (_chanceZero, _chanceOne, _chanceTwo, _chanceThree); } internal bool IsVisualEnabled() { return EnableUpgradeVisuals.Value; } internal bool IsStatPreviewEnabled() { return EnableStatPreview.Value; } internal bool IsVerboseLoggingEnabled() { return VerboseLogging.Value; } internal bool IsSkipForPointEnabled() { return EnableSkipForPoint.Value; } internal bool IsSkipRerollOverlayEnabled() { return EnableSkipRerollOverlay.Value; } internal int GetSkipPointsForReroll() { return Math.Max(1, SkipPointsForReroll.Value); } internal bool IsMultipleRerollsPerPickAllowed() { return AllowMultipleRerollsPerPick.Value; } internal HashSet GetBlacklist() { return new HashSet(from v in (UpgradeBlacklist.Value ?? string.Empty).Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries) select v.Trim().ToLowerInvariant() into v where !string.IsNullOrWhiteSpace(v) select v); } private void BindConfig() { NormalCardCount = ((BaseUnityPlugin)this).Config.Bind("General", "NormalCardCount", 4, "Number of visible card choices per draft pick. Values other than 4 are experimental."); ChanceZeroUpgrades = ((BaseUnityPlugin)this).Config.Bind("Chances", "ChanceZeroUpgrades", 25, "Chance weight for showing 0 upgrade cards."); ChanceOneUpgrade = ((BaseUnityPlugin)this).Config.Bind("Chances", "ChanceOneUpgrade", 50, "Chance weight for showing 1 upgrade card."); ChanceTwoUpgrades = ((BaseUnityPlugin)this).Config.Bind("Chances", "ChanceTwoUpgrades", 20, "Chance weight for showing 2 upgrade cards."); ChanceThreeUpgrades = ((BaseUnityPlugin)this).Config.Bind("Chances", "ChanceThreeUpgrades", 5, "Chance weight for showing 3 upgrade cards."); EnableUpgradeVisuals = ((BaseUnityPlugin)this).Config.Bind("UI", "EnableUpgradeVisuals", true, "Adds UPGRADE visuals and ownership info to upgrade cards."); EnableStatPreview = ((BaseUnityPlugin)this).Config.Bind("UI", "EnableStatPreview", true, "Appends conservative stacked stat preview lines for upgrade cards."); EnableSkipRerollOverlay = ((BaseUnityPlugin)this).Config.Bind("UI", "EnableSkipRerollOverlay", true, "Show the Upgrade Draft status panel during card pick."); UpgradeBlacklist = ((BaseUnityPlugin)this).Config.Bind("Rules", "UpgradeBlacklist", string.Empty, "Comma-separated card names that are not allowed as upgrades."); EnableSkipForPoint = ((BaseUnityPlugin)this).Config.Bind("Rules", "EnableSkipForPoint", true, "Enable the 5th SKIP action card. If disabled, the action card is still shown as a no-stats pass card for compatibility."); SkipPointsForReroll = ((BaseUnityPlugin)this).Config.Bind("Rules", "SkipPointsForReroll", 2, "Skip points required for the 5th action card to become REROLL."); AllowMultipleRerollsPerPick = ((BaseUnityPlugin)this).Config.Bind("Rules", "AllowMultipleRerollsPerPick", false, "Allow repeatedly picking REROLL during the same card choice."); VerboseLogging = ((BaseUnityPlugin)this).Config.Bind("Debug", "VerboseLogging", false, "Enable extra debug logging."); } private void ValidateConfig() { NormalCardCount.Value = Math.Max(1, NormalCardCount.Value); SkipPointsForReroll.Value = Math.Max(1, SkipPointsForReroll.Value); int num = Math.Max(0, ChanceZeroUpgrades.Value); int num2 = Math.Max(0, ChanceOneUpgrade.Value); int num3 = Math.Max(0, ChanceTwoUpgrades.Value); int num4 = Math.Max(0, ChanceThreeUpgrades.Value); int num5 = num + num2 + num3 + num4; if (num5 <= 0) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Upgrade chance weights are invalid (sum <= 0). Falling back to defaults: 25/50/20/5."); _chanceZero = DefaultChances[0]; _chanceOne = DefaultChances[1]; _chanceTwo = DefaultChances[2]; _chanceThree = DefaultChances[3]; return; } if (num5 == 100) { _chanceZero = num; _chanceOne = num2; _chanceTwo = num3; _chanceThree = num4; return; } double num6 = 100.0 / (double)num5; _chanceZero = (int)Math.Round((double)num * num6); _chanceOne = (int)Math.Round((double)num2 * num6); _chanceTwo = (int)Math.Round((double)num3 * num6); _chanceThree = (int)Math.Round((double)num4 * num6); int num7 = _chanceZero + _chanceOne + _chanceTwo + _chanceThree; int num8 = 100 - num7; _chanceOne += num8; ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Upgrade chance weights sum to {num5} instead of 100. Normalized to {_chanceZero}/{_chanceOne}/{_chanceTwo}/{_chanceThree}."); } } } namespace UpgradeDraft.UI { public sealed class DraftActionCardMarker : MonoBehaviour { public DraftActionType ActionType; public int PickerId; } public sealed class SkipRerollOverlay : MonoBehaviour { private GUIStyle _badgeStyle; private GUIStyle _actionStyle; private GUIStyle _labelStyle; private GUIStyle _titleStyle; private void Awake() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005a: 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_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008e: 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_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Expected O, but got Unknown //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Expected O, but got Unknown GUIStyle val = new GUIStyle(GUI.skin.box) { fontSize = 22, fontStyle = (FontStyle)1, alignment = (TextAnchor)4 }; val.normal.textColor = Color.cyan; _badgeStyle = val; GUIStyle val2 = new GUIStyle(GUI.skin.box) { fontSize = 18, fontStyle = (FontStyle)1, alignment = (TextAnchor)4 }; val2.normal.textColor = Color.white; _actionStyle = val2; GUIStyle val3 = new GUIStyle(GUI.skin.label) { fontSize = 15, alignment = (TextAnchor)3 }; val3.normal.textColor = Color.white; _labelStyle = val3; GUIStyle val4 = new GUIStyle(GUI.skin.label) { fontSize = 18, fontStyle = (FontStyle)1, alignment = (TextAnchor)3 }; val4.normal.textColor = Color.yellow; _titleStyle = val4; } private void Update() { if (IsReady()) { CardChoice instance = CardChoice.instance; _ = instance.pickrID; if (UpgradeDraftPlugin.SkipRerollService.IsCardChoiceOpen(instance) && !UpgradeDraftPlugin.SkipRerollService.IsCardChoiceBusy(instance)) { UpgradeDraftPlugin.SkipRerollService.IsLocalClientControllingPick(instance); } } } private void OnGUI() { //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) if (!IsReady()) { return; } CardChoice instance = CardChoice.instance; if (instance == null) { return; } int pickrID = instance.pickrID; if (UpgradeDraftPlugin.SkipRerollService.IsCardChoiceOpen(instance)) { int skipPoints = UpgradeDraftPlugin.State.GetSkipPoints(pickrID); bool num = UpgradeDraftPlugin.SkipRerollService.IsLocalClientControllingPick(instance); int skipPointsForReroll = UpgradeDraftPlugin.Instance.GetSkipPointsForReroll(); string text = ((skipPoints >= skipPointsForReroll) ? "REROLL" : "SKIP"); string text2 = ((skipPoints >= skipPointsForReroll) ? "Pick the 5th card to redraw this choice." : "Pick the 5th card to pass and gain a point."); float num2 = 390f; float num3 = 150f; float num4 = 24f; float num5 = Mathf.Max(24f, (float)Screen.height * 0.18f); GUI.Box(new Rect(num4, num5, num2, num3), string.Empty); GUI.Box(new Rect(num4 + 12f, num5 + 12f, 54f, 54f), "UD", _badgeStyle); GUI.Label(new Rect(num4 + 78f, num5 + 10f, num2 - 90f, 28f), "Upgrade Draft", _titleStyle); GUI.Label(new Rect(num4 + 78f, num5 + 38f, num2 - 90f, 22f), $"Skip Points: {skipPoints}/{skipPointsForReroll}", _labelStyle); GUI.Label(new Rect(num4 + 78f, num5 + 60f, num2 - 90f, 22f), "4 draft cards + 1 action card", _labelStyle); GUI.Box(new Rect(num4 + 16f, num5 + 88f, 110f, 34f), text, _actionStyle); if (!num) { GUI.Label(new Rect(num4 + 138f, num5 + 94f, num2 - 154f, 22f), "Waiting for the picking player.", _labelStyle); } else { GUI.Label(new Rect(num4 + 138f, num5 + 94f, num2 - 154f, 22f), text2, _labelStyle); } } } private static bool IsReady() { if ((Object)(object)UpgradeDraftPlugin.Instance == (Object)null || UpgradeDraftPlugin.State == null || UpgradeDraftPlugin.SkipRerollService == null) { return false; } if (!UpgradeDraftPlugin.Instance.IsSkipRerollOverlayEnabled()) { return false; } return CardChoice.instance != null; } } public sealed class UpgradeCardMarker : MonoBehaviour { public bool IsUpgrade; public int OwnedBefore; public int OwnedAfter; public string SourceCardName; } public sealed class UpgradeCardVisualService { private readonly ManualLogSource _logger; private readonly UpgradeDraftPlugin _plugin; private readonly UpgradePreviewService _previewService; public UpgradeCardVisualService(ManualLogSource logger, UpgradeDraftPlugin plugin, UpgradePreviewService previewService) { _logger = logger; _plugin = plugin; _previewService = previewService; } public void ApplyUpgradeVisual(GameObject spawnedCard, DraftCardEntry entry) { if (!_plugin.IsVisualEnabled() || spawnedCard == null || entry == null) { return; } try { PrepareChoiceCard(spawnedCard); UpgradeCardMarker obj = spawnedCard.GetComponent() ?? spawnedCard.AddComponent(); obj.IsUpgrade = true; obj.OwnedBefore = entry.OwnedBefore; obj.OwnedAfter = entry.OwnedAfter; obj.SourceCardName = entry.Card.cardName; ApplyTextComponents(spawnedCard, entry); } catch (Exception ex) { _logger.LogWarning((object)("Failed to apply upgrade visuals on card object '" + ((Object)spawnedCard).name + "'. Error: " + ex.Message)); } } public void ApplyUpgradeVisual(GameObject spawnedCard, string sourceCardName, int ownedBefore, int ownedAfter) { if (!_plugin.IsVisualEnabled() || spawnedCard == null) { return; } try { PrepareChoiceCard(spawnedCard); CardInfo component = spawnedCard.GetComponent(); if (component != null) { UpgradeCardMarker upgradeCardMarker = spawnedCard.GetComponent() ?? spawnedCard.AddComponent(); upgradeCardMarker.IsUpgrade = true; upgradeCardMarker.OwnedBefore = ((ownedBefore >= 0) ? ownedBefore : 0); upgradeCardMarker.OwnedAfter = ((ownedAfter < 0) ? (upgradeCardMarker.OwnedBefore + 1) : ownedAfter); upgradeCardMarker.SourceCardName = (string.IsNullOrWhiteSpace(sourceCardName) ? component.cardName : sourceCardName); ApplyUpgradeTextComponents(spawnedCard, upgradeCardMarker.SourceCardName, upgradeCardMarker.OwnedBefore, upgradeCardMarker.OwnedAfter); } } catch (Exception ex) { _logger.LogWarning((object)("Failed to apply networked upgrade visuals on card object '" + ((Object)spawnedCard).name + "'. Error: " + ex.Message)); } } public void ApplyActionVisual(GameObject spawnedCard, DraftCardEntry entry, int pickerId) { if (spawnedCard != null && entry != null && entry.IsAction) { ApplyActionVisual(spawnedCard, entry.ActionType, pickerId); } } public void ApplyActionVisual(GameObject spawnedCard, DraftActionType actionType, int pickerId) { if (spawnedCard == null || actionType == DraftActionType.None) { return; } try { PrepareChoiceCard(spawnedCard); DraftActionCardMarker obj = spawnedCard.GetComponent() ?? spawnedCard.AddComponent(); obj.ActionType = actionType; obj.PickerId = pickerId; ApplyActionTextComponents(spawnedCard, actionType); } catch (Exception ex) { _logger.LogWarning((object)("Failed to apply action card visuals on card object '" + ((Object)spawnedCard).name + "'. Error: " + ex.Message)); } } public void PrepareChoiceCard(GameObject spawnedCard) { if (spawnedCard == null) { return; } try { Collider2D[] componentsInChildren = spawnedCard.GetComponentsInChildren(true); foreach (Collider2D val in componentsInChildren) { if (val != null) { ((Behaviour)val).enabled = false; } } Collider[] componentsInChildren2 = spawnedCard.GetComponentsInChildren(true); foreach (Collider val2 in componentsInChildren2) { if (val2 != null) { val2.enabled = false; } } Rigidbody[] componentsInChildren3 = spawnedCard.GetComponentsInChildren(true); foreach (Rigidbody val3 in componentsInChildren3) { if (val3 != null) { val3.detectCollisions = false; val3.isKinematic = true; } } Rigidbody2D[] componentsInChildren4 = spawnedCard.GetComponentsInChildren(true); foreach (Rigidbody2D val4 in componentsInChildren4) { if (val4 != null) { val4.simulated = false; } } } catch (Exception ex) { if (_plugin.IsVerboseLoggingEnabled()) { _logger.LogWarning((object)("Failed to prepare card-choice physics for '" + ((Object)spawnedCard).name + "'. Error: " + ex.Message)); } } } private void ApplyTextComponents(GameObject spawnedCard, DraftCardEntry entry) { string titleText = _previewService.BuildUpgradeTitle(entry); string descText = BuildShortUpgradeDescription(entry); ApplyTextComponents(spawnedCard, titleText, descText, "Upgrade card"); } private void ApplyUpgradeTextComponents(GameObject spawnedCard, string sourceCardName, int ownedBefore, int ownedAfter) { string titleText = "UPGRADE: " + sourceCardName; string descText = $"Upgrade: grants another copy. Owned: {ownedBefore} -> {ownedAfter}"; ApplyTextComponents(spawnedCard, titleText, descText, "Networked upgrade card"); } private void ApplyTextComponents(GameObject spawnedCard, string titleText, string descText, string label) { List list = (from c in spawnedCard.GetComponentsInChildren(true) where (Object)(object)c != (Object)null where HasStringTextProperty(((object)c).GetType()) select c).ToList(); if (list.Count == 0) { _logger.LogWarning((object)(label + " '" + ((Object)spawnedCard).name + "' has no discovered text components to patch.")); return; } bool flag = false; foreach (Component item in list) { string text = ((Object)item.gameObject).name.ToLowerInvariant(); if (!string.IsNullOrWhiteSpace(GetTextProperty(item))) { if (text.Contains("title") || text.Contains("name")) { SetTextProperty(item, titleText); flag = true; } else if (text.Contains("desc") || text.Contains("description")) { SetTextProperty(item, descText); flag = true; } } } if (!flag) { _logger.LogWarning((object)(label + " '" + ((Object)spawnedCard).name + "' did not expose recognizable title/description text components.")); } } private void ApplyActionTextComponents(GameObject spawnedCard, DraftActionType actionType) { string value = ((actionType == DraftActionType.Reroll) ? "REROLL" : "SKIP"); string value2 = ((actionType == DraftActionType.Reroll) ? "Spend skip points to redraw this card choice." : "Gain 1 skip point and pass this card choice."); foreach (Component item in (from c in spawnedCard.GetComponentsInChildren(true) where (Object)(object)c != (Object)null where HasStringTextProperty(((object)c).GetType()) select c).ToList()) { string text = ((Object)item.gameObject).name.ToLowerInvariant(); if (!string.IsNullOrWhiteSpace(GetTextProperty(item))) { if (text.Contains("title") || text.Contains("name")) { SetTextProperty(item, value); } else if (text.Contains("desc") || text.Contains("description")) { SetTextProperty(item, value2); } } } } private static string BuildShortUpgradeDescription(DraftCardEntry entry) { string text = entry.Card.cardDestription ?? string.Empty; text = text.Trim(); string text2 = $"Upgrade: grants another copy. Owned: {entry.OwnedBefore} -> {entry.OwnedAfter}"; if (string.IsNullOrWhiteSpace(text)) { return text2; } return text + "\n" + text2; } private static bool HasStringTextProperty(Type type) { PropertyInfo property = type.GetProperty("text", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && property.PropertyType == typeof(string) && property.CanRead) { return property.CanWrite; } return false; } private static string GetTextProperty(Component component) { return ((object)component).GetType().GetProperty("text", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(component, null) as string; } private static void SetTextProperty(Component component, string value) { ((object)component).GetType().GetProperty("text", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(component, value, null); } } } namespace UpgradeDraft.Services { public sealed class CardSelectionService { private readonly ManualLogSource _logger; private readonly UpgradeDraftPlugin _plugin; private readonly PlayerDeckService _deckService; public CardSelectionService(ManualLogSource logger, UpgradeDraftPlugin plugin, PlayerDeckService deckService) { _logger = logger; _plugin = plugin; _deckService = deckService; } public List BuildDraftEntries(CardChoice cardChoice, Player player, DraftPlan plan) { HashSet blacklist = _plugin.GetBlacklist(); List eligibleUpgradeCards = _deckService.GetEligibleUpgradeCards(player, blacklist); HashSet hashSet = new HashSet(StringComparer.Ordinal); List list = new List(); foreach (CardInfo item in PickRandomDistinct(eligibleUpgradeCards, plan.ActualUpgradeCount)) { int ownedCount = _deckService.GetOwnedCount(player, item); list.Add(new DraftCardEntry(item, isUpgrade: true, ownedCount)); hashSet.Add(_deckService.GetStableCardId(item)); } List list2 = SelectNewCards(cardChoice, player, plan.NewCardCount, hashSet, strictUnique: true, strictNotOwned: true); if (list2.Count < plan.NewCardCount) { _logger.LogWarning((object)$"Unable to fill all new-card slots with strict unique+not-owned rules. Needed {plan.NewCardCount}, got {list2.Count}. Trying duplicate-new fallback."); int wanted = plan.NewCardCount - list2.Count; list2.AddRange(SelectNewCards(cardChoice, player, wanted, hashSet, strictUnique: false, strictNotOwned: true)); } if (list2.Count < plan.NewCardCount) { _logger.LogWarning((object)$"Still missing new cards after duplicate-new fallback. Needed {plan.NewCardCount}, got {list2.Count}. Trying broad safety fallback."); int wanted2 = plan.NewCardCount - list2.Count; list2.AddRange(SelectNewCards(cardChoice, player, wanted2, hashSet, strictUnique: false, strictNotOwned: false)); } foreach (CardInfo item2 in list2) { int ownedCount2 = _deckService.GetOwnedCount(player, item2); list.Add(new DraftCardEntry(item2, isUpgrade: false, ownedCount2)); hashSet.Add(_deckService.GetStableCardId(item2)); } if (list.Count < plan.TotalCardCount) { _logger.LogWarning((object)$"Draft pool has {list.Count}/{plan.TotalCardCount} cards after all selection fallbacks. Letting vanilla SpawnUniqueCard fill remainder as safety."); } return list; } private List SelectNewCards(CardChoice cardChoice, Player player, int wanted, ISet existingCardIds, bool strictUnique, bool strictNotOwned) { List list = new List(); if (wanted <= 0) { return list; } int num = 0; int num2 = Math.Max(1000, wanted * 400); while (list.Count < wanted && num < num2) { num++; CardInfo randomCardWithCondition = Cards.instance.GetRandomCardWithCondition(player, (Gun)null, (GunAmmo)null, (CharacterData)null, (HealthHandler)null, (Gravity)null, (Block)null, (CharacterStatModifiers)null, BuildSelectionCondition(cardChoice, player, existingCardIds, list, strictUnique, strictNotOwned), 1000); if (randomCardWithCondition != null && (!strictNotOwned || !_deckService.PlayerOwnsCard(player, randomCardWithCondition)) && !_deckService.IsBlacklisted(randomCardWithCondition, _plugin.GetBlacklist())) { string stableCardId = _deckService.GetStableCardId(randomCardWithCondition); if (!strictUnique || !existingCardIds.Contains(stableCardId)) { list.Add(randomCardWithCondition); existingCardIds.Add(stableCardId); } } } return list; } private Func BuildSelectionCondition(CardChoice cardChoice, Player player, ISet selectedIds, IEnumerable localNewCards, bool strictUnique, bool strictNotOwned) { HashSet localIds = new HashSet(localNewCards.Where((CardInfo c) => c != null).Select(_deckService.GetStableCardId), StringComparer.Ordinal); Func baseCondition = BuildCardChoiceCompatibleCondition(cardChoice); return delegate(CardInfo card, Player p, Gun gun, GunAmmo gunAmmo, CharacterData data, HealthHandler health, Gravity gravity, Block block, CharacterStatModifiers stats) { if (card == null) { return false; } if (strictNotOwned && _deckService.PlayerOwnsCard(player, card)) { return false; } if (_deckService.IsBlacklisted(card, _plugin.GetBlacklist())) { return false; } if (!Cards.instance.PlayerIsAllowedCard(player, card)) { return false; } if (!baseCondition(card, player)) { return false; } if (strictUnique) { string stableCardId = _deckService.GetStableCardId(card); if (selectedIds.Contains(stableCardId) || localIds.Contains(stableCardId)) { return false; } } return true; }; } private Func BuildCardChoiceCompatibleCondition(CardChoice cardChoice) { return delegate(CardInfo card, Player player) { if (card == null) { return false; } try { if ((Object)(object)cardChoice != (Object)null && cardChoice.pickrID != -1) { object fieldOrPropertyValue = ReflectionUtils.GetFieldOrPropertyValue(((Component)player.data).GetComponent("Holding"), "holdable"); Component val = (Component)((fieldOrPropertyValue is Component) ? fieldOrPropertyValue : null); if (val != null) { Gun component = val.GetComponent(); Gun component2 = ((Component)card).GetComponent(); if (component != null && component2 != null && component2.lockGunToDefault && component.lockGunToDefault) { return false; } } } } catch (Exception ex) { if (_plugin.IsVerboseLoggingEnabled()) { _logger.LogWarning((object)("CardChoice-compatible condition failed to inspect lockGunToDefault: " + ex.Message)); } } return true; }; } private List PickRandomDistinct(List cards, int amount) { List list = new List(); if (cards == null || cards.Count == 0 || amount <= 0) { return list; } List list2 = cards.Where((CardInfo c) => c != null).ToList(); for (int i = 0; i < list2.Count; i++) { int index = Random.Range(i, list2.Count); CardInfo value = list2[i]; list2[i] = list2[index]; list2[index] = value; } for (int j = 0; j < Math.Min(amount, list2.Count); j++) { list.Add(list2[j]); } return list; } } public sealed class DraftCardNetworkService : IDisposable { [CompilerGenerated] private sealed class d__9 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public int viewId; public bool isUpgrade; public DraftCardNetworkService <>4__this; public string sourceName; public int ownedBefore; public int ownedAfter; public DraftActionType actionType; public int pickerId; private int 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__9(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { int num = <>1__state; DraftCardNetworkService draftCardNetworkService = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; 5__2 = 0; break; case 1: <>1__state = -1; 5__2++; break; } if (5__2 < 90) { PhotonView photonView = PhotonNetwork.GetPhotonView(viewId); if (photonView != null && ((Component)photonView).gameObject != null) { if (isUpgrade) { draftCardNetworkService._visualService.ApplyUpgradeVisual(((Component)photonView).gameObject, sourceName, ownedBefore, ownedAfter); } else if (actionType != 0) { draftCardNetworkService._visualService.ApplyActionVisual(((Component)photonView).gameObject, actionType, pickerId); } else { draftCardNetworkService._visualService.PrepareChoiceCard(((Component)photonView).gameObject); } return false; } <>2__current = null; <>1__state = 1; return true; } if (draftCardNetworkService._plugin.IsVerboseLoggingEnabled()) { draftCardNetworkService._logger.LogWarning((object)$"Timed out waiting for PhotonView {viewId} to apply UpgradeDraft visual sync."); } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private const byte VisualEventCode = 87; private const int MaxApplyAttempts = 90; private readonly ManualLogSource _logger; private readonly UpgradeDraftPlugin _plugin; private readonly UpgradeCardVisualService _visualService; public DraftCardNetworkService(ManualLogSource logger, UpgradeDraftPlugin plugin, UpgradeCardVisualService visualService) { _logger = logger; _plugin = plugin; _visualService = visualService; PhotonNetwork.NetworkingClient.EventReceived += OnEventReceived; } public void Dispose() { if (PhotonNetwork.NetworkingClient != null) { PhotonNetwork.NetworkingClient.EventReceived -= OnEventReceived; } } public void BroadcastSpawn(GameObject spawnedCard, DraftCardEntry entry, int pickerId) { //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown //IL_009c: Unknown result type (might be due to invalid IL or missing references) if (spawnedCard != null && entry != null) { PhotonView component = spawnedCard.GetComponent(); if (component != null) { object[] array = new object[7] { component.ViewID, entry.IsUpgrade, (int)entry.ActionType, pickerId, entry.OwnedBefore, entry.OwnedAfter, ((Object)(object)entry.Card != (Object)null) ? entry.Card.cardName : string.Empty }; RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)0 }; PhotonNetwork.RaiseEvent((byte)87, (object)array, val, SendOptions.SendReliable); } } } private void OnEventReceived(EventData photonEvent) { if (photonEvent.Code != 87 || !(photonEvent.CustomData is object[] array) || array.Length < 7) { return; } try { int viewId = Convert.ToInt32(array[0]); bool isUpgrade = Convert.ToBoolean(array[1]); DraftActionType actionType = (DraftActionType)Convert.ToInt32(array[2]); int pickerId = Convert.ToInt32(array[3]); int ownedBefore = Convert.ToInt32(array[4]); int ownedAfter = Convert.ToInt32(array[5]); string sourceName = (array[6] as string) ?? string.Empty; ((MonoBehaviour)_plugin).StartCoroutine(ApplyWhenSpawned(viewId, isUpgrade, actionType, pickerId, ownedBefore, ownedAfter, sourceName)); } catch (Exception ex) { _logger.LogWarning((object)("Failed to parse UpgradeDraft visual sync event: " + ex.Message)); } } [IteratorStateMachine(typeof(d__9))] private IEnumerator ApplyWhenSpawned(int viewId, bool isUpgrade, DraftActionType actionType, int pickerId, int ownedBefore, int ownedAfter, string sourceName) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__9(0) { <>4__this = this, viewId = viewId, isUpgrade = isUpgrade, actionType = actionType, pickerId = pickerId, ownedBefore = ownedBefore, ownedAfter = ownedAfter, sourceName = sourceName }; } } public sealed class DraftRollService { private readonly ManualLogSource _logger; private readonly UpgradeDraftPlugin _plugin; public DraftRollService(ManualLogSource logger, UpgradeDraftPlugin plugin) { _logger = logger; _plugin = plugin; } public DraftPlan BuildPlan(int eligibleUpgradeCount) { int cardCount = _plugin.GetCardCount(); int rollValue; int num = RollDesiredUpgradeCount(out rollValue); int num2 = Math.Min(Math.Max(0, num), Math.Max(0, eligibleUpgradeCount)); DraftPlan draftPlan = new DraftPlan { DesiredUpgradeCount = num, ActualUpgradeCount = num2, NewCardCount = Math.Max(0, cardCount - num2), TotalCardCount = cardCount, RollValue = rollValue, FallbackReason = string.Empty }; if (num2 != num) { draftPlan.FallbackReason = $"Clamped upgrades from desired {num} to {num2} because only {eligibleUpgradeCount} eligible upgrade card(s) exist."; _logger.LogWarning((object)draftPlan.FallbackReason); } return draftPlan; } private int RollDesiredUpgradeCount(out int rollValue) { (int, int, int, int) weightedChances = _plugin.GetWeightedChances(); rollValue = Random.Range(0, 100); int item = weightedChances.Item1; int num = item + weightedChances.Item2; int num2 = num + weightedChances.Item3; if (rollValue < item) { return 0; } if (rollValue < num) { return 1; } if (rollValue < num2) { return 2; } return 3; } } public sealed class PlayerDeckService { private readonly ManualLogSource _logger; private readonly UpgradeDraftPlugin _plugin; public PlayerDeckService(ManualLogSource logger, UpgradeDraftPlugin plugin) { _logger = logger; _plugin = plugin; } public List GetOwnedCards(Player player) { if (player == null) { return new List(); } try { object fieldOrPropertyValue = ReflectionUtils.GetFieldOrPropertyValue(player, "data"); if (fieldOrPropertyValue == null) { return new List(); } List list = ReflectionUtils.ToTypedList(ReflectionUtils.GetFieldOrPropertyValue(fieldOrPropertyValue, "currentCards")); if (list.Count > 0) { return list; } list = ReflectionUtils.ToTypedList(ReflectionUtils.GetFieldOrPropertyValue(fieldOrPropertyValue, "cards")); if (list.Count > 0) { return list; } } catch (Exception ex) { _logger.LogWarning((object)$"Failed to read owned cards for player {player.playerID}: {ex.Message}"); } return new List(); } public int GetOwnedCount(Player player, CardInfo card) { if (player == null || card == null) { return 0; } string cardName = ((Object)((Component)card).gameObject).name; return GetOwnedCards(player).Count((CardInfo c) => c != null && string.Equals(((Object)((Component)c).gameObject).name, cardName, StringComparison.Ordinal)); } public bool PlayerOwnsCard(Player player, CardInfo card) { return GetOwnedCount(player, card) > 0; } public List GetEligibleUpgradeCards(Player player, ISet blacklistLower) { List ownedCards = GetOwnedCards(player); HashSet hashSet = new HashSet(StringComparer.Ordinal); List list = new List(); foreach (CardInfo item in ownedCards) { if (IsEligibleUpgradeCard(player, item, blacklistLower)) { string stableCardId = GetStableCardId(item); if (hashSet.Add(stableCardId)) { list.Add(item); } } } return list; } public bool IsEligibleUpgradeCard(Player player, CardInfo card, ISet blacklistLower) { if (player == null || card == null) { return false; } if (!PlayerOwnsCard(player, card)) { return false; } if (!card.allowMultiple) { return false; } if (IsBlacklisted(card, blacklistLower)) { return false; } if (!Cards.instance.PlayerIsAllowedCard(player, card)) { return false; } return true; } public bool IsBlacklisted(CardInfo card, ISet blacklistLower) { if (card == null || blacklistLower == null || blacklistLower.Count == 0) { return false; } string item = (card.cardName ?? string.Empty).Trim().ToLowerInvariant(); string item2 = (((Object)(object)((Component)card).gameObject != (Object)null) ? ((Object)((Component)card).gameObject).name : string.Empty).Replace("(Clone)", string.Empty).Trim().ToLowerInvariant(); if (!blacklistLower.Contains(item)) { return blacklistLower.Contains(item2); } return true; } public string GetStableCardId(CardInfo card) { if (card == null) { return "null"; } if (!string.IsNullOrWhiteSpace(card.cardName)) { return card.cardName.Trim().ToLowerInvariant(); } string text = (((Object)(object)((Component)card).gameObject != (Object)null) ? ((Object)((Component)card).gameObject).name : string.Empty); if (!string.IsNullOrWhiteSpace(text)) { return text.Replace("(Clone)", string.Empty).Trim().ToLowerInvariant(); } return ((Object)card).GetInstanceID().ToString(); } } internal static class ReflectionUtils { internal static object GetFieldOrPropertyValue(object instance, string name) { if (instance == null || string.IsNullOrWhiteSpace(name)) { return null; } Type type = instance.GetType(); FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(instance); } return type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(instance, null); } internal static bool TrySetProperty(object instance, string propertyName, object value) { if (instance == null || string.IsNullOrWhiteSpace(propertyName)) { return false; } PropertyInfo property = instance.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property == null || !property.CanWrite) { return false; } property.SetValue(instance, value, null); return true; } internal static List ToTypedList(object maybeList) where T : class { if (maybeList == null) { return new List(); } if (maybeList is IList source) { return source.Where((T item) => item != null).ToList(); } if (maybeList is IEnumerable enumerable) { List list = new List(); { foreach (object item3 in enumerable) { if (item3 is T item2) { list.Add(item2); } } return list; } } return new List(); } } public sealed class SkipRerollService { [CompilerGenerated] private sealed class d__9 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public CardChoice cardChoice; public int pickerId; public SkipRerollService <>4__this; public int pointsSpent; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__9(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { int num = <>1__state; SkipRerollService skipRerollService = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>2__current = null; <>1__state = 1; return true; case 1: { <>1__state = -1; if (cardChoice == null) { return false; } cardChoice.pickrID = pickerId; UpgradeDraftPlugin.State.ClearDraftPlanOnly("Reroll action card picked."); Traverse val = Traverse.Create((object)cardChoice).Field("picks"); int value = val.GetValue(); val.SetValue((object)(value + 1)); if (AccessTools.Method(((object)cardChoice).GetType(), "ReplaceCards", (Type[])null, (Type[])null).Invoke(cardChoice, new object[2] { null, true }) is IEnumerator enumerator) { UpgradeDraftPlugin.State.MarkRerolledThisPick(); ((MonoBehaviour)cardChoice).StartCoroutine(enumerator); int skipPoints = UpgradeDraftPlugin.State.GetSkipPoints(pickerId); skipRerollService._logger.LogInfo((object)$"Player {pickerId} picked REROLL. Spent {pointsSpent} skip points (remaining: {skipPoints})."); } else { val.SetValue((object)value); skipRerollService._logger.LogWarning((object)"CardChoice.ReplaceCards did not return IEnumerator. Reroll action could not start coroutine."); } return false; } } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private readonly ManualLogSource _logger; private readonly UpgradeDraftPlugin _plugin; public SkipRerollService(ManualLogSource logger, UpgradeDraftPlugin plugin) { _logger = logger; _plugin = plugin; } public bool IsCardChoiceBusy(CardChoice cardChoice) { try { object value = Traverse.Create((object)cardChoice).Field("isPlaying").GetValue(); if (value is bool) { return (bool)value; } } catch (Exception) { } return false; } public bool IsCardChoiceOpen(CardChoice cardChoice) { if (cardChoice == null || cardChoice.pickrID < 0) { return false; } try { object value = Traverse.Create((object)cardChoice).Field("IsPicking").GetValue(); bool flag = default(bool); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { return true; } } catch (Exception) { } try { if (Traverse.Create((object)cardChoice).Field("spawnedCards").GetValue() is ICollection collection) { return collection.Count > 0; } } catch (Exception) { } return false; } public bool IsLocalClientControllingPick(CardChoice cardChoice) { Player val = ResolvePickingPlayer(cardChoice); if (val == null) { return true; } try { CharacterData data = val.data; if (data == null) { return true; } object fieldOrPropertyValue = ReflectionUtils.GetFieldOrPropertyValue(data, "view"); if (fieldOrPropertyValue == null) { return true; } object fieldOrPropertyValue2 = ReflectionUtils.GetFieldOrPropertyValue(fieldOrPropertyValue, "IsMine"); if (fieldOrPropertyValue2 is bool) { return (bool)fieldOrPropertyValue2; } } catch (Exception ex) { if (_plugin.IsVerboseLoggingEnabled()) { _logger.LogWarning((object)("Unable to resolve local pick ownership. Defaulting to visible controls. Error: " + ex.Message)); } } return true; } public bool CanReroll(CardChoice cardChoice, int pickerId, out string reason) { reason = string.Empty; if (cardChoice == null || pickerId < 0) { reason = "invalid_card_choice"; return false; } if (IsCardChoiceBusy(cardChoice)) { reason = "card_choice_busy"; return false; } int skipPointsForReroll = _plugin.GetSkipPointsForReroll(); if (UpgradeDraftPlugin.State.GetSkipPoints(pickerId) < skipPointsForReroll) { reason = "not_enough_points"; return false; } if (UpgradeDraftPlugin.State.RerolledThisPick && !_plugin.IsMultipleRerollsPerPickAllowed()) { reason = "already_rerolled_this_pick"; return false; } if (AccessTools.Method(((object)cardChoice).GetType(), "ReplaceCards", (Type[])null, (Type[])null) == null) { reason = "replace_cards_method_missing"; return false; } return true; } private Player ResolvePickingPlayer(CardChoice cardChoice) { if (cardChoice == null || PlayerManager.instance == null) { return null; } try { if (Convert.ToInt32(Traverse.Create((object)cardChoice).Field("pickerType").GetValue()) == 0) { return PlayerManager.instance.GetPlayersInTeam(cardChoice.pickrID).FirstOrDefault(); } if (cardChoice.pickrID >= 0 && cardChoice.pickrID < PlayerManager.instance.players.Count()) { return PlayerManager.instance.players[cardChoice.pickrID]; } } catch { } return null; } public bool TryReroll(CardChoice cardChoice, int pickerId) { if (!CanReroll(cardChoice, pickerId, out var reason)) { if (_plugin.IsVerboseLoggingEnabled()) { _logger.LogInfo((object)$"Reroll denied for player {pickerId}. Reason={reason}"); } return false; } int skipPointsForReroll = _plugin.GetSkipPointsForReroll(); if (!UpgradeDraftPlugin.State.TryConsumeSkipPoints(pickerId, skipPointsForReroll)) { _logger.LogWarning((object)$"Failed to consume reroll points for player {pickerId}. This should not happen after CanReroll check."); return false; } ((MonoBehaviour)cardChoice).StartCoroutine(RerollAfterSelectionFrame(cardChoice, pickerId, skipPointsForReroll)); return true; } [IteratorStateMachine(typeof(d__9))] private IEnumerator RerollAfterSelectionFrame(CardChoice cardChoice, int pickerId, int pointsSpent) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__9(0) { <>4__this = this, cardChoice = cardChoice, pickerId = pickerId, pointsSpent = pointsSpent }; } } public sealed class UpgradeDraftState { private readonly ManualLogSource _logger; private readonly Queue _pendingEntries = new Queue(); private readonly Dictionary _spawnedCardMap = new Dictionary(); private readonly Dictionary _skipPointsByPlayerId = new Dictionary(); private int _currentPickerId = -999; private bool _planReady; private bool _rerolledThisPick; public bool HasPlanForCurrentPick => _planReady; public int CurrentPickerId => _currentPickerId; public bool RerolledThisPick => _rerolledThisPick; public UpgradeDraftState(ManualLogSource logger) { _logger = logger; } public void BeginPick(int pickerId) { if (_currentPickerId != pickerId) { Clear($"Picker changed from {_currentPickerId} to {pickerId}."); } _currentPickerId = pickerId; _rerolledThisPick = false; } public void SetPlannedEntries(IEnumerable entries) { _pendingEntries.Clear(); foreach (DraftCardEntry entry in entries) { _pendingEntries.Enqueue(entry); } _planReady = true; } public bool TryDequeueNext(out DraftCardEntry entry) { entry = null; if (_pendingEntries.Count == 0) { return false; } entry = _pendingEntries.Dequeue(); return true; } public void RegisterSpawnedCard(GameObject spawnedCard, DraftCardEntry entry) { if (spawnedCard != null && entry != null) { _spawnedCardMap[((Object)spawnedCard).GetInstanceID()] = entry; } } public bool TryGetSpawnedEntry(GameObject spawnedCard, out DraftCardEntry entry) { entry = null; if (spawnedCard == null) { return false; } return _spawnedCardMap.TryGetValue(((Object)spawnedCard).GetInstanceID(), out entry); } public void Clear(string reason = "") { if (!string.IsNullOrWhiteSpace(reason)) { _logger.LogDebug((object)("Clearing UpgradeDraft state: " + reason)); } _pendingEntries.Clear(); _spawnedCardMap.Clear(); _planReady = false; _currentPickerId = -999; _rerolledThisPick = false; } public void ClearDraftPlanOnly(string reason = "") { if (!string.IsNullOrWhiteSpace(reason)) { _logger.LogDebug((object)("Clearing draft plan only: " + reason)); } _pendingEntries.Clear(); _spawnedCardMap.Clear(); _planReady = false; } public int GetSkipPoints(int playerId) { if (!_skipPointsByPlayerId.TryGetValue(playerId, out var value)) { return 0; } return value; } public int AddSkipPoint(int playerId) { int num = GetSkipPoints(playerId) + 1; _skipPointsByPlayerId[playerId] = num; return num; } public bool TryConsumeSkipPoints(int playerId, int pointsToConsume) { if (pointsToConsume <= 0) { return true; } int skipPoints = GetSkipPoints(playerId); if (skipPoints < pointsToConsume) { return false; } _skipPointsByPlayerId[playerId] = skipPoints - pointsToConsume; return true; } public void MarkRerolledThisPick() { _rerolledThisPick = true; } } public sealed class UpgradePreviewService { private static readonly Regex NumericRegex = new Regex("([+-]?\\d+(\\.\\d+)?)", RegexOptions.Compiled); private readonly ManualLogSource _logger; private readonly UpgradeDraftPlugin _plugin; public UpgradePreviewService(ManualLogSource logger, UpgradeDraftPlugin plugin) { _logger = logger; _plugin = plugin; } public string BuildUpgradeTitle(DraftCardEntry entry) { return "UPGRADE: " + entry.Card.cardName; } public string BuildUpgradeDescription(DraftCardEntry entry) { StringBuilder stringBuilder = new StringBuilder(); string text = entry.Card.cardDestription ?? string.Empty; if (!string.IsNullOrWhiteSpace(text)) { stringBuilder.AppendLine(text.Trim()); } stringBuilder.AppendLine("Upgrade pick: selecting this card grants another copy of the same card."); stringBuilder.AppendLine($"Owned: {entry.OwnedBefore} -> {entry.OwnedAfter}"); if (_plugin.IsStatPreviewEnabled()) { string value = BuildStackedStatPreview(entry); if (!string.IsNullOrWhiteSpace(value)) { stringBuilder.AppendLine(); stringBuilder.AppendLine(value); } } return stringBuilder.ToString().TrimEnd(); } public string BuildOwnedLine(DraftCardEntry entry) { return $"Owned: {entry.OwnedBefore} -> {entry.OwnedAfter}"; } public string BuildStackedStatPreview(DraftCardEntry entry) { CardInfoStat[] cardStats = entry.Card.cardStats; if (cardStats == null || cardStats.Length == 0) { return "Stack preview: no card stat lines available."; } List list = new List(); CardInfoStat[] array = cardStats; foreach (CardInfoStat val in array) { if (TryBuildStackLine(val, entry.OwnedAfter, out var line)) { list.Add(line); } else { list.Add(val.stat + ": " + val.amount); } } return string.Join("\n", list); } private bool TryBuildStackLine(CardInfoStat stat, int ownedAfter, out string line) { line = null; if (string.IsNullOrWhiteSpace(stat.amount)) { return false; } Match match = NumericRegex.Match(stat.amount); if (!match.Success) { return false; } if (!float.TryParse(match.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return false; } string text = stat.amount.Replace(match.Value, string.Empty).Trim(); float value = result * (float)ownedAfter; if (stat.amount.Contains("%")) { line = stat.stat + ": " + stat.amount + " (" + FormatSigned(value) + "% total from this card after pick)"; return true; } if (!string.IsNullOrWhiteSpace(text)) { line = stat.stat + ": " + stat.amount + " (" + FormatSigned(value) + " " + text + " total from this card after pick)"; return true; } line = stat.stat + ": " + stat.amount + " (" + FormatSigned(value) + " total from this card after pick)"; return true; } private static string FormatSigned(float value) { if (!(value >= 0f)) { return value.ToString("0.##", CultureInfo.InvariantCulture); } return $"+{value:0.##}"; } } } namespace UpgradeDraft.Patches { [HarmonyPatch(typeof(CardChoice), "Pick")] internal static class CardChoiceActionPickPatch { private static bool Prefix(CardChoice __instance, GameObject pickedCard) { if (pickedCard == null) { return true; } DraftActionCardMarker component = pickedCard.GetComponent(); if (component == null) { return true; } try { if (component.ActionType == DraftActionType.Skip) { return !HandleSkip(__instance, pickedCard, component.PickerId); } if (component.ActionType == DraftActionType.Reroll) { if (UpgradeDraftPlugin.SkipRerollService.TryReroll(__instance, component.PickerId)) { return false; } UpgradeDraftPlugin.Log.LogWarning((object)"REROLL action card failed. Ending the pick without applying action-card stats to avoid corrupting card choice."); return !HandleActionEndPick(__instance, pickedCard, component.PickerId); } } catch (Exception arg) { UpgradeDraftPlugin.Log.LogWarning((object)$"Failed to handle UpgradeDraft action card: {arg}"); } return false; } private static bool HandleSkip(CardChoice cardChoice, GameObject pickedCard, int pickerId) { if (cardChoice == null || pickedCard == null || pickerId < 0) { return false; } int num = UpgradeDraftPlugin.State.AddSkipPoint(pickerId); UpgradeDraftPlugin.State.ClearDraftPlanOnly("Skip action card picked."); if (!HandleActionEndPick(cardChoice, pickedCard, pickerId)) { return false; } UpgradeDraftPlugin.Log.LogInfo((object)$"Player {pickerId} picked SKIP and gained 1 skip point (total: {num})."); return true; } private static bool HandleActionEndPick(CardChoice cardChoice, GameObject pickedCard, int pickerId) { if (cardChoice == null || pickedCard == null || pickerId < 0) { return false; } int[] array = AccessTools.Method(typeof(CardChoice), "CardIDs", (Type[])null, (Type[])null)?.Invoke(cardChoice, null) as int[]; PhotonView component = ((Component)cardChoice).GetComponent(); PhotonView component2 = pickedCard.GetComponent(); PublicInt component3 = pickedCard.GetComponent(); if (array == null || component == null || component2 == null || component3 == null) { UpgradeDraftPlugin.Log.LogWarning((object)"Skip action card could not access vanilla card-pick RPC data."); return false; } component.RPC("RPCA_DoEndPick", (RpcTarget)0, new object[4] { array, component2.ViewID, component3.theInt, pickerId }); return true; } } [HarmonyPatch(typeof(CardChoice), "RPCA_DoEndPick")] internal static class CardChoiceEndPickCleanupPatch { [CompilerGenerated] private sealed class d__1 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public int[] cardIDs; public int targetCardID; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__1(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(0.45f); <>1__state = 1; return true; case 1: <>1__state = -1; CleanupNow(cardIDs, targetCardID, includePickedCard: false); <>2__current = (object)new WaitForSeconds(1.05f); <>1__state = 2; return true; case 2: <>1__state = -1; CleanupNow(cardIDs, targetCardID, includePickedCard: true); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static void Postfix(int[] cardIDs, int targetCardID) { if (!((Object)(object)UpgradeDraftPlugin.Instance == (Object)null) && cardIDs != null && cardIDs.Length != 0) { ((MonoBehaviour)UpgradeDraftPlugin.Instance).StartCoroutine(CleanupLeftoverChoiceCards(cardIDs, targetCardID)); } } [IteratorStateMachine(typeof(d__1))] private static IEnumerator CleanupLeftoverChoiceCards(int[] cardIDs, int targetCardID) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__1(0) { cardIDs = cardIDs, targetCardID = targetCardID }; } private static void CleanupNow(int[] cardIDs, int targetCardID, bool includePickedCard) { foreach (int num in cardIDs) { if (!includePickedCard && num == targetCardID) { continue; } PhotonView photonView = PhotonNetwork.GetPhotonView(num); if (photonView != null && ((Component)photonView).gameObject != null) { UpgradeDraftPlugin.VisualService?.PrepareChoiceCard(((Component)photonView).gameObject); if (PhotonNetwork.InRoom && photonView.IsMine) { PhotonNetwork.Destroy(((Component)photonView).gameObject); } else { Object.Destroy((Object)(object)((Component)photonView).gameObject); } } } } } [HarmonyPatch(typeof(CardChoice), "StartPick")] internal static class CardChoiceStartPickPatch { private static void Prefix(CardChoice __instance, int pickerIDToSet) { if (UpgradeDraftPlugin.State != null) { UpgradeDraftPlugin.State.Clear("Starting new StartPick flow."); UpgradeDraftPlugin.State.BeginPick(pickerIDToSet); } } } [HarmonyPatch(typeof(CardChoice), "DoPick")] internal static class CardChoiceDoPickPatch { private static void Prefix(CardChoice __instance, int picketIDToSet) { if (UpgradeDraftPlugin.State != null) { UpgradeDraftPlugin.State.Clear("Starting new DoPick flow."); UpgradeDraftPlugin.State.BeginPick(picketIDToSet); } } } internal static class CardChoiceSlotLimiter { public static void Apply(CardChoice cardChoice) { UpgradeDraftPlugin instance = UpgradeDraftPlugin.Instance; if ((Object)(object)instance == (Object)null || cardChoice == null) { return; } int num = instance.GetCardCount() + 1; if (num <= 0) { return; } Traverse val = Traverse.Create((object)cardChoice); Transform[] value = val.Field("children").GetValue(); if (value != null && value.Length > num) { Transform[] array = (Transform[])(object)new Transform[num]; Array.Copy(value, array, num); val.Field("children").SetValue((object)array); if (instance.IsVerboseLoggingEnabled()) { UpgradeDraftPlugin.Log.LogInfo((object)$"Limited CardChoice spawn slots from {value.Length} to {num}."); } } } } [HarmonyPatch(typeof(CardChoice), "Start")] internal static class CardChoiceStartLimitSlotsPatch { private static void Postfix(CardChoice __instance) { CardChoiceSlotLimiter.Apply(__instance); } } [HarmonyPatch(typeof(CardChoice), "SpawnUniqueCard")] internal static class CardChoiceSpawnUniqueCardPatch { private static bool Prefix(ref GameObject __result, CardChoice __instance, Vector3 pos, Quaternion rot) { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) UpgradeDraftPlugin instance = UpgradeDraftPlugin.Instance; if ((Object)(object)instance == (Object)null || UpgradeDraftPlugin.State == null) { return true; } try { Player val = ResolvePickingPlayer(__instance); if (val == null) { UpgradeDraftPlugin.Log.LogWarning((object)"Could not resolve picking player. Falling back to vanilla SpawnUniqueCard."); return true; } UpgradeDraftPlugin.State.BeginPick(val.playerID); if (!UpgradeDraftPlugin.State.HasPlanForCurrentPick) { BuildPlanForCurrentPick(__instance, val); } if (!UpgradeDraftPlugin.State.TryDequeueNext(out var entry) || entry == null || entry.Card == null) { if (instance.IsVerboseLoggingEnabled()) { UpgradeDraftPlugin.Log.LogInfo((object)"No queued draft entry available; falling back to vanilla SpawnUniqueCard."); } return true; } GameObject val2 = SpawnCardObject(__instance, entry.Card, pos, rot); if (val2 == null) { UpgradeDraftPlugin.Log.LogWarning((object)"Custom spawn returned null. Falling back to vanilla SpawnUniqueCard."); return true; } UpgradeDraftPlugin.VisualService.PrepareChoiceCard(val2); if (entry.IsAction) { UpgradeDraftPlugin.VisualService.ApplyActionVisual(val2, entry, val.playerID); } else if (entry.IsUpgrade) { UpgradeDraftPlugin.VisualService.ApplyUpgradeVisual(val2, entry); } UpgradeDraftPlugin.NetworkService?.BroadcastSpawn(val2, entry, val.playerID); UpgradeDraftPlugin.State.RegisterSpawnedCard(val2, entry); __result = val2; return false; } catch (Exception arg) { UpgradeDraftPlugin.Log.LogWarning((object)$"UpgradeDraft SpawnUniqueCard patch failed: {arg}"); return true; } } private static void BuildPlanForCurrentPick(CardChoice cardChoice, Player pickingPlayer) { int count = UpgradeDraftPlugin.DeckService.GetEligibleUpgradeCards(pickingPlayer, UpgradeDraftPlugin.Instance.GetBlacklist()).Count; DraftPlan draftPlan = UpgradeDraftPlugin.RollService.BuildPlan(count); List list = UpgradeDraftPlugin.SelectionService.BuildDraftEntries(cardChoice, pickingPlayer, draftPlan); CardInfo val = ResolveActionCardSource(cardChoice); if (val != null) { DraftActionType actionType = ((UpgradeDraftPlugin.State.GetSkipPoints(pickingPlayer.playerID) < UpgradeDraftPlugin.Instance.GetSkipPointsForReroll()) ? DraftActionType.Skip : DraftActionType.Reroll); list.Add(new DraftCardEntry(val, actionType)); } else { UpgradeDraftPlugin.Log.LogWarning((object)"Unable to resolve an action-card source. The 5th skip/reroll card will not be shown."); } UpgradeDraftPlugin.State.SetPlannedEntries(list); if (!string.IsNullOrWhiteSpace(draftPlan.FallbackReason)) { UpgradeDraftPlugin.Log.LogWarning((object)draftPlan.FallbackReason); } if (UpgradeDraftPlugin.Instance.IsVerboseLoggingEnabled()) { UpgradeDraftPlugin.Log.LogInfo((object)$"Built draft plan (roll={draftPlan.RollValue}): desiredUpgrades={draftPlan.DesiredUpgradeCount}, actualUpgrades={draftPlan.ActualUpgradeCount}, newCards={draftPlan.NewCardCount}, total={draftPlan.TotalCardCount}, generatedEntries={list.Count}."); } } private static CardInfo ResolveActionCardSource(CardChoice cardChoice) { try { return Traverse.Create((object)cardChoice).Field("cards").GetValue()?.FirstOrDefault((Func)((CardInfo card) => card != null && ((Component)card).gameObject != null)); } catch (Exception ex) { UpgradeDraftPlugin.Log.LogWarning((object)("Failed to resolve action-card source: " + ex.Message)); return null; } } private static Player ResolvePickingPlayer(CardChoice cardChoice) { try { if (Convert.ToInt32(Traverse.Create((object)cardChoice).Field("pickerType").GetValue()) == 0) { return PlayerManager.instance.GetPlayersInTeam(cardChoice.pickrID).FirstOrDefault(); } if (cardChoice.pickrID >= 0 && cardChoice.pickrID < PlayerManager.instance.players.Count()) { return PlayerManager.instance.players[cardChoice.pickrID]; } } catch (Exception ex) { UpgradeDraftPlugin.Log.LogWarning((object)("Failed to resolve picking player from CardChoice. Error: " + ex.Message)); } return null; } private static GameObject SpawnCardObject(CardChoice cardChoice, CardInfo sourceCard, Vector3 pos, Quaternion rot) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) if (sourceCard == null) { return null; } try { object? obj = typeof(CardChoice).InvokeMember("Spawn", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod, null, cardChoice, new object[3] { ((Component)sourceCard).gameObject, pos, rot }); GameObject val = (GameObject)((obj is GameObject) ? obj : null); if (val == null) { return null; } CardInfo component = val.GetComponent(); if (component != null) { component.sourceCard = sourceCard; } return val; } catch (Exception ex) { UpgradeDraftPlugin.Log.LogWarning((object)("Failed to spawn custom card object for '" + sourceCard.cardName + "': " + ex.Message)); return null; } } } [HarmonyPatch(typeof(CardVisuals), "ChangeSelected")] internal static class CardVisualsChoiceOverlayPatch { private static bool Prefix(CardVisuals __instance, bool setSelected) { if (!ShouldStabilizeChoiceVisual(__instance)) { return true; } try { ApplyStableChoiceVisual(__instance, setSelected); } catch (Exception ex) { if ((Object)(object)UpgradeDraftPlugin.Instance != (Object)null && UpgradeDraftPlugin.Instance.IsVerboseLoggingEnabled()) { UpgradeDraftPlugin.Log.LogWarning((object)("Failed to stabilize card-choice visual: " + ex.Message)); } } return false; } private static bool ShouldStabilizeChoiceVisual(CardVisuals visuals) { if (visuals == null || (Object)(object)UpgradeDraftPlugin.Instance == (Object)null) { return false; } CardChoice instance = CardChoice.instance; if (instance == null || instance.pickrID < 0 || !instance.IsPicking) { return false; } return ((Component)visuals).GetComponentInParent() != null; } private static void ApplyStableChoiceVisual(CardVisuals visuals, bool setSelected) { //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: 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_00d3: 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_0120: 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) visuals.isSelected = setSelected; if (visuals.objectsToToggle != null) { GameObject[] objectsToToggle = visuals.objectsToToggle; foreach (GameObject val in objectsToToggle) { if (val != null) { val.SetActive(false); } } } CanvasGroup[] componentsInChildren = ((Component)visuals).GetComponentsInChildren(true); foreach (CanvasGroup val2 in componentsInChildren) { if (val2 != null) { val2.alpha = 1f; } } CardAnimation[] componentsInChildren2 = ((Component)visuals).GetComponentsInChildren(true); foreach (CardAnimation val3 in componentsInChildren2) { if (val3 != null) { ((Behaviour)val3).enabled = true; } } CurveAnimation[] componentsInChildren3 = ((Component)visuals).GetComponentsInChildren(true); foreach (CurveAnimation val4 in componentsInChildren3) { if (val4 != null && (int)val4.currentState != 0) { val4.PlayIn(); } } Color color = (setSelected ? visuals.defaultColor : Color.Lerp(visuals.defaultColor, visuals.chillColor, 0.35f)); if (visuals.images != null) { Image[] images = visuals.images; foreach (Image val5 in images) { if (val5 != null) { ((Graphic)val5).color = color; } } } if (visuals.nameText != null) { ((Graphic)visuals.nameText).color = color; } ScaleShake component = ((Component)visuals).GetComponent(); if (component != null) { component.targetScale = (setSelected ? 1.12f : 1f); } } } } namespace UpgradeDraft.Models { public enum DraftActionType { None, Skip, Reroll } public sealed class DraftCardEntry { public CardInfo Card { get; } public bool IsUpgrade { get; } public bool IsAction => ActionType != DraftActionType.None; public DraftActionType ActionType { get; } public int OwnedBefore { get; } public int OwnedAfter { get; } public DraftCardEntry(CardInfo card, bool isUpgrade, int ownedBefore) { Card = card ?? throw new ArgumentNullException("card"); IsUpgrade = isUpgrade; OwnedBefore = ((ownedBefore >= 0) ? ownedBefore : 0); OwnedAfter = (IsUpgrade ? (OwnedBefore + 1) : OwnedBefore); ActionType = DraftActionType.None; } public DraftCardEntry(CardInfo card, DraftActionType actionType) { if (actionType == DraftActionType.None) { throw new ArgumentException("Action card entry requires an action type.", "actionType"); } Card = card ?? throw new ArgumentNullException("card"); ActionType = actionType; IsUpgrade = false; OwnedBefore = 0; OwnedAfter = 0; } } public sealed class DraftPlan { public int DesiredUpgradeCount { get; set; } public int ActualUpgradeCount { get; set; } public int NewCardCount { get; set; } public int TotalCardCount { get; set; } public int RollValue { get; set; } public string FallbackReason { get; set; } } }