The quest system
Track mission objectives and quest states attached to any agent or shared globally.
Overview
Quests are data records stored on an agent's inventory under the key __quests__. Each quest has a unique string ID (supports /-separated nesting like "main/patrol"), a display name, a description, and a QuestState. When a quest changes state a signal is emitted (quest_activated or quest_completed) so other parts of the mission can react.
Quests can be attached to a specific player ship, a specific client console, or the shared game agent (Agent.SHARED_ID) for mission-wide tracking. quest_flatten_list() collects from all three sources and returns a flat list ready for display in a gui_property_list_box.
Quest states
| State | Value | Meaning |
|---|---|---|
QuestState.IDLE |
0 | Not yet started |
QuestState.ACTIVE |
1 | In progress |
QuestState.SECRET |
2 | Hidden from player |
QuestState.POSTING |
3 | Job board posting |
QuestState.FAILED |
98 | Failed |
QuestState.COMPLETE |
99 | Completed |
Quick example
== setup ==
quest_add(ship_id, "patrol", "Patrol Sector 7", "Keep the peace in sector 7.")
quest_set_key(ship_id, "patrol", "state", QuestState.ACTIVE)
== on_patrol_done ==
quest_complete(ship_id, "patrol")
quest_set_key(ship_id, "patrol", "state", QuestState.COMPLETE)
->END
from sbs_utils.procedural.quest import quest_add, quest_set_key, quest_complete, QuestState
quest_add(ship_id, "patrol", "Patrol Sector 7", "Keep the peace in sector 7.")
quest_set_key(ship_id, "patrol", "state", QuestState.ACTIVE)
# ... later ...
quest_complete(ship_id, "patrol")
quest_set_key(ship_id, "patrol", "state", QuestState.COMPLETE)
Displaying quests
items = quest_flatten_list()
gui_property_list_box(items, style="area:0,0,100,100;")
Loading quests from YAML
yaml_text = """
patrol:
display_text: Patrol Sector 7
description: Keep the peace.
state: ACTIVE
rescue:
display_text: Rescue the crew
description: Find the survivors.
"""
quest_add_yaml(ship_id, yaml_text)
API
Quests modules
Quest are a data model for tracking various quests Quests are attached to an Agent. The tracking of quests are done by outside logic
When a quest is activated or completed a signal is sent
amd_doc_cache_clear()
Per-mission: the next mission's files are different files.
document_flatten(doc_obj, header=None, indent=0, data=None)
Flatten a nested quest/document tree into an ordered display list.
Recursively walks the tree and returns gui_list_box_header items sorted
active → idle → complete → failed at each level. Used internally by
quest_flatten_list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
doc_obj
|
MastDataObject | dict | None
|
The node to flatten. |
required |
header
|
str
|
Display label for this node. Defaults to None. |
None
|
indent
|
int
|
Current nesting depth for visual indentation. Defaults to 0. |
0
|
data
|
optional
|
Data object attached to this node. Defaults to None. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
list |
Flat ordered list of |
document_get_amd_file(file_path, root_display_text='', strip_comments=True, content=None, data_parser=None, allow_bare_headings=False)
Parse an AMD markdown file into a nested quest/document structure.
AMD files use # [Display Name](key) headings to define hierarchical
sections. The heading level controls depth (# = level 1, ## = level
2, etc.). Lines between headings are accumulated as the section's
description. Lines starting with // are stripped when
strip_comments is True. Query-string parameters in the key URI
(key?param=value&…) are parsed as extra attributes on the section.
Returns a dict with keys "key", "display_text", "description",
and "children" (list of the same structure). On parse error the
exception message is returned as the root "display_text".
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
file_path
|
str | None
|
Path to the |
required |
root_display_text
|
str
|
Label for the root node.
Defaults to |
''
|
strip_comments
|
bool
|
Skip |
True
|
content
|
str | None
|
Raw AMD text to parse instead of
reading |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
dict |
Nested document tree rooted at |
Example
doc = document_get_amd_file("consoles/quest.amd", "Quests") items = document_flatten(doc)
quest_activate(agents, quest_id)
Emit a quest_activated signal for one or more agents.
Fires signal_emit("quest_activated", ...) for each agent. To also
update the stored state, call quest_set_key(agent, quest_id, "state",
QuestState.ACTIVE) or handle the signal in a //signal/quest_activated
route that sets the state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agents
|
Agent ID, object, or list/set of either. |
required | |
quest_id
|
str
|
Quest to activate. |
required |
Example
quest_activate(SHIP_ID, "patrol") quest_set_key(SHIP_ID, "patrol", "state", QuestState.ACTIVE)
quest_add(agents, quest_id, display_text, description, state=QuestState.IDLE, data=None)
Add a quest to one or more agents.
Creates a new quest entry in each agent's quest tree. If the agent has no
quest tree yet, one is initialized automatically. The quest_id may use
/ separators for nested quests (e.g. "main/rescue"), but all parent
levels must already exist.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agents
|
Agent ID, object, or list/set of either. |
required | |
quest_id
|
str
|
Unique key for this quest, e.g. |
required |
display_text
|
str
|
Short label shown to the player. |
required |
description
|
str
|
Longer description text. |
required |
state
|
QuestState
|
Initial state. Defaults to
|
IDLE
|
data
|
object
|
Arbitrary data attached to the quest and
accessible via |
None
|
Example
quest_add(SHIP_ID, "patrol", "Patrol Sector 7", "Keep the peace in sector 7.") quest_add(Agent.SHARED_ID, "rescue", "Rescue the crew", "Find the survivors.", state=QuestState.ACTIVE)
quest_add_object(agents, obj, quest_id=None)
Add a quest from a dictionary object to one or more agents.
Reads display_text, description, state, and data from
obj. The state value may be a QuestState enum or a string name
(e.g. "ACTIVE"); unknown strings default to QuestState.IDLE.
Nested children are recursively added with /-separated IDs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agents
|
Agent ID, object, or list/set of either. |
required | |
obj
|
dict
|
Quest definition dict (typically from parsed YAML). |
required |
quest_id
|
str
|
Override key. If |
None
|
Example
quest_add_object(SHIP_ID, { "display_text": "Patrol", "description": "Patrol sector 7.", "state": "ACTIVE", }, "patrol")
quest_add_yaml(agents, yaml_text)
Parse a YAML string and add all quests defined in it to one or more agents.
The YAML should be a mapping of quest IDs to quest objects. Each quest
object supports the same keys as quest_add_object (display_text,
description, state, data, and nested children).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agents
|
Agent ID, object, or list/set of either. |
required | |
yaml_text
|
str
|
YAML-formatted quest definitions. |
required |
Example
quest_add_yaml(SHIP_ID, ~~ patrol: display_text: "Patrol Sector 7" description: "Keep the peace." state: ACTIVE ~~)
quest_agent_quests(agent_id)
Return the raw quest tree stored on an agent, or None if none exist yet.
The tree is a MastDataObject with a children dict keyed by quest ID.
Most scripts should prefer quest_get over accessing the tree directly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent_id
|
Agent ID, object, or |
required |
Returns:
| Type | Description |
|---|---|
|
MastDataObject | None: The root quest container, or |
Example
tree = quest_agent_quests(SHIP_ID) if tree is not None: ~~ print(tree.get("children").keys()) ~~
quest_complete(agents, quest_id)
Emit a quest_completed signal for one or more agents.
quest_completed is an INPUT: you emit it to ASK for a quest to be completed.
The LegendaryMissions quest driver listens for it and calls quest_mark_complete.
To REACT to a quest finishing, listen for quest_succeeded - that is what the
driver emits once it has finished processing a completion (quest_failed_done for
a failure, quest_started for an activation). Do not write
//signal/quest_completed to catch a completion: nothing emits it except callers
like this one, so the route waits forever and fails silently. The two names are
near-identical and point opposite ways; this bug killed every narrated beat in the
2.8 converter's output.
To also update the stored state without the driver, call
quest_set_key(agent, quest_id, "state", QuestState.COMPLETE).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agents
|
Agent ID, object, or list/set of either. |
required | |
quest_id
|
str
|
Quest to complete. |
required |
Example
quest_complete(SHIP_ID, "patrol") quest_set_key(SHIP_ID, "patrol", "state", QuestState.COMPLETE)
quest_console_enable(console, enable=True)
Mark one or more console types as quest-panel-enabled.
Controls which console types display the quest panel. Multiple console names can be passed as a comma-separated string. Names are normalized to lowercase before storage.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
console
|
str
|
Console name(s) to update, e.g. |
required |
enable
|
bool
|
|
True
|
Example
quest_console_enable("helm,comms") quest_console_enable("engineering", False)
quest_consoles_clear()
Drop the quest-tab console names. CONTENT: a mission declares these from its own .mast at compile scope, so without a clear the set is add-only across an in-process reload and the next mission shows a Quests tab on consoles it never enabled.
quest_flatten_list()
Build a flat display list of all quests for the current client.
Collects quests from three sources — shared game quests (Agent.SHARED),
client quests, and the client's assigned ship quests — and flattens each
tree into a sorted list of gui_list_box_header items ready for display
in a listbox.
Returns:
| Name | Type | Description |
|---|---|---|
list |
Flat list of listbox header objects, ordered active → idle → complete → failed within each source group. |
Example
items = quest_flatten_list() gui_property_list_box(items, style="area:0,0,100,100;")
quest_folder(agent_id, quest_id)
Return the parent container and child key for a quest path.
Navigates the quest tree along the /-separated components of
quest_id, creating the root tree if it does not yet exist. Used
internally by most other quest functions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent_id
|
Agent ID or object that owns the quest tree. |
required | |
quest_id
|
str
|
Quest path, e.g. |
required |
Returns:
| Type | Description |
|---|---|
|
tuple[MastDataObject | None, str | None]: The parent container and the
final path component (the child key), or |
quest_generation()
A number that changes whenever any quest tree does. Drive an on change off
this instead of walking the trees.
quest_get(agent, quest_id)
Return a quest object by ID, or None if it does not exist.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent
|
Agent ID or object that owns the quest. |
required | |
quest_id
|
str
|
Quest identifier, e.g. |
required |
Returns:
| Type | Description |
|---|---|
|
MastDataObject | None: The quest data object, or |
Example
q = quest_get(SHIP_ID, "patrol") if q is not None: "Patrol state: {q.get('state')}"
quest_get_data(agent, quest_id)
Return the data value attached to a quest.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent
|
Agent ID or object that owns the quest. |
required | |
quest_id
|
str
|
Quest identifier. |
required |
Returns:
| Type | Description |
|---|---|
|
object | None: The data value passed to |
Example
d = quest_get_data(SHIP_ID, "patrol")
quest_get_description(agent, quest_id)
Return the description of a quest.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent
|
Agent ID or object that owns the quest. |
required | |
quest_id
|
str
|
Quest identifier. |
required |
Returns:
| Type | Description |
|---|---|
|
str | None: The description string, or |
Example
desc = quest_get_description(SHIP_ID, "patrol") "Objective: {desc}"
quest_get_display_name(agent, quest_id)
Return the display name of a quest.
Reads display_text - the field quest_add actually writes. It used to read
only display_name, which NOTHING in the codebase ever sets, so this returned
None for every quest and each caller fell back to the raw quest id. That is why
the text waterfall announced job_ghost/hail instead of "Hail the Derelict": not a
missing display name on some quests, but a key mismatch affecting all of them.
display_name is still honored first, so anything that deliberately set it with
quest_set_key keeps overriding.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent
|
Agent ID or object that owns the quest. |
required | |
quest_id
|
str
|
Quest identifier. |
required |
Returns:
| Type | Description |
|---|---|
|
str | None: The display name, or |
Example
name = quest_get_display_name(SHIP_ID, "patrol") "Mission: {name}"
quest_get_key(agent, quest_id, key, defa=None)
Return an arbitrary attribute from a quest object.
Reads any key stored on the quest's MastDataObject. Built-in keys are
"state", "display_text", "description", and "data".
Custom keys can be set with quest_set_key.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent
|
Agent ID or object that owns the quest. |
required | |
quest_id
|
str
|
Quest identifier. |
required |
key
|
str
|
Attribute name to read. |
required |
defa
|
optional
|
Value returned when the quest is missing or the key
has not been set. Defaults to |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
object |
The stored value, or |
Example
difficulty = quest_get_key(SHIP_ID, "patrol", "difficulty", "normal")
quest_get_parent(agent, quest_id)
Return the parent container of a quest without the child itself.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent
|
Agent ID or object that owns the quest. |
required | |
quest_id
|
str
|
Quest identifier whose parent to retrieve. |
required |
Returns:
| Type | Description |
|---|---|
|
MastDataObject | None: The parent container, or |
quest_get_state(agent, quest_id)
Return the current state of a quest.
Returns QuestState.IDLE both when the quest does not exist and when
its state has never been explicitly set.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent
|
Agent ID or object that owns the quest. |
required | |
quest_id
|
str
|
Quest identifier. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
QuestState |
Current state value. |
Example
if quest_get_state(SHIP_ID, "patrol") == QuestState.COMPLETE: "Patrol complete!"
quest_is_active(agent, quest_id)
Whether this agent is currently working this quest.
quest_is_complete(agent, quest_id)
Whether this agent has finished this quest.
QuestState is a CLASS, and MAST only imports module-level functions - so a script asking "is it done?" had no way to say so except comparing quest_get_state() to a bare 99. These three exist so nobody has to write the magic number.
quest_is_console_enabled(console)
Return whether a console type has quest-panel display enabled.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
console
|
str
|
Console name to check, e.g. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
bool |
|
Example
if quest_is_console_enabled("helm"): ~~ show_quest_panel() ~~
quest_is_failed(agent, quest_id)
Whether this agent has failed this quest.
quest_kill_count_for_difficulty(count, difficulty, baseline=5, grind_min=3, floor=1)
Scale a 'grind' kill target to the current difficulty.
The authored count is treated as the target at baseline difficulty and
scaled linearly (count * difficulty / baseline). Only grind-sized targets
(>= grind_min) scale, so single-target / boss kill quests are left exactly
as authored. The result never drops below floor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
count
|
int
|
Authored kill target (the value at |
required |
difficulty
|
float
|
Current difficulty (e.g. the DIFFICULTY setting). |
required |
baseline
|
int
|
Difficulty at which |
5
|
grind_min
|
int
|
Smallest target that scales; below this, return as-is. |
3
|
floor
|
int
|
Minimum returned target. |
1
|
Returns:
| Name | Type | Description |
|---|---|---|
int |
The difficulty-adjusted kill target (or |
|
|
below |
quest_log_build_items(sources)
Build the collapsible quest-log item list shared by both logs.
sources is a list of (section_label, agent_id). Each becomes a
gui_list_box_header followed by that agent's non-SECRET quests. Child quests
(nested via /-separated keys, e.g. arc/step1) render indented under their
parent as a TREE (see _quest_log_rows); empty sections are skipped. Rows are
MastDataObject with agent_id / key / group / depth / title / state /
state_label / progress / desc, so quest_log_template renders them the same
everywhere. The ONLY thing the two callers vary is sources.
quest_log_detail(row)
The second line of a row - the most useful thing known about it.
It used to repeat the state, which the icon's COLOR already says; a line that says
what the reader can already see is a line they stop reading. In order: how far along,
what it pays while it is still a choice, how long is left, and only then the state
(which for Done / Failed IS the news).
quest_log_icon(row)
The icon NAME for a row: its kind if it has one, else the plain state pip.
Shape says what KIND of thing it is, color says what STATE it is in - two facts in one glyph, where before every row was the same square and the kind was invisible. The name resolves through the icon sheet, so a mission that ships its own art re-skins every quest log without touching this.
quest_log_parent_summary(row)
Detail-pane text for a PARENT quest (an arc): its own description, then a checklist of its steps.
The quest tab used to answer a selected arc with "Select a quest from the list" - it skipped anything that rendered as a header, and a parent quest renders as one. But an arc IS a quest: it has a description, and what a player wants from it is "where am I in this?".
SECRET STEPS ARE NOT COUNTED. A step the story has not revealed is neither listed nor tallied; if any remain, a single "more to follow" line stands in for however many there are. So the pane shows real progress without disclosing how long the arc is - which is the whole point of authoring a step secret in the first place.
row is the list-box row/header data for the parent (it carries agent_id and
key). Returns "" for a bare Game/You/Ship group header, which has no quest.
quest_log_state_icon_color(state)
Hex color for the state icon (defaults to gray).
quest_log_state_label(state)
Display label for a quest state (Active / Available / Done / Failed / ...).
quest_log_template(item)
Canonical quest-log row renderer (section headers + quest rows), shared by the in-game and end-game logs. Fix the look here and both update.
quest_log_title()
Shared list title for the quest log.
quest_remove(agent, quest_id)
Remove a quest from an agent and return it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent
|
Agent ID or object that owns the quest. |
required | |
quest_id
|
str
|
Quest identifier to remove. |
required |
Returns:
| Type | Description |
|---|---|
|
MastDataObject | None: The removed quest, or |
Example
removed = quest_remove(SHIP_ID, "patrol")
quest_run_action(agent, quest_id)
Run this quest's Action: stage directions, if it declares any. Returns how
many applied.
Called automatically when a quest goes ACTIVE. Public because a mission that drives quests its own way still wants the block to fire.
ONE PER AGENT. A quest activated on five player ships runs its block five times -
the same multiplicity as a //signal route, and the same footgun. It is safe today
because every built-in verb is idempotent (becomes/joins set state,
arrives is keyed on the landmark, departs deletes something already gone), and
a mission registering its own verb has to keep that property or scope the quest to
Agent.SHARED_ID.
quest_set_key(agent, quest_id, key, value)
Set an arbitrary attribute on a quest object.
Use this to write any key — including "state" when you want to update
it directly. quest_activate and quest_complete only emit signals;
call this to actually store the new state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent
|
Agent ID or object that owns the quest. |
required | |
quest_id
|
str
|
Quest identifier. |
required |
key
|
str
|
Attribute name to write. |
required |
value
|
Value to store. |
required |
Example
quest_set_key(SHIP_ID, "patrol", "state", QuestState.ACTIVE) quest_set_key(SHIP_ID, "patrol", "difficulty", "hard")
quest_set_state(agent, quest_id, state)
Set the state of a quest and emit the appropriate signal.
Emits quest_activated when state is QuestState.ACTIVE,
quest_completed when state is QuestState.COMPLETE, and
quest_failed when state is QuestState.FAILED. Does nothing if
the quest is already in the requested state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent
|
Agent ID or object that owns the quest. |
required | |
quest_id
|
str
|
Quest identifier. |
required |
state
|
QuestState
|
The new state to assign. |
required |
quest_transfer(from_agent_id, to_agent_id, quest_id)
Move a quest from one agent to another.
Removes the quest from from_agent_id and adds it to to_agent_id
under the same quest_id. Returns False if the quest does not exist
on the source agent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
from_agent_id
|
Source agent ID or object. |
required | |
to_agent_id
|
Destination agent ID or object. |
required | |
quest_id
|
str
|
The quest to transfer, e.g. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
bool |
|
Example
quest_transfer(SHIP_ID, Agent.SHARED_ID, "rescue_mission")