using System.Text.Json.Serialization; namespace Theriapolis.Core.Data; /// /// Immutable NPC stat-block template loaded from npc_templates.json. /// Phase 5 instantiates one per per /// chunk, with the actual template id chosen via the per-zone lookup /// table on . /// public sealed record NpcTemplateDef { [JsonPropertyName("id")] public string Id { get; init; } = ""; [JsonPropertyName("name")] public string Name { get; init; } = ""; /// Body size category, snake_case (small / medium / medium_large / large). [JsonPropertyName("size")] public string Size { get; init; } = "medium"; /// STR/DEX/CON/INT/WIS/CHA absolute values (10 = average). [JsonPropertyName("ability_scores")] public Dictionary AbilityScores { get; init; } = new(); [JsonPropertyName("hp")] public int Hp { get; init; } = 1; [JsonPropertyName("ac")] public int Ac { get; init; } = 10; [JsonPropertyName("speed_ft")] public int SpeedFt { get; init; } = 30; [JsonPropertyName("attacks")] public NpcAttack[] Attacks { get; init; } = Array.Empty(); /// Behavior id ("brigand", "wild_animal", "poi_guard"). Maps to INpcBehavior in Phase 5 M5. [JsonPropertyName("behavior")] public string Behavior { get; init; } = "brigand"; /// Starts as Hostile / Neutral / Friendly. Phase 5 reads this on instantiation. [JsonPropertyName("default_allegiance")] public string DefaultAllegiance { get; init; } = "hostile"; /// /// Phase 6 M5 โ€” faction id this NPC owes allegiance to (matches /// FactionDef.Id). Empty for unaligned templates (wild animals, /// brigands). Drives M5 patrol-aggression: a non-hostile NPC with a /// faction flips to Hostile when the player's local standing with /// that faction crosses the HOSTILE threshold. /// [JsonPropertyName("faction")] public string Faction { get; init; } = ""; /// Loot table id (Phase 5 ships ~5; lookup deferred to M6). [JsonPropertyName("loot_table")] public string LootTable { get; init; } = ""; [JsonPropertyName("xp_award")] public int XpAward { get; init; } = 0; } public sealed record NpcAttack { [JsonPropertyName("name")] public string Name { get; init; } = ""; [JsonPropertyName("to_hit")] public int ToHit { get; init; } = 0; /// Damage dice expression (e.g. "1d6+2", "2d8"). [JsonPropertyName("damage")] public string Damage { get; init; } = ""; [JsonPropertyName("damage_type")] public string DamageType { get; init; } = "bludgeoning"; [JsonPropertyName("reach_tiles")] public int ReachTiles { get; init; } = 1; [JsonPropertyName("range_short_tiles")] public int RangeShortTiles { get; init; } = 0; [JsonPropertyName("range_long_tiles")] public int RangeLongTiles { get; init; } = 0; } /// /// Top-level wrapper for npc_templates.json: the template list plus the /// per-spawnkind, per-zone template-id lookup table. /// public sealed record NpcTemplateContent { [JsonPropertyName("templates")] public NpcTemplateDef[] Templates { get; init; } = Array.Empty(); /// /// SpawnKind name (e.g. "Brigand") โ†’ array of template ids indexed by /// DangerZone (0..4). Length should equal C.DANGER_ZONE_MAX + 1. /// [JsonPropertyName("spawn_kind_to_template_by_zone")] public Dictionary SpawnKindToTemplateByZone { get; init; } = new(); /// /// Phase 7 M2 โ€” per-dungeon-type override. Resolves a /// (PoiType, SpawnKind) pair to a single template id, used by /// when filling /// in-room encounter slots. Per Phase 7 plan ยง10 open-decision #6: /// DungeonType supersedes DangerZone entirely once the player is inside /// a dungeon, so this map's value is a single template id (no zone /// indexing). Outer key matches the enum /// name (e.g. "ImperiumRuin"); inner key is the spawn-kind name /// (e.g. "PoiGuard" / "WildAnimal" / "Brigand" / /// "Boss"). Missing keys fall back to /// at DangerZone=2 mid. /// [JsonPropertyName("spawn_kind_to_template_by_dungeon_type")] public Dictionary> SpawnKindToTemplateByDungeonType { get; init; } = new(); }