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
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"
objectives
class-attribute
instance-attribute
objectives: tuple[ObjectiveSpec, ...] = Field(min_length=1)
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"