New SpellMetadata + SpellTable. Loads docs/research/data/spells.csv at GameWindow construction (3,956 spells x 11 useful fields including Family for buff stacking which issue #6 needs). The CSV is copied to bin/<config>/net10.0/data/spells.csv via the csproj <None Include> entry; SpellTable.LoadFromCsv resolves relative to AppContext.BaseDirectory. Hand-rolled CSV parser handles RFC 4180 quoted fields with embedded commas (the Description column) + escaped double-quotes ("" -> "). No external CsvHelper dep. Falls back to SpellTable.Empty + console warning if the file is missing (tooling contexts). Spellbook now accepts an optional SpellTable in its constructor + exposes TryGetMetadata(spellId, out SpellMetadata). When the table is absent (legacy `new Spellbook()` calls), TryGetMetadata returns false gracefully so existing tests keep passing. GameWindow: - SpellTable field initialized via LoadSpellTable() helper that handles the missing-file case + emits the spells: loaded N entries log line. - SpellBook field constructor-initialized with the loaded SpellTable so TryGetMetadata works for the live session. 10 new tests (SpellTableTests): - Empty table behavior - Header-only loads to empty - Single row populates all metadata - Quoted Description with embedded commas - Blank lines skipped - Bad-spell-id rows silently skipped (third-party data is messy) - Unknown spell-id lookup returns false - ParseRow primitive: simple comma split, quoted-field with comma, escaped double-quote. Total tests: 818 -> 828. Closes #11. Phase G (issue #6 — fold enchantment buffs into vital max via EnchantmentMath using SpellTable.Family for stacking) unblocked. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
44 lines
1.5 KiB
C#
44 lines
1.5 KiB
C#
namespace AcDream.Core.Spells;
|
|
|
|
/// <summary>
|
|
/// Per-spell static metadata loaded once at startup from
|
|
/// <c>docs/research/data/spells.csv</c> via <see cref="SpellTable"/>.
|
|
/// One record per known spell id (3,956 entries in the retail dump).
|
|
///
|
|
/// <para>
|
|
/// Used for:
|
|
/// </para>
|
|
/// <list type="bullet">
|
|
/// <item>Buff / debuff bar labels (<see cref="Name"/>,
|
|
/// <see cref="School"/>, <see cref="IconId"/>).</item>
|
|
/// <item>Stacking aggregation (<see cref="Family"/> — only one
|
|
/// enchantment per family-bucket is active; this is what
|
|
/// <c>EnchantmentMath</c> uses to filter out superseded
|
|
/// buffs in <c>LocalPlayerState.GetMaxApprox</c>).</item>
|
|
/// <item>Spell tooltips (<see cref="Description"/>,
|
|
/// <see cref="ManaCost"/>, <see cref="Duration"/>,
|
|
/// <see cref="SpellWords"/>).</item>
|
|
/// <item>Cast-bar audio + animation cues
|
|
/// (<see cref="SpellWords"/> drives the chant).</item>
|
|
/// </list>
|
|
///
|
|
/// <para>
|
|
/// Fields not exposed (yet) from the 35-column source CSV: SortKey,
|
|
/// Difficulty, Flags, Generation, IsFastWindup, IsIrresistible,
|
|
/// IsOffensive, IsUntargetted, Speed, CasterEffect, TargetEffect,
|
|
/// TargetMask, Type, plus 10 anonymous Unknown1..10. Add them on
|
|
/// demand as panels grow.
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed record SpellMetadata(
|
|
uint SpellId,
|
|
string Name,
|
|
string School,
|
|
uint Family,
|
|
uint IconId,
|
|
string SpellWords,
|
|
float Duration,
|
|
int ManaCost,
|
|
bool IsDebuff,
|
|
bool IsFellowship,
|
|
string Description);
|