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.zip placed in your server's Mods/ 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:

BehaviorPurpose
nametagGives the NPC a random name
controlledphysicsLets it walk on stairs and slabs
healthMakes the NPC immortal (1M HP)
conversableEnables dialogue — points to your dialogue file
questgiverMakes 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:

FieldWhat it does
idUnique 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: trueEach player has their own progress
onAcceptedActionsThings that happen when the player takes the quest (sound, journal entry)
gatherObjectivesWhat the player needs to collect (validCodes = item types, demand = how many)
itemRewardsItems given on completion
actionRewardsActions 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:

CommandWhat it does
/avq qlistList 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 reloadReload all configs (no restart needed)

Common Pitfalls

  • Quests don't appear? Check that the quest ID in quests array matches exactly with the quest's id field
  • Dialogue doesn't open? Make sure the dialogue path in the conversable behavior matches the file path (minus .json)
  • No sound? Sound paths are relative to the mod's assets/ folder. Use game: 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:

TopicWhy read it
QuestsMore objective types, quest chains, stages
ObjectivesAll 28+ objective types (kill, gather, escort, etc.)
ActionsAll actions (give items, spawn mobs, run commands)
NPCs & DialoguesConditional dialogue, variables, reputation
ItemsCustom items with stat modifiers, quality tiers
Visual EditorA GUI tool instead of editing JSON by hand

Press ESC to close