UnityTowerDefense/Assets/_Project/Scripts/Gameplay/Waves/WaveGroup.cs
Matt F 16706a1ecf Scale difficulty by run position; wipe towers at phase end
Follow-up pass on the 2.0 refactor, closing the gap between "waves are drawn
at random" and "difficulty still lives in the enemy assets".

- Enemy health is no longer authored per enemy type. New EnemyScalingConfig
  scales a per-zone HP *budget* by encounter number; per-enemy health is that
  budget divided by the wave's enemy count. Scaling the total rather than the
  per-enemy value keeps enemy count a texture knob (few tanks vs many swarmers)
  instead of a second, uncontrolled difficulty axis. EnemyDefinition.MaxHp
  becomes HpMultiplier, a deviation around 1.0. Speed stays archetype-only and
  unscaled -- escalating it compounds with health and invalidates tower balance
  mid-run.
- GoldConfig entries no longer reference a WaveDefinition. Payout is a property
  of run position, not of which wave got drawn: WaveGoldEntry ->
  EncounterGoldEntry, Waves -> Encounters, keyed by global encounter number.
  Its inspector labels elements "Encounter N" and projects cumulative earnings.
- The wave's voted buffs now show as icon badges in the top bar, read from the
  same RunState slot the spawn path uses so the display can't drift from what
  the enemies actually carry. Hover names the buff; unillustrated cards fall
  back to a lettered badge rather than vanishing.
- Clearing a phase's boss destroys every built tower, unrefunded. Queued build
  jobs still refund -- those towers were never delivered. Runs with the field
  empty, so the walkability churn doesn't hit the re-path scheduler.
- Fixed an RPC codegen break: [ClientRpc] requires a ClientRpc suffix, unlike
  the newer [Rpc(SendTo...)] style this file doesn't use.
- Setup checklist reordered into dependency order; it previously asked for a
  RunDefinition two sections before creating one.

Also carries the editor-side asset reorganisation into Definitions/RunDefinitions
and the sprite move into Enemy/Player draft icon folders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 00:32:33 -07:00

69 lines
3.4 KiB
C#

// Assets/_Project/Scripts/Gameplay/Waves/WaveGroup.cs
using System;
using UnityEngine;
using TD.Core;
namespace TD.Gameplay.Waves
{
/// <summary>
/// One weighted candidate inside a <see cref="WaveGroup"/>: a wave that may be drawn,
/// and how likely it is to be drawn relative to the group's other entries.
/// </summary>
/// <remarks>
/// <b>Weight is RELATIVE, not an absolute probability.</b> 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.
///
/// <para>A class rather than a struct specifically so <see cref="Weight"/> 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
/// <see cref="EnemyUpgrades.EnemyUpgradePoolEntry"/>.</para>
/// </remarks>
[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;
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// <b>Why weights live on the entry, not on <see cref="WaveDefinition"/>.</b> Weight is a
/// property of "how common is this wave <i>in this pool</i>", not of the wave itself. Keeping
/// it here means the same <see cref="WaveDefinition"/> 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.
///
/// <para><b>Boss groups use the same type.</b> A boss wave is just a
/// <see cref="WaveDefinition"/> whose entries spawn a single powerful enemy, so boss pools
/// are ordinary <see cref="WaveGroup"/>s referenced from
/// <see cref="PhaseDefinition.BossGroups"/>.</para>
/// </remarks>
[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;
/// <summary>Name used in validation and log messages. Falls back to the asset name.</summary>
public string Label => string.IsNullOrWhiteSpace(DisplayName) ? name : DisplayName;
/// <summary>Number of entries, including any that are null or zero-weight.</summary>
public int Count => Waves?.Length ?? 0;
}
}