WoW-style quest markers over FFXI NPCs, for Windower 4.
Reads your quest and mission state from packet 0x056, joins it against
BG-Wiki-derived quest metadata, evaluates prerequisites, works out which step
of the quest you are on, and draws a marker above the NPC (or the monster)
in the 3D world.
Contents
Download or clone this repository into your Windower addons folder, so that
it sits at Windower4/addons/questmarks/ (you might need to remove the suffixes that GitHub adds when downloading (-1.0.0) or cloning (-main)), then in game:
//lua load questmarks
To load it every session, add lua load questmarks to your Windower init
script.
A browsable list, if you want one. questmarks-ui is a
separate addon, in its own repository, that puts your accepted quests in an
in-game window grouped by region, with each quest's full step ladder beside it.
It reads this addon's data and its build/ directory off disk; nothing here
reads it back, and nothing here may, which is the rule
tools/test_standalone.lua enforces.
Colour carries the state. The glyph says what the NPC is for.
| Quest | Mission | Campaign | |
|---|---|---|---|
| available, all prerequisites met | yellow ! | green ! | red ! |
| accepted, ready to hand in | yellow ? | green ? | red ? |
| accepted, in progress | grey ? | muted green ? | muted red ? |
| a prerequisite is definitely unmet | grey ! | grey ! | grey ! |
| something could not be verified | black ! | black ! | black ! |
| repeatable, already done | blue ! | blue ! | blue ! |
Missions are green when they're actionable, Campaign red, so the main storyline and the war content are not lost in a crowd of yellow side quests. Once blocked or unverifiable they drop back to the shared grey/black, and on an NPC offering both, the mission wins the marker.
Nation missions are filtered to your allegiance. A Windurst character never sees San d'Oria mission markers, because those lines are mutually exclusive. San d'Oria quests still show; only missions are gated.
The black ! means "I can't verify this", not "go do it" and not "you
can't". In practice it almost always means unknown fame.
Anything you can start always beats a repeatable you've already done. An NPC with both shows yellow (or green for a mission), never blue. Repeatable quests and missions toggle separately, since there are far more of the former:
//qm show repeat_quest off 278 repeatable quests
//qm show repeat_mission off 29 repeatable missions
Before you accept a quest the glyph is always !: "there is something here to
take" is the whole message. Once accepted, it refines into what that step
asks of you:
| glyph | meaning | step kinds |
|---|---|---|
| ? | talk to finish, or nothing more specific is known | turnin, travel, wait, cutscene, other |
| speech bubble | talk to this NPC | talk |
| hand-over | trade the listed items here | trade |
| sack | this drops something the step needs | obtain, monster role drop |
| sword | defeat this | fight, monster roles kill and spawn |
| magnifier | examine this (a ???, a door, a monument) |
examine |
! and ? carry the state colour. The action glyphs keep a fixed identity
colour in every state instead, except trade: on the hand-over, "you have the
items" versus "you don't" is worth seeing at a glance.
Monsters get markers too, a sword where the quest wants one dead and a sack where it wants its drop. Duplicate monsters are not collapsed the way duplicate NPCs are: two Sand Sweepers are two real targets. Corpses are skipped.
Packet 0x056 carries no step data. Nothing in any packet says "you are on
step 3 of 7", so the position is inferred from the items and key items you
hold. Steps that leave no trace are stepped over rather than waited for, which
means the position can lag, and can never run ahead of you: the marker
points somewhere you have already been rather than somewhere you have not
earned. With no evidence at all it sits one step past the giver.
A Question of Taste is six steps, three of them observable, over four key
items:
| you are on | evidence held | inferred | marker lands on |
|---|---|---|---|
| not yet accepted | none | none | Etteh Sulaej, Kazham ✔ |
| 2 | KI 213 | 2 | Angelica, Windurst Waters ✔ |
| 3 | + KI 259, 214 | 3 | Etteh Sulaej ✔ |
| 4 | unchanged | 3 | Etteh Sulaej (stale) |
| 5 | unchanged | 3 | Etteh Sulaej (stale) |
| 6 | KI 215 | 6 | Etteh Sulaej ✔ |
Four of six right, two stale, none ever ahead of the player. It plays better
than that reads: a stale marker is still somewhere you have been, and the last
step of most quests is a hand-in whose step usually is observable, so the marker
tends to be right when you accept and when you can finish.
python tools/questgraph/qg.py report measures the rest.
//qm why <npc> prints the whole ladder, marking the inferred step, the
step actually carrying the marker, and a verdict on every piece of evidence.
That is the command for "why is my marker here". //qm steps off turns the
feature off, pinning every marker back on its quest giver.
One thing that will look like a bug and isn't. While you stand in the
giver's zone with a quest in progress whose current step is somewhere else, that
quest shows no marker at all. There is genuinely nothing to do at the giver, and
an empty city reads better than a marker meaning "elsewhere". Nothing is lost:
//qm todo still lists the quest and names where the step actually is, and
//qm why <npc> prints a - hidden: line explaining it.
//qm help
//qm diag dump everything to questmarks.log
//qm debug [on|off] caption each marker with NPC, distance and lag push
//qm on | off | toggle
//qm todo [what] [n] what you are in the middle of, anywhere in the world
//qm why <npc|t> explain every marker on an NPC ('t' = your target)
//qm steps [on|off] follow a quest along its steps (default on)
//qm npcs quest NPCs resolved in this zone
//qm new what became newly available at the last check
//qm notify [on|off] announce newly available quests (default on)
//qm fame show fame levels and where each came from
//qm fame <region> <0-9> set manually //qm fame <region> reset
//qm fame dump checker lines seen but not recognised
//qm show <state> [on|off] turnin progress ready unknown blocked
repeat_quest | repeat_mission
//qm dist <yalms> default 50 //qm floor <yalms> default 6
//qm max <n> default 16 //qm size <px> default 60
//qm perspective [on|off] scale markers with distance (default on)
//qm refdist <yalms> distance at which //qm size is exact, default 8
//qm pxmin | pxmax <px> perspective size clamps, default 10 / 180
//qm offset <yalms> default 2.3 marker height above the NPC
//qm sizescale <0-1> scale marker height by NPC race (0 = fixed height)
//qm lag <0-3> camera lag compensation, default 1 (0 = off, see below)
//qm smooth <0-0.9> damp marker shimmer, default 0.3 (0 = raw)
//qm dump <npc|t> compare every entity sharing that name
//qm visible [on|off] only mark NPCs the client has spawned (default on)
//qm menu [on|off] hide markers while a menu is open (default on)
//qm kills [n] monsters beaten this session (display only)
//qm settings effective values, flagging stale saved ones
//qm reset confirm clear saved settings (keeps fame)
//qm data | state <area> | probe | perf
//qm rebuild [all|v1] regenerate the index (only use if you know what you're doing!)
//qm why t is the one to reach for when a marker looks wrong. Every
command's output is also written to questmarks.log, because in-game chat
cannot be copied out: when something looks wrong, run //qm diag and send it.
//qm todo walks the whole index and lists what you are in the middle of,
newest-actionable first, naming the step and the city it is in, which markers
alone can never do:
questmarks: 3 to do (accepted): 1 turnin, 2 progress
yellow ? turnin A Question of Taste Etteh Sulaej, Kazham (J-9) step 6/6
grey ? progress Waking the Colossus Iron Eater, Metalworks (J-8) step 5/14
grey ? progress The Gobbiebag Part I Bluffnix, Lower Jeuno (H-9) step 1/2
It defaults to what you have accepted. //qm todo ready lists what you
could start right now, //qm todo all every state, and any of the six state
names on its own filters to it. A trailing number raises the chat cap; the full
list always goes to questmarks.log.
FFXI never sends your numeric fame to the client, so questmarks assembles it from three sources and takes the highest. Dialogue: talk to a checker and your level is read from what they say, saved per character the moment it is learned, so you only visit each one once.
| Region | Checker | Where |
|---|---|---|
| Bastok | Flaco | Port Bastok (E-6) |
| San d'Oria | Namonutice | Southern San d'Oria (K-6) |
| Windurst | Zabirego-Hajigo | Windurst Waters (F-10) |
| Jeuno | Mendi | Lower Jeuno (H-8) |
| Norg / Tenshodo | Vaultimand | Norg (H-8) |
| Rabao / Selbina | Waylea | Rabao (G-9) |
Inferred floor: if you have accepted a quest gated at fame 5 you must have
had fame 5, so that becomes a lower bound for free. Manual:
//qm fame bastok 6. Three regions in the data have no checker anchors
shipped, aht_urhgan, kazham and mhaura, so their level can only come
from the floor or from you; //qm fame flags them. If a checker ever says
something the addon doesn't recognise, //qm fame dump captures it verbatim so
the anchor can be corrected from real observation.
Completion is decoded from packet 0x056 and is exact. Everything else is
inferred: which step you're on, your fame floor, whether you've beaten
enough of something. A wrong inference moves a marker rather than deleting one,
and is never written to disk.
- Markers draw through walls. Windower's Lua API exposes no depth buffer, so
true occlusion is impossible. A distance cap, a same-floor check
(
//qm floor) and a fade with distance reduce the problem rather than eliminate it. - The step position is inferred, and can be stale. It is never ahead of you
and never a marker where none existed before, but on a long unobservable
stretch it points at the last place it could prove you reached.
//qm why tshows which step and why. - Linear mission progress is inferred. CoP, SoA, RoV, ACP, MKD, ASA and TVR send only a "current mission" integer with no completed bitfield, so the addon resolves it to the nearest known ID, treats everything below as complete and blocks anything past it. Nation and Zilart missions do get an exact bitfield and are reliable.
- Items needed to complete a quest don't gate it. Items needed to start
one do. A quest is never greyed out for turn-in items you have not collected
yet, but some cannot be begun empty-handed, and a yellow
!there sends you across Jeuno for nothing. - Kill counts are counted, not reported. No packet says "3 of 5 Sand
Sweepers", so the addon tallies defeat messages itself: session-local,
approximate, never saved, and never advancing a step.
//qm killslists it. - Markers trail slightly while the camera turns, ~31-37px behind during a
pan and ~1px at rest.
//qm lagcompensates imperfectly;//qm debugdraws a dim ghost at the uncompensated position so you can see which is closer. - Markers only appear for NPCs the client has spawned, so a giver it has not
loaded yet gets none even within
//qm dist.//qm visible offmarks every indexed entity in range instead, at the price of markers over empty air. This also hides ghost NPCs: FFXI keeps spare entity slots for conditionally-visible NPCs, so a zone can hold several entities with one name while only one is rendered. The addon skips untargetable copies and keeps the nearest of the rest; if a marker still floats over nothing,//qm dump <npc>prints every copy. - Marker height follows the NPC's race (Galka high, Tarutaru low), the only usable signal there is. Roughly a third of quest givers report a race that says nothing about height and get the default offset.
Completing a quest or gaining a fame level can unlock content anywhere in the world, where no marker will ever be visible, so questmarks diffs the whole index on every zone-in and reports it in one line. The first check after login is deliberately silent, because otherwise every quest in the game would count as new. Some content starts just by entering a zone, with no NPC to mark, so that is announced on arrival instead, counted and tagged by kind:
questmarks: entering this zone starts 1 mission(s):
Unraveling Reason [mission]
There are two quite different ways to help, and they need different things from you. Pick the one you came for; you do not need the other.
| you want to | you need | go to |
|---|---|---|
| fix a marker that points at the wrong NPC, or a quest whose steps are wrong | Python 3, LuaJIT, and to have played the quest | Correcting quest data |
| change how the addon behaves, looks or performs | LuaJIT and a Windower install | Working on the addon |
Corrections are the more valuable of the two and the easier to start on. There is no Lua and no packet knowledge in it.
Everything the addon knows about a quest is one JSON record, in
build/authored/. There are 1546 of them. Each is a step ladder: what you do,
where, to whom, and what you are holding when it is done. data/*.lua is
generated from those records and is not where you fix anything.
build/authored/ the source of truth: one step ladder per quest <- edit here
-> build/validated/ names resolved to resource ids
-> data/quest_steps.lua the emitted ladders
-> data/quest_index.lua the shipped index the addon reads
You edit the first box. Everything downstream is rebuilt from it.
Python 3, and LuaJIT at %LOCALAPPDATA%\Programs\LuaJIT\LuaJIT\bin\luajit.exe.
No pip install, no Node, no build step. Clone the repository and you have
everything else already: the corpus is committed.
In game, target the NPC and run:
//qm why t
That prints the quest's whole ladder, which step the addon thinks you are on,
which step is carrying the marker, and a verdict on every piece of evidence it
checked. It also prints the quest id, in cat/area/id form, which is what you
search for next.
Every diagnostic command writes to questmarks.log in the addon folder as well
as to chat, so you do not have to copy anything out of the game. If you played
for an evening and want the whole session's worth at once:
python tools/questgraph/qg.py why questmarks.logwhich lists every quest the log mentions, with its state, and flags any that have no authored record at all.
python tools/questgraph/qg.pyIt serves on http://127.0.0.1:8787 and opens your browser. Loopback only, and
it can write to build/authored/, so do not expose it. --no-browser and
--port are there if you need them.
You land on a list of areas, quest and mission both. Click into one and you get every quest in it as a row of marks, one mark per position in its ladder, filled where the addon can observe that position and hollow where it cannot. A long hollow run is the answer to "why does my marker not move". Quests are grouped into bands by what is wrong with them, worst first; the biggest band, never moves, is mostly correct and is labelled so you do not waste an evening in it.
Click a quest to open it as a numbered work instruction, with the zone in the margin and, under each step, what it needs, what it grants and which monsters are involved.
If you already know the quest, the search box takes a name or a cat/area/id.
There is no JSON to type. Click a value in the sentence and the right control opens: the verb picks the step kind, the target name opens an entity picker, the zone opens a search over the game's own zone table. The number in the left gutter opens the structural verbs — add a step before or after, move it, delete it, make it optional, group it with the next one, and edit the note. Renumbering happens for you.
Nobody types a resource id. Items, key items and zones are searched against
the same index the build's resolver uses, so the picker cannot offer you a name
that then fails to resolve. NPC and monster names are in no game table at all,
so there the picker suggests what this quest's own cached wiki page links, as a
hint rather than a rule — an object like a door or a ??? will not be in it,
and that is normal.
Step notes are the easiest useful contribution in the project. The sibling
addon questmarks-ui draws each step's note under it, expanded, so a note
that reads badly or says something untrue is on a player's screen. Fixing one
needs nothing but the game: open the step menu and choose edit the note. Write
for somebody standing in Vana'diel who has never seen this repository. Words
from the wiki are fine ("the page says", "bullet 3"); words from this pipeline
are not, and the build will fail and name the term if one gets through. The
quest-level notes field is a different thing, read by the next reviewer and
never shown to a player.
When you choose review and commit, questgraph asks for two things and then writes the record itself:
- basis, which is
gameorreading, and - reason, one sentence.
basis is not paperwork; it changes what the build does.
| what it means | what it changes | |
|---|---|---|
game |
you stood there and saw it. The page is wrong | the build stops requiring that the value appear on the wiki page, for the fields you changed and no others. Without it your correction is refused at build time |
reading |
the page supports your fix and the earlier reading of it was wrong | the value still has to be attested on the page |
Either way, the quest is never scheduled for automatic re-reading again, so a later authoring pass cannot quietly overwrite you.
Before it writes anything the tool shows you a diff, and refuses a set of things outright: writing to a generated file, editing a record's provenance, removing a marker without saying so, recording evidence on a later step than the one you actually obtain it on, or writing to a file that changed on disk while you had it open. There is no bulk edit and no find-and-replace, deliberately.
python tools/pipeline/validate.py # authored -> validated
python tools/pipeline/emit_lua.py # validated -> data/quest_steps.lua
luajit tools/build_index.lua --all-areas # -> data/quest_index.lua, mission_index.luathen run the six suites (see below). python tools/questgraph/qg.py verify does
the build and the suites in one command.
Commit your change to build/authored/ and nothing else. Rebuild locally by
all means, but leave the regenerated data/*.lua out of the pull request. It is
a megabyte and a half of generated Lua at one line per row, so two people fixing
two different quests would conflict on every line either of them touched. The
maintainer regenerates data/ once at release time. A good pull request is one
or two edited JSON files and a diff a reviewer can actually read.
Everything except the rendering itself is testable without launching the game.
The six suites should be green before anything is published, and smoke.lua
before you play: a nil index in a command handler is otherwise found
mid-session, in a chat window you cannot copy out of. They find the addon
directory from their own location, so run them from the root of a clone
anywhere.
luajit tools/check.lua # parse every file, and reject any global write
luajit tools/test_project.lua # world->screen, vs a real captured camera
luajit tools/test_state.lua # 0x056 decode
luajit tools/test_core.lua # fame / inventory / prereq / steps / index join
luajit tools/test_standalone.lua # no runtime dependency on anything else
luajit tools/smoke.lua # run every //qm command against a stub client
Two rules those suites exist to enforce, worth knowing before you change anything:
- No shipped file writes a global.
check.luascans the bytecode for it. - An unknown must never collapse into a definite answer. A quest whose
prerequisites cannot be verified draws a black
!, not a grey one; that distinction is the whole ofcore/prereq.luaand most oftest_core.lua.
If your change touches the build rather than the addon, prove it moved no data:
luajit tools/diff_index.lua <old.lua> <new.lua>It compares two generated indexes as an unordered multiset, so a change to the
emitter cannot masquerade as a change to the data, and it reports only the
differences that actually move markers: a quest that disappeared, a zone that
went from resolved to nil, a fame gate that vanished, an NPC key whose entry
count grew sharply. Keep a copy of data/quest_index.lua before your build and
point this at both.
Then run the build twice and confirm the generated files are byte-identical.
Nothing may depend on pairs() order reaching the output: a build that is not
reproducible leaves git status dirty after a no-op rebuild, and turns every
real data change into a diff nobody can read. .gitattributes pins the line
endings that guarantee holds on.
Where the data comes from, if you need to work on the pipeline itself: every
quest and mission fact is read from a local cache of
BG Wiki pages, except identity, which comes from the
game client's own DAT name table. The page cache is ~293 MB and is not
published; only check_authored.py and check_grounded.py read it, and
tools/pipeline/fetch_wiki.py rebuilds it. Most of build/ is committed rather
than treated as an artifact: build/authored/ is the source of truth,
build/rendered/ is runtime data questmarks-ui reads off disk,
build/authored-quarantine/ holds records that were read but do not ship, and
build/res.json is the client's own name tables dumped for the build. Only the
three regenerable stages are ignored, and .gitignore says so at the top.
Everything under data/ is generated and will be overwritten by the next
build, bar data/npc_overrides.lua and data/fame_dialogue.lua, which are
hand-maintained and say so at the top.
The code is MIT (LICENSE). The bundled quest and mission data derives from
BG Wiki and is CC BY-NC-SA 3.0; data/dat_names.lua is extracted from the
game client and is Square Enix content, under neither. LICENSE-DATA states
which terms cover which files, and NOTICE is the full attribution.
The non-commercial clause is inherited from BG Wiki and is not optional. questmarks as a whole cannot be sold or bundled into anything monetised.
questmarks started as a consumer of chronicle
by jintawk. Its bundled BG Wiki tables answered which quests exist, who gives
them, what they need and what they lead to, and that was enough to prove the
idea was worth building. Every one of those questions is now answered from this
project's own page cache and from the game client's own data, and nothing here
reads that addon any more, but the shape of the answer was learned there first.
Two readings of its code also survive, cited where they are used: the packet
0x056 decode in core/state.lua (which chronicle in turn credits to the
Mastery addon for the original story_logs mapping) and the batched
multi-bag inventory sweep in core/inventory.lua. Thank you both.
core/fame.lua's dialogue reading was learned from
Balloon.
Quest and mission data from BG Wiki and its contributors. Final Fantasy XI is © SQUARE ENIX CO., LTD. This is an unofficial, non-commercial fan tool with no affiliation to or endorsement by Square Enix.
