130 lines
4.9 KiB
C#
130 lines
4.9 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>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; }
|
||
|
|
}
|