Files

187 lines
6.8 KiB
C#
Raw Permalink Normal View History

using System.Text.Json.Serialization;
namespace Theriapolis.Core.Data;
/// <summary>
/// Phase 6 M3 — JSON-loaded dialogue tree. Each tree is a directed graph
/// of nodes; the runner walks from <see cref="Root"/> per option choice.
/// Nodes are addressed by a string id local to the tree.
///
/// Author convention: keep one tree per file in
/// <c>Content/Data/dialogues/*.json</c>. <see cref="Id"/> matches the
/// filename (sans extension) so authors can find the file by id.
/// </summary>
public sealed record DialogueDef
{
[JsonPropertyName("id")]
public string Id { get; init; } = "";
/// <summary>Starting node id when the dialogue opens.</summary>
[JsonPropertyName("root")]
public string Root { get; init; } = "";
[JsonPropertyName("nodes")]
public DialogueNodeDef[] Nodes { get; init; } = System.Array.Empty<DialogueNodeDef>();
}
public sealed record DialogueNodeDef
{
[JsonPropertyName("id")]
public string Id { get; init; } = "";
/// <summary>"npc" / "pc" / "narration".</summary>
[JsonPropertyName("speaker")]
public string Speaker { get; init; } = "npc";
/// <summary>Display text. Supports placeholders {pc.name}, {npc.role}, {disposition_label}.</summary>
[JsonPropertyName("text")]
public string Text { get; init; } = "";
/// <summary>Effects applied automatically when the runner enters this node.</summary>
[JsonPropertyName("on_enter")]
public DialogueEffectDef[] OnEnter { get; init; } = System.Array.Empty<DialogueEffectDef>();
[JsonPropertyName("options")]
public DialogueOptionDef[] Options { get; init; } = System.Array.Empty<DialogueOptionDef>();
}
public sealed record DialogueOptionDef
{
[JsonPropertyName("text")]
public string Text { get; init; } = "";
/// <summary>Visibility predicates. Option is hidden if any condition fails.</summary>
[JsonPropertyName("conditions")]
public DialogueConditionDef[] Conditions { get; init; } = System.Array.Empty<DialogueConditionDef>();
/// <summary>
/// When set, selecting this option rolls the named skill against
/// <see cref="DialogueSkillCheckDef.Dc"/>. The runner branches into
/// <see cref="EffectsOnSuccess"/>+<see cref="NextOnSuccess"/> on success
/// or <see cref="EffectsOnFailure"/>+<see cref="NextOnFailure"/> on
/// failure. <see cref="Effects"/> and <see cref="Next"/> are ignored
/// when a skill check is present.
/// </summary>
[JsonPropertyName("skill_check")]
public DialogueSkillCheckDef? SkillCheck { get; init; }
/// <summary>Node id to jump to when this option is selected. Empty / "&lt;end&gt;" closes the dialogue.</summary>
[JsonPropertyName("next")]
public string Next { get; init; } = "";
[JsonPropertyName("effects")]
public DialogueEffectDef[] Effects { get; init; } = System.Array.Empty<DialogueEffectDef>();
[JsonPropertyName("next_on_success")]
public string NextOnSuccess { get; init; } = "";
[JsonPropertyName("next_on_failure")]
public string NextOnFailure { get; init; } = "";
[JsonPropertyName("effects_on_success")]
public DialogueEffectDef[] EffectsOnSuccess { get; init; } = System.Array.Empty<DialogueEffectDef>();
[JsonPropertyName("effects_on_failure")]
public DialogueEffectDef[] EffectsOnFailure { get; init; } = System.Array.Empty<DialogueEffectDef>();
}
public sealed record DialogueSkillCheckDef
{
/// <summary>Skill id (snake_case, matches SkillId.FromJson — e.g. "intimidation", "persuasion").</summary>
[JsonPropertyName("skill")]
public string Skill { get; init; } = "";
[JsonPropertyName("dc")]
public int Dc { get; init; }
}
/// <summary>Visibility predicate evaluated when the option is offered.</summary>
public sealed record DialogueConditionDef
{
/// <summary>
/// One of: "rep_at_least", "rep_below", "has_item", "not_has_item",
/// "has_flag", "not_has_flag", "ability_min".
/// </summary>
[JsonPropertyName("kind")]
public string Kind { get; init; } = "";
/// <summary>Faction id (rep_at_least / rep_below). Empty = effective disposition vs current NPC.</summary>
[JsonPropertyName("faction")]
public string Faction { get; init; } = "";
/// <summary>Item id (has_item / not_has_item).</summary>
[JsonPropertyName("id")]
public string Id { get; init; } = "";
/// <summary>Flag id (has_flag / not_has_flag).</summary>
[JsonPropertyName("flag")]
public string Flag { get; init; } = "";
/// <summary>Ability id (ability_min): "STR" / "DEX" / "CON" / "INT" / "WIS" / "CHA".</summary>
[JsonPropertyName("ability")]
public string Ability { get; init; } = "";
/// <summary>Numeric threshold for rep / ability. Inclusive lower bound for *_at_least and *_min.</summary>
[JsonPropertyName("value")]
public int Value { get; init; }
}
/// <summary>Side effect applied on option selection (or on node enter).</summary>
public sealed record DialogueEffectDef
{
/// <summary>
/// One of: "set_flag", "clear_flag", "give_item", "take_item",
/// "rep_event", "open_shop", "start_quest", "give_xp".
/// </summary>
[JsonPropertyName("kind")]
public string Kind { get; init; } = "";
/// <summary>Flag id for set_flag/clear_flag.</summary>
[JsonPropertyName("flag")]
public string Flag { get; init; } = "";
/// <summary>Integer value for set_flag (defaults to 1).</summary>
[JsonPropertyName("value")]
public int Value { get; init; } = 1;
/// <summary>Item id for give_item/take_item.</summary>
[JsonPropertyName("id")]
public string Id { get; init; } = "";
/// <summary>Quantity for give_item/take_item (defaults to 1).</summary>
[JsonPropertyName("qty")]
public int Qty { get; init; } = 1;
/// <summary>RepEvent payload for rep_event.</summary>
[JsonPropertyName("event")]
public DialogueRepEventDef? Event { get; init; }
/// <summary>Quest id for start_quest. Phase 6 M3 ignores; M4 wires the quest engine.</summary>
[JsonPropertyName("quest")]
public string Quest { get; init; } = "";
/// <summary>XP magnitude for give_xp.</summary>
[JsonPropertyName("xp")]
public int Xp { get; init; }
}
public sealed record DialogueRepEventDef
{
/// <summary>RepEventKind name: "Dialogue" / "Quest" / "Combat" / "Rescue" / "Betrayal" / "Gift" / "Trade" / "Aid" / "Crime" / "Misc".</summary>
[JsonPropertyName("kind")]
public string Kind { get; init; } = "Dialogue";
[JsonPropertyName("magnitude")]
public int Magnitude { get; init; }
[JsonPropertyName("faction")]
public string Faction { get; init; } = "";
/// <summary>If empty, defaults to the current NPC's role tag.</summary>
[JsonPropertyName("role_tag")]
public string RoleTag { get; init; } = "";
[JsonPropertyName("note")]
public string Note { get; init; } = "";
}