66055f9549
Switch Step 0 (Clade) and Step 1 (Species) from a 3-column card grid to a 1-column layout, with each card carrying a codex-voice description paragraph between the meta line and the trait chips. Rationale: establish the world's tone before mechanics — the player reads who Canidae or Wolf-Folk *are* before evaluating ability mods and trait pills. Trade is more vertical scrolling, but the card content was already going wider than three columns comfortably allowed once the parchment theme bumped padding. Schema: CladeDef and SpeciesDef gain a Description field (string, empty default). Populated for all 7 clades and 19 species, sourced from the doc's italicized blockquote + a one-sentence summary of the prose paragraph that follows. Empty descriptions fall through silently — a species without a description still renders, just without the paragraph. UI: MakeGrid in both steps becomes Columns = 1 with ExpandFill; BuildCard sets card.SizeFlagsHorizontal = ExpandFill (replaces the fixed CustomMinimumSize 200) and prepends the autowrap description label after the meta line. Hybrid mode stacks sire and dam single- column grids vertically — same logic as before, just one card wide each. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
84 lines
3.1 KiB
C#
84 lines
3.1 KiB
C#
using System.Text.Json.Serialization;
|
|
|
|
namespace Theriapolis.Core.Data;
|
|
|
|
/// <summary>
|
|
/// Immutable species (subrace-equivalent) record loaded from species.json.
|
|
/// Refines the parent <see cref="CladeDef"/>: adds a body size, additional
|
|
/// ability mods, species-specific traits, and species-specific detriments.
|
|
/// </summary>
|
|
public sealed record SpeciesDef
|
|
{
|
|
[JsonPropertyName("id")]
|
|
public string Id { get; init; } = "";
|
|
|
|
[JsonPropertyName("clade_id")]
|
|
public string CladeId { get; init; } = "";
|
|
|
|
[JsonPropertyName("name")]
|
|
public string Name { get; init; } = "";
|
|
|
|
/// <summary>Codex-voice species description: usually the doc's italicized
|
|
/// blockquote followed by a one-sentence physical/cultural summary.
|
|
/// Surfaced on the Step 1 card.</summary>
|
|
[JsonPropertyName("description")]
|
|
public string Description { get; init; } = "";
|
|
|
|
/// <summary>Body size category, snake_case (small / medium / medium_large / large).</summary>
|
|
[JsonPropertyName("size")]
|
|
public string Size { get; init; } = "medium";
|
|
|
|
/// <summary>Additional ability mods on top of the clade's mods.</summary>
|
|
[JsonPropertyName("ability_mods")]
|
|
public Dictionary<string, int> AbilityMods { get; init; } = new();
|
|
|
|
/// <summary>Base movement speed in feet per turn (5 ft. = 1 tactical tile per d20 standard).</summary>
|
|
[JsonPropertyName("base_speed_ft")]
|
|
public int BaseSpeedFt { get; init; } = 30;
|
|
|
|
[JsonPropertyName("traits")]
|
|
public TraitDef[] Traits { get; init; } = Array.Empty<TraitDef>();
|
|
|
|
[JsonPropertyName("detriments")]
|
|
public TraitDef[] Detriments { get; init; } = Array.Empty<TraitDef>();
|
|
|
|
/// <summary>
|
|
/// "sex" (male/female) or "lineage" (e.g. sheep/goat for Ram-Folk).
|
|
/// Empty when the species has no variants. Determines how the variant
|
|
/// is resolved: sex-axis is auto-keyed off character Sex (purebred) or
|
|
/// parent role for hybrids (sire = male, dam = female); lineage-axis
|
|
/// requires an explicit per-species pick.
|
|
/// </summary>
|
|
[JsonPropertyName("variant_axis")]
|
|
public string VariantAxis { get; init; } = "";
|
|
|
|
/// <summary>
|
|
/// Variant entries layered onto the species' base traits/detriments.
|
|
/// The variant's id keys it: for sex-axis variants, "male" or "female";
|
|
/// for lineage variants, the lineage tag (e.g. "sheep", "goat").
|
|
/// Empty when the species has no variants.
|
|
/// </summary>
|
|
[JsonPropertyName("variants")]
|
|
public SpeciesVariantDef[] Variants { get; init; } = Array.Empty<SpeciesVariantDef>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// One sex- or lineage-keyed variant of a species. Layered on top of the
|
|
/// base species' traits and detriments — the player ends up with both
|
|
/// sets, not one or the other.
|
|
/// </summary>
|
|
public sealed record SpeciesVariantDef
|
|
{
|
|
[JsonPropertyName("id")]
|
|
public string Id { get; init; } = "";
|
|
|
|
[JsonPropertyName("name")]
|
|
public string Name { get; init; } = "";
|
|
|
|
[JsonPropertyName("traits")]
|
|
public TraitDef[] Traits { get; init; } = Array.Empty<TraitDef>();
|
|
|
|
[JsonPropertyName("detriments")]
|
|
public TraitDef[] Detriments { get; init; } = Array.Empty<TraitDef>();
|
|
}
|