Quest Packs
A quest pack is a set of quests and conversations written as JSON files, with no code. The Timbn Quest Packs mod reads them and calls the same quest builder a C# mod calls (see Quests, Dialog, and Text), so a pack can do nearly everything the builder does except run code of its own. Small sets of conditions, actions and values stand in for that code.
Note
Quest Packs is its own mod and needs Timbn Core 2.0.0. Players install it to use any pack, and a mod that ships a pack should tell them so.
A pack cannot make repeatable quests or quest runs yet (Repeatable, Setup, NewRun and {name} values), since those need code to fill each run.
Where Packs Live
There are two kinds of pack. They use the same files and differ in who owns them.
A standalone pack
A folder anywhere under BepInEx/plugins (at any depth) that has a pack.json and a quests folder and no DLL. That covers a Nexus or Vortex install, r2modman's plugins/<Team-Package>/ folders and the Steam Workshop folders WorkshopSync sets up, with no change to any of them.
BepInEx/plugins/LarrysWine/
pack.json
quests/ any number of .json files
lang/ en.txt, de.txt and so on, the same format as a mod's lang folder
sprites/ PNGs, each added under the pack id plus its file name
The Quest Packs mod owns these. It reads them when the game starts and again each time the main menu opens, but only if a pack was added or removed or a file in one changed (it compares file names, sizes, and timestamps). To try an edit, change the file, go back to the main menu and load the save again. A folder with a DLL in it is never a standalone pack.
A mod's pack
A quests folder next to a Timbn mod's DLL. The mod owns it, so it loads when the mod starts (right after the mod's own lang folder) and goes away when the mod unloads. That means F6 reloads it for a mod in scripts.
Its id is the mod's GUID in lowercase, with anything other than letters, digits and underscores turned into an underscore, so Timbn.GraveyardKeeperTwo.Examples becomes timbn_graveyardkeepertwo_examples. A pack.json next to the DLL can name a shorter id and a bubble color. Without one, the bubble color is the mod's Dialog.BubbleColor. The mod's own lang folder already translates the pack. A sprites folder next to the DLL works the same as in a standalone pack.
The mod's players need the Quest Packs mod installed, since Core itself does not read packs.
A Small Pack
This pack is a pack.json and one quest file. Larry asks for a wine, then Herm does, and Larry mentions it once the first is done.
{ "id": "larrys_wine", "name": "Larry's Wine", "bubbleColor": "#9ad0ff" }
{
"$schema": "https://raw.githubusercontent.com/TimbnMods/Timbn.GraveyardKeeperTwo/main/src/Timbn.GraveyardKeeperTwo.QuestPacks/quests.schema.json",
"quests": [
{
"id": "ask",
"giver": "npc_larry",
"name": "Wine for Larry",
"icon": "quest_icon_larry_portal",
"description": "Bring Larry a bottle of wine.",
"done": "Larry got his wine.",
"offerWhen": "questStarted:6_intro_guards_burial",
"offer": {
"lines": [ "Can you spare a bottle of wine?" ],
"accept": "Sure.",
"decline": "Not now."
},
"cost": { "items": { "grape_wine:1": 1 } },
"reward": { "money": 100, "reputation": 5 },
"talk": {
"accept": { "text": "Here you go.", "then": [ "Lovely. Herm might want one too." ] }
}
},
{
"id": "herm",
"giver": "npc_herm",
"name": "Wine for Herm",
"description": "Bring Herm a bottle of wine.",
"after": "ask",
"offer": {
"lines": [ "Larry says you have good wine. Can I have a bottle?" ],
"accept": "Sure.",
"decline": "No."
},
"cost": { "items": { "grape_wine:1": 1 } },
"reward": { "items": { "nails_copper": 3 } },
"talk": {
"accept": {
"text": "Here.",
"then": [
{
"when": "carries:grape_wine:1",
"then": [ { "say": "Thanks. You still have %1 left.", "values": [ "itemCount:grape_wine:1" ] } ],
"otherwise": [ "Thanks." ]
}
]
}
}
}
],
"talks": [
{
"id": "tip",
"npc": "npc_larry",
"when": { "all": [ "questCompleted:ask", "questNotStarted:herm" ] },
"once": true,
"lines": [ "Herm likes wine too. Go see him." ]
}
]
}
pack.json
Only a standalone pack needs one. A mod's pack uses it to override its id or bubble color.
| Field | What it does |
|---|---|
id |
Required. Lowercase letters, digits and underscores. Never change it once players have the pack, since saves keep progress under the quest ids made from it. |
name |
A readable name for the pack. Optional, and it defaults to the id. |
bubbleColor |
The color of the pack's speech bubbles, as #RRGGBB or #RRGGBBAA. Core's default color when left out. |
Any other field is an error and the pack is skipped. When two standalone packs share an id, only the first loads (folders are read in path order) and the log says so.
Ids and Text Keys
Every quest, talk and sprite id a pack writes gets the pack id in front, so "id": "ask" in the larrys_wine pack is larrys_wine_ask in the game, in saves and in text keys. Ids hold lowercase letters, digits and underscores.
Wherever a quest id is expected (after, continues, the quest conditions and startQuest), a short id that names a quest in the same pack means that quest. Anything else is used as written, so it can be a game quest such as 6_intro_guards_burial or another pack's full id. For a quest with stages, after, continues and questCompleted mean its last step, so they wait for the whole quest, while questStarted, questNotStarted and startQuest mean its first. continues only takes a quest from the same pack.
An icon that matches a PNG in the pack's sprites folder (without .png) means that sprite. Anything else is a game sprite id such as quest_icon_larry_portal.
Text keys follow the quest builder's rules, using the full ids. For the quest above they are the following.
| Text | Key |
|---|---|
| Name | larrys_wine_ask |
| Offer lines | larrys_wine_ask_offer_1, _2 and so on |
| Offer answers | larrys_wine_ask_accept, larrys_wine_ask_decline |
| Lines after an offer answer | larrys_wine_ask_accept_1 and so on |
| Talk lines | larrys_wine_ask_talk_1, _2 and so on |
| Talk answers | larrys_wine_ask_talk_accept, _talk_decline, _talk_resume |
| Lines after the talk's accept | larrys_wine_ask_talk_accept_1 and so on |
A later stage uses its own id in place of ask, such as larrys_wine_ask_deliver_talk_accept. A talk's lines are larrys_wine_tip_1, _2 and so on in the order written. The full list of quest keys is under Notes in Quests, Dialog, and Text.
Lines are numbered in the order written, so adding a line in the middle of a talk that has shipped moves the numbered keys after it, and a translation then lands on the wrong line. A say, playerSay, sayAs or answer can carry "key": "ask" to be larrys_wine_tip_ask instead. A keyed line takes no number, so it never moves the others. A key holds lowercase letters, digits and underscores and starts with a letter.
Translations
A standalone pack's lang folder holds one <language id>.txt per language, one key = text per line, using the keys above. Lines starting with # are comments and \n is a line break. The text in the JSON is the default, and a lang file overrides it for its language. Ship an en.txt too so translators have something to copy, since the log warns about any key that a lang folder has no English line for. The language ids and markup are the same as a mod's (see Translations in Quests, Dialog, and Text).
# lang/de.txt
larrys_wine_ask = Wein für Larry
larrys_wine_ask_offer_1 = Kannst du eine Flasche Wein entbehren?
larrys_wine_ask_accept = Klar.
A Quest File
Each file in quests is one JSON object with two optional lists. Files are read in name order, and a quest or talk can sit in any of them.
{
"$schema": "...",
"quests": [ ],
"talks": [ ]
}
JSON comments (// and /* */) are allowed, so authors can leave notes. Any other top level field is reported and ignored. Ids must be unique across all of a pack's files, and a second quest or talk with the same id is skipped.
Schema
quests.schema.json and pack.schema.json sit next to the Quest Packs code, and each release carries the schema that matches it. A file points at one with a $schema line, which the mod ignores. VS Code and most editors then give autocomplete, hover text and red underlines for misspelled fields, wrong types and unknown conditions, events and actions.
https://raw.githubusercontent.com/TimbnMods/Timbn.GraveyardKeeperTwo/main/src/Timbn.GraveyardKeeperTwo.QuestPacks/quests.schema.jsonhttps://raw.githubusercontent.com/TimbnMods/Timbn.GraveyardKeeperTwo/main/src/Timbn.GraveyardKeeperTwo.QuestPacks/pack.schema.json
Quests
A quest is an object in the quests list. id, giver and name are required. Everything else is optional, but a quest has to be able to finish, so give it a talk, readyOn or readyWhen.
| Field | What it does | C# call |
|---|---|---|
id |
The quest's id, before the pack id is added. | Quests.Add |
giver |
The NPC who gives it, such as npc_larry. |
Quests.Add |
name |
Its name in the quest tree. | Quests.Add |
icon |
The quest's icon, a game sprite id or a PNG from the pack. A continues step with none uses the icon of the step it continues. |
Icon |
phase |
The ring in the quest tree, 1 to 5. | Phase |
description |
The quest window text while in progress. | Description |
done |
The quest window text once done. With stages it goes to the last step. | DoneDescription |
after |
One quest id or a list. The quest is offered once they are all done. | OfferAfter |
continues |
A quest in the same pack. This quest becomes its next step and starts by itself when that one is done. | ContinuesFrom |
offerWhen |
A condition that has to be true to offer or start the quest. | OfferWhen |
offer |
The conversation that offers the quest (see Offer). | Offer |
startsOn |
An event that starts the quest. | StartsOn |
npc |
The NPC the step's talk is with. Defaults to the giver. |
TalksWith |
needs |
What has to be true before the NPC offers the step's talk. It is checked and never taken. |
Needs |
cost |
What the player pays to finish the step, taken when they pick the talk's accept answer. |
Cost |
talk |
The conversation that finishes the step (see Quest talk). | Talk |
reward |
What the player gets, paid when the talk's accept is picked or, with no talk, when the step is done. |
Reward |
readyOn |
An event that readies the step. | ReadyOn |
readyWhen |
A condition checked about once a second while the quest is in progress. When it turns true the step is ready. | ReadyByCode |
then |
Lines that play when a step with no talk is done. |
DoneLines |
onDone |
One action or a list, run when the step is done. | OnDone |
runOnStart, runOnDone |
One game script expression or a list, run by the game itself when the quest starts or completes. Use them for changes the save keeps. | OnStartExpression, OnDoneExpression |
stages |
A list of the next steps (see Stages). | AddStage |
The quest builder's own rules apply and are reported the same way. A step needs a talk, readyOn or readyWhen, and cannot have both readyOn and readyWhen. An offer and a talk each need an accept. needs and cost need a talk. A step with a talk keeps its last lines in the talk instead of then.
Needs and cost
needs and cost are objects. Money is in the game's raw units, where 100 is one silver coin. An item id can hold a colon for its quality, so grape_wine:1 is a wine of quality 1.
| Field | In | What it is |
|---|---|---|
items |
needs, cost |
Items and counts, written as { "grape_wine:1": 3 }. |
resources |
needs, cost |
Resource ids and amounts, such as { "tech_red": 5 }. |
money |
needs, cost |
A whole number. |
reputation |
needs |
Reputation with the NPC the player is talking to. |
happiness |
needs |
The gratitude a town vendor asks for. |
onDay |
needs |
One of Gluttony, Sloth, Lust, Envy, Pride, or Wrath. |
orderCompleted |
needs |
A town vendor's order finished this week, such as quest_order_iron. |
The player keeps what needs checks. cost is taken when the answer is picked, shows on the answer and grays it out until the player can pay. See Needs and Costs in Quests, Dialog, and Text.
Reward
| Field | What the player gets |
|---|---|
money |
A whole number of the game's raw units. |
items |
Items and counts. What does not fit drops at the player's feet. |
resources |
Resource ids and amounts. |
reputation |
Reputation with the NPC of the talk, or the giver for a step with no talk. |
shows, showsResources |
Items or resources drawn on the answer but not handed out, for a reward a give line hands out itself. |
A talk's accept shows the reward and the game pays it when the answer is picked.
Offer
"offer": {
"lines": [ "Can you spare a bottle of wine?" ],
"accept": "Sure.",
"decline": "Not now."
}
lines are the lines the NPC says first. accept is required. accept and decline are either the answer text alone or { "text": "...", "then": [lines] }, where then plays after the answer. The offer's accept can also hold needs and cost, so an offer can be locked to a day or cost money to take. The cost is given back if the quest cannot start.
Quest talk
The conversation that finishes a step. It is with the step's npc, or the giver. Its lines play, then the player picks accept, which takes the step's cost, gives its reward and completes the step, or decline to come back later.
| Field | What it does |
|---|---|
lines |
Lines the NPC says first. |
accept |
Required. The answer text alone, { "text", "then" }, or true. |
decline |
The answer text alone or { "text", "then" }. |
resume |
A line the NPC says in place of lines when the player comes back after hearing them once. An opening playerSay still plays first. |
"accept": true has no answer to pick and completes the step as soon as the lines are done, for something the NPC simply hands over. That talk needs lines, cannot have decline or resume, and its step cannot have a cost. The cost sits on the step, so a cost inside accept is reported as not belonging there.
When the NPC has a menu of its own, as Herm and Larry do, the talk joins it as one answer.
"npc": "npc_herm",
"cost": { "money": 200 },
"talk": {
"lines": [ { "playerSay": "Larry wants a bottle of quality wine." }, "That'll cost you 2 silver." ],
"accept": { "text": "A little steep, but ok.", "then": [ "Come back on Pride." ] },
"decline": "I don't have the money right now.",
"resume": "Here to pay the 2 silver?"
}
Stages
A quest of several steps can be written as one quest with stages, the same as the builder's AddStage. Each stage needs an id and a description, and takes any of name, icon, phase, done, npc, needs, cost, talk, reward, readyOn, readyWhen, then, onDone, runOnStart and runOnDone. An offer, offerWhen, startsOn, after or continues on a stage is reported and ignored, since those belong to the first step. Stage ids hold lowercase letters, digits and underscores.
Core makes each step its own game quest continuing the one before. When the quest has step fields of its own (description, phase, npc, needs, cost, talk, reward, the ready fields, then, onDone or the run expressions), it is the first step and keeps the quest's id, and each stage is a later step whose id is the quest id plus the stage id, such as larrys_wine_ask_deliver. Otherwise the first stage is the first step and keeps the quest's id, and the quest's offer, offerWhen, startsOn, after and icon belong to it. The quest's done goes to the last step either way.
{
"id": "ask", "giver": "npc_larry", "name": "Gold Wine",
"offer": { "lines": [ "Could you find me gold wine?" ], "accept": "Sure." },
"description": "Pay Herm for the gold wine.",
"npc": "npc_herm",
"cost": { "money": 200 },
"talk": {
"lines": [ { "playerSay": "Larry wants gold wine." }, "That'll be 2 silver." ],
"accept": "Here's the silver.",
"decline": "Not now."
},
"done": "Larry got his gold wine.",
"stages": [
{
"id": "deliver",
"description": "Bring Larry the gold wine.",
"cost": { "items": { "grape_wine:3": 1 } },
"talk": { "accept": "Here's your wine." }
}
]
}
A stage after the first is saved under its id, so keep stage ids stable and add new stages at the end.
Lines
Lines are a list of steps. A plain string is a line the NPC says, so the simplest conversation is ["Hello.", "Bring me wine."]. Lines go before the answers, and what follows an answer goes in that answer's then.
| Step | What it does |
|---|---|
"Some text" or { "say": "...", "values": [...], "key": "..." } |
The NPC says it. |
{ "playerSay": "...", "values": [...], "key": "..." } |
The player says it. |
{ "sayAs": "npc_herm", "text": "...", "key": "..." } |
Another NPC says it, skipped when they are not around. |
{ "give": "flitch", "count": 2 } |
Gives items. count defaults to 1. What does not fit drops at the player's feet. |
{ "take": "flitch", "count": 2 } |
Takes items. count defaults to 1. |
{ "wait": 1.5 } |
Pauses, in real seconds. |
{ "cinematic": true } |
The game's black story bars until the conversation ends. |
{ "when": <condition>, "then": [...], "otherwise": [...] } |
Picks lines by the game's state. otherwise is optional. |
{ "ask": [answers] } |
Answer choices. Each is a string or { "text", "then", "needs", "cost", "reward", "key" }. |
{ "do": <action or list> } |
Runs actions. |
{ "run": "AddRep(\"npc_larry\", 5)" } |
Runs a game script expression against the NPC being talked to. |
A step object can only hold the fields listed for it, and anything else is reported and ignored.
Values
values fill a line's %1, %2 and so on, worked out when the line shows.
| Value | What it shows |
|---|---|
itemCount:<item> |
How many of the item the player carries. |
ownedCount:<item> |
How many they have anywhere, chests included. |
resource:<id> |
How much of the resource they have. |
Any other string or a number shows as written.
Conditions
A condition is a string name:argument, an object such as { "carries": "grape_wine:1", "count": 3 } when it needs a count, or { "all": [...] }, { "any": [...] } and { "not": ... } around others. An item id can hold a colon for its quality, which is why a count goes in the object form. For resource and reputation, the number can be written as amount in the object form.
| Condition | True when |
|---|---|
questStarted:<id> |
The quest is in progress or done. |
questCompleted:<id> |
The quest is done. |
questNotStarted:<id> |
The quest is neither in progress nor done. |
carries:<item> |
The player carries at least one (or count). |
owns:<item> |
The player has at least one (or count) anywhere, chests included. |
hasTech:<id> |
The tech is researched. |
resource:<id>:<amount> |
The player has at least that much of the resource, such as resource:money:500. |
reputation:<npc>:<amount> |
The player has at least that much reputation with the NPC. |
day:<day> |
It is that day of the week, one of Gluttony, Sloth, Lust, Envy, Pride, or Wrath. |
time:<from>-<to> |
The game hour is in the range, such as time:18-24. A range like time:22-4 wraps past midnight. |
expr:<expression> |
One of the game's script expressions is true, such as expr:$money >= 500 or expr:IsTechUnlocked("wood_basic"). |
Actions
An action is a string name:argument or an object with a count. A list of actions runs in order.
| Action | What it does |
|---|---|
startQuest:<id> |
Starts a mod quest that can start now. The log notes when nothing started. |
give:<item>, take:<item> |
Gives or takes items, with count in the object form. |
addResource:<id>:<amount> |
Adds to a resource, such as money. |
reputation:<npc>:<amount> |
Reputation with the NPC. |
run:<expression> |
Runs one of the game's script expressions, such as run:AddPerk("perk_x"). |
Events
Events are strings. Only slept takes no argument.
| Event | Fires when |
|---|---|
slept |
The player wakes up after sleeping. |
crafted:<craft> |
The player finishes the craft. |
built:<object> |
The player finishes building the object. |
enteredZone:<tag> |
The player walks into one of the game's marked areas. |
walkedInto:<world zone> |
The player walks into a world zone, such as the graveyard. |
wonFight:<fight> |
The player wins the fight. |
usedItem:<item> |
The player uses the item. |
These are the builder's TimbnQuestEvent factories, and walkedInto is TimbnQuestEvent.EnteredWorldZone in C#.
Game Expressions
The game's own quests, items and conversations run formula strings, such as AddPerk("perk_x"), $money += 50 or IsQuestCompleted("x"). A pack reaches them through runOnStart and runOnDone (the game runs them at its own exact moment), the run line and action, and the expr condition. Core compiles each one when the pack loads, so a typo is reported like any other mistake instead of failing in the middle of a quest. See Game Expressions for the functions.
Talks
A talk is a conversation that is not a quest, the same as Dialog.AddTalk. It is an object in the talks list. id, npc and lines are required.
| Field | What it does |
|---|---|
id |
The talk's id, before the pack id is added. |
npc |
The NPC who offers it. |
lines |
The lines of the talk. |
when |
A condition for when the NPC offers it. Always when left out. |
once |
True to stop offering it once the player starts it in that save. False when left out. |
bubbleColor |
A color for this talk's speech bubble. The pack's color when left out. |
{ "id": "tip", "npc": "npc_larry", "when": "hasTech:wood_basic", "once": true, "bubbleColor": "#ffcc66", "lines": [ "Herm sells nails." ] }
Mistakes
A pack is checked as it loads, and one mistake never stops the rest.
- A file that is not valid JSON is skipped entirely.
- A quest or talk with a mistake is skipped and the rest of the pack loads. That covers a missing field, a wrong type, a number too big for the game, an unknown condition, action or event, an NPC id the game does not have, and any of the quest builder's own errors.
- A field that does not belong is reported and ignored, so a typo like
descriptoindoes not go unnoticed. - An NPC id (
giver, a talk'snpc, a quest or stagenpc,sayAs, and thereputationcondition and action) is checked againstTimbnGameIds.Npcs, so a typo likenpc_laryis reported instead of the quest never showing up. See Game Ids. - A quest id that another mod or pack already adds is reported and the second one skipped.
- A
pack.jsonthat is not valid skips a standalone pack. For a mod's pack it falls back to the mod's own id. - An unhandled exception while loading a pack rolls back what that pack added and reports it, so one broken pack never stops the other packs.
- Core's save start checks also apply to pack quests, so an
afterid that matches no quest, and a loop ofafter, both get a warning.
Every message names the pack folder, the file, the quest or talk and the line, and is written to BepInEx/LogOutput.log as a warning.
LarrysWine/quests/wine.json, quest 'ask': line 12, "phase" should be a whole number. It was skipped.
Each pack that loads also logs a line with how many quests and talks it added and how many problems it had. The Quest Packs setting General / ShowProblemsOnMainMenu is off by default. When on, it also lists the problems in a main menu popup, the first six and a count of the rest (see Main Menu).
Using C# Instead
Everything in a pack maps onto the C# quest builder, as the Quests table shows, so a pack can grow into a mod when it needs code. See Quests, Dialog, and Text for the builder, including the pieces a pack does not have, such as repeatable quests and runs.