Skip to content

Quest System

Status: Current Last reviewed: 2026-09-08 Source: shared/src/quests.ts, server/src/game/quests.ts

Quests are data-driven and server-authoritative. Definitions load from a JSON document; progress lives on the player and persists. The design goal is to teach systems and point players at sandbox activities, not to build a themepark.

Schema

A QuestDef has:

Field Meaning
id Stable quest id.
title Display name.
giverNpcId The npcId that offers and completes it.
description Flavour and instruction.
objectives Ordered list of QuestObjective.
rewards QuestReward: items and/or skillUses.
requires Optional list of quest ids that must be turned in first.
repeatable Optional flag.
chapter, region, giverName Optional journal labels; existing quests can derive them from the zone.
giverArea, conclusion Optional navigation area and text after turn-in.

A QuestObjective is { id, type, description, target, count }, with optional area and requires (earlier objective IDs). A QuestReward grants item stacks and skill-use amounts (fed through the normal use-based progression, so a reward can nudge a skill without a fake XP system).

Objective Types

Gameplay code reports events as string keys of the form type:target. The quest engine advances any active objective whose type:target matches.

Type Target Fires when
talk_to_npc npcId You interact with that NPC.
kill_mob variant You help defeat that variant; recent living contributors within 35 metres receive credit.
collect_item itemId Your inventory count of the item reaches count.
loot_item itemId You loot that item (cumulative).
reach_area trigger id You stand inside that quest-trigger volume.
interact_object object/prefab id You interact with a matching object.
hit_target_weapon <prefab>:<weaponId> You strike a target prop with that weapon.
hit_target_spell <prefab>:<spellId> You strike a target prop with that spell.
hit_target_arrow prefab You strike a target prop with a drawn arrow.
raise_ember level name Ember pressure reaches stirring or awakened.
defeat_boss variant You defeat the named mini-boss.

Example keys: kill_mob:scrapper, loot_item:goblin_ear, hit_target_weapon:training_dummy:sword, raise_ember:awakened.

Progress Tracking

Each player has a questLog mapping quest id to { state, counts[] }, where counts is one counter per objective. States are:

stateDiagram-v2
  [*] --> available
  available --> active: accept
  active --> ready: all objectives met
  ready --> active: progress lost (rare)
  ready --> turned_in: turn in at giver
  turned_in --> [*]

QuestSystem.onEvent advances matching counters and re-evaluates completion. When all objectives are met the quest flips to ready and the player is told to return to the giver. onInventoryChanged re-syncs collect_item objectives whenever the bag changes (looting, using, reward grants).

Quest Events

The server pushes quest_update (per-objective progress, with an objective index for combat-log lines) and quest_event (accepted, ready, turned_in with rewards). The client renders a HUD tracker, a quest log panel (J), a completed-quest popup, and combat-log lines as objectives tick up. NPC dialogue lists the quests that NPC can offer or complete, and pressing G in dialogue accepts or turns in the actionable one.

Quest Editor

Definitions load from server/data/quests/starter_quests.json, seeded from DEFAULT_QUESTS in shared/src/quests.ts on first boot. The /admin editor has a Quests button that opens the JSON, and Save Quests writes it back via the save_quests admin command. Players pick up new definitions on their next join. See Adding Quests.

Example Starter Quests

The eight original quests remain intact, from the training yard through Emberwake and Ironwood:

  1. A Blade Before Breakfast (Captain Rusk) — hit a training dummy 3 times with the sword and 3 with the mace. Rewards coins and a healing draught.
  2. Feathers in the Mud (Quartermaster Elen) — hit archery targets 3 times with drawn shots. Rewards arrows and Archery progress.
  3. The Old Spark (Old Varyn) — strike magic targets with Fireball, Lightning Bolt, and Rift Burst. Rewards a mana draught.
  4. Goblins at the Fenline (Captain Rusk, requires quest 1) — kill 3 Goblin Scrappers and loot 2 goblin ears. Rewards coins and Defense progress.
  5. Smoke from the Shrine (Old Varyn, requires quest 4) — reach the shrine trigger and raise ember pressure to Stirring. Rewards coins and Arcane progress.
  6. The First Emberwake (Captain Rusk, requires quest 5) — push pressure to Awakened and defeat the Goblin Champion. Rewards the Emberglass Trinket and coins.

  7. Ironwood Tracks — follow the tougher orc branch after the Fenline quest.

  8. Break the Warband — conclude the Ironwood branch.

M1 adds Tracks by the Road, The Hollow Howl, Greyfen's Missing Stores and Quiet the Watch, bringing the catalogue to twelve. For current objective counts, prerequisites and rewards, use the generated quest catalogue and implemented journal.

M1 authority and persistence

shared/src/starterContent.ts defines stable quest/interaction IDs. The server resolves interaction positions from authored zone objects and checks range, height, sight line, personal eligibility and cooldown. Supply bundles have separate objectives; repeated clicks on one cannot satisfy the other. Rescue requires that character's alarm objective. Accepting and turning in also requires the correct nearby giver and their offered quest list.

The server marks a quest returned before granting its rewards, making repeated turn-in messages harmless. Existing JSON persistence saves objective counts and rewards; it remains a local prototype store, not a transactional account backend. Acceptance seeds Emberwake objectives if the relay is already at the requested level, allowing later arrivals to continue the shared encounter.

npm run content:install adds the M1 zone objects and quest definitions to existing data, keeps content backups and leaves player records untouched. npm run test:starter verifies personal multiplayer progress, ordering, persistence, reward replay refusal, combat and animation.