Files
TheriapolisV3/Theriapolis.Core/Data/ClassDef.cs
T
Christopher Wiebe 39117a09ed M6.17: Variant content + Sheep/Goat split + calling lore + uniform card layout
Species variants populated against the M6.13 schema:
- Lion-Folk sex axis: Mane Guard (male) / Huntress Reflexes (female,
  +5 ft speed + advantage on initiative).
- Elk-Folk sex axis: Antler Combat with 10 ft reach when full rack
  (male, retains seasonal Antler Drag) / Kick (female, prone on crit).
  Base traits restored to doc canon: Herd Coordination (Help → +3) +
  Endurance Runner (40 ft + advantage CON vs forced march); base speed
  bumped 30 → 40; new base detriment Herd Instinct.

Ram-Folk replaced with separate Sheep-Folk + Goat-Folk species rather
than a lineage-axis variant on a single Ram entry. Bovidae now has 4
species. The lineage-axis toggle UI in StepSpecies BuildCard rolled
back; the schema stays for sex-axis (Lion/Elk) which auto-resolves.
ContentLoadTests + HybridCharacterTests updated; Size.cs comment too.

Calling lore: ClassDef gains Description; classes.json populated for
all 8 callings with the doc's italic blockquote + paragraph profile.
StepClass surfaces the description on the card.

Card layout uniformity: StepClass / StepSubclass / StepBackground all
switched to single-column ExpandFill grids (matching StepClade /
StepSpecies). Each card now spans the wizard's content width so the
description and feature chips have room to breathe.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-07 22:23:47 -07:00

136 lines
5.2 KiB
C#

using System.Text.Json.Serialization;
namespace Theriapolis.Core.Data;
/// <summary>
/// Immutable class definition loaded from classes.json. Phase 5 reads
/// every field — including the full level table — but only level-1
/// features have runtime effect; higher-level entries are forward-compat
/// scaffolding for the level-up flow shipped in Phase 5.5 / 6.
/// </summary>
public sealed record ClassDef
{
[JsonPropertyName("id")]
public string Id { get; init; } = "";
[JsonPropertyName("name")]
public string Name { get; init; } = "";
/// <summary>Codex-voice calling description: in-character quote followed
/// by a one-paragraph profile (mirrors CladeDef / SpeciesDef). Surfaced
/// on the Step III calling card.</summary>
[JsonPropertyName("description")]
public string Description { get; init; } = "";
/// <summary>Hit die size: 6 / 8 / 10 / 12.</summary>
[JsonPropertyName("hit_die")]
public int HitDie { get; init; } = 8;
/// <summary>Primary ability key(s) (STR / DEX / CON / INT / WIS / CHA).</summary>
[JsonPropertyName("primary_ability")]
public string[] PrimaryAbility { get; init; } = Array.Empty<string>();
/// <summary>Saving-throw proficiencies.</summary>
[JsonPropertyName("saves")]
public string[] Saves { get; init; } = Array.Empty<string>();
/// <summary>Armor proficiency tags: "light", "medium", "heavy", "shields".</summary>
[JsonPropertyName("armor_proficiencies")]
public string[] ArmorProficiencies { get; init; } = Array.Empty<string>();
/// <summary>Weapon proficiency tags: "simple", "martial", "natural", or specific item ids.</summary>
[JsonPropertyName("weapon_proficiencies")]
public string[] WeaponProficiencies { get; init; } = Array.Empty<string>();
/// <summary>Tool proficiency tags.</summary>
[JsonPropertyName("tool_proficiencies")]
public string[] ToolProficiencies { get; init; } = Array.Empty<string>();
[JsonPropertyName("skills_choose")]
public int SkillsChoose { get; init; } = 0;
[JsonPropertyName("skill_options")]
public string[] SkillOptions { get; init; } = Array.Empty<string>();
/// <summary>
/// Per-level entries. Level 1..20. Phase 5 only consults level 1, but
/// the full table loads so the level-up flow doesn't need a schema bump.
/// </summary>
[JsonPropertyName("level_table")]
public ClassLevelEntry[] LevelTable { get; init; } = Array.Empty<ClassLevelEntry>();
/// <summary>Description of each named feature referenced from level_table.</summary>
[JsonPropertyName("feature_definitions")]
public Dictionary<string, ClassFeatureDef> FeatureDefinitions { get; init; } = new();
/// <summary>Allowed subclass ids (cross-reference into subclasses.json).</summary>
[JsonPropertyName("subclass_ids")]
public string[] SubclassIds { get; init; } = Array.Empty<string>();
/// <summary>
/// Items handed to a level-1 character of this class at creation time.
/// <see cref="Rules.Character.CharacterBuilder"/> adds each entry to the
/// inventory and, if <see cref="StartingKitItem.AutoEquip"/> is true,
/// equips it into <see cref="StartingKitItem.EquipSlot"/>.
/// </summary>
[JsonPropertyName("starting_kit")]
public StartingKitItem[] StartingKit { get; init; } = Array.Empty<StartingKitItem>();
}
/// <summary>
/// One row in <see cref="ClassDef.StartingKit"/>: the item id, quantity, and
/// optional auto-equip target. ItemId must resolve against items.json.
/// </summary>
public sealed record StartingKitItem
{
[JsonPropertyName("item_id")]
public string ItemId { get; init; } = "";
[JsonPropertyName("qty")]
public int Qty { get; init; } = 1;
/// <summary>If true, the item is equipped into <see cref="EquipSlot"/> at creation.</summary>
[JsonPropertyName("auto_equip")]
public bool AutoEquip { get; init; } = false;
/// <summary>"main_hand" / "off_hand" / "body" / "helm" / "cloak" / "boots" / "adaptive_pack" / etc.</summary>
[JsonPropertyName("equip_slot")]
public string EquipSlot { get; init; } = "";
}
public sealed record ClassLevelEntry
{
[JsonPropertyName("level")]
public int Level { get; init; } = 1;
[JsonPropertyName("prof")]
public int ProficiencyBonus { get; init; } = 2;
/// <summary>Feature ids unlocked at this level. Resolves into <see cref="ClassDef.FeatureDefinitions"/>.</summary>
[JsonPropertyName("features")]
public string[] Features { get; init; } = Array.Empty<string>();
}
public sealed record ClassFeatureDef
{
[JsonPropertyName("name")]
public string Name { get; init; } = "";
[JsonPropertyName("description")]
public string Description { get; init; } = "";
/// <summary>"passive", "active", "choice", "bonus_action", "reaction", "stub".</summary>
[JsonPropertyName("kind")]
public string Kind { get; init; } = "passive";
[JsonPropertyName("uses_per_short_rest")]
public int? UsesPerShortRest { get; init; }
[JsonPropertyName("uses_per_long_rest")]
public int? UsesPerLongRest { get; init; }
/// <summary>For "choice" features: the available pick ids.</summary>
[JsonPropertyName("options")]
public string[]? Options { get; init; }
}