Golarion Chronicles Wiki

Writ Sub-Dialogue Tree (Individual Quest Dialogues)

This document describes the structure and parameters of the individual writ sub-dialogues — one .dlg.json per quest, such as q_pyrami.dlg.json. These are the conversations that cv_venturecaptai.dlg.json launches when a player selects a writ from the venture captain's dynamic list. This guide covers only what happens after the player picks a specific writ; the hub/list mechanics live in cv_venturecaptai.dlg.json and its handlers (cv_venturecapsc / cv_venturecat).

How the hub hands off to a sub-dialogue

When the player chooses a writ name in cv_venturecaptai.dlg.json, the cv_venturecat action resolves the display name to a quest resref and calls:

player.ActionStartConversation(npcSpeaker, questResref, true, false);

The fourth argument (false) means the conversation starts at entry level 0, i.e. a fresh start. The engine then loads <questResref>.dlg.json and begins at its first starting node. Everything below is contained within that single file; there is no further chaining back into the hub except via the q_return exit replies.

Canonical shape

Every writ sub-dialogue follows the same template: 15 entries, 27 replies, three starting nodes [10, 5, 0]. The three starting nodes correspond to the three NPC variants who can offer the writ:

Starting node Speaker tag Faction
E0 ColsonMaldris libertysedge
E5 AaqiralHakam theexchange
E10 GlorianaMorilla sovereigncourt

Each variant owns a contiguous block of five entries (E0–E4 male, E5–E9 female-A, E10–E14 female-B), so the same logical flow exists three times with gendered flavor text. The reply pool is shared across all three blocks.

Entry roles (per variant block)

Within each block of five entries, the roles are fixed:

Offset Role Script on its replies
+0 Hub / greeting — "what can I help you with" none (plain routing)
+1 Lore beat (reaction to the writ page) q_return
+2 Problem description q_finishquestat (turn-in) + q_return
+3 "Not ready" dead-end guard q_return
+4 Job briefing → accept q_startquestat (accept) + q_return

So for the male variant: E0 = hub, E1 = lore, E2 = problem/turn-in, E3 = not-ready, E4 = accept. Female-A is E5–E9, female-B is E10–E14.

The hub entry (+0) and its three gated replies

The hub entry offers exactly three options, gated by the quest's own state using the legacy parameter set:

Reply text Condition param Meaning
"Ask how you can help." HIDEINPROGRESS=<self> Shown before acceptance (hidden once the quest is active). Routes to the accept/not-ready split.
"You ask what the problem is about." QUESTINPROGRESS=<self> Shown while the quest is active. Leads to the problem/turn-in node.
"Tell them it's already been dealt with." QUESTCOMPLETE=<self> Shown after completion. Leads to the turn-in/lore path.

<self> is the sub-dialogue's own quest resref (e.g. q_pyrami). These three gates partition the player's lifecycle: pre-acceptance, in-progress, and completed.

The "ask how you can help" split (the critical routing)

The "Ask how you can help." reply routes to two targets, one of which must be chosen based on whether the prerequisite writ has been finished. This is the modern database-backed contract:

Target Condition params Meaning
Accept node (+4) QUESTRESREF=<self> + QUESTSTATE=ABSENT Quest not yet started. Gated by prereq: only fires when the prerequisite is finished. Shows the job briefing with "Accept the job."
Not-ready node (+3) QUESTRESREF=<self> + QUESTSTATE=FALLBACK Catch-all. Always fires. Shows "you're not ready yet." Reached when the ABSENT branch is blocked by an incomplete prereq.

Both branches use QUESTRESREF=<self> — the sub-dialogue's own resref. Do not put the parent resref here. At runtime EvaluateDatabaseBackedQuestCondition loads the <self> row from gc_quests.quests, reads its requiredquest column, and blocks positive states (ABSENT, INPROGRESS, READY, FINISHED) until that prerequisite is finished. Negative/catch-all states (NOTFINISHED, FALLBACK) are exempt from the prereq gate so they remain reachable as fallback messages (see QuestFunctions.cs). The parent therefore lives only in the database; the dialogue never names it. The current prerequisites, as stored in gc_quests.quests:

Sub-dialogue (<self>) Prerequisite (requiredquest in DB)
q_pyrami q_uppersiphons
q_floodedcrypt q_puddlesdistric
q_razorhall q_uppersiphons
q_ransackedretre q_foreignquarter
q_lodgelamented q_foreignquarter

The branches are ordered: ABSENT is evaluated first. When the prereq is finished, ABSENT matches (quest not started) and routes to accept. When the prereq is not finished, ABSENT is blocked by the gate and evaluation falls through to FALLBACK, which always returns true and routes to the not-ready message. This guarantees "ask how you can help" always resolves to exactly one valid target and can never dead-end.

Historical bug: these splits originally used the legacy QUESTCOMPLETE/NOTCOMPLETE params, and in some files pointed at the wrong quest entirely (e.g. q_sewersoggy). A later revision used FINISHED/NOTFINISHED, but FINISHED evaluated the quest's own completion status rather than the prerequisite's, causing both branches to fail when the quest hadn't been started. The current ABSENT/FALLBACK pattern correctly separates the prereq gate (handled by the database lookup) from the state check (quest-not-started vs. catch-all).

The "not ready" node (+3)

The not-ready node explains the prerequisite ("Better you aid the Upper Siphons first…") and carries a single q_return reply ("Ask about something else.") so the player can exit back to the hub instead of being stuck. Without this exit reply the node is a dead end.

Accept node (+4)

Offers "Accept the job." running q_startquestat with STARTQUEST=<self>, plus a q_return escape. Accepting starts the quest for the player.

Turn-in node (+2)

Reached via the in-progress or completed hub replies. Offers "Tell them the job is done." running q_finishquestat with QUESTRESREF=<self>, gated by COMPLETEPROGRESS=<self> (shown only at 100% progress), plus a q_return escape.

Parameters used by writ sub-dialogues

All parameters are consumed by the handlers catalogued in DIALOGUE_PARAMETERS.md. The ones that appear in these sub-dialogues:

Conditions (on reply references, ConditionParams)

Param Value Where Purpose
HIDEINPROGRESS <self> hub entry Hide "ask how you can help" once the quest is active.
QUESTINPROGRESS <self> hub entry Show "ask what the problem is" only while active.
QUESTCOMPLETE <self> hub entry Show "already dealt with" only after completion.
QUESTRESREF + QUESTSTATE <self> + ABSENT/FALLBACK ask-help split ABSENT = accept (gated by prereq); FALLBACK = not-ready catch-all. Prereq resolved from the DB row for <self>.
COMPLETEPROGRESS <self> turn-in node Show turn-in only at 100% progress.

Actions (on replies, ActionParams / Script)

Script Param Value Purpose
q_startquestat STARTQUEST <self> Accept the quest.
q_finishquestat QUESTRESREF <self> Turn in the quest.
q_return (none) — End the conversation and return control to the hub.

<self> is always the sub-dialogue's own quest resref. There are no GIVEITEM/REMOVE/NOREWARD overrides in the current set; rewards come from the gc_quests row.

JSON structure reminder

In these .dlg.json files, condition parameters live in ConditionParams on the reply reference (inside an entry's RepliesList), and action parameters live in ActionParams on the reply itself. Both are flat key/value dictionaries:

// Ask-help split: two child entries on the "Ask how you can help." reply.
// Child [0] — accept branch (evaluated first):
{
  "Quest": { "type": "cexostring", "value": "q_queststatusaw" },
  "ConditionParams": {
    "type": "list",
    "value": [
      { "__struct_id": 0, "Key": {"type":"cexostring","value":"QUESTRESREF"}, "Value": {"type":"cexostring","value":"q_pyrami"} },
      { "__struct_id": 1, "Key": {"type":"cexostring","value":"QUESTSTATE"},  "Value": {"type":"cexostring","value":"ABSENT"} }
    ]
  },
  "Target": { "type": "dword", "value": 4 }
}

// Child [1] — not-ready fallback (catch-all):
{
  "Quest": { "type": "cexostring", "value": "q_queststatusaw" },
  "ConditionParams": {
    "type": "list",
    "value": [
      { "__struct_id": 0, "Key": {"type":"cexostring","value":"QUESTRESREF"}, "Value": {"type":"cexostring","value":"q_pyrami"} },
      { "__struct_id": 1, "Key": {"type":"cexostring","value":"QUESTSTATE"},  "Value": {"type":"cexostring","value":"FALLBACK"} }
    ]
  },
  "Target": { "type": "dword", "value": 3 }
}

// On the accept node's reply (action):
"Script": { "type": "resref", "value": "q_startquestat" },
"ActionParams": {
  "type": "list",
  "value": [
    { "__struct_id": 0, "Key": {"type":"cexostring","value":"STARTQUEST"}, "Value": {"type":"cexostring","value":"q_pyrami"} }
  ]
}

Gotchas