EndCore Framework

Create a quest

Add a quest-giver NPC and a quest with steps, requirements and rewards, in config or live from the Build tab, and drive custom objectives from code.

Quests give survivors a reason to go somewhere and a story to follow. In EndCore a quest has a giver (a survivor NPC), a list of steps done in order, optional requirements, and rewards. Progress is decided on the server from real game events, so players can't fake finishing a step.

This guide adds a farmer named Marla and a repeatable daily task for her.

1. Add the quest giver

Survivors are defined in en-quests/data/npcs.lua, keyed by id:

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.' },
},
FieldWhat it does
nameName shown in dialogue
titleA short line under the name
modelPed model
coordsvec4 position and heading
scenarioOptional idle scenario
greetingLines they say when they have nothing for you
bliptrue always shows them on the map. Otherwise they only show while they have something for you.

2. Write the quest

Quests are defined in en-quests/data/quests.lua, keyed by quest id (up to 60 characters). This file stays on the server; players never see quest definitions.

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 },
    },
},

Quest fields

FieldWhat it does
labelName in the offer, tracker and journal
descriptionLonger text in the journal
typestory (once, often chained), side (once) or task (repeatable)
giverThe npc id who offers it
offerLines the giver says before offering it
cooldownHoursTasks only: hours before it can be taken again. Defaults to 24.
requiresAny of level = n, skill = { name, level } and quests = { ids }
rewardsAny of cash, xp (character XP), items = { { item, count } } and skills = { [skill] = xp }
stepsOrdered list of steps

Step types

Every step has a label for the tracker. Any step can also have a hint (a longer journal line) and coords (a waypoint players can set from the journal).

TypeFieldsDone when
talknpc, dialogThe player talks to that survivor
delivernpc, item, count, dialogThe player hands the items over. They are taken.
collectitem, countThe player is carrying them. They are kept.
reachcoords (required), radius (default 10, 1 to 500)The player gets there. The server re-checks the distance.
killcount, optional zombie type such as 'runner'Enough zombies are killed
searchcountEnough zombie corpses are searched
craftcount, and recipe (an en-crafting recipe id) or item (the result item)Enough are crafted

With Config.partyCredit = true (the default), kills and searches also count for nearby party members on the same step.

Chaining quests

Use requires.quests to make a story chain. The shipped clean_water quest only unlocks after first_steps:

lua
requires = { quests = { 'first_steps' } },

3. Restart and test

Restart en-quests (or the server). Marla appears at the farm with a marker while she has something for you. Talk to her, accept the task, and press F5 to open the journal.

A few limits to keep in mind while testing:

  • Players can have 3 quests active at once (Config.maxActive).
  • Story and side quests can only be done once.
  • Reward items that don't fit are reported as "No room for ..." and not given, so keep item rewards small.

To run a quest again on the same character, reset it:

text
/resetquest 1 cloth_for_marla

4. Or build it in game

Both survivors and quests can be created live from the admin panel:

  1. Press F10, open the Build tab and choose Quest NPCs.
  2. Choose New, fill in the name, model and greeting, and press Use my position where they should stand. Save.
  3. Switch to Quests and choose New.
  4. Fill in the name, type, giver, offer lines, requirements and rewards.
  5. Add steps. Each step has a type, tracker label, hint, survivor, item, count and recipe field; fill in the ones that step type uses.
  6. Save. en-quests checks that the giver and every step's survivor exist before accepting it.

A placed quest or survivor with the same id as a config one replaces it, and deleting it restores the config version. Editing a quest keeps players' progress, clamped to the new number of steps.

5. Custom objectives from code

The built-in step types cover most quests. For anything else, your own resource can move a quest on with AdvanceQuest:

  • On a kill, search or craft step, it adds amount (default 1) to the count.
  • On any other step type, it finishes the current step.

It always acts on the player's current step, so the safest pattern is a quest whose custom objective is its only step, or the step you know is current.

Here is a daily task where searching dumpsters (from the resource in Writing a resource) counts as searching:

lua
-- en-quests/data/quests.lua
dumpster_diver = {
    label = 'Dumpster Diver', type = 'task', giver = 'marla', cooldownHours = 20,
    description = 'Marla wants to know what the town threw away.',
    offer = { 'Check the dumpsters in Grapeseed. Five of them.' },
    steps = {
        { type = 'search', count = 5, label = 'Search dumpsters', hint = 'Look at a dumpster and hold Alt.' },
    },
    rewards = { cash = 60, xp = 50 },
},
lua
-- my-scavenge/server/main.lua, after a successful dumpster search
if GetResourceState('en-quests') == 'started'
    and exports['en-quests']:IsQuestActive(source, 'dumpster_diver') then
    exports['en-quests']:AdvanceQuest(source, 'dumpster_diver', 1)
end

Zombie corpse searches still count toward this step too, because it is a search step.

Reacting to quest events

en-quests fires server events you can use for announcements, achievements or unlocking other content:

EventPayload
en-quests:server:startedsource, questId
en-quests:server:stepChangedsource, questId, stepIndex
en-quests:server:completedsource, questId
lua
AddEventHandler('en-quests:server:completed', function(source, questId)
    if questId == 'first_steps' then
        encore.notify(-1, {
            title = 'Burton Checkpoint',
            description = ('%s has been vouched for.'):format(exports['en-core']:GetCharacterName(source)),
        })
    end
end)

Use exports['en-quests']:HasCompletedQuest(source, questId) to gate your own features on quest progress.