UnityTowerDefense/Assets/_Project/Scripts/Gameplay/GoldConfig.cs
Matt F 4892d7253d First pass at full refactor to 2.0 design
Restructures the game around the cyclical run loop from Game Design Doc V2:
5 waves = a cycle, 3 cycles = a phase, each phase ends in a boss.

- New TD.Gameplay.Waves: WaveGroup / PhaseDefinition / RunDefinition author the
  run as draggable weighted pools; RunState owns phase/cycle position, the drawn
  wave slots, and the per-slot enemy buff sets. WaveManager's flat wave array is
  gone -- it now only runs the encounter RunState points at.
- New TD.Gameplay.EnemyUpgrades: the post-wave enemy-buff vote, with public
  live-replicated ballots so the HUD can show who voted for what.
- Inter-wave flow is now strictly sequential: draft -> vote -> build, each stage
  ending early once every player has acted.
- Enemy abilities inverted from per-instance random rolls to deterministic,
  stacking per-wave-slot sets. Six cards ship: Split (reworked), Flight, Blink,
  No Bounty, Gold Theft, Double Up.
- Tower upgrades are a two-step tree: a draft pick unlocks a node, gold converts
  an already-placed tower in place.
- Boss encounters flag their enemies and drive a boss HP bar.
- Player cap reduced to 3 via MatchRules.MaxPlayers.
- GoldConfig is now keyed by global encounter number rather than wave index.

Compiles clean; NOT yet verified in-engine. Editor wiring still required --
see Docs/2.0_Setup_Checklist.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 17:45:11 -07:00

120 lines
5.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Assets/_Project/Scripts/Gameplay/GoldConfig.cs
using System;
using UnityEngine;
namespace TD.Gameplay
{
/// <summary>
/// Per-wave gold rules nested under <see cref="GoldConfig"/>. Each entry maps to one
/// wave by array index in <see cref="GoldConfig.Waves"/>.
/// </summary>
/// <remarks>
/// <b>Pairing with WaveDefinition.</b> The runtime pairing of gold entry to wave is by
/// array index — entry 0 applies to wave 1, etc. The <see cref="Wave"/> reference here
/// is OPTIONAL and exists purely so the inspector can compute and display the
/// <see cref="PreviewTotalGold"/> read-only value (which needs to know the wave's
/// total enemy count to multiply against <see cref="GoldPerEnemy"/>). Leaving it
/// unset just hides the kill-reward portion of the preview; runtime is unaffected.
/// </remarks>
[Serializable]
public class WaveGoldEntry
{
[Tooltip("Optional reference to the WaveDefinition this entry pairs with. " +
"Used ONLY for the inspector preview total — runtime pairing is by " +
"array index in GoldConfig.Waves. Drag the matching WaveDefinition " +
"here to see the live total computed under this entry.")]
public WaveDefinition Wave;
[Tooltip("Gold awarded to the killing player per enemy slain during this wave. " +
"Applies uniformly to every enemy type in the wave.")]
public int GoldPerEnemy = 10;
[Tooltip("Flat bonus gold awarded to every active player when this wave is fully " +
"cleared (all enemies dead or reached the goal).")]
public int CompletionBonus = 50;
[Tooltip("Extra bonus gold awarded to a player who cleared this wave without any " +
"enemy from their own zone escaping their leak volume. Stacks on top of " +
"CompletionBonus for the no-leak achievement.")]
public int NoLeaksBonus = 50;
/// <summary>
/// Read-only preview of the maximum gold a single player could earn in this wave:
/// <c>GoldPerEnemy × totalEnemyCount + CompletionBonus + NoLeaksBonus</c>.
/// </summary>
/// <remarks>
/// Returns just <c>CompletionBonus + NoLeaksBonus</c> if <see cref="Wave"/> is null
/// (no enemy count to multiply against). Pure computation — safe at edit time and
/// at runtime. The custom inspector under <see cref="GoldConfig"/> renders this as
/// a non-editable label under the entry's fields.
/// </remarks>
public int PreviewTotalGold
{
get
{
int total = CompletionBonus + NoLeaksBonus;
if (Wave != null && Wave.Entries != null)
{
int enemies = 0;
foreach (var e in Wave.Entries)
{
if (e.EnemyType != null && e.Count > 0)
enemies += e.Count;
}
total += GoldPerEnemy * enemies;
}
return total;
}
}
}
/// <summary>
/// Single source of truth for every gold-related tunable in the game.
/// </summary>
/// <remarks>
/// <b>Inspector layout.</b> StartingGold is at the top and applies to every player.
/// The Waves array follows, each element collapsible in the inspector with the
/// per-wave rules and a read-only preview total computed from the entry's data.
///
/// <b>Wiring.</b> Assign one GoldConfig asset to <c>WaveManager.goldConfig</c> in
/// the Match scene. WaveManager initializes per-player starting gold from
/// <see cref="StartingGold"/> at match start, uses <see cref="WaveGoldEntry.GoldPerEnemy"/>
/// to compute kill rewards, and awards <see cref="WaveGoldEntry.CompletionBonus"/> /
/// <see cref="WaveGoldEntry.NoLeaksBonus"/> when each wave clears.
///
/// <b>No per-enemy-type bounties.</b> Every enemy killed in wave N grants the same
/// <see cref="WaveGoldEntry.GoldPerEnemy"/> regardless of <see cref="EnemyDefinition"/>.
/// If per-type variation becomes a design need, extend WaveGoldEntry with a per-type
/// table; the current model intentionally favors simplicity.
/// </remarks>
[CreateAssetMenu(fileName = "GoldConfig", menuName = "TD/Gold Config", order = 2)]
public class GoldConfig : ScriptableObject
{
[Tooltip("Gold each player starts the match with. Same for every player.")]
public int StartingGold = 100;
[Tooltip("Per-encounter gold rules. Element 0 = the run's first encounter. Encounters " +
"are counted continuously across the whole run — cycles do NOT restart the " +
"count — so a phase of 5 waves × 3 cycles + a boss needs 16 entries. Missing " +
"entries fall back to zero gold for that encounter.")]
public WaveGoldEntry[] Waves;
/// <summary>
/// Returns the gold entry for the given 1-based encounter number
/// (<c>RunState.GlobalEncounterNumber</c>), or null if out of range.
/// </summary>
/// <remarks>
/// Keyed by <b>encounter</b>, not by wave slot. Under the cyclical run structure the same
/// five waves come round three times per phase, so a slot-keyed table would pay the same
/// on cycle 3 as on cycle 1 while the enemies got steadily stronger. A run-long counter
/// lets payouts climb monotonically with difficulty.
/// </remarks>
public WaveGoldEntry GetWaveEntry(int encounterNumber)
{
if (Waves == null) return null;
int index = encounterNumber - 1;
if (index < 0 || index >= Waves.Length) return null;
return Waves[index];
}
}
}