Skip to content

osrlib.crawl.quests

Authored quests: the matching clause, the objective spec, and the quest spec.

A quest composes the trigger vocabulary rather than introducing one of its own. An activation, an objective's completion, and a hidden objective's reveal are each a TriggerClause: a TriggerPattern naming the observable, plus the ConditionSpecs that must hold when it matches — the same edge-triggered patterns and the same live condition evaluation an authored TriggerSpec uses. Rewards are the same ConsequenceCommand surface under the same party selectors (PARTY_SELECTOR and FIRST_LIVING_SELECTOR).

A quest observes; it does not take. A clause condition with consumes=True is rejected at parse for the reason a trigger's is: the event a clause matches has already happened, so there is no attempt of the quest's own to charge a toll against.

Document order is the order of the Adventure.quests tuple, and an objective's order is its position in QuestSpec.objectives — the order a session's quest state (QuestState) keys its objectives in, so every walk over either is deterministic.

A spec is inert data; the Interpreter is the shipped listener that plays it, advancing quest state through its only four writers — the lifecycle commands ActivateQuest, RevealObjective, CompleteObjective, and CompleteQuest.

ObjectiveSpec

Bases: BaseModel

One objective: what it is called, how it completes, whether it starts hidden, and its text.

Objectives are monotonic — hidden becomes revealed, incomplete becomes complete, and neither goes back — because the quest vocabulary authors no repeat.

name is the objective's display label, the words a quest log shows beside its checkbox. It defaults empty — a document written before the field existed loads unchanged, additive within the schema version — and empty means unauthored: everywhere a label is shown (the view, the lifecycle events, the default formatter), an unauthored name falls back to the objective's id.

A hidden objective with no reveal_when is a normal shape: it surfaces when it completes, because completing an objective reveals it. reveal_when on an objective that starts visible is rejected at parse — a reveal clause for something already on the list is authored dead weight.

narrative carries the objective's own beats: offer is the line its reveal shows and journals, progress the line its completion shows and journals.

Examples:

from osrlib.crawl.narrative import NarrativeBlock
from osrlib.crawl.quests import ObjectiveSpec, TriggerClause
from osrlib.crawl.triggers import ItemAcquiredPattern

recover = ObjectiveSpec(
    id="recover-idol",
    name="Recover the flask",
    when=TriggerClause(pattern=ItemAcquiredPattern(item_id="holy_water")),
    narrative=NarrativeBlock(progress="The flask is yours; the shrine is quiet again."),
)
assert not recover.hidden and recover.reveal_when is None

id class-attribute instance-attribute

id: str = Field(min_length=1)

name class-attribute instance-attribute

name: str = ''

when instance-attribute

hidden class-attribute instance-attribute

hidden: bool = False

reveal_when class-attribute instance-attribute

reveal_when: TriggerClause | None = None

narrative class-attribute instance-attribute

narrative: NarrativeBlock | None = None

QuestSpec

Bases: BaseModel

One authored quest: when it starts, what it asks for, and what it pays.

activation absent means the quest is active from session start — a standing charge the party carries from round 0, with no activation beat to show, because there is no command channel before the first command. An authored clause makes activation an event the party crosses.

completion is "all" (every objective) or "any" (the first one to land). objectives holds at least one: an objective-less quest under the all rule would be born complete. concludes_adventure=True marks the quest whose completion ends the adventure in victory (CompleteQuest).

rewards are issued after the quest completes, in authored order, with the party selectors expanded to the members they name; an authored source is rejected at parse, because whoever issues a command stamps it.

narrative carries the quest's own beats: offer is the line its activation shows and journals, completion the line its completion shows and journals. Per-objective beats live on the objectives.

Examples:

from osrlib.crawl.commands import AwardXP
from osrlib.crawl.narrative import NarrativeBlock
from osrlib.crawl.quests import ObjectiveSpec, QuestSpec, TriggerClause
from osrlib.crawl.triggers import PARTY_SELECTOR, DungeonEnteredPattern, ItemAcquiredPattern

recover = ObjectiveSpec(id="recover", when=TriggerClause(pattern=ItemAcquiredPattern(item_id="holy_water")))
errand = QuestSpec(
    id="the-flask",
    name="The Stolen Reliquary",
    activation=TriggerClause(pattern=DungeonEnteredPattern(dungeon_id="barrow")),
    objectives=(recover,),
    rewards=(AwardXP(character_id=PARTY_SELECTOR, amount=200),),
    concludes_adventure=True,
    narrative=NarrativeBlock(
        offer="Sister Halda wants the reliquary back, and she is not asking twice.",
        completion="The flask returns to its niche. The temple bells answer.",
    ),
)
assert errand.completion == "all"

id class-attribute instance-attribute

id: str = Field(min_length=1)

name class-attribute instance-attribute

name: str = Field(min_length=1)

activation class-attribute instance-attribute

activation: TriggerClause | None = None

objectives class-attribute instance-attribute

objectives: tuple[ObjectiveSpec, ...] = Field(min_length=1)

rewards class-attribute instance-attribute

rewards: tuple[ConsequenceCommand, ...] = ()

completion class-attribute instance-attribute

completion: Literal['all', 'any'] = 'all'

concludes_adventure class-attribute instance-attribute

concludes_adventure: bool = False

narrative class-attribute instance-attribute

narrative: NarrativeBlock | None = None

TriggerClause

Bases: BaseModel

One matching clause: the observable, and what must hold when it happens.

conditions all have to hold: the tuple is an AND with no combinators, each condition evaluated live against session state at the moment of the match, through condition_holds. The field is pattern rather than when, so an objective's completion clause reads objective.when.pattern.

Examples:

from osrlib.crawl.gates import HasItemCondition
from osrlib.crawl.quests import TriggerClause
from osrlib.crawl.triggers import TownEnteredPattern

walked_home_carrying_it = TriggerClause(
    pattern=TownEnteredPattern(),
    conditions=(HasItemCondition(item_id="holy_water"),),
)
assert walked_home_carrying_it.pattern.pattern_type == "town_entered"

pattern instance-attribute

pattern: TriggerPattern

conditions class-attribute instance-attribute

conditions: tuple[ConditionSpec, ...] = ()