AI Generation
QuestLines provides a self-contained system prompt that lets any AI assistant generate valid quest JSON for you. Copy it from the System Prompt section below, paste it into a new AI chat session, then describe the quest you want. This page also covers how to write effective prompts and what to watch for when reviewing AI output.
What Is the System Prompt
The QuestLines system prompt is a complete, self-contained specification of the plugin's data model — written specifically to be pasted into an AI chat session as the first message.
It covers every requirement type, every action type, valid entity and item ID conventions, page ordering rules, tracking tag requirements, timer format nuances, and worked examples of common quest patterns. When an AI model has this context loaded it can produce syntactically correct quest JSON without you needing to specify every rule manually.
The prompt works with any instruction-following language model — ChatGPT, Claude, Gemini, or a locally running model. The larger and more capable the model, the better the output quality for complex multi-page quests.
System Prompt
Copy the full block below and paste it as the first message in a new AI chat session. The model will acknowledge the spec — then describe the quest you want in your next message.
You are helping design and write quest content for a Hytale server plugin called QuestLines.
Below is the complete technical reference for the plugin. Use it to answer questions, write
quest configs, and validate quest logic.
═══════════════════════════════════════════════════════
OVERVIEW
═══════════════════════════════════════════════════════
QuestLines ties NPC dialogue to quests. When a player interacts with an NPC (via HyCitizens),
the plugin finds the first quest page assigned to that NPC whose requirements all pass for
that player, then shows it as a dialogue screen with response buttons. Each response
can have its own requirements and actions. This drives all quest progression.
═══════════════════════════════════════════════════════
CONFIG FILES (stored in the plugin data folder)
═══════════════════════════════════════════════════════
quests/<questId>.json — one file per quest; pages are embedded inside under "PageData"
npcs.json — NPC-to-quest assignments
players.json — Per-player state (auto-managed)
The quests/ directory is created automatically on first run.
Each file contains only the quest body — the ID is the filename, not a key.
All JSON keys are PascalCase.
═══════════════════════════════════════════════════════
DATA MODEL
═══════════════════════════════════════════════════════
── Quest (file: quests/my_quest.json) ────────────────
{
"Title": "The Lost Sword",
"Description": "Recover the blacksmith's stolen sword.",
"Requirements": ["questNotStarted:my_quest"],
"Actions": [],
"Pages": ["my_quest_page1", "my_quest_page2"],
"PageData": {
"my_quest_page1": {
"Id": "my_quest_page1",
"Title": "my_quest_page1",
"Name": "Blacksmith",
"Requirements": ["questNotStarted:my_quest"],
"LoadActions": [],
"GoalText": "",
"Objectives": [],
"Dialog": "Adventurer! Someone stole my sword. Can you help?",
"Responses": [ { "Text": "I'll find it!", "Actions": ["questStarted:my_quest"] } ]
},
"my_quest_page2": {
"Id": "my_quest_page2",
"Title": "my_quest_page2",
"Name": "Blacksmith",
"Requirements": ["questCompleted:my_quest"],
"LoadActions": [],
"GoalText": "",
"Objectives": [],
"Dialog": "You found it! Thank you.",
"Responses": [ { "Text": "Any time.", "Actions": [] } ]
}
}
}
The filename (without .json) is the quest ID. No outer wrapper key.
Pages lists page IDs in resolution order — the first page whose requirements pass is shown.
PageData embeds all page objects keyed by page ID. Every page listed in Pages must have an
entry in PageData. Pages not listed in Pages are unreachable and should be omitted.
── Page (fields reference) ───────────────────────────
{
"Id": "my_quest_page1", required — must match the PageData key
"Title": "my_quest_page1", optional internal label
"Name": "Blacksmith", required — NPC display name shown in dialogue
"Requirements": ["questNotStarted:my_quest"],
"LoadActions": [], actions fired every time this page is displayed
"GoalText": "", optional HUD text for this page when quest is tracked
"Objectives": [], optional list of requirement strings shown in journal
"AutoTrigger": false, see AutoTrigger section below
"Dialog": "...", required — dialogue text shown to player
"Responses": [ { ... }, { ... } ] required — response buttons
}
The page ID must be unique across all pages.
Recommended convention: questId_pageN.
── AutoTrigger pages ──────────────────────────────────
Setting "AutoTrigger": true on a page causes QuestLines to fire that page's LoadActions
automatically every HUD tick as soon as all of its Requirements pass — without any NPC
interaction. This is used for background effects, auto-complete triggers, and timed events.
IMPORTANT: AutoTrigger pages re-fire every tick while requirements pass. You MUST use a
guard tag to prevent repeated firing:
"Requirements": ["questStarted:my_quest", "notTag:my_quest_done"],
"AutoTrigger": true,
"LoadActions": ["questCompleted:my_quest", "addTag:my_quest_done"]
The notTag check stops the page from firing again after the tag is added.
Auto-trigger pages do NOT need to be interacted with — they never open a dialogue UI.
They should still be listed in the quest's Pages array so the plugin includes them in
its resolution pass.
── Response ───────────────────────────────────────────
{
"Text": "I'll find it for you!",
"Requirements": ["questNotStarted:my_quest"],
"Actions": ["questStarted:my_quest"]
}
Responses are stored as a JSON array under the key "Responses". There is no hardcoded
limit on the number of responses. A response is shown if and only if its Text is non-empty.
Note: old configs using Response1–Response4 keys are automatically migrated on load;
new files should always use the "Responses" array.
── NpcConfig ──────────────────────────────────────────
{
"Npcs": {
"citizen_abc123": "my_quest,another_quest"
},
"RandomGroups": {
"citizen_abc123": "questA|questB;questC|questD"
}
}
The citizenId comes from HyCitizens. One NPC can serve multiple quests.
═══════════════════════════════════════════════════════
PAGE RESOLUTION — HOW THE PLUGIN PICKS WHICH PAGE TO SHOW
═══════════════════════════════════════════════════════
When a player interacts with an NPC, the plugin:
1. Gets all quest IDs assigned to that NPC (from Npcs map), in order.
2. For each quest, iterates Pages in order.
3. Returns the FIRST page whose Requirements all pass for this player.
4. If no page matches, no dialogue opens.
Page order matters enormously. Put the most specific/restrictive pages first
(e.g. the "quest completed" response), and the generic opener last.
═══════════════════════════════════════════════════════
REQUIREMENTS (colon-delimited strings)
═══════════════════════════════════════════════════════
All requirements return true or false. A page or response shows only when ALL of its
requirements return true. An empty requirements list always passes.
Quest state:
questCompleted:questId true if player has completed that quest
questNotCompleted:questId true if player has NOT completed it
questStarted:questId true if quest is in progress (started but not completed)
questNotStarted:questId true if quest is not in the started list
Tags:
hasTag:tagName true if player has this tag
notTag:tagName true if player does NOT have this tag
Named tracker (new system — preferred):
track:id:qty true if named tracker "id" has accumulated >= qty
Requires a prior track:id:type:target action.
Legacy count requirements (require the corresponding addTag:tracking:* tag):
kill:entityTypeId:qty true if player has killed >= qty of that entity type
(requires addTag:tracking:kill:entityTypeId to be active)
killCitizen:citizenName:qty true if player has killed >= qty citizens with that name
(requires addTag:tracking:killcitizen:citizenName to be active)
break:blockId:qty true if player has broken >= qty of that block
(requires addTag:tracking:block:blockId to be active)
place:blockId:qty true if player has placed >= qty of that block
(requires addTag:tracking:place:blockId to be active)
NOTE: Prefer track:id:qty over the legacy requirements above for new quests.
Inventory:
item:itemId:qty true if player has >= qty of that item in inventory
item:itemId:qty:true same, AND consumes the items when the response is used
NPC talk count:
talk:citizenName:qty true if player has talked to this NPC >= qty times
Cooldown:
cooldown:key:seconds true if >= N seconds have passed since setTimestamp:key
ALSO true if setTimestamp:key was never called
Timed quest:
timedActive:key true if timer is still running (uses duration from timedStart:key:N)
timedActive:key:seconds same, but overrides the stored duration with an explicit value
timedExpired:key true if timer has expired (uses duration from timedStart:key:N)
timedExpired:key:seconds same, but overrides the stored duration with an explicit value
Both return FALSE if timedStart was never called for this key.
Position:
nearPosition:x:y:z:radius true if player is within radius blocks of the coordinate
World time:
timeOfDay:morning hour 6–11
timeOfDay:afternoon hour 12–16
timeOfDay:evening hour 17–20
timeOfDay:night hour 21–5
Moon phase:
moonPhase:full phase 0
moonPhase:waning phases 1, 2, 3
moonPhase:new phase 4
moonPhase:waxing phases 5, 6, 7
moonPhase:3 exact numeric phase (0–7)
World:
world:worldName true if player is in the named world
Named variables:
variable:name:greater:value true if the variable's value > value
variable:name:less:value true if the variable's value < value
variable:name:equal:value true if the variable's value == value
variable:name:greaterOrEqual:value true if the variable's value >= value
variable:name:lessOrEqual:value true if the variable's value <= value
Logic operators:
any:req1|req2|req3 true if ANY of the pipe-separated requirements passes
not:requirement negates any requirement (e.g. not:questStarted:my_quest,
not:track:zombie_kills:10)
!requirement shorthand for not: (e.g. !hasTag:my_tag)
═══════════════════════════════════════════════════════
ACTIONS (colon-delimited strings)
═══════════════════════════════════════════════════════
Actions run in order when a response is clicked.
Quest state:
questStarted:questId adds questId to startedQuests, shows notification
questCompleted:questId adds to completedQuests, removes from startedQuests
questRemoved:questId removes from both startedQuests and completedQuests
Tags:
addTag:tagName adds a tag to the player
removeTag:tagName removes a tag
Cooldown / Timer:
setTimestamp:key records current time (for cooldown requirements)
timedStart:key records current time (timedActive/timedExpired require
the seconds arg explicitly in that case)
timedStart:key:durationSeconds records current time AND stores the duration so
timedActive:key and timedExpired:key can omit the seconds arg
Items:
item:itemId:qty gives qty of itemId to the player
Commands:
command:commandText:server runs the command as the server console
command:commandText:player runs the command as the player
NOTE: commandText cannot itself contain colons.
Dialogue navigation:
page:pageId navigates to a different dialogue page (put this last)
Named variables:
variable:name:set:value sets a named numeric variable to value
variable:name:add:value adds value to the variable
variable:name:subtract:value subtracts value from the variable
variable:name:multiply:value multiplies the variable by value
variable:name:divide:value divides the variable by value
Economy (requires VaultUnlocked):
economy:give:amount deposit amount into player's account
economy:take:amount withdraw amount from player's account
Sound:
sound:soundName play a 2D sound event to the player
sound:soundName:true same, but broadcasts to all online players
Random:
random:[action1,action2,...] pick and execute one action at random from the bracket list
Note: page: cannot be used inside random:
Delay:
delay:N pause N seconds, then continue the remaining actions
NPC spawning:
spawnCitizen:citizenName:x:y:z[:delay[:quantity]]
Spawns a clone of an existing HyCitizens citizen at the
given coordinates. Clone is temporary — removed on death.
Optional quantity spawns multiple clones. Coordinates support
~ prefix for player-relative positioning (e.g. ~5 = player + 5).
e.g. spawnCitizen:Dark Knight:~0:~2:~5:3:1
spawn:npcRoleId:x:y:z[:delay[:quantity]]
Spawns a standard Hytale NPC by role ID.
Optional quantity. Coordinates support ~ prefix.
e.g. spawn:village_guard:~0:64:~5:3:2
═══════════════════════════════════════════════════════
NAMED TRACKERS (count player actions)
═══════════════════════════════════════════════════════
Kill, block, and other progress only counts for players who have an active named tracker.
Use track:id:type:[targets] to create a tracker; untrack:id to remove it and reset its count.
Setup (global — counts anywhere):
track:id:kill:entityTypeId starts counting kills of that entity type
track:id:killcitizen:citizenName starts counting citizen kills with that name
track:id:block:blockId starts counting breaks of that block type
track:id:place:blockId starts counting placements of that block type
track:id:playerkill:* starts counting player kills
track:id:travel:* starts counting blocks traveled
track:id:harvest:itemId starts counting item harvests
track:id:pickup:itemId starts counting item pickups
Location-gated — only counts events within radius blocks of a coordinate:
track:id:kill:entityTypeId:loc:x:y:z:radius
track:id:block:blockId:loc:x:y:z:radius
track:id:place:blockId:loc:x:y:z:radius
World-gated — only counts events in a named world:
track:id:kill:entityTypeId:world:worldName
Multiple targets (bracket list):
track:id:kill:[Goblin_Duke,Goblin_Miner,Goblin_Scout]
Wildcard target:
track:id:kill:Goblin* counts any entity whose ID starts with "Goblin"
Reset:
untrack:id removes tracker and resets count to 0
untrack:[id1,id2] removes multiple trackers at once
Talk progress tracks automatically — no tracker needed.
═══════════════════════════════════════════════════════
DIALOG TEXT — VARIABLES & FORMATTING
═══════════════════════════════════════════════════════
Variables (substituted at display time):
{username} player's username
{npcname} the page's Name field
{questname} title of the player's first started quest
{track:id} current count for the named tracker with that id
{cooldown:key:totalSeconds} remaining cooldown as human-readable string
{timeleft:key:totalSeconds} remaining time on a timed quest
{variable:name} current value of a named variable (whole numbers without decimal)
{balance} player's economy balance (requires VaultUnlocked)
{distancefrom:x:y:z} distance from player to (x,y,z), 1 decimal place
Inline formatting:
{b}bold{/} {i}italic{/} {m}monospace{/} {#FF0000}red{/}
═══════════════════════════════════════════════════════
RANDOM QUEST POOLS
═══════════════════════════════════════════════════════
RandomGroups format: "questA|questB;questC|questD"
"|" separates quests within one pool (one chosen at random)
";" separates pools (one quest chosen from EACH pool)
Assignment stored as tag: assigned_quest:questId:citizenId
Use hasTag:assigned_quest:questId:citizenId in requirements to gate content per-player.
═══════════════════════════════════════════════════════
COMMON QUEST PATTERNS
═══════════════════════════════════════════════════════
── Linear quest with kill objective ───────────────────
Quest pages (in order):
1. intro — Requirements: [questNotStarted:quest_id]
Response 1 "Accept": Actions: [questStarted:quest_id, track:zombie_kills:kill:Zombie]
2. progress — Requirements: [questStarted:quest_id, not:track:zombie_kills:10]
Dialog: "You've killed {track:zombie_kills}/10 so far."
3. done — Requirements: [questStarted:quest_id, track:zombie_kills:10]
Response 1 "Claim": Actions: [questCompleted:quest_id,
untrack:zombie_kills, item:Reward_Item:1]
4. finished — Requirements: [questCompleted:quest_id]
Dialog: "Thanks again!"
── Cooldown gate ──────────────────────────────────────
Page "daily_ready" — Requirements: [cooldown:daily_key:86400]
Response 1 "Claim": Actions: [item:Reward:1, setTimestamp:daily_key]
Page "daily_wait" — Requirements: [not:cooldown:daily_key:86400]
Dialog: "Come back later. {cooldown:daily_key:86400}"
(Put daily_ready BEFORE daily_wait in the quest pages list.)
── Timed quest ────────────────────────────────────────
Quest pages (in order):
1. complete — Requirements: [questCompleted:quest_id]
2. expired — Requirements: [questStarted:quest_id, timedExpired:arena_timer]
3. active — Requirements: [questStarted:quest_id, timedActive:arena_timer]
4. intro — Requirements: [questNotStarted:quest_id]
Accept response: Actions: [questStarted:quest_id, timedStart:arena_timer:60]
── Item turn-in ───────────────────────────────────────
Page "turn_in" — Requirements: [questStarted:my_quest]
Response 1 "Hand over":
Requirements: [item:Ore_Iron:5:true] ← :true consumes the items
Actions: [questCompleted:my_quest, item:Reward_Item:1]
═══════════════════════════════════════════════════════
RULES & GOTCHAS
═══════════════════════════════════════════════════════
1. Page order is critical. completed → in-progress → not-started (most specific first).
2. untrack:id resets the counter. Always call untrack:id on quest completion.
3. cooldown returns TRUE if setTimestamp was never called. Plan page order accordingly.
4. item:itemId:qty:true consumes items; item:itemId:qty alone only checks inventory.
5. Tags can contain colons: hasTag:my:custom:tag is valid.
6. command:text — commandText is parts[1] only; commands with colons need a wrapper command.
7. page: navigation action should always be last in the Actions list.
8. timedStart:key:N stores the duration. timedActive:key and timedExpired:key read it back.
9. Multiple quests on one NPC are evaluated in the order listed in the Npcs map value.
10. LoadActions fire automatically when a page is shown (on first open AND on page: navigation),
before the player clicks any button. Use them for per-view side effects. All action types
that work in Response.Actions also work in LoadActions.
Getting Started
Using prompt.txt with an AI assistant takes only a few steps.
-
Copy the system prompt.
Scroll up to the System Prompt section and click Copy to clipboard. This copies the full QuestLines specification, ready to paste into any AI assistant.
-
Paste it as the first message in a new AI chat session.
Start a fresh conversation with your preferred AI assistant. Paste the copied text as your first message and send it. The model will acknowledge the spec — you don't need to do anything special here.
-
Write your quest request in the next message.
Describe what you want the quest to do. Be specific about NPC names, quest IDs, objectives, and rewards. The model will generate the quest JSON, page JSON files, and any npcs.json changes needed.
-
Copy the generated JSON into your server files.
Save each generated quest file to the
quests/folder on your server (pages are embedded inside the quest JSON under"PageData"), and updatenpcs.jsonif the AI provided changes for it. -
Validate before deploying.
Review the output against the checklist in the Validating AI Output section below before testing in-game.
Once prompt.txt is loaded, you can request multiple quests in the same session without repasting it. Ask for revisions, additional pages, or variant quests and the model will maintain context across messages.
Writing Good Quest Prompts
The quality of the AI output depends heavily on how precisely you describe the quest. Vague prompts produce generic quests; specific prompts with named objectives produce production-ready JSON. Here are the most effective strategies.
Specify the quest ID and NPC name
Always provide the quest ID you want to use and the NPC's display name. The AI uses
these to generate consistent page IDs and to populate the Name field on
each page.
// Good — specific IDs provided
Create a kill quest for an NPC named "Guard Captain".
Quest ID: bandit_patrol
// Less good — AI must invent IDs that may conflict with existing content
Create a kill quest for a guard NPC.
Name every objective with exact IDs
Entity IDs in QuestLines are PascalCase with no namespace prefix (e.g. Zombie,
not zombie or hytale:zombie). Item IDs use PascalCase with
underscores (e.g. Weapon_Sword_Iron, not iron_sword). Providing
the correct IDs in your prompt avoids the most common AI error.
// Good — exact IDs specified (Goblin* matches all goblin variants)
The player must kill 5 Zombie mobs and 3 Goblin* mobs.
Reward: 1 Weapon_Sword_Iron.
// Risky — AI may invent IDs in the wrong format
Kill some zombies and goblins. Give a sword as a reward.
Describe the full quest flow, not just the objective
Tell the AI how many pages the quest should have and what each state should show. At minimum, describe the intro state, the in-progress state, and the completion state. If you want a turn-in NPC or a separate completion NPC, say so explicitly.
The quest has three pages:
1. Intro: NPC asks the player to clear out bandits. Player can accept or decline.
2. Progress: NPC tells the player how many bandits remain. Shows current kill count.
3. Complete: NPC thanks the player and gives the reward.
State any special requirements
If the quest should only be available at a certain time of day, require a previous quest to be completed, have a cooldown, or use a timed objective, include that in the prompt. Do not assume the AI will infer these from context.
The quest requires the player to have completed "goblin_intro" first.
The reward NPC gives a daily chest with a 24-hour cooldown (86400 seconds).
Use the cooldown key "guard_daily".
Ask for npcs.json changes too
If you want the AI to also generate the npcs.json entry that links the
new quest to an NPC, ask for it explicitly. Otherwise you will need to add the mapping
manually or use /ql wand.
Also provide the npcs.json entry to link this quest to citizen ID "citizen_15".
Example AI Prompts
These ready-to-use prompts can be pasted directly after loading prompt.txt. Each covers a different quest pattern. Adjust IDs, names, and quantities to match your server.
Kill Quest
Create a kill quest for an NPC named "Guard Captain".
The player must kill 5 Zombie mobs and 3 Goblin* mobs (any goblin variant).
Reward: 1 Weapon_Sword_Iron.
Quest ID: bandit_patrol
NPC citizen ID: citizen_15
Generate the quest file, all page files, and the npcs.json entry.
Gather / Item Turn-In Quest
Create an item turn-in quest for an NPC named "Aldric the Smith".
The player must deliver 5 Ore_Iron to the NPC.
The items should be consumed on turn-in.
Reward: 1 Ingredient_Bar_Gold and 1 Weapon_Sword_Iron.
Quest ID: smiths_request
NPC citizen ID: citizen_08
The quest has three pages: intro (accept/decline), waiting (not enough iron ore yet),
and turn-in (player has 5+ ore and can hand them over).
Daily Reward Quest
Create a daily reward quest for an NPC named "Town Crier".
The NPC gives the player 1 Item_Chest_Daily once per day (86400 second cooldown).
Use cooldown key "town_crier_daily".
Quest ID: daily_bounty
NPC citizen ID: citizen_03
The quest has two pages:
- Ready page: cooldown has elapsed or never been set. Player clicks to claim reward.
- Waiting page: cooldown is still active. Show remaining time in the dialog using the
{cooldown:town_crier_daily:86400} variable.
Timed Delivery Quest
Create a timed quest for an NPC named "Merchant Relay".
The player must talk to a second NPC named "Warehouse Keeper" within 3 minutes (180 seconds)
of accepting the quest.
Use timer key "relay_timer".
Quest ID: courier_run
Starting NPC citizen ID: citizen_22
Ending NPC citizen ID: citizen_31
Pages needed:
- Intro (at citizen_22): accept the quest and start the timer.
- Active (at citizen_22): player has started but not yet delivered.
- Expired (at citizen_22): timer ran out. Quest fails and is removed.
- Delivery (at citizen_31): timer is still active. Player talks to the warehouse to complete.
- Already done (at citizen_31): quest is completed, friendly thanks.
Branching Dialogue Quest
Create a branching dialogue quest for an NPC named "Elder Maren".
The player is given a choice: help the merchants (tag: chose_merchants) or help the
rebels (tag: chose_rebels). Each path leads to a different follow-up page.
Quest ID: elder_choice
NPC citizen ID: citizen_07
The quest has five pages:
1. Intro: player picks a side (two response buttons). Each response adds the
appropriate tag and navigates to the matching follow-up page.
2. Merchants path page: explains the merchant task.
3. Rebels path page: explains the rebel task.
4. Both paths resolved: if player has both tags (both tasks done), show a final
thanks page and complete the quest.
5. Completed: quest is done, short acknowledgement.
Validating AI Output
AI models occasionally make systematic errors with QuestLines syntax. Before deploying AI-generated JSON, run through this checklist.
Page ordering
The most common structural mistake is pages listed in the wrong order. The rule is: most specific requirements first. The correct ordering is:
- Completed state (
questCompleted:id) — listed first. - In-progress / success state (
questStarted:id+ completion check) — listed second. - Intro / not-started state (
questNotStarted:id) — listed last.
For timed quests, the expired page must come before the active page, since both require
questStarted. Check that the AI has put expired before active in the
Pages array.
// Correct page order in a quest file
"Pages": [
"patrol_complete", // questCompleted — most specific, checked first
"patrol_progress", // questStarted — in-progress
"patrol_intro" // questNotStarted — least specific, checked last
]
Tracking tags in quest-start actions
Any quest that uses kill:entityId:qty, break:blockId:qty,
place:blockId:qty, or killCitizen:name:qty requirements
must have the corresponding tracking tag added in the quest-start
response's actions. Check that the intro page's accept-response includes the tag.
// Correct — trackers started on accept (Goblin* matches all goblin variants)
"Actions": [
"questStarted:bandit_patrol",
"track:zombie_kills:kill:Zombie",
"track:goblin_kills:kill:Goblin*"
]
// Also correct — trackers removed on quest completion
"Actions": [
"questCompleted:bandit_patrol",
"untrack:zombie_kills",
"untrack:goblin_kills",
"item:Weapon_Sword_Iron:1"
]
timedStart format
The timedStart action in the plugin stores both the start time and the
duration together. The correct format is timedStart:key:durationSeconds
with the duration attached to the action, not split across the action and requirement.
Verify the AI has used this three-segment form.
// Correct — duration included in the action
"timedStart:courier_timer:180"
// Correct — requirement uses the key only (no seconds needed here)
"timedActive:courier_timer"
"timedExpired:courier_timer"
// Wrong — AI sometimes omits the duration from the action
"timedStart:courier_timer" // missing :180
Item and entity ID casing
QuestLines resolves IDs case-sensitively. An incorrect case will silently fail — the kill counter will never increment, or the item will never be given.
| ID Type | Correct Format | Wrong Format |
|---|---|---|
| Entity (mob) IDs | Zombie, Goblin*, Skeleton |
zombie, hytale:goblin, SKELETON |
| Item IDs | Ore_Iron, Weapon_Sword_Iron |
ore_iron, iron_sword, OreIron |
| Block IDs | Rock_Stone_Brick, Wood_Oak_Trunk |
stone_brick, stoneBrick |
JSON key casing
All QuestLines codec keys are PascalCase. If the AI outputs
camelCase or lowercase keys ("requirements" instead of
"Requirements"), the plugin will not load the values and the page will
behave as if the fields are empty.
// Correct — PascalCase keys
"Requirements": ["questStarted:bandit_patrol"]
"Responses": [{ "Text": "Accept", "Actions": [] }]
// Wrong — camelCase keys will be silently ignored
"requirements": ["questStarted:bandit_patrol"]
"responses": [{ "text": "Accept" }]
Common Mistakes
These are the most frequent errors seen in AI-generated quest content. Knowing them in advance lets you spot and fix them in seconds.
Wrong page order — intro before in-progress
The AI sometimes lists intro pages first because that matches narrative order. In
QuestLines, this is incorrect — the intro page has the least specific requirements
(questNotStarted) and must be last, otherwise it matches every player
who has not yet started the quest, including players who are in-progress or done.
Open the quest's Pages array and read requirements top-to-bottom.
Each page should have more requirements or more specific
requirements than the page below it. A page with questNotStarted should
be the last entry.
Missing tracker on quest accept
The AI generates the kill:Zombie:5 requirement correctly but forgets to
add track:zombie_kills:kill:Zombie to the accept response. Without the tracker,
the kill counter never increments and the requirement can never pass. Always check
that the intro page's accept response starts every required named tracker.
Tracking tags not removed on completion
Named trackers left active after quest completion cause counters
to accumulate indefinitely. If the player later does another quest that uses the same
tracker ID, the count will be incorrect. The completion response should always
include untrack:id for every tracker that was started.
timedStart missing the duration segment
A common error is writing "timedStart:my_timer" without the duration.
The action requires three segments: timedStart:key:durationSeconds.
The timedActive and timedExpired requirements, however,
take only the key — they retrieve the stored duration automatically. Make sure the
duration is on the action, not the requirements.
Lowercase or wrong-format entity / item IDs
AI models trained on general Minecraft content often produce Minecraft-style IDs
(iron_ore, zombie) rather than Hytale-style IDs
(Ore_Iron, Zombie). These will silently fail. Check every
entity and item ID against your server's entity and item registries before deploying.
Timed quest page ordering
For timed quests, the expired page must come before the active page in the
Pages array. The AI sometimes reverses these because "active" feels
more natural first. If the active page is listed first, an expired player will still
see the "timer running" message instead of the failure/retry screen.
// Correct timed quest page order
"Pages": [
"courier_complete", // questCompleted — first
"courier_expired", // questStarted + timedExpired — before active
"courier_active", // questStarted + timedActive — after expired
"courier_intro" // questNotStarted — last
]
page: navigation action not last in the list
When a response uses page:pageId to navigate to another dialogue page,
this action should be the last entry in the Actions list. If the AI
places other actions after the page: action, those actions will still
execute — but since dialogue has already navigated away, this can cause confusing
double-execution or state mutations the player never sees confirmation of.