Skip to content

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 gui_list_box_header items.

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 .amd file to read. Ignored if content is provided.

required
root_display_text str

Label for the root node. Defaults to "".

''
strip_comments bool

Skip // lines. Defaults to True.

True
content str | None

Raw AMD text to parse instead of reading file_path. Defaults to None.

None

Returns:

Name Type Description
dict

Nested document tree rooted at "__root__".

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. "patrol" or "main/patrol".

required
display_text str

Short label shown to the player.

required
description str

Longer description text.

required
state QuestState

Initial state. Defaults to QuestState.IDLE.

IDLE
data object

Arbitrary data attached to the quest and accessible via quest_get_data. Defaults to None.

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, uses obj["id"].

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 Agent.SHARED_ID for global quests.

required

Returns:

Type Description

MastDataObject | None: The root quest container, or None.

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. "helm" or "helm,comms,science".

required
enable bool

True to enable, False to disable. Defaults to True.

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. "main/patrol".

required

Returns:

Type Description

tuple[MastDataObject | None, str | None]: The parent container and the final path component (the child key), or (None, None) if the agent does not exist.

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. "patrol" or "main/patrol".

required

Returns:

Type Description

MastDataObject | None: The quest data object, or None.

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 quest_add, or None.

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 None if the quest does not exist.

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 None if the quest does not exist or has no name of either kind.

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.

None

Returns:

Name Type Description
object

The stored value, or defa.

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 None.

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. "helm".

required

Returns:

Name Type Description
bool

True if enabled via quest_console_enable.

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 baseline difficulty).

required
difficulty float

Current difficulty (e.g. the DIFFICULTY setting).

required
baseline int

Difficulty at which count is used unchanged. Default 5.

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 count unchanged if it is

below grind_min or the inputs aren't numeric).

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 None if not found.

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. "patrol/sector7".

required

Returns:

Name Type Description
bool

True if the quest was found and transferred, False otherwise.

Example

quest_transfer(SHIP_ID, Agent.SHARED_ID, "rescue_mission")