Violet

Quester

On this page

Provides quest interaction and management functionality.

Functions

core.quester.get_quest_state(quest_id: number) -> number

Available on GMS and CW

Returns the quest state for the specified quest.

Quest States:

  • -1 - Not available
  • 0 - Available
  • 1 - In progress
  • 2 - Completed

core.quester.get_quest_progress(quest_id: number) -> table | nil

Available on GMS and CW

Returns a read-only view of a quest's completion requirements and your live progress. Returns nil for an unknown quest or one with no completion requirements.

Returned table:

  • id (number) — the quest id.
  • name (string) — the quest's display name.
  • state (string) — "available", "in_progress", "complete", or "not_available".
  • objective (string) — "kill", "collect", "reach", "talk", "condition", or "unknown".
  • start_npc (number | nil) — the NPC that offers the quest.
  • target_npc (number | nil) — the turn-in / talk NPC.
  • target_map (number | nil) — the destination map (for "reach" steps).
  • min_level / max_level (number | nil) — the level range required to accept the quest.
  • items (table) — item-collection requirements, each { id, need, have }.
  • mobs (table) — kill requirements, each { id, need, have, order } (explained below).
  • conditions (table) — record-based requirements, each { key, need, have, cond } (explained below).

How kill requirements work

Kill quests carry a dedicated mob list. Each entry is one monster: id is the monster id, need is the required kill count, have is your live count (updated on every kill), and order is the quest step the requirement belongs to (multi-step quests gate later entries behind earlier ones; single-step quests use 1 throughout).

lua
{ id = 8220008, need = 200, have = 37, order = 1 }
-- "kill monster 8220008 two hundred times; you're at 37."

How conditions work

Separately from kills, quests can require record conditions — scripted flags the game writes during cutscenes, events, or story progression. key names the record, need is the target, have is the current value, and cond is one of "==" / ">=" / "<=":

lua
{ key = "bh3", need = 1, have = 0, cond = "==" }
-- "the record bh3 must equal 1; it's 0" — advance by playing the story, not by grinding.
Edge cases

A handful of quests express kills as record conditions keyed by a monster id (a 6–8 digit numeric key) — the objective is still classified "kill" when that happens. A few event quests compare date stamps; those conditions carry a 14-digit timestamp in need.

Examples

Dump everything about a quest:

lua
local q = core.quester.get_quest_progress(3301)
if q then
    core.log(q.name .. "  [" .. q.objective .. "]")
    for _, it in ipairs(q.items)      do core.log(("  item %d: %d/%d"):format(it.id, it.have, it.need)) end
    for _, m  in ipairs(q.mobs)       do core.log(("  mob %d: %d/%d"):format(m.id, m.have, m.need)) end
    for _, c  in ipairs(q.conditions) do core.log(("  %s %s %d (have %d)"):format(c.key, c.cond, c.need, c.have)) end
end

Check whether a quest is ready to hand in (all items, kills, and conditions satisfied):

lua
local function is_ready(quest_id)
    local q = core.quester.get_quest_progress(quest_id)
    if not q then return false end
    for _, it in ipairs(q.items) do
        if it.have < it.need then return false end
    end
    for _, m in ipairs(q.mobs) do
        if m.have < m.need then return false end
    end
    for _, c in ipairs(q.conditions) do
        local ok = (c.cond == "==" and c.have == c.need)
                or (c.cond == ">=" and c.have >= c.need)
                or (c.cond == "<=" and c.have <= c.need)
        if not ok then return false end
    end
    return true
end

Auto-complete a kill quest when it's done:

lua
if is_ready(quest_id) then
    local q = core.quester.get_quest_progress(quest_id)
    if q and q.target_npc then
        core.quester.complete_quest(q.target_npc, quest_id)
    end
end

core.quester.get_quest_info(quest_id: number) -> table | nil

Available on GMS and CW

Returns what a quest needs to be accepted and to be handed in, including which NPC offers it and which NPC takes it back, and where those NPCs and the monsters it asks for can be found. Returns nil for an unknown quest.

Returned table:

  • id (number), name (string)
  • auto_start (boolean) — the quest starts without talking to its NPC.
  • auto_complete (boolean) — the quest completes on its own once its requirements are met.
  • start (table) — what accepting needs:
    • npc (number | nil) — the NPC that offers the quest; npc_maps (table) — maps that NPC stands in.
    • min_level / max_level (number | nil)
    • quests (table) — prerequisite quests, each { id, state } where state is the state that quest must be in: "complete", "in_progress", or "not_started" (the quest can't be taken once that one is started). quests_any (boolean) is true when one of them is enough.
    • items (table) — items to hold, each { id, need }.
    • maps (table) — when not empty, the quest can only be accepted in one of these maps.
  • complete (table) — what handing in needs:
    • npc (number | nil) — the NPC that takes the quest back; npc_maps (table) — maps that NPC stands in.
    • items (table) — { id, need }.
    • mobs (table) — { id, need, maps }, where maps lists maps the monster appears in.
    • maps (table) — maps to visit.
    • conditions (table) — record rules, each { key, need, cond } (see How conditions work).

npc_maps and maps are empty when the game data doesn't place that NPC or monster anywhere, such as ones that only appear during a quest.

lua
local info = core.quester.get_quest_info(2952)
if info then
    core.log(("accept at NPC %s, hand in to NPC %s"):format(tostring(info.start.npc), tostring(info.complete.npc)))
end

core.quester.can_start_quest(quest_id: number) -> boolean, table

Available on GMS and CW

Returns whether the character can accept the quest right now, and what is missing. Returns nil while the character isn't in a map.

The second value lists every unmet requirement; it is empty when the first value is true. See What's missing for the entries.

lua
local ok, missing = core.quester.can_start_quest(2951)
for _, m in ipairs(missing) do
    if m.type == "quest" then core.log(("finish quest %d first"):format(m.id)) end
    if m.type == "level" and m.need then core.log(("needs level %d"):format(m.need)) end
end

core.quester.start_quest(npc_id: number, quest_id: number) -> boolean

Available on GMS and CW

Attempts to start the quest with the specified NPC. Check quest state first with get_quest_state().

If the NPC is farther away than one screen, the character is teleported to it first.

Returns: true if the request was sent. false if the NPC isn't on the current map (quests that start without an NPC are exempt), or if the game could not act on the quest.


core.quester.complete_quest(npc_id: number, quest_id: number) -> boolean

Available on GMS and CW

Attempts to complete the quest with the specified NPC. Check if completable first with can_complete_quest().

If the NPC is farther away than one screen, the character is teleported to it first.

Returns: true if the request was sent. false if the NPC isn't on the current map (quests that complete without an NPC are exempt), or if the game could not act on the quest.


core.quester.can_complete_quest(npc_id: number, quest_id: number) -> boolean, table

Available on GMS and CW

Returns true if the quest's requirements are met and it can be handed in to npc_id, and a list of what is missing (empty when the first value is true). Returns nil while the character isn't in a map.

When every requirement is met but npc_id is not the NPC that takes the quest back, the list holds one npc entry naming the right one.


core.quester.get_quest_record(quest_id: number, key?: string) -> string | nil

Available on GMS and CW

Reads a quest's saved progress values: the named values quests keep, such as step counters, clear flags and world states. With key, returns that value as a string, or nil when it isn't set. Without key, returns a table of every named value plus the whole record as a second string value.

Some records don't belong to a visible quest and hold world or event state. For example, record 63090 holds the Afterlands' day/night (DnN, "1" at night), raining and lightning values.

lua
local night = core.quester.get_quest_record(63090, "DnN") == "1"
-- hit the Solar Pulley, then check the value flipped

core.quester.get_quest_timers() -> table

Available on GMS and CW

Returns the quest time limits counting down on screen, each { quest_id, remaining, total } with times in seconds. Empty when no timed quest is running or the character isn't in a map.

lua
for _, t in ipairs(core.quester.get_quest_timers()) do
    core.log(("quest %d: %.0fs left"):format(t.quest_id, t.remaining))
end

What's missing

can_start_quest and can_complete_quest return one entry per unmet requirement. Every entry has a type:

typeFieldsMeaning
"level"need, haveCharacter level is below need
"level"max, haveCharacter level is above max
"job"–The quest isn't offered to this job
"quest"id, need, haveQuest id must be in state need; have is its state now ("not_started", "in_progress" or "complete")
"item"id, need, haveHold need of item id; have is the count held
"mob"id, need, haveKill need of monster id; have is the live count
"map"idAccept in (start) or visit (complete) map id
"condition"key, need, have, condA record rule (see How conditions work)
"npc"idHand in to NPC id instead
"state"id, need, haveThe quest itself is in the wrong state: it must be "not_started" to accept it and "in_progress" to hand it in; have is its state now
"blocked"idThe game has this quest turned off
"other"codeAnother requirement the game reports

A quest that is already in progress or complete, or turned off, gets a single state or blocked entry. For a quest with several steps, can_complete_quest lists only what the current step still needs.

Auto Quest

Auto Quest accepts, travels to, fights for and hands in every quest between two quest IDs, in the order their prerequisites allow. It is the same run as Autos → Auto Quest in the web menu, so starting it here fills in that panel.

While a run is active it:

  • travels between maps on its own and approaches NPCs before talking to them;
  • answers NPC dialogs and blue-box prompts the way Auto NPC and Auto Blue Boxes do, even if those are switched off;
  • turns on Kami (with Kami Loot) and Auto Attack, using your settings for them, while a quest needs kills or drops, as long as Fight for Quests is on in the panel; outside of that it keeps them off;
  • skips quests your character can never take (another job, a finished repeatable, an event that ended);
  • pauses during evasion, runes, lie detectors, conversations, cutscenes and while the character is dead or not in a map.

A run stops on its own when every quest it can do is complete, or when a quest needs something it can't provide, such as story progress, an item with no known source, or a higher level. get_line_status() then tells you why.

core.quester.run_line(start_id: number, end_id: number) -> boolean, string?

Available on GMS and CW

Starts an Auto Quest run over every quest from start_id to end_id (inclusive; the order of the two doesn't matter; at most 5000 IDs). Replaces any run already going.

Returns: true when the run started. false and a reason when it didn't.


core.quester.stop_line()

Available on GMS and CW

Stops the current Auto Quest run. Does nothing when no run is active.


core.quester.get_line_status() -> table

Available on GMS and CW

Returns the current or last Auto Quest run.

FieldTypeDescription
runningbooleanA run is active
stepstring"planning", "accepting", "traveling", "hunting", "visiting", "turning_in", "waiting", "finished", "stopped" or "idle"
quest_idnumber or nilQuest being worked on
quest_namestring or nilIts name
detailstring or nilWhat the run is doing for that quest right now
donenumberQuests in the line already complete
totalnumberQuests found in the line
stop_reasonstring or nilWhy the last run ended, once it has
lua
local watching = core.quester.run_line(2950, 2960)

function on_tick(stage)
    if not watching then return end
    local s = core.quester.get_line_status()
    if not s.running then
        watching = false
        core.log("Auto Quest ended: " .. tostring(s.stop_reason))
    end
end

NPC Dialogue Selection

The quester uses a queue system for handling NPC dialogue choices and text inputs.

Advanced: on_dialog callback

For full dialog control (reading dialog text, inspecting choices, conditionally responding), use the on_dialog callback instead. It fires before the queue system and lets scripts intercept, respond to, or block dialogs dynamically.

core.quester.queue_npc_selection(selection: string)

Available on GMS and CW

Enqueues a dialogue option substring. When the NPC dialogue appears with auto NPC enabled, the option containing this substring will be selected. For text input prompts, the queued string will be used as the answer.


core.quester.clear_npc_selection()

Available on GMS and CW

Clears the entire NPC selection queue.