Decompiled source of UpgradeDraft v1.0.3

plugins\UpgradeDraft\UpgradeDraft.dll

Decompiled an hour ago
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 HarmonyLib;
using Microsoft.CodeAnalysis;
using ModdingUtils.Utils;
using UnityEngine;
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.3")]
	[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.3";

		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 UpgradeDraftState State { get; private set; }

		internal static NetworkAuthorityService AuthorityService { get; private set; }

		internal static SkipRerollService SkipRerollService { get; private set; }

		internal ConfigEntry<int> NormalCardCount { get; private set; }

		internal ConfigEntry<int> ChanceZeroUpgrades { get; private set; }

		internal ConfigEntry<int> ChanceOneUpgrade { get; private set; }

		internal ConfigEntry<int> ChanceTwoUpgrades { get; private set; }

		internal ConfigEntry<int> ChanceThreeUpgrades { get; private set; }

		internal ConfigEntry<bool> EnableUpgradeVisuals { get; private set; }

		internal ConfigEntry<bool> EnableStatPreview { get; private set; }

		internal ConfigEntry<string> UpgradeBlacklist { get; private set; }

		internal ConfigEntry<bool> VerboseLogging { get; private set; }

		internal ConfigEntry<bool> EnableSkipForPoint { get; private set; }

		internal ConfigEntry<int> SkipPointsForReroll { get; private set; }

		internal ConfigEntry<bool> EnableSkipRerollOverlay { get; private set; }

		internal ConfigEntry<bool> AllowMultipleRerollsPerPick { get; private set; }

		private void Awake()
		{
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: 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);
			AuthorityService = new NetworkAuthorityService(((BaseUnityPlugin)this).Logger);
			SkipRerollService = new SkipRerollService(((BaseUnityPlugin)this).Logger, this);
			if ((Object)(object)((Component)this).GetComponent<SkipRerollOverlay>() == (Object)null)
			{
				((Component)this).gameObject.AddComponent<SkipRerollOverlay>();
			}
			_harmony = new Harmony("com.damian.rounds.upgradedraft");
			_harmony.PatchAll();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Upgrade Draft 1.0.3 loaded.");
		}

		private void OnDestroy()
		{
			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<string> GetBlacklist()
		{
			return new HashSet<string>(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<int>("General", "NormalCardCount", 4, "Number of visible card choices per draft pick. Values other than 4 are experimental.");
			ChanceZeroUpgrades = ((BaseUnityPlugin)this).Config.Bind<int>("Chances", "ChanceZeroUpgrades", 25, "Chance weight for showing 0 upgrade cards.");
			ChanceOneUpgrade = ((BaseUnityPlugin)this).Config.Bind<int>("Chances", "ChanceOneUpgrade", 50, "Chance weight for showing 1 upgrade card.");
			ChanceTwoUpgrades = ((BaseUnityPlugin)this).Config.Bind<int>("Chances", "ChanceTwoUpgrades", 20, "Chance weight for showing 2 upgrade cards.");
			ChanceThreeUpgrades = ((BaseUnityPlugin)this).Config.Bind<int>("Chances", "ChanceThreeUpgrades", 5, "Chance weight for showing 3 upgrade cards.");
			EnableUpgradeVisuals = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "EnableUpgradeVisuals", true, "Adds UPGRADE visuals and ownership info to upgrade cards.");
			EnableStatPreview = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "EnableStatPreview", true, "Appends conservative stacked stat preview lines for upgrade cards.");
			EnableSkipRerollOverlay = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "EnableSkipRerollOverlay", true, "Show skip/reroll buttons during card pick.");
			UpgradeBlacklist = ((BaseUnityPlugin)this).Config.Bind<string>("Rules", "UpgradeBlacklist", string.Empty, "Comma-separated card names that are not allowed as upgrades.");
			EnableSkipForPoint = ((BaseUnityPlugin)this).Config.Bind<bool>("Rules", "EnableSkipForPoint", true, "Allow players to skip card pick and gain one skip point.");
			SkipPointsForReroll = ((BaseUnityPlugin)this).Config.Bind<int>("Rules", "SkipPointsForReroll", 2, "Skip points required (and consumed) to reroll cards.");
			AllowMultipleRerollsPerPick = ((BaseUnityPlugin)this).Config.Bind<bool>("Rules", "AllowMultipleRerollsPerPick", false, "Allow more than one reroll during a single card pick when player has enough points.");
			VerboseLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("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 SkipRerollOverlay : MonoBehaviour
	{
		private GUIStyle _buttonStyle;

		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_0024: Expected O, but got Unknown
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: 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_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Expected O, but got Unknown
			//IL_0063: 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_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: 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: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Expected O, but got Unknown
			_buttonStyle = new GUIStyle(GUI.skin.button)
			{
				fontSize = 18,
				alignment = (TextAnchor)4
			};
			GUIStyle val = new GUIStyle(GUI.skin.label)
			{
				fontSize = 16,
				alignment = (TextAnchor)4
			};
			val.normal.textColor = Color.white;
			_labelStyle = val;
			GUIStyle val2 = new GUIStyle(GUI.skin.label)
			{
				fontSize = 18,
				fontStyle = (FontStyle)1,
				alignment = (TextAnchor)4
			};
			val2.normal.textColor = Color.yellow;
			_titleStyle = val2;
		}

		private void Update()
		{
			if (!IsReady())
			{
				return;
			}
			CardChoice instance = CardChoice.instance;
			int pickrID = instance.pickrID;
			if (UpgradeDraftPlugin.SkipRerollService.IsCardChoiceOpen(instance) && !UpgradeDraftPlugin.SkipRerollService.IsCardChoiceBusy(instance) && UpgradeDraftPlugin.SkipRerollService.IsLocalClientControllingPick(instance))
			{
				if (Input.GetKeyDown((KeyCode)115))
				{
					UpgradeDraftPlugin.SkipRerollService.TrySkip(instance, pickrID);
				}
				if (Input.GetKeyDown((KeyCode)114))
				{
					UpgradeDraftPlugin.SkipRerollService.TryReroll(instance, pickrID);
				}
			}
		}

		private void OnGUI()
		{
			//IL_0084: 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)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: 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_0249: Unknown result type (might be due to invalid IL or missing references)
			//IL_028b: 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) || !UpgradeDraftPlugin.SkipRerollService.IsLocalClientControllingPick(instance))
			{
				return;
			}
			int skipPoints = UpgradeDraftPlugin.State.GetSkipPoints(pickrID);
			int skipPointsForReroll = UpgradeDraftPlugin.Instance.GetSkipPointsForReroll();
			float num = 430f;
			float num2 = 190f;
			float num3 = ((float)Screen.width - num) * 0.5f;
			float num4 = (float)Screen.height - num2 - 30f;
			GUI.Box(new Rect(num3, num4, num, num2), string.Empty);
			GUI.Label(new Rect(num3 + 8f, num4 + 8f, num - 16f, 28f), "Upgrade Draft: Skip & Reroll", _titleStyle);
			GUI.Label(new Rect(num3 + 8f, num4 + 38f, num - 16f, 24f), $"Skip Points: {skipPoints}", _labelStyle);
			GUI.Label(new Rect(num3 + 8f, num4 + 60f, num - 16f, 24f), $"Reroll Cost: {skipPointsForReroll} points", _labelStyle);
			Rect val = default(Rect);
			((Rect)(ref val))..ctor(num3 + 18f, num4 + 92f, (num - 54f) / 2f, 42f);
			Rect val2 = default(Rect);
			((Rect)(ref val2))..ctor(((Rect)(ref val)).xMax + 18f, num4 + 92f, (num - 54f) / 2f, 42f);
			bool flag = UpgradeDraftPlugin.SkipRerollService.IsCardChoiceBusy(instance);
			GUI.enabled = !flag && UpgradeDraftPlugin.Instance.IsSkipForPointEnabled();
			if (GUI.Button(val, "Skip (+1 point)  [S]", _buttonStyle))
			{
				UpgradeDraftPlugin.SkipRerollService.TrySkip(instance, pickrID);
			}
			string reason;
			bool flag2 = UpgradeDraftPlugin.SkipRerollService.CanReroll(instance, pickrID, out reason);
			bool num5 = skipPoints >= skipPointsForReroll;
			if (num5)
			{
				GUI.enabled = !flag && flag2;
				if (GUI.Button(val2, "Reroll (-points)  [R]", _buttonStyle))
				{
					UpgradeDraftPlugin.SkipRerollService.TryReroll(instance, pickrID);
				}
			}
			GUI.enabled = true;
			if (!num5)
			{
				GUI.Label(new Rect(num3 + 8f, num4 + 140f, num - 16f, 24f), $"Need {skipPointsForReroll} skip points to reroll.", _labelStyle);
			}
			else if (!flag2)
			{
				GUI.Label(new Rect(num3 + 8f, num4 + 140f, num - 16f, 24f), "Reroll unavailable right now.", _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
			{
				UpgradeCardMarker obj = spawnedCard.GetComponent<UpgradeCardMarker>() ?? spawnedCard.AddComponent<UpgradeCardMarker>();
				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));
			}
		}

		private void ApplyTextComponents(GameObject spawnedCard, DraftCardEntry entry)
		{
			string value = _previewService.BuildUpgradeTitle(entry);
			string value2 = BuildShortUpgradeDescription(entry);
			List<Component> list = (from c in spawnedCard.GetComponentsInChildren<Component>(true)
				where (Object)(object)c != (Object)null
				where HasStringTextProperty(((object)c).GetType())
				select c).ToList();
			if (list.Count == 0)
			{
				_logger.LogWarning((object)("Upgrade card '" + ((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, value);
						flag = true;
					}
					else if (text.Contains("desc") || text.Contains("description"))
					{
						SetTextProperty(item, value2);
						flag = true;
					}
				}
			}
			if (!flag)
			{
				_logger.LogWarning((object)("Upgrade card '" + ((Object)spawnedCard).name + "' did not expose recognizable title/description text components."));
			}
		}

		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<DraftCardEntry> BuildDraftEntries(CardChoice cardChoice, Player player, DraftPlan plan)
		{
			HashSet<string> blacklist = _plugin.GetBlacklist();
			List<CardInfo> eligibleUpgradeCards = _deckService.GetEligibleUpgradeCards(player, blacklist);
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			List<DraftCardEntry> list = new List<DraftCardEntry>();
			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<CardInfo> 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<CardInfo> SelectNewCards(CardChoice cardChoice, Player player, int wanted, ISet<string> existingCardIds, bool strictUnique, bool strictNotOwned)
		{
			List<CardInfo> list = new List<CardInfo>();
			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<CardInfo, Player, Gun, GunAmmo, CharacterData, HealthHandler, Gravity, Block, CharacterStatModifiers, bool> BuildSelectionCondition(CardChoice cardChoice, Player player, ISet<string> selectedIds, IEnumerable<CardInfo> localNewCards, bool strictUnique, bool strictNotOwned)
		{
			HashSet<string> localIds = new HashSet<string>(localNewCards.Where((CardInfo c) => c != null).Select(_deckService.GetStableCardId), StringComparer.Ordinal);
			Func<CardInfo, Player, bool> 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<CardInfo, Player, bool> 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>();
							Gun component2 = ((Component)card).GetComponent<Gun>();
							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<CardInfo> PickRandomDistinct(List<CardInfo> cards, int amount)
		{
			List<CardInfo> list = new List<CardInfo>();
			if (cards == null || cards.Count == 0 || amount <= 0)
			{
				return list;
			}
			List<CardInfo> 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 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;
		}
	}
	internal sealed class NetworkAuthorityService
	{
		private readonly ManualLogSource _logger;

		public NetworkAuthorityService(ManualLogSource logger)
		{
			_logger = logger;
		}

		public bool IsAuthoritativeClient()
		{
			try
			{
				Type type = Type.GetType("Photon.Pun.PhotonNetwork, PhotonUnityNetworking");
				if (type != null)
				{
					PropertyInfo property = type.GetProperty("IsMasterClient", BindingFlags.Static | BindingFlags.Public);
					if (property != null)
					{
						return (bool)property.GetValue(null, null);
					}
				}
				Type type2 = Type.GetType("PhotonNetwork, Assembly-CSharp");
				if (type2 != null)
				{
					PropertyInfo property2 = type2.GetProperty("isMasterClient", BindingFlags.Static | BindingFlags.Public);
					if (property2 != null)
					{
						return (bool)property2.GetValue(null, null);
					}
				}
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)("Failed to query network authority. Defaulting to local-authoritative execution. Error: " + ex.Message));
			}
			return true;
		}
	}
	public sealed class PlayerDeckService
	{
		private readonly ManualLogSource _logger;

		private readonly UpgradeDraftPlugin _plugin;

		public PlayerDeckService(ManualLogSource logger, UpgradeDraftPlugin plugin)
		{
			_logger = logger;
			_plugin = plugin;
		}

		public List<CardInfo> GetOwnedCards(Player player)
		{
			if (player == null)
			{
				return new List<CardInfo>();
			}
			try
			{
				object fieldOrPropertyValue = ReflectionUtils.GetFieldOrPropertyValue(player, "data");
				if (fieldOrPropertyValue == null)
				{
					return new List<CardInfo>();
				}
				List<CardInfo> list = ReflectionUtils.ToTypedList<CardInfo>(ReflectionUtils.GetFieldOrPropertyValue(fieldOrPropertyValue, "currentCards"));
				if (list.Count > 0)
				{
					return list;
				}
				list = ReflectionUtils.ToTypedList<CardInfo>(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<CardInfo>();
		}

		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<CardInfo> GetEligibleUpgradeCards(Player player, ISet<string> blacklistLower)
		{
			List<CardInfo> ownedCards = GetOwnedCards(player);
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			List<CardInfo> list = new List<CardInfo>();
			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<string> 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<string> 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<T> ToTypedList<T>(object maybeList) where T : class
		{
			if (maybeList == null)
			{
				return new List<T>();
			}
			if (maybeList is IList<T> source)
			{
				return source.Where((T item) => item != null).ToList();
			}
			if (maybeList is IEnumerable enumerable)
			{
				List<T> list = new List<T>();
				{
					foreach (object item3 in enumerable)
					{
						if (item3 is T item2)
						{
							list.Add(item2);
						}
					}
					return list;
				}
			}
			return new List<T>();
		}
	}
	public sealed class SkipRerollService
	{
		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 TrySkip(CardChoice cardChoice, int pickerId)
		{
			if (!_plugin.IsSkipForPointEnabled())
			{
				return false;
			}
			if (cardChoice == null || pickerId < 0)
			{
				return false;
			}
			if (IsCardChoiceBusy(cardChoice))
			{
				return false;
			}
			try
			{
				cardChoice.Pick((GameObject)null, true);
				cardChoice.pickrID = -1;
				int num = UpgradeDraftPlugin.State.AddSkipPoint(pickerId);
				UpgradeDraftPlugin.State.ClearDraftPlanOnly("Skip pressed.");
				_logger.LogInfo((object)$"Player {pickerId} skipped card pick and gained 1 skip point (total: {num}).");
				return true;
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)$"Failed to skip pick for player {pickerId}: {ex.Message}");
				return false;
			}
		}

		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 (!_plugin.IsMultipleRerollsPerPickAllowed() && UpgradeDraftPlugin.State.RerolledThisPick)
			{
				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;
			}
			try
			{
				UpgradeDraftPlugin.State.ClearDraftPlanOnly("Reroll requested.");
				Traverse.Create((object)cardChoice).Field("picks").SetValue((object)_plugin.GetCardCount());
				if (AccessTools.Method(((object)cardChoice).GetType(), "ReplaceCards", (Type[])null, (Type[])null).Invoke(cardChoice, new object[2] { null, true }) is IEnumerator enumerator)
				{
					((MonoBehaviour)cardChoice).StartCoroutine(enumerator);
					UpgradeDraftPlugin.State.MarkRerolledThisPick();
					int skipPoints = UpgradeDraftPlugin.State.GetSkipPoints(pickerId);
					_logger.LogInfo((object)$"Player {pickerId} rerolled card choices. Spent {skipPointsForReroll} skip points (remaining: {skipPoints}).");
					return true;
				}
				_logger.LogWarning((object)"CardChoice.ReplaceCards did not return IEnumerator. Reroll request could not start coroutine.");
				return false;
			}
			catch (Exception ex)
			{
				_logger.LogWarning((object)$"Failed to reroll cards for player {pickerId}: {ex.Message}");
				return false;
			}
		}
	}
	public sealed class UpgradeDraftState
	{
		private readonly ManualLogSource _logger;

		private readonly Queue<DraftCardEntry> _pendingEntries = new Queue<DraftCardEntry>();

		private readonly Dictionary<int, DraftCardEntry> _spawnedCardMap = new Dictionary<int, DraftCardEntry>();

		private readonly Dictionary<int, int> _skipPointsByPlayerId = new Dictionary<int, int>();

		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<DraftCardEntry> 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<string> list = new List<string>();
			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), "StartPick")]
	internal static class CardChoiceStartPickPatch
	{
		private static void Prefix(ref int picksToSet, int pickerIDToSet)
		{
			UpgradeDraftPlugin instance = UpgradeDraftPlugin.Instance;
			if (!((Object)(object)instance == (Object)null) && UpgradeDraftPlugin.State != null)
			{
				picksToSet = instance.GetCardCount();
				UpgradeDraftPlugin.State.Clear("Starting new StartPick flow.");
				UpgradeDraftPlugin.State.BeginPick(pickerIDToSet);
			}
		}
	}
	[HarmonyPatch(typeof(CardChoice), "Pick")]
	internal static class CardChoicePickPatch
	{
		private static void Prefix(CardChoice __instance)
		{
			if (UpgradeDraftPlugin.State != null)
			{
				UpgradeDraftPlugin.State.Clear("Starting new Pick flow.");
				UpgradeDraftPlugin.State.BeginPick(__instance.pickrID);
			}
		}
	}
	[HarmonyPatch(typeof(CardChoice), "DoPick")]
	internal static class CardChoiceDoPickPatch
	{
		private static void Prefix(CardChoice __instance, ref int picksToSet, int picketIDToSet)
		{
			UpgradeDraftPlugin instance = UpgradeDraftPlugin.Instance;
			if (!((Object)(object)instance == (Object)null) && UpgradeDraftPlugin.State != null)
			{
				picksToSet = instance.GetCardCount();
				UpgradeDraftPlugin.State.Clear("Starting new DoPick flow.");
				UpgradeDraftPlugin.State.BeginPick(picketIDToSet);
			}
		}
	}
	[HarmonyPatch(typeof(CardChoice), "SpawnUniqueCard")]
	internal static class CardChoiceSpawnUniqueCardPatch
	{
		private static bool Prefix(ref GameObject __result, CardChoice __instance, Vector3 pos, Quaternion rot)
		{
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: 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
			{
				if (!UpgradeDraftPlugin.AuthorityService.IsAuthoritativeClient())
				{
					if (instance.IsVerboseLoggingEnabled())
					{
						UpgradeDraftPlugin.Log.LogInfo((object)"Non-authoritative client detected; letting vanilla SpawnUniqueCard run.");
					}
					return true;
				}
				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;
				}
				if (entry.IsUpgrade)
				{
					UpgradeDraftPlugin.VisualService.ApplyUpgradeVisual(val2, entry);
				}
				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<DraftCardEntry> list = UpgradeDraftPlugin.SelectionService.BuildDraftEntries(cardChoice, pickingPlayer, draftPlan);
			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 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<CardInfo>();
				if (component != null)
				{
					component.sourceCard = sourceCard;
				}
				DisableChoiceCardCollider(val);
				return val;
			}
			catch (Exception ex)
			{
				UpgradeDraftPlugin.Log.LogWarning((object)("Failed to spawn custom card object for '" + sourceCard.cardName + "': " + ex.Message));
				return null;
			}
		}

		private static void DisableChoiceCardCollider(GameObject spawned)
		{
			try
			{
				DamagableEvent componentInChildren = spawned.GetComponentInChildren<DamagableEvent>();
				if (componentInChildren != null)
				{
					Collider2D component = ((Component)componentInChildren).GetComponent<Collider2D>();
					if (component != null)
					{
						((Behaviour)component).enabled = false;
					}
				}
			}
			catch (Exception)
			{
			}
		}
	}
}
namespace UpgradeDraft.Models
{
	public sealed class DraftCardEntry
	{
		public CardInfo Card { get; }

		public bool IsUpgrade { 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);
		}
	}
	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; }
	}
}