178 lines
6.1 KiB
C#
178 lines
6.1 KiB
C#
|
|
using System.Text.Json.Serialization;
|
||
|
|
|
||
|
|
namespace Theriapolis.Core.Data;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Phase 6 M4 — JSON-loaded quest definition. A quest is a directed graph
|
||
|
|
/// of steps; the engine starts at <see cref="EntryStep"/>, evaluates each
|
||
|
|
/// active step's <see cref="QuestStepDef.TriggerConditions"/> per tick,
|
||
|
|
/// and runs <see cref="QuestStepDef.OnEnter"/> + <see cref="QuestStepDef.Outcomes"/>
|
||
|
|
/// when the step fires.
|
||
|
|
///
|
||
|
|
/// Author convention: one tree per file in
|
||
|
|
/// <c>Content/Data/quests/*.json</c>. <see cref="Id"/> matches the
|
||
|
|
/// filename. Step ids are strings local to the tree.
|
||
|
|
/// </summary>
|
||
|
|
public sealed record QuestDef
|
||
|
|
{
|
||
|
|
[JsonPropertyName("id")]
|
||
|
|
public string Id { get; init; } = "";
|
||
|
|
|
||
|
|
[JsonPropertyName("title")]
|
||
|
|
public string Title { get; init; } = "";
|
||
|
|
|
||
|
|
[JsonPropertyName("description")]
|
||
|
|
public string Description { get; init; } = "";
|
||
|
|
|
||
|
|
/// <summary>True when the quest doesn't appear in the journal until first activation (Maw discovery, etc.).</summary>
|
||
|
|
[JsonPropertyName("hidden")]
|
||
|
|
public bool Hidden { get; init; } = false;
|
||
|
|
|
||
|
|
/// <summary>Step id the engine activates on quest start.</summary>
|
||
|
|
[JsonPropertyName("entry_step")]
|
||
|
|
public string EntryStep { get; init; } = "";
|
||
|
|
|
||
|
|
[JsonPropertyName("steps")]
|
||
|
|
public QuestStepDef[] Steps { get; init; } = System.Array.Empty<QuestStepDef>();
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Optional: triggers that auto-start this quest. The engine checks
|
||
|
|
/// these against world state on every tick; when any fires, the quest
|
||
|
|
/// activates at <see cref="EntryStep"/>. Empty = manual-start (e.g.
|
||
|
|
/// dialogue's <c>start_quest</c> effect).
|
||
|
|
/// </summary>
|
||
|
|
[JsonPropertyName("auto_start_when")]
|
||
|
|
public QuestConditionDef[] AutoStartWhen { get; init; } = System.Array.Empty<QuestConditionDef>();
|
||
|
|
}
|
||
|
|
|
||
|
|
public sealed record QuestStepDef
|
||
|
|
{
|
||
|
|
[JsonPropertyName("id")]
|
||
|
|
public string Id { get; init; } = "";
|
||
|
|
|
||
|
|
[JsonPropertyName("title")]
|
||
|
|
public string Title { get; init; } = "";
|
||
|
|
|
||
|
|
[JsonPropertyName("description")]
|
||
|
|
public string Description { get; init; } = "";
|
||
|
|
|
||
|
|
/// <summary>Optional waypoint hint — anchor or role tag the player should head toward.</summary>
|
||
|
|
[JsonPropertyName("waypoint")]
|
||
|
|
public string Waypoint { get; init; } = "";
|
||
|
|
|
||
|
|
/// <summary>Conditions that fire this step's onEnter + outcomes when ALL true.</summary>
|
||
|
|
[JsonPropertyName("trigger_conditions")]
|
||
|
|
public QuestConditionDef[] TriggerConditions { get; init; } = System.Array.Empty<QuestConditionDef>();
|
||
|
|
|
||
|
|
/// <summary>Effects applied once when the step fires.</summary>
|
||
|
|
[JsonPropertyName("on_enter")]
|
||
|
|
public QuestEffectDef[] OnEnter { get; init; } = System.Array.Empty<QuestEffectDef>();
|
||
|
|
|
||
|
|
/// <summary>Step ids this step transitions into (any one is selected via outcome conditions).</summary>
|
||
|
|
[JsonPropertyName("outcomes")]
|
||
|
|
public QuestOutcomeDef[] Outcomes { get; init; } = System.Array.Empty<QuestOutcomeDef>();
|
||
|
|
|
||
|
|
/// <summary>True if reaching this step completes the quest (success).</summary>
|
||
|
|
[JsonPropertyName("completes_quest")]
|
||
|
|
public bool CompletesQuest { get; init; } = false;
|
||
|
|
|
||
|
|
/// <summary>True if reaching this step fails the quest.</summary>
|
||
|
|
[JsonPropertyName("fails_quest")]
|
||
|
|
public bool FailsQuest { get; init; } = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
public sealed record QuestOutcomeDef
|
||
|
|
{
|
||
|
|
/// <summary>Step id to transition to. <c>"<end>"</c> closes the quest.</summary>
|
||
|
|
[JsonPropertyName("next")]
|
||
|
|
public string Next { get; init; } = "";
|
||
|
|
|
||
|
|
/// <summary>Conditions for THIS outcome to be selected. Empty = always.</summary>
|
||
|
|
[JsonPropertyName("when")]
|
||
|
|
public QuestConditionDef[] When { get; init; } = System.Array.Empty<QuestConditionDef>();
|
||
|
|
|
||
|
|
[JsonPropertyName("effects")]
|
||
|
|
public QuestEffectDef[] Effects { get; init; } = System.Array.Empty<QuestEffectDef>();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>Trigger / outcome predicate.</summary>
|
||
|
|
public sealed record QuestConditionDef
|
||
|
|
{
|
||
|
|
/// <summary>
|
||
|
|
/// One of: "flag_set", "flag_clear", "flag_at_least", "enter_anchor",
|
||
|
|
/// "enter_role_proximity", "npc_dead", "npc_alive", "time_elapsed_seconds",
|
||
|
|
/// "rep_at_least", "rep_below", "has_item", "not_has_item",
|
||
|
|
/// "quest_complete", "quest_active", "dialogue_choice".
|
||
|
|
/// </summary>
|
||
|
|
[JsonPropertyName("kind")]
|
||
|
|
public string Kind { get; init; } = "";
|
||
|
|
|
||
|
|
[JsonPropertyName("flag")]
|
||
|
|
public string Flag { get; init; } = "";
|
||
|
|
|
||
|
|
[JsonPropertyName("anchor")]
|
||
|
|
public string Anchor { get; init; } = "";
|
||
|
|
|
||
|
|
[JsonPropertyName("role")]
|
||
|
|
public string Role { get; init; } = "";
|
||
|
|
|
||
|
|
[JsonPropertyName("npc")]
|
||
|
|
public string Npc { get; init; } = "";
|
||
|
|
|
||
|
|
[JsonPropertyName("faction")]
|
||
|
|
public string Faction { get; init; } = "";
|
||
|
|
|
||
|
|
[JsonPropertyName("id")]
|
||
|
|
public string Id { get; init; } = "";
|
||
|
|
|
||
|
|
[JsonPropertyName("quest")]
|
||
|
|
public string Quest { get; init; } = "";
|
||
|
|
|
||
|
|
[JsonPropertyName("value")]
|
||
|
|
public int Value { get; init; }
|
||
|
|
|
||
|
|
[JsonPropertyName("seconds")]
|
||
|
|
public long Seconds { get; init; }
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>Quest-step side effect.</summary>
|
||
|
|
public sealed record QuestEffectDef
|
||
|
|
{
|
||
|
|
/// <summary>
|
||
|
|
/// One of: "set_flag", "clear_flag", "give_item", "take_item",
|
||
|
|
/// "give_xp", "rep_event", "spawn_npc", "despawn_npc",
|
||
|
|
/// "start_quest", "end_quest", "fail_quest".
|
||
|
|
/// </summary>
|
||
|
|
[JsonPropertyName("kind")]
|
||
|
|
public string Kind { get; init; } = "";
|
||
|
|
|
||
|
|
[JsonPropertyName("flag")]
|
||
|
|
public string Flag { get; init; } = "";
|
||
|
|
|
||
|
|
[JsonPropertyName("value")]
|
||
|
|
public int Value { get; init; } = 1;
|
||
|
|
|
||
|
|
[JsonPropertyName("id")]
|
||
|
|
public string Id { get; init; } = "";
|
||
|
|
|
||
|
|
[JsonPropertyName("qty")]
|
||
|
|
public int Qty { get; init; } = 1;
|
||
|
|
|
||
|
|
[JsonPropertyName("xp")]
|
||
|
|
public int Xp { get; init; }
|
||
|
|
|
||
|
|
[JsonPropertyName("event")]
|
||
|
|
public DialogueRepEventDef? Event { get; init; }
|
||
|
|
|
||
|
|
[JsonPropertyName("quest")]
|
||
|
|
public string Quest { get; init; } = "";
|
||
|
|
|
||
|
|
/// <summary>For spawn_npc: resident template id (named takes precedence).</summary>
|
||
|
|
[JsonPropertyName("template")]
|
||
|
|
public string Template { get; init; } = "";
|
||
|
|
|
||
|
|
/// <summary>For spawn_npc/despawn_npc: which named role tag is being mutated.</summary>
|
||
|
|
[JsonPropertyName("role")]
|
||
|
|
public string Role { get; init; } = "";
|
||
|
|
}
|