Abstract
Problem: How do you actually code a quest system in an RPG? What data structures, states, and patterns make quests work under the hood?
Approach: Tim Cain walks through the exact quest system he's used across Fallout, Arcanum, Temple of Elemental Evil, Vampire: Bloodlines, and The Outer Worlds β from basic state machines to advanced multi-objective quests.
Findings: A quest is fundamentally a state machine with five forward-only states (unknown β mentioned β accepted β achieved β completed) plus a botched flag. This simple structure, exposed to scripting, handles virtually everything RPG players expect from quests.
Key insight: Quest state changes happen in too many places (dialogue, proximity triggers, NPC deaths) to hard-code β you must expose state manipulation to your scripting language and let narrative/level designers handle it.
The Expert Blindspot
Tim opens with an observation about expertise: when you first enter the game industry, you have questions about everything. After decades, you forget you ever didn't know these things. It would never have occurred to him to talk about quest implementation because he just assumes people know how it works. A friend once told him that early in becoming an expert, you realize all the stuff you don't know β and later, you forget that you ever didn't know it.
The Five Quest States
Every quest is a state machine with five states, representable as an enum:
- Unknown β the player has never heard of this quest
- Mentioned β the quest has been talked about, but the player was non-committal
- Accepted β the player has agreed to take on the quest
- Achieved β the objective is done but hasn't been turned in
- Completed β objectives done, quest turned in, rewards given
The Botched Flag
There's a sixth pseudo-state: botched. Rather than being a state in the enum, it's implemented as a separate boolean flag on the quest. Botched can be set on any state except completed β once a quest is completed, it's done forever and cannot be botched.
How botched interacts with the UI is a design decision:
- If an unknown quest gets botched (e.g., you kill a quest giver you never met), some games tell the player, others don't
- For mentioned/accepted/achieved quests already in the quest log, botched typically moves them to a "botched" section
- Tim has made games both ways β it depends on your UI philosophy
The Quest Class
The quest class is minimal. Its variables are just:
- State (the enum of five values)
- Botched (a boolean flag)
Methods
- SetState β advances the quest to a new state. Enforces forward-only movement (you can't go from mentioned back to unknown). Ignored if the quest is already completed.
- GetState β returns the current state. If botched is set, returns "botched" instead (unless the state is unknown and you've decided not to reveal unknown botched quests to the player).
- Botch / Unbotch β toggles the botched flag. Also ignored if the quest is already completed.
The Quest Manager (Singleton)
The quest class instances live in an array managed by a Quest Manager singleton. Tim notes this is a recurring C++ pattern he's not fond of β "welcome to C++, you're going to be making a singleton here."
The quest manager handles:
- Loading and saving quest state (writes out state + botched flag for all quests)
- Providing the interface between game systems and individual quests
Exposing to Scripting
The critical architectural decision: SetState, GetState, Botch, and Unbotch must be exposed as script calls. Quest state can change from too many sources to hard-code:
- Kill a quest giver β botch their quests
- Walk past NPCs talking on the street β quest mentioned
- Dialogue choice where the player says "yes" β quest accepted
- Pick up a key item β quest achieved
These script calls are used heavily by both narrative designers and level designers.
Advanced: Multiple Objectives
To support quests with multiple objectives:
- Add extra variables to the quest class for each objective
- SetState(achieved) now takes a parameter specifying which objective was achieved (or make a separate
SetStateAchievedmethod) - Objectives can be flagged as ordered (must complete A before B) or optional
- A quest can be set to completed once all non-optional objectives are achieved, even if optional ones aren't done
Tim's Advice
Start simple. Make a basic quest: go get the Crown of Diamonds from a cave. It goes from mentioned β accepted β achieved (when you pick it up) β completed (when you turn it in). Everything beyond that β ordered objectives, optional objectives β is an advanced extension that isn't hard to add once the foundation works.
This structure has been used across Fallout, Arcanum, Temple of Elemental Evil, Vampire: Bloodlines, and The Outer Worlds. It's tested, tried and true, and handles virtually everything RPG players expect.