// Assets/_Project/Scripts/Gameplay/EnemyUpgrades/EnemyUpgradeGroup.cs using System; using System.Collections.Generic; using UnityEngine; using TD.Core; namespace TD.Gameplay.EnemyUpgrades { /// /// One weighted candidate inside an . /// /// /// Weight is relative and normalized at draw time — see . /// Kept on the entry rather than on the option asset for the same reason wave weights are: /// a card can be a staple of one phase's pool and a rarity in another's. /// [Serializable] public class EnemyUpgradePoolEntry { [Tooltip("The enemy-buff card that may be offered from this group.")] public EnemyUpgradeOption Option; [Tooltip("Relative draw weight within the pool this group contributes to. Entries at " + "equal weight are equally likely; 0.5 is half as likely as 1.0.")] [PoolWeight("buff")] public float Weight = 1f; } /// /// A named, reusable bag of enemy-buff cards. Groups are the unit designers move between /// phases: dragging a group into Phase 2's list makes every card in it votable from phase 2 /// onward, without touching the cards themselves. /// /// /// Buffs are gated by phase, not by cycle — every card a phase allows is votable from /// its very first wave. Gating by cycle was considered and rejected: the whole point of the /// cyclical structure is that players choose how hard to make their own next lap, and holding /// the interesting cards back until cycle 3 would flatten that decision into a formality. /// [CreateAssetMenu(fileName = "EnemyUpgradeGroup", menuName = "TD/Enemy Upgrades/Upgrade Group", order = 21)] public class EnemyUpgradeGroup : ScriptableObject { [Tooltip("Designer-facing name, shown in validation messages. Falls back to the asset name.")] public string DisplayName; [Tooltip("Cards in this group, each with a relative draw weight.")] public EnemyUpgradePoolEntry[] Options; /// Name used in validation and log messages. Falls back to the asset name. public string Label => string.IsNullOrWhiteSpace(DisplayName) ? name : DisplayName; /// /// Appends every non-null, non-zero-weight entry across into /// . Zero-weight entries are filtered here so "weight 0 means it /// can never appear" lives in exactly one place. /// public static void CollectCandidates(EnemyUpgradeGroup[] groups, List into) { into.Clear(); if (groups == null) return; foreach (var group in groups) { if (group?.Options == null) continue; foreach (var entry in group.Options) { if (entry?.Option == null) continue; if (entry.Weight <= 0f) continue; into.Add(entry); } } } } }