Getting Started
This guide walks you through setting up the framework and creating your first quest giver NPC — from zero to a working quest in the game. No prior modding experience needed.
The Big Picture
Here's how the pieces fit together when a player talks to your NPC:
graph TD
Player["Player (in game)"] -->|"steps into range"| NPC["NPC Entity (priest)"]
NPC -->|"conversable behavior"| Dialogue["Dialogue
config/dialogue/priest.json"]
Player -->|"selects 'I need work'"| Dialogue
Dialogue -->|"trigger: openquests"| Quests["Quest Menu
shows available quests"]
Player -->|"accepts and completes"| Quest["Quest
config/quests/intro.json"]Three files define a quest giver NPC:
- NPC entity (
entities/) — what the NPC is, what behaviors it has - Dialogue tree (
config/dialogue/) — what the NPC says, what the player can answer - Quest definition (
config/quests/) — objectives, rewards, actions
All text is stored in lang files (lang/) as translation keys.
Before You Start
Make sure you have these ready:
- Vintage Story 1.22.1+ — the game installed
- The framework mod —
alegacyvsquest.zipplaced in your server'sMods/folder - A text editor — VS Code, Notepad++, or any editor that can edit JSON
- Your server running — so you can test
Step 1: Create Your Mod Folder
Quest packs are standard Vintage Story content mods. Create this folder structure:
your-quest-pack/
├── modinfo.json
└── assets/
└── your-quest-pack/
├── config/
│ ├── dialogue/
│ ├── quests/
│ └── (other configs as needed)
├── entities/
└── lang/The folder name your-quest-pack is your mod's domain — you'll use it as a prefix in IDs.
Important: The framework finds your quest configs by scanning the assets/ folder. Keep the structure exactly as shown.
modinfo.json
{
"type": "content",
"modid": "your-quest-pack",
"name": "Your Quest Pack",
"version": "1.0.0",
"dependencies": {
"game": "1.22.1",
"alegacyvsquest": "3.0.0"
}
}This tells Vintage Story that your mod depends on the quest framework.
Step 2: Create the NPC Entity
Every quest giver is an in-game NPC entity. Create assets/your-quest-pack/entities/villager-elder.json:
{
"code": "villager-elder",
"class": "EntityAgent",
"server": {
"behaviors": [
{ "code": "nametag", "selectFromRandomName": ["Elder Marcus"] },
{ "code": "controlledphysics", "stepHeight": 1.01 },
{ "code": "health", "currenthealth": 1000000, "maxhealth": 1000000 },
{ "code": "conversable", "dialogue": "config/dialogue/villager-elder" },
{
"code": "questgiver",
"quests": ["your-quest-pack:elder-quest"],
"noAvailableQuestDescLangKey": "your-quest-pack:no-quests"
}
]
}
}What each behavior does:
| Behavior | Purpose |
|---|---|
nametag | Gives the NPC a random name |
controlledphysics | Lets it walk on stairs and slabs |
health | Makes the NPC immortal (1M HP) |
conversable | Enables dialogue — points to your dialogue file |
questgiver | Makes it offer quests — lists which quests it gives |
The quests array lists the IDs of quests this NPC can offer. Each quest ID uses the format yourdomain:quest-id.
Step 3: Create the Dialogue
The dialogue file controls the conversation flow. Create assets/your-quest-pack/config/dialogue/villager-elder.json:
{
"components": [
{
"code": "intro",
"owner": "npc",
"type": "talk",
"text": [{ "value": "your-quest-pack:elder-greeting" }],
"jumpTo": "main"
},
{
"code": "main",
"owner": "player",
"type": "talk",
"text": [
{ "value": "your-quest-pack:elder-ask-work", "jumpTo": "openquests" },
{ "value": "your-quest-pack:elder-leave", "jumpTo": "close" }
]
},
{
"code": "openquests",
"owner": "npc",
"type": "talk",
"trigger": "openquests",
"text": [{ "value": "your-quest-pack:elder-opening" }]
},
{
"code": "close",
"owner": "npc",
"type": "talk",
"text": [{ "value": "your-quest-pack:elder-goodbye" }]
}
]
}How dialogue flow works:
graph LR
A["intro
NPC speaks"] --> B["main
Player chooses"]
B -->|"I need work"| C["openquests"]
C --> D["trigger runs"]
B -->|"Goodbye"| E["close"]The magic line is "trigger": "openquests" — this calls the framework's quest menu action, which shows the player all available quests from this NPC.
Step 4: Create the Quest
Create assets/your-quest-pack/config/quests/elder-quest.json:
{
"id": "your-quest-pack:elder-quest",
"cooldown": -1,
"perPlayer": true,
"onAcceptedActions": [
{ "id": "playsound", "args": ["sounds/effect/writing", "0.5"] },
{ "id": "addjournalentry", "args": ["your-quest-pack:elder-quest", "your-quest-pack:elder-journal", "Elder's Request", "Bring the elder some flint."] }
],
"gatherObjectives": [
{ "validCodes": ["game:flint"], "demand": 5 }
],
"itemRewards": [
{ "itemCode": "game:gear-rusty", "amount": 2 }
],
"actionRewards": [
{ "id": "notify", "args": ["your-quest-pack:quest-complete"] },
{ "id": "playsound", "args": ["sounds/effect/receptionbell", "0.5"] }
]
}Key fields explained:
| Field | What it does |
|---|---|
id | Unique quest identifier. Must match what's in the NPC's quests array |
cooldown: -1 | -1 = one-time only. Any positive number = hours before the player can retake it |
perPlayer: true | Each player has their own progress |
onAcceptedActions | Things that happen when the player takes the quest (sound, journal entry) |
gatherObjectives | What the player needs to collect (validCodes = item types, demand = how many) |
itemRewards | Items given on completion |
actionRewards | Actions that run on completion (notification, sound) |
Step 5: Add Translations
Create assets/your-quest-pack/lang/en.json:
{
"your-quest-pack:elder-greeting": "Ah, a traveler. Good.",
"your-quest-pack:elder-ask-work": "Got any tasks for me?",
"your-quest-pack:elder-leave": "Not now.",
"your-quest-pack:elder-opening": "Let me see...",
"your-quest-pack:elder-goodbye": "Safe travels.",
"your-quest-pack:no-quests": "Nothing needs doing right now.",
"your-quest-pack:quest-complete": "Task complete! The elder is grateful.",
"your-quest-pack:elder-journal": "The elder asked me to gather flint."
}Why use lang keys instead of writing text directly?
- The game's translation system will pick them up automatically
- You can add
ru.json,de.json, etc. later - Non-English players see text in their language
Step 6: Test Your Work
- Restart your server (or use
/avq reload) - Spawn the NPC with
/spawn villager-elder - Right-click the NPC — the dialogue should appear
- Select "Got any tasks for me?" — the quest menu opens
- Accept the quest — you should hear a writing sound, and a journal entry is added
- Collect 5 flint — the objective updates
- Return to the NPC (or wait) — rewards are granted
Useful debug commands:
| Command | What it does |
|---|---|
/avq qlist | List all registered quests |
/avq qcheck <playername> | Check quest progress |
/avq qstart your-quest-pack:elder-quest <playername> | Force-start a quest |
/avq qcomplete your-quest-pack:elder-quest <playername> | Force-complete a quest |
/avq reload | Reload all configs (no restart needed) |
Common Pitfalls
- Quests don't appear? Check that the quest ID in
questsarray matches exactly with the quest'sidfield - Dialogue doesn't open? Make sure the
dialoguepath in theconversablebehavior matches the file path (minus.json) - No sound? Sound paths are relative to the mod's
assets/folder. Usegame:prefix for vanilla sounds - Quest doesn't complete? Make sure your lang keys exist in the translation file
See Troubleshooting for more.
What Next?
Now that you have a working quest giver, explore:
| Topic | Why read it |
|---|---|
| Quests | More objective types, quest chains, stages |
| Objectives | All 28+ objective types (kill, gather, escort, etc.) |
| Actions | All actions (give items, spawn mobs, run commands) |
| NPCs & Dialogues | Conditional dialogue, variables, reputation |
| Items | Custom items with stat modifiers, quality tiers |
| Visual Editor | A GUI tool instead of editing JSON by hand |