en-quests gives your settlements survivors with stories. Quest givers stand around the map. When one has something for you, they show a map blip and a marker. Talk to them to open a dialogue with the camera on the speaker. They offer three kinds of quest:
- Story quests: done once, often chained together.
- Side jobs: done once.
- Daily tasks: repeatable after a cooldown.
A quest is a list of steps: talk to someone, deliver items, carry items, reach a place, kill zombies (optionally a specific type), search bodies, or craft something. A tracker shows the current step and its progress. The journal (F5) lists active and completed quests, and lets you set a map waypoint or abandon a quest.
The server works out all progress from real game events, so a client can never simply claim a step is done. The one exception is "reach", which the server checks again. Kills and searches can also count for nearby party members on the same step. Rewards can be cash, character XP, items and skill XP.
At a glance
| Depends on | oxmysql, en-core, en-ui |
| Works with | en-inventory, en-skills, en-party, en-radialmenu, en-zombies, en-crafting |
| Database tables | encore_quest_progress |
| Config files | config/shared.lua, data/npcs.lua, data/quests.lua |
| Key binds | en_quests_journal (F5), en_quests_talk (E, only when en-target isn't running) |
| Radial menu | en-quests:journal under survivor |
| In-game builder | Content kinds questnpc and quest in en-admin |
How it works
Offers
A survivor offers a quest when all of these are true:
- the quest isn't already active
- a story quest or side job hasn't been completed before
- a task's cooldown since its last completion has passed
- you meet the character level, skill level and prerequisite quests in
requires - you have fewer than
Config.maxActivequests active
When several quests are on offer, the survivor says Config.offersLine and you pick one. Offers are ordered by Config.types.
Objectives
| Step type | Completed by |
|---|---|
talk | Talking to the named survivor |
deliver | Handing the items to the survivor (they're taken) |
collect | Carrying the items (you keep them) |
reach | Arriving within radius of coords; the server checks within radius + 5 m |
kill | en-zombies:server:zombieKilled, optionally a specific zombie type |
search | en-zombies:server:corpseLooted |
craft | en-crafting:server:crafted, matching a recipe id or result item |
Abandoning and rewards
Abandoning a repeatable task you've completed before sets it back to completed, so the cooldown still applies. Otherwise it becomes abandoned. Reward items that don't fit in your inventory aren't given, and you get a "No room for ..." message.
Changing quest definitions
Editing a quest keeps player progress, clamped to the new number of steps. Deleting a quest leaves progress rows alone, and they resume if a quest with that id comes back.
Configuration
config/shared.lua
| Key | Default | What it does |
|---|---|---|
Config.keys.journal | 'F5' | Journal key bind |
Config.maxActive | 3 | Most quests active at once |
Config.talkDistance | 2.5 | Talk reach |
Config.partyCredit | true | Kill and search credit for nearby party members |
Config.giverMarkers | true | Marker over survivors with something for you |
Config.blips | { sprite = 280, colour = 47, scale = 0.75 } | Survivor blips |
Config.dialogCamera | true | Camera on the speaker |
Config.offersLine | string | What a survivor with several offers says first |
Config.types | story (order 1), side (2), task (3) | Quest type labels and offer order |
data/npcs.lua
A table keyed by survivor id. Defaults: rhodes, hale, vance.
| Field | What it does |
|---|---|
name, title | Display name and subtitle |
model | Ped model |
coords | vec4 |
scenario | Idle scenario |
greeting | Lines when they have nothing for you |
blip | true = always on the map; otherwise only while they have something for you |
data/quests.lua
A table keyed by quest id (up to 60 characters). Quest definitions stay on the server; clients never see them. Defaults: first_steps, clean_water (needs first_steps), thin_the_herd, runner_hunt (level 5) and scrap_run (task, 20 h cooldown).
| Field | What it does |
|---|---|
label, description | Journal text |
type | story, side or task |
giver | Survivor id |
offer | Lines spoken when offering |
cooldownHours | Tasks only; defaults to 24 |
requires | { level = n, skill = { name, level }, quests = { ids } } |
rewards | { cash, xp, items = { { item, count } }, skills = { [skill] = xp } } |
steps | Ordered list of steps |
Every step has a type and label, and can have a hint (journal line) and coords (waypoint). Extra fields by type:
| Type | Fields |
|---|---|
talk | npc, dialog |
deliver | npc, item, count, dialog |
collect | item, count |
reach | coords (vec3, required), radius (default 10, 1–500) |
kill | count, zombie (optional type, e.g. 'runner') |
search | count |
craft | count, and recipe (en-crafting recipe id) or item (result item) |
Adding a survivor and a daily task
-- data/npcs.lua
marla = {
name = 'Marla', title = 'Grapeseed Farm',
model = 'a_f_m_farmer_01', coords = vec4(2440.0, 4975.0, 46.8, 90.0),
scenario = 'WORLD_HUMAN_GARDENER_PLANT',
greeting = { 'Fields don\'t tend themselves.' },
},
-- data/quests.lua
cloth_for_marla = {
label = 'Rags to Riches', type = 'task', giver = 'marla', cooldownHours = 12,
description = 'Marla patches tarps for the farm and always needs cloth.',
offer = { 'Bring me cloth and clear the walkers off the east field.' },
requires = { level = 2 },
steps = {
{ type = 'kill', count = 8, label = 'Clear the east field', coords = vec3(2470.0, 4990.0, 45.0) },
{ type = 'collect', item = 'cloth', count = 15, label = 'Gather cloth' },
{ type = 'deliver', npc = 'marla', item = 'cloth', count = 15, label = 'Bring Marla the cloth',
dialog = { 'That\'ll hold the rain off. Here.' } },
},
rewards = { cash = 120, xp = 100, items = { { item = 'canned_beans', count = 2 } }, skills = { scavenging = 10 } },
},For a step-by-step walkthrough, see Create a quest. Survivors and quests placed with en-admin override config entries with the same id, and deleting the placement brings the config version back.
Exports
All exports are server-side. There are no client exports.
| Export | Arguments | Returns |
|---|---|---|
IsQuestActive | source, questId | boolean |
HasCompletedQuest | source, questId | boolean |
AdvanceQuest | source, questId, amount? | nil. On kill, search and craft steps it adds amount (default 1); on other steps it finishes the current step |
BuilderList | kind | For 'questnpc' or 'quest': { id, label, subtitle, coords, placed, inConfig }[] |
BuilderGet | kind, id | Editable data |
BuilderValidate | kind, id, data | ok, err. For quests it also checks that giver and step npc ids exist |
BuilderSchema | kind | en-admin forms |
BuilderTemplate | kind | Defaults for a new survivor or quest |
Commands
| Command | Restricted | What it does |
|---|---|---|
/resetquest <target> <quest> | group.admin | Resets that quest for the player (status abandoned, completions 0) |
Events
Emitted, server-local:
| Event | Payload |
|---|---|
en-quests:server:started | source, questId |
en-quests:server:stepChanged | source, questId, stepIndex |
en-quests:server:completed | source, questId |
Listened to, as objective sources:
| Event | Used for |
|---|---|
en-zombies:server:zombieKilled | kill steps |
en-zombies:server:corpseLooted | search steps |
en-crafting:server:crafted | craft steps |
en-inventory:server:changed | collect and deliver tracking |
encore:server:onPlayerLoaded, encore:server:onPlayerUnload, encore:server:levelChanged | Loading progress and refreshing offers |
Database
encore_quest_progress: primary key (citizenid, quest_id), plus status (active, completed or abandoned), step, count, completions, completed_at and started_at (unix times).
Examples
Use AdvanceQuest for an objective en-quests doesn't track itself. Here the current step of a signal_boost quest is finished when a player repairs a radio tower in your own script:
-- server
AddEventHandler('myradio:server:towerRepaired', function(source)
if exports['en-quests']:IsQuestActive(source, 'signal_boost') then
exports['en-quests']:AdvanceQuest(source, 'signal_boost')
end
end)Gate content behind a finished story quest:
if not exports['en-quests']:HasCompletedQuest(source, 'clean_water') then
return false, 'Rhodes hasn\'t vouched for you yet.'
endAnnounce completed quests:
AddEventHandler('en-quests:server:completed', function(source, questId)
print(('%s completed %s'):format(GetPlayerName(source), questId))
end)