// Assets/_Project/Scripts/Gameplay/Waves/WaveGroup.cs using System; using UnityEngine; using TD.Core; namespace TD.Gameplay.Waves { /// /// One weighted candidate inside a : a wave that may be drawn, /// and how likely it is to be drawn relative to the group's other entries. /// /// /// Weight is RELATIVE, not an absolute probability. The draw normalizes every /// candidate's weight against the summed weight of the whole candidate set, so three /// entries at 1.0 each are equally likely (33% apiece) and an entry at 0.5 is half as /// likely to be drawn as one at 1.0. This is what makes the default sane: new entries /// start at 1.0, so adding a wave to a group never silently re-weights the others. /// /// A class rather than a struct specifically so can carry a /// field initializer — Unity runs it when the inspector creates a fresh array element, /// which is what gives new entries their equal-by-default weighting. Mirrors /// . /// [Serializable] public class WavePoolEntry { [Tooltip("The wave that may be drawn from this group.")] public WaveDefinition Wave; [Tooltip("Relative draw weight within this group's pool. Entries at equal weight are " + "equally likely; an entry at 0.5 is half as likely as one at 1.0. NOT an " + "absolute percentage.")] [PoolWeight("wave")] public float Weight = 1f; } /// /// A named, reusable bag of candidate waves. Groups are the unit designers move between /// phases: dragging a group asset from Phase 1's pool to Phase 2's moves every wave in it /// (and their weights) in one action. /// /// /// Why weights live on the entry, not on . Weight is a /// property of "how common is this wave in this pool", not of the wave itself. Keeping /// it here means the same asset can be a staple of one group and /// a rarity in another, and a group carries its tuning with it when moved between phases. /// /// Boss groups use the same type. A boss wave is just a /// whose entries spawn a single powerful enemy, so boss pools /// are ordinary s referenced from /// . /// [CreateAssetMenu(fileName = "WaveGroup", menuName = "TD/Run/Wave Group", order = 10)] public class WaveGroup : ScriptableObject { [Tooltip("Designer-facing name for this group, shown in run-structure validation " + "messages. Falls back to the asset name when empty.")] public string DisplayName; [Tooltip("Candidate waves in this group, each with a relative draw weight.")] public WavePoolEntry[] Waves; /// Name used in validation and log messages. Falls back to the asset name. public string Label => string.IsNullOrWhiteSpace(DisplayName) ? name : DisplayName; /// Number of entries, including any that are null or zero-weight. public int Count => Waves?.Length ?? 0; } }