How It Works

When a player interacts with an NPC, QuestLines looks up all quests linked to that NPC and walks through their pages in order. The first page whose requirements all pass is displayed as a dialogue screen. The player sees the NPC's text and response buttons. Choosing a response executes its actions — updating quest state, giving items, running commands, or navigating to another page.

Core Concepts

Quest

A Quest is the top-level container. It has a title, description, and an ordered list of page IDs. When a player interacts with an NPC, the quest's pages are evaluated in the order they appear in this list.

Page

A Page represents one screen of dialogue. It has its own requirements (which must pass for the page to be selected), NPC display name, dialogue text, optional load actions, and any number of responses. Load actions run automatically whenever the page is shown — on initial NPC interaction and on any page: navigation — regardless of which response the player clicks.

Pages also support an optional JournalText field that overrides the dialogue text shown in the Quest Journal — useful when the in-world dialogue is spoken by an NPC but you want the journal entry written from the player's perspective. When blank, the Dialog field is used in the journal.

Response

A Response is one button on a dialogue screen. It has display text, its own requirements (controlling whether the button appears), and a list of actions executed when clicked.

PlayerData

Each player has a persistent data record storing: started quests, completed quests, custom tags, named tracker progress counters, and timestamps for cooldown checks. This data is stored in players.json and managed automatically.

Data Model

All configuration is stored as JSON files on the server. Below is the structure of each config type.

Quest File — quests/<id>.json

{
  "Title":        "The Lost Sword",
  "Description": "Help the blacksmith retrieve his stolen blade.",
  "Requirements": [],
  "Actions":      [],
  "Pages": [
    "lost_sword_intro",
    "lost_sword_progress",
    "lost_sword_turnin",
    "lost_sword_complete"
  ]
}

Page (in quest JSON under PageData)

{
  "Id":           "lost_sword_intro",
  "Title":       "A Favour to Ask",
  "Name":        "Aldric the Smith",  // NPC display name on this page
  "Requirements": ["questNotStarted:lost_sword"],
  "LoadActions":  [],  // actions run automatically when this page is shown
  "Objectives":  [],  // requirement strings shown as pass/fail objectives in HUD + journal
  "Dialog":      "Ah, {username}! My sword was stolen by goblins. Will you help?",
  "JournalText": "",  // optional: overrides Dialog in the Quest Journal (e.g. a player-perspective entry)
  "Portrait":    "",  // optional: per-page portrait (filename or resource-pack path)
  "SecondaryPortrait": "",  // optional: second portrait on opposite side
  "Responses": [
    { "Text": "I'll find it for you.", "Requirements": [], "Actions": ["startquest:lost_sword", "track:goblin_kills:kill:Goblin*"] },
    { "Text": "Not right now.", "Requirements": [], "Actions": [] }
  ]
}

NPC Config — npcs.json

{
  "Npcs": {
    "citizen_42": "lost_sword,daily_reward",
    "citizen_07": "goblin_king"
  }
}

players.json — auto-managed

{
  "Players": {
    "player-uuid-here": {
      "CompletedQuests": ["tutorial"],
      "StartedQuests":   ["lost_sword"],
      "Tags":           ["vip"],
      "Progress":       { "goblin_kills": 3 },
      "Timestamps":     { "daily_key": 1735000000000 },
      "Variables":      { "score": 7 },
      "TrackedQuests":  ["lost_sword"],
      "PreferredLanguage": "en-US",
      "AutoTrackQuests": true
    }
  }
}

Page Resolution Algorithm

Understanding how QuestLines selects a page is essential for authoring correct quest flows. The algorithm runs every time a player interacts with an NPC:

  1. Look up quests for this NPC.

    The NPC's citizen ID is looked up in npcs.json. The comma-separated list of quest IDs is the evaluation order for that NPC.

  2. For each quest, iterate its Pages list.

    Pages are evaluated in the order they appear in the Quest's Pages array.

  3. Check this page's Requirements.

    All requirements must pass. If any fails, skip to the next page.

  4. Show the first matching page.

    As soon as a page passes all requirements, that dialogue is opened. No further pages are evaluated.

  5. If no page matches, no dialogue opens.

    The player's interaction with the NPC silently does nothing.

Order Matters

Put your most specific pages first. A completed-state page must come before an in-progress page, which must come before the intro page. If the intro page (with questNotStarted) were listed last, it would never match once the quest was started because the in-progress page would match first. See Quest Patterns for full worked examples.

Admin Commands

Command Description
/ql create Open the quest creation wizard to scaffold a new quest and its pages. Item-ID fields include a searchable dropdown of every registered item.
/ql quest  (aliases: /ql ui, /ql q) Open the full QuestLines editor UI to browse and edit quests, pages, NPCs, and players. Includes a Translate Quest button that fills missing locale strings via the configured translation backend. The Users tab lists every known player sorted by online-then-last-seen, with an [O] tag marking currently-online players.
/ql npc Open the NPC management panel.
/ql wand <questId> Enter wand mode. The next NPC you punch will be linked to the specified quest.
/ql wandcancel Cancel an active wand mode assignment.
/ql reload Reload all config files from disk without restarting the server. Quests are validated as part of the reload; problems are reported to console but never block the reload.
/ql validate Run the quest validator on demand without reloading. Mirrors the checks performed at startup and on /ql reload; output is logged to console.
/ql achievement <list|grant|revoke|reset|reload> Manage player achievement state. list [player] shows every achievement and its unlock state; grant <player> <id> [stage] force-unlocks a stage and runs its UnlockActions; revoke/reset <player> <id> removes every stage of the achievement; reload re-reads achievements/ from disk. See Achievements below.
/ql info Show plugin version and dependency information.
/ql help List all available /ql commands.
💡
Tip

Use /ql wand <questId> as the fastest way to link quests to NPCs in-world. See NPC Setup for full details on wand mode and alternative linking methods.

Player Commands

Command Description
/journal  (aliases: /j, /log) Open the Quest Journal. Shows active and completed quests with the last dialogue seen for each, plus objectives for the current page. Players can toggle quest tracking to show objectives on the HUD. The journal remembers the last tab and quest the player was viewing and reopens to it next time; if the remembered entry no longer exists (quest removed, completed), it opens to the first available quest instead.
/qlconfig Open the personal quest settings UI to configure preferred language, auto-track HUD behaviour, HUD anchor corner, and exact HUD pixel offsets. Language changes take effect immediately. Requires the questlines.config.use permission.
/achievements  (alias: /ach) Open the Achievements UI to browse server-defined milestones, view unlock progress per stage, and see total points earned. Requires the questlines.achievements.use permission.

Permissions

QuestLines uses the following permission nodes. Admin commands require questlines.admin; it defaults to false (op-only). Player commands have their own nodes that default to true.

Permission Default Description
questlines.admin false Required for all /ql admin commands (quest editor, NPC setup, wand mode, reload, etc.).
questlines.journal.use true Required to open the Quest Journal via /journal.
questlines.config.use true Required to open the player settings UI via /qlconfig.
questlines.achievements.use true Required to open the Achievements UI via /achievements.
Multi-Language Support

QuestLines ships with built-in translations for English (en-US), Spanish (es-ES), German (de-DE), French (fr-FR), Portuguese (pt-BR), and Russian (ru-RU). Players can choose their preferred language via /qlconfig. Admins can add or override translations by placing .lang files in {pluginDataDir}/lang/{locale}.lang. The server default locale is set in the plugin config; individual players override it per-player.

Quest text itself (name, description, page dialogue, responses, objectives) is stored as a locale->string map. Authors can translate a quest in one click with the Translate Quest button in the editor, which fills missing locales via a pluggable HTTP backend — see Translation Backend. Bare-string legacy configs remain valid and are preserved byte-identical on disk until a translation is added.

Configuration

Global plugin settings live in mods/QuestLines/QuestLines.conf — a simple key=value file that is created with sensible defaults on first startup and re-saved with any new keys after a plugin upgrade. Reload with /ql reload.

HUD & Dialogue Defaults

KeyDefaultDescription
default_languageen-USLanguage assigned to newly-created player records. Existing players keep their /qlconfig selection. Supported: en-US, de-DE, es-ES, fr-FR, pt-BR, ru-RU.
default_hud_positionTopRightStarting HUD corner for new players. Options: TopRight, TopLeft, BottomRight, BottomLeft.
default_hud_offset_x10Default horizontal offset (pixels) from the HUD anchor corner for new players. Players can fine-tune their own values via /qlconfig.
default_hud_offset_y10Default vertical offset (pixels) from the HUD anchor corner for new players. Players can fine-tune their own values via /qlconfig.
hud_refresh_rate500 msHow often the Quest Goal HUD re-evaluates and redraws per player.
damage_tracking_time5000 msWindow during which damage contributors receive kill credit.
dialog_width_full / _mid / _compact20 / 350 / 570Horizontal anchor offsets for each dialogue width option.
hit_to_talk_default / f_to_talk_defaulttrueGlobal fallbacks for whether hitting or pressing F on an NPC opens dialogue. Overridable per-NPC in npcs.json.
debug_toastsfalseShow toast notifications on the right of the screen when tracking events fire (useful when authoring quests).

Storage Backend

QuestLines stores per-player state in a file by default, but can be switched to a database for production deployments. The choice is transparent to quests and actions — the same PlayerData model is used either way.

KeyDefaultDescription
storage_backendfileOne of file (JSON at players.json), sqlite, mysql, or mariadb.
db_url(empty)Full JDBC URL override. When set, db_host/_port/_name are ignored. For SQLite, a relative path here becomes the database filename.
db_host / db_portlocalhost / 3306MySQL/MariaDB host and port.
db_name / db_user / db_passwordquestlines / (empty) / (empty)MySQL/MariaDB schema and credentials.
db_pool_size5HikariCP maximum pool size. SQLite is capped at 1 regardless.
db_table_prefixql_Prefix applied to every table name (e.g. ql_player_data).
Switching Backends

There is no automatic migration between file and database backends. Stop the server, copy players.json aside if you need to seed, switch storage_backend, and restart. Existing player data in the previous backend is not read by the new one.

Translation Backend

The in-editor Translate Quest button uses a pluggable HTTP backend to auto-fill missing locale strings on a quest. Source language defaults to default_language; the button fills the remaining supported locales in one pass. Existing translations are never overwritten.

KeyDefaultDescription
translation_providermymemoryOne of mymemory (free, zero-setup), libretranslate (self-host or hosted), or none (disables the Translate button).
translation_endpoint(empty)Optional URL override for the provider. Blank uses the provider's default public endpoint.
translation_email(empty)Optional contact email for MyMemory — raises the daily character quota.
translation_api_key(empty)Optional API key for LibreTranslate instances that require authentication.

Achievements

Achievements are server-defined milestones unlocked when a player’s state satisfies a list of requirement criteria. Each achievement is stored in QuestLines/achievements/<id>.json and is made up of one or more ordered stages. A single-stage achievement is just an achievement with one stage; the editor and runtime treat all achievements uniformly. Players open the Achievements UI with /achievements (alias /ach); admins manage state with /ql achievement.

When achievements are evaluated

The AchievementManager walks every loaded achievement and unlocks any stage whose Criteria currently pass. Cascading stages can unlock together on a single pass. Re-evaluation happens after:

  • Tracking events (kill, break, place, craft, harvest, pickup, fishing) processed by QuestManager.
  • Quest completion (the questCompleted: action and the implicit completion that fires when a quest’s last page is reached).
  • Player join (after PlayerReadyEvent) so quests-completed-while-offline show up at next login.

When a stage unlocks the manager fires that stage’s UnlockActions through the normal ActionManager, so any action available to a response (give items, grant titles, run commands, send chat, etc.) can be used as an achievement reward.

Achievement counters are isolated from quest progress

Tracking-style criteria (kill:, break:, craft:, fishing, etc.) used in achievement Criteria are stored in private named trackers under the reserved __ach__ prefix. They are completely independent of the tag-based progress counters quests use, so a quest’s removeTag:tracking:kill:zombie never rolls back the achievement’s lifetime kill count.

Achievement File — achievements/<id>.json

{
  "Id":          "goblin_slayer",
  "Title":       "Goblin Slayer",
  "Description": "Slay goblins across the realm.",
  "Category":    "combat",
  "Icon":        "Sword_Iron",
  "Hidden":      false,
  "Stages": [
    {
      "Title":         "", // blank → falls back to "Goblin Slayer I"
      "Points":        5,
      "Rewards":       "100 gold",
      "Criteria":     ["kill:Goblin*:10"],
      "UnlockActions": ["economy:give:100", "chat:You earned a stage of Goblin Slayer!"]
    },
    {
      "Points":        10,
      "Rewards":       "250 gold + Goblin Slayer title",
      "Criteria":     ["kill:Goblin*:50"],
      "UnlockActions": ["economy:give:250", "giveTitle:combat:goblinslayer"]
    }
  ]
}
FieldDescription
IdUnique achievement ID. Must NOT contain : — the unlock-time map keys use id:tier internally.
Title / DescriptionAchievement-level localized text. Falls back to the per-stage equivalents when those are blank.
CategoryFree-form grouping string used by the Achievements UI to organize entries.
IconItem ID shown next to the achievement in the UI.
HiddenWhen true the achievement is concealed in the UI until the first stage unlocks.
StagesOrdered array of one or more stages. Each stage carries its own Title, Description, Points, Rewards text, Criteria, and UnlockActions. Criteria accept any requirement; UnlockActions accept any action.

Editing

Achievements have a dedicated ACHIEVEMENTS tab in /ql quest. The tab supports full CRUD on both single-stage and staged achievements; the editor distinguishes the achievement-level Title/Description/Icon/Category/Hidden fields from the per-stage Points/Rewards/Criteria/UnlockActions fields by switching between a Base context and a Stage N context, shown in the #AchievementContextLabel at the top of the editor.

Achievement Requirements

Quests can react to achievement progress via three requirement types (full details on the Requirements page):

  • achievement:<id>[:<minStage>] — player has unlocked the achievement (any stage) or at least the given tier.
  • achievementcount:<n> — player has unlocked at least n distinct achievements.
  • achievementpoints:<n> — player’s total achievement points are at least n.
  • achievementstages:<id>:<n> — player has unlocked at least n stages of a specific achievement.

Integrations

QuestLines ships with opt-in integrations that activate automatically when a compatible plugin is detected. These are in addition to the optional requirement/action dependencies (VaultUnlocked, MMOSkillTree, RPGLeveling, OrbisGuard, HyFishing) documented on the Requirements and Actions pages.

Insight HUD

When the Insight HUD plugin is installed alongside QuestLines Core, the plugin registers contributors that surface live quest data in the Insight HUD target panel while the player is looking at an NPC:

  • Quest availability — whether the NPC has a quest available, in progress, completed, or on cooldown for the viewing player.
  • Active tracking — for any quest on the NPC that is currently tracked, the player's goal text / objective progress is displayed.

There is no configuration required. If Insight HUD is not installed, the integration is silently skipped at startup.

QL Claims

When QuestLines Claims is loaded, Core registers actions, requirements, and variables that bridge quest state to claim flags and region membership. See the Claims wiki page for the full list; all of them are only active when the Claims plugin is present.

Anglers’ Almanac

When Anglers’ Almanac (AA) is detected on startup, QuestLines registers a bridge listener for AA’s catch and minigame events. Successful catches from the AA minigame feed the existing catchfish: and catchfishrarity: counters using the caught item’s ID and rarity, so existing fishing quests work transparently whether the player is using base HyFishing or AA. AA also exposes legendary/new-discovery flags and a per-cast performance rating, which drive a new family of requirements, trackers, and text variables — and a separate counter for fish lost in the minigame:

  • Requirements: fishmiss:, catchfishlegendary:, catchfishnew:, catchfishperf:<rating>:.
  • Tracking tags / tracker types: tracking:fishmiss:, tracking:catchfishlegendary, tracking:catchfishnew, tracking:catchfishperf:<rating> (plus the matching track:id:<type>:[targets] forms).
  • Text variables: {fishmissprogress:<fishItemId>}, {catchlegendaryprogress}, {catchnewprogress}, {catchperfprogress:<rating>}.

Performance ratings are perfect, great, good, fail, and nil. Biome and zone are not provided by AA, so catchfishbiome: / catchfishzone: remain HyFishing-only. The bridge is fully optional — if AA is not installed, Core never references its classes.

Books and Papers

When the Books and Papers plugin is detected on startup, Core registers two action types and one requirement that route through Books-and-Papers state without forcing the dependency.

  • Actions: mail:itemId:qty pushes an item stack to the player’s mailbox in their current world; givebook:title:author:body (with the optional givebook:itemId:title:author:body form for letter items) gives the player a signed book whose pages come from body. Use \f to break pages and \n for line breaks within a page.
  • Requirement: hasmail — true while the player has at least one queued item in their mailbox.

All three are no-ops when Books and Papers is absent, and all direct references to the plugin’s types are isolated in BooksAndPapersBridge so the QuestLines JAR loads cleanly without the dependency.

What to Read Next