Quiz

The quiz system provides interactive multiple-choice tests (4 options per question). Quizzes are used in quests to test player knowledge, with randomized answer order and score tracking. <span class="since-badge">v3.0.0</span>


How It Works

graph TD
    A[Quest triggers showquiz] --> B[Server loads quiz definition]
    B --> C[Options randomized per question]
    C --> D[Send question to client]
    D --> E[Player selects answer]
    E --> F{More questions?}
    F -->|YES| C
    F -->|NO| G[Show result screen]
    G --> H[Score stored in attribute]
    H --> I[Auto-check objective]

Key behavior:

  • Sequential — Questions are answered one at a time, in order.
  • Randomized options — The order of A/B/C/D is randomized per player per question. The same player always sees the same order for the same question.
  • Persistent — Progress (index, correct count, wrong count, option order) is stored in player WatchedAttributes.
  • Auto-check — When the quiz finishes, the framework automatically calls checkobjective so the quest can verify the result.
  • Retry — The player can retry the quiz. This resets all progress and re-randomizes options.

Quiz Definition

Quizzes are loaded from mod assets: config/quizzes/*.json

{
  "id": "lore-quiz-chapter1",
  "titleLangKey": "yourmod:quiz-chapter1-title",
  "bodyLangKey": "yourmod:quiz-chapter1-body",
  "questionCount": 5,
  "neededCorrect": 3,
  "questionLangKeyFormat": "yourmod:quiz-chapter1-q{0}",
  "optionALangKeyFormat": "yourmod:quiz-chapter1-q{0}-a",
  "optionBLangKeyFormat": "yourmod:quiz-chapter1-q{0}-b",
  "optionCLangKeyFormat": "yourmod:quiz-chapter1-q{0}-c",
  "optionDLangKeyFormat": "yourmod:quiz-chapter1-q{0}-d",
  "correctOptions": [1, 3, 2, 4, 1],
  "scoreAttributeKey": "vsquest:quiz:lore-quiz-chapter1:score",
  "progressTemplateLangKey": "yourmod:quiz-progress-template",
  "resultTemplateLangKey": "yourmod:quiz-result-template",
  "retryButtonLangKey": "yourmod:quiz-retry",
  "closeButtonLangKey": "yourmod:quiz-close",
  "resultBodyLangKeys": [
    "yourmod:quiz-chapter1-result-bad",
    "yourmod:quiz-chapter1-result-ok",
    "yourmod:quiz-chapter1-result-good"
  ],
  "resultBodyScoreThresholds": [0, 3, 5]
}

Fields Reference

FieldTypeDescription
idstringUnique quiz identifier.
titleLangKeystringLocalization key for the quiz dialog title.
bodyLangKeystringLocalization key for the quiz description/instruction text.
questionCountintNumber of questions in the quiz.
neededCorrectintMinimum correct answers required to pass (shown in UI).
questionLangKeyFormatstringFormat string for question localization keys. {0} is replaced with question index (1-based).
optionALangKeyFormatstringFormat string for option A localization keys.
optionBLangKeyFormatstringFormat string for option B localization keys.
optionCLangKeyFormatstringFormat string for option C localization keys.
optionDLangKeyFormatstringFormat string for option D localization keys.
correctOptionsint[]Array of correct answer indices (1=A, 2=B, 3=C, 4=D) for each question.
scoreAttributeKeystringPlayer attribute key where the final score is stored.
progressTemplateLangKeystringLocalization key for the progress bar text (e.g., "Question {current} of {total}").
resultTemplateLangKeystringLocalization key for the result summary text (e.g., "You got {correct} out of {total}").
retryButtonLangKeystringLocalization key for the retry button.
closeButtonLangKeystringLocalization key for the close button.
resultBodyLangKeysstring[]Array of localization keys for result body text (different messages based on score).
resultBodyScoreThresholdsint[]Score thresholds for selecting result body text. Must match resultBodyLangKeys length.

Localization

The quiz uses a format-based localization system. You define patterns in the quiz JSON, and the framework resolves keys at runtime.

Example lang file:

{
  "yourmod:quiz-chapter1-title": "Chapter 1 Quiz",
  "yourmod:quiz-chapter1-body": "Answer these questions to prove your knowledge.",
  "yourmod:quiz-chapter1-q1": "What is the capital of the Northern Kingdom?",
  "yourmod:quiz-chapter1-q1-a": "Winterhold",
  "yourmod:quiz-chapter1-q1-b": "Duskwall",
  "yourmod:quiz-chapter1-q1-c": "Ironhaven",
  "yourmod:quiz-chapter1-q1-d": "Stonegate",
  "yourmod:quiz-chapter1-q2": "Which creature is immune to fire?",
  "yourmod:quiz-chapter1-q2-a": "Wolf",
  "yourmod:quiz-chapter1-q2-b": "Drifter",
  "yourmod:quiz-chapter1-q2-c": "Phoenix",
  "yourmod:quiz-chapter1-q2-d": "Bear",
  "yourmod:quiz-progress-template": "Question {0} of {1}",
  "yourmod:quiz-result-template": "You got {0} correct out of {1}",
  "yourmod:quiz-chapter1-result-bad": "You need to study more...",
  "yourmod:quiz-chapter1-result-ok": "Not bad, but you can do better.",
  "yourmod:quiz-chapter1-result-good": "Excellent! You know this chapter well.",
  "yourmod:quiz-retry": "Try Again",
  "yourmod:quiz-close": "Close"
}

Option Randomization

Answer options are randomized per player per question. The order is stored in player WatchedAttributes so reopening the same question shows the same order.

Storage key: vsquest:quiz:{quizId}:order:{questionIndex}

Example: For quiz lore-quiz-chapter1, question 1:

  • Server stores "vsquest:quiz:lore-quiz-chapter1:order:1" = "3,1,4,2"
  • This means option A shows original C, B shows A, C shows D, D shows B
  • The correct answer is tracked internally via the original index

Retry behavior: When the player clicks retry, all order keys are deleted, so the next attempt gets a new randomization.


Result Body Selection

If resultBodyLangKeys and resultBodyScoreThresholds are defined, the result text is selected based on the player's score:

score >= thresholds[2] → resultBodyLangKeys[2]  (best)
score >= thresholds[1] → resultBodyLangKeys[1]  (medium)
score >= thresholds[0] → resultBodyLangKeys[0]  (worst)

The system iterates from index 0 upward, selecting the last threshold the score meets or exceeds.

Example:

  • thresholds: [0, 3, 5]
  • keys: ["bad", "ok", "good"]
  • Score 0–2 → "bad"
  • Score 3–4 → "ok"
  • Score 5 → "good"

Player Attribute Storage

Quiz progress is stored in the player's WatchedAttributes:

KeyDescription
vsquest:quiz:{quizId}:indexCurrent question index (1-based).
vsquest:quiz:{quizId}:correctNumber of correct answers so far.
vsquest:quiz:{quizId}:wrongNumber of wrong answers so far.
vsquest:quiz:{quizId}:order:{n}Randomized option order for question n.
{scoreAttributeKey}Final score (set only after quiz completion).

Network Messages

MessageDirectionDescription
OpenQuizMessageClient → ServerPlayer opens the quiz dialog or clicks retry.
ShowQuizMessageServer → ClientQuestion data or result screen for display.
SubmitQuizAnswerMessageClient → ServerPlayer selected an answer (1–4).

Integration with Quests

Starting a Quiz (Action)

Use the showquiz action in a quest or dialogue:

{
  "id": "showquiz",
  "args": ["lore-quiz-chapter1"]
}

This resets the quiz and sends the first question to the player.

Checking the Result (Objective)

After the quiz finishes, the score is stored in the player attribute defined by scoreAttributeKey. Use checkvariable to check it:

{
  "id": "checkvariable",
  "args": ["vsquest:quiz:lore-quiz-chapter1:score", ">=", "3"]
}

Note: The quiz automatically calls checkobjective on completion, so the quest objective system checks immediately when the player finishes.

Complete Quest Example

A quest that requires the player to pass a quiz:

{
  "id": "yourmod:quiz-quest",
  "cooldown": -1,
  "perPlayer": true,
  "onAcceptedActions": [
    { "id": "showquiz", "args": ["lore-quiz-chapter1"] }
  ],
  "actionObjectives": [
    {
      "id": "pass_quiz",
      "objectiveId": "checkvariable",
      "args": ["vsquest:quiz:lore-quiz-chapter1:score", ">=", "3"]
    }
  ],
  "actionRewards": [
    { "id": "notify", "args": ["yourmod:quiz-passed"] },
    { "id": "addreputation", "args": ["npc", "scholar", "10"] }
  ]
}

What happens:

  • Player accepts the quest → quiz opens automatically.
  • Player answers 5 questions.
  • If score ≥ 3, the objective completes → quest rewards fire.
  • If score < 3, the player can retry by clicking "Try Again".

Tips

  • Always use scoreAttributeKey — Without it, the score is not stored, and checkvariable cannot verify the result.
  • Match thresholds to neededCorrect — If neededCorrect: 3, set thresholds to [0, 3, 5] so the UI shows appropriate feedback.
  • Keep questions short — The quiz dialog is compact. 1–2 sentences per question is ideal.
  • Use neededCorrect for UI guidance — The result screen shows "Required: X" so the player knows what they need.
  • Test retry — After failing, click retry to verify the quiz resets correctly and option order changes.
  • One quiz per quest — The quiz system is designed for one quiz at a time per quest. Don't trigger multiple quizzes simultaneously.
  • Private shop integration — Quizzes can be used as survey gates for private shops. If a shop's surveyAttribute matches the quiz's scoreAttributeKey, passing the quiz automatically grants the shop's one-time bonus.

Press ESC to close