Quest Script Manager
This page describes committed source. It does not certify in-game action outcomes, quest-stage convergence, or quest completion. Those claims require a rebuilt live-client check and an observed domain postcondition.
Quest Script Manager records selected player actions into an in-memory sequential script and can play supported steps back through n3 interaction APIs. It is registered in runelite-plugin.properties, uses config group questscriptmanager, and is disabled by default.
It is not currently a full quest authoring environment. The panel does not edit conditions or branches, the model has no stage/branch node, and ordinary Gson import cannot reconstruct its interface-typed steps and conditions reliably. Contributors can read the repository-only docs/_internal/quest-helper-vendor-script-generation-audit.md for the complete source comparison and roadmap.
Current model
QuestScript stores metadata, one QuestScriptConfig, and an ordered List<QuestScriptStep>. Playback advances through that list; it does not select a branch from quest state.
The six concrete step classes are:
| Step | Expressed action | Important boundary |
|---|---|---|
TalkToNpcStep | Interact with a primary or alternate NPC ID using an action | Does not encode conditional Quest Helper ownership or NPC-state transitions |
InteractObjectStep | Interact with a primary or alternate object ID, optionally near an exact location | Does not cover arbitrary widgets, world entities, or simulated objects |
UseItemStep | Inventory action or item on NPC/object/item/ground item | Target discovery and postconditions remain author responsibilities |
DialogueStep | Continue or select a numbered option | Does not model patterns, exclusions, last-line checks, or varbit-dependent choices |
WalkStep | Walk to a destination, optionally through waypoints | Arrival is not proof that the intended quest stage advanced |
WaitStep | Wait fixed ticks or for one condition | A wait description is not a replayable generic UI operation |
Every step can store descriptions, preconditions, postconditions, required/acquired items, priority, and estimated ticks. The player evaluates preconditions and postconditions. It does not use priority for interrupts or estimated ticks for scheduling.
Conditions
QuestConditionFactory provides:
- item quantity in inventory;
- item quantity in inventory or bank;
- distance from a
WorldPoint; - named quest state;
- varbit equality or minimum;
- nearby NPC or object by ID and radius;
- widget visibility;
- dialogue text containment;
- composite AND and OR;
- logical NOT.
These conditions can be attached in Java through the step builders. The current panel has no condition editor. The condition system does not include a varplayer comparator, polygon zone, chat-message history, exact widget text/model/sprite, NPC interaction relation, follower/instance/sailing predicate, or an event-owned transition graph.
What recording captures
QuestScriptRecorder listens to menu, dialogue, movement, and game-tick events. Depending on the recorder configuration, it can produce:
| Observed action | Recorded form |
|---|---|
| NPC menu action | TalkToNpcStep with observed ID/name/action |
| Object menu action | InteractObjectStep with observed ID/name/action/location |
| Inventory or ground-item action | UseItemStep |
| Item on NPC/object/item | UseItemStep with source and target IDs |
| Dialogue continue/selection | DialogueStep with continue or inferred option number |
Movement beyond movementThreshold | WalkStep |
| Generic/widget action without a concrete decoder | One-tick WaitStep carrying descriptive text |
Actions are buffered and periodically flushed. Movement records the path that was observed, not a route requirement or transport contract. The recorder does not infer alternate transformed IDs, quest stages, conditional branches, eligibility requirements, item-acquisition policy, or postconditions. Dialogue recording does not retain Quest Helper's text patterns, exclusions, last-line conditions, or varbit-dependent answer rules. A widget fallback is documentation in the script, not an executable widget action.
The panel exposes record, pause/resume, stop, undo, and add-wait controls. It does not offer a branch, condition, item-requirement, or stage editor.
Playback behavior
QuestScriptPlayer requests the shared input lock when started. It releases the lock on pause, stop, reset, failure, and completion. Each running tick:
- checks the global tick limit;
- waits for the process-wide
ActionPacerwhen pacing is enabled; - selects the current sequential step;
- checks preconditions;
- executes the step;
- checks postconditions after a successful result, respecting the
verifyPostconditionsconfig field; - advances, retries, skips, or fails according to the player configuration.
The step timeout now tracks actual elapsed ticks on the current step via stepTimeoutCounter, not the attempt counter (currentStepAttempts). Precondition failures and DISPATCHED actions increment the attempt counter, while pacer waits do not - they are tracked separately.
DISPATCHED means the action was accepted for dispatch. The player increments its attempt counter and revisits the step. A distinct stepStatus tracks dispatched state, and postconditions can re-check after dispatch. There is no operation handle or per-step convergence watcher, so dispatch does not prove that the interaction completed or that the quest state changed.
Four contracts need repair before generated scripts can be trusted (see audit Phase 1):
- Step timeout mislabeling: The timeout now tracks elapsed ticks on the current step (
stepTimeoutCounter), not the attempt counter (currentStepAttempts). Pacer waits do not increment it, while precondition failures andDISPATCHEDactions do. - DISPATCHED result can be followed by immediate postcondition failure and re-dispatch: A distinct
stepStatusenum (PENDING,ACCEPTED,DISPATCHED,FAILED,COMPLETED) tracks state, and postconditions are re-checked after dispatch. - Retry and timeout share the same attempt counter: Now separated -
currentStepAttemptstracks retry attempts only,stepTimeoutCountertracks elapsed ticks. Both are reset independently inretryCurrentStep()andjumpToStep(). - Postconditions not consistently represented: The
verifyPostconditionsconfig field now actually controls whether postconditions are checked. Whenfalse, postconditions are skipped and the step is accepted as-is, with the config field properly represented in the active player path.
Player completion means the sequential list was exhausted. It does not confirm the named quest's QuestState.
Configuration truth
There are two separate configuration objects.
Active RuneLite plugin configuration
QuestScriptManagerConfig controls the current recorder/player path:
| Field | Current use |
|---|---|
| Pacing, minimum/maximum ticks, jitter | Mapped into QuestScriptPlayerConfig and the shared ActionPacer |
| Maximum retries, stop on failure | Mapped into QuestScriptPlayerConfig |
| Auto-record dialogue/movement, movement threshold | Used by the recorder |
| Auto-stop on complete | Calls stopPlayback() after either COMPLETED or FAILED; despite its name, it also clears failed playback |
| Debug logging/log level | Logging/presentation setting, not quest semantics |
Stored per-script configuration
QuestScriptConfig stores automatic acquisition, banking, teleport use, failure policy, retries, global and step timeout ticks, pacing, Quest Helper requirement/profile, blacklisted areas, and custom rules. QuestScriptManagerPlugin.startPlayback does not read QuestScript.getConfig(). Those fields are metadata in the current playback path.
Do not rely on the source comments that translate 36,000 or 600 ticks into wall-clock minutes or seconds; those comments assume 50 ms ticks and are not RuneLite game-tick conversions.
Script construction
Scripts can be assembled in Java with the builders:
QuestScript script = QuestScript.builder(
"cooks_assistant_draft",
"Cook's Assistant draft",
"Cook's Assistant")
.description("Sequential authoring draft")
.addStep(WalkStep.builder()
.destination(new WorldPoint(3210, 3212, 0))
.maxDistance(3)
.description("Approach the Lumbridge kitchen")
.build())
.addStep(TalkToNpcStep.builder()
.npcId(1234)
.npcName("Cook")
.action("Talk-to")
.addPrecondition(QuestConditionFactory.npcNearby(1234, 8))
.addPostcondition(QuestConditionFactory.dialogueContains("ingredients"))
.description("Talk to the Cook")
.build())
.build();
IDs in examples are illustrative. Confirm current gameval/RuneLite identifiers and define a real postcondition before live use.
The model has no ConditionalStep. Branching examples using ConditionalStep.builder() do not compile against Quest Script Manager. Representing branches requires a future model and editor change; a list of mutually exclusive steps is not an equivalent substitute.
Storage, import, and export
Loaded scripts are held in the plugin's in-memory map. The panel can export a selected script as Gson JSON and choose a JSON file for import.
The current model is not a proven JSON persistence schema. QuestScript contains List<QuestScriptStep>, and step condition lists contain the QuestCondition interface. Import uses ordinary new Gson().fromJson(json, QuestScript.class) without type discriminators or adapters. A meaningful polymorphic round trip is therefore structurally unsupported and has no automated test. Treat exported files as diagnostic/authoring output until a versioned DTO schema and round-trip tests exist.
Integration boundaries
Quest Helper
Quest Script Manager does not read Quest Helper (official or internal) during playback. requireQuestHelper and questHelperProfile exist only in the unused per-script configuration path.
Questing Assistant is separate. It reflects the current step from the installed official Quest Helper and executes a coarse subset of current intent; it does not supply a branch graph to Quest Script Manager.
Walker and Packet Utils
Concrete steps call n3 interaction and walking facilities. The shared input lock and action pacer apply during playback. Dispatch or a walker acceptance result remains weaker evidence than the requested domain postcondition.
Break Handler
Quest Script Manager does not register with or consult Break Handler in current source. Older claims that playback automatically respects Break Handler were incorrect. The repository's broad Break Handler policy test presently classifies Quest Script Manager as a full-automation plugin that should integrate; that source/test discrepancy remains to be resolved in implementation work.
Agent Server
No Quest Script Manager route, operation, or MCP tool exists in the current Agent Server source. Scripts must be controlled through the plugin/panel APIs shown by live source; documentation examples for an Agent Server playback route were removed.
Panel facilities
The current panel provides:
- status and current-script information;
- recording controls, undo, and manual wait insertion;
- loaded-script selection, deletion, import, and export;
- playback start, pause/resume, stop, and skip controls.
It does not provide a general step property editor, condition builder, item planner, stage graph, conditional sub-script editor, unsupported-node task list, or import validation report.
Automated coverage
The committed quest-script test surface contains QuestScriptManagerPluginTest. It covers config provision and limited recording-stop behavior. There are no dedicated committed tests for recorder event decoding, concrete step execution, condition evaluation, player result/timeout semantics, input-lock lifecycle, script configuration mapping, panel editing, JSON round trips, branching, or quest completion.
Run the current package tests with:
.\gradlew.bat :test --tests 'com.n3plugins.questscript.*' --console plain --rerun-tasks --no-daemon
Passing these tests establishes only the behavior they exercise. It does not validate imported JSON or live quest playback.
Safe authoring guidance
- Treat recordings as linear drafts, not complete quest definitions.
- Add explicit preconditions and domain postconditions in Java for every consequential action.
- Do not turn unknown widget, puzzle, cutscene, combat, or manual behavior into a blind wait.
- Keep mutually exclusive routes separate until the model supports explicit branches.
- Confirm revision-sensitive IDs against the current RuneLite/game revision.
- Rebuild and reload the plugin before live acceptance.
- Require the actual quest/stage postcondition; do not count
DISPATCHED, list exhaustion, tests, or JAR presence as quest completion.