promotional bannermobile promotional banner

LibQuestDB

A standalone, offline quest database for World of Warcraft Classic. It answers questions about a quest without the quest being in your log or the game's cache; the quest counterpart to LibItemDB and LibProfessionDB.
Back to Files

QuestDB-v0.0.1

File nameQuestDB-QuestDB-v0.0.1.zip
Uploader
PmptastyPmptasty
Uploaded
Sep 18, 2026
Downloads
16
Size
2.6 MB
Flavors
Classic TBCClassic
File ID
8913931
Type
R
Release
Supported game versions
  • 2.5.6
  • 1.15.9

What's new

QuestDB Changelog

[v0.0.1] (2026-09-18) - Initial release: quest rewards, derived XP and English quest text

The first release of LibQuestDB-1.0, an offline quest database for Classic Era and TBC that fills the gap Questie leaves: what a quest pays (items, choices, money, reputation, spells, the item handed over on accept), the XP it gives at any player level, and the quest's full text. Everything is static data generated from the CMaNGOS world database and read through LibStub; the library declares no dependency. Library MINOR is 5.

Quest text and the locale framework (MINOR 5)

  • Decided by the operator on 2026-09-18: "we need to create a locale and wire everything into an english locale, we can add other languages later after we're up and running, but we need the locale framework in place as mvp so we can just add new stuff later."
  • GetQuestText(questId) returns title, details, objectives, offerReward, requestItems, endText and objectiveTexts in the loaded locale, with WoW's $N / $C / $R / $B / $G markup left for the consumer to expand. HasText, GetTextLocale alongside. Rows are packed like the rewards; newlines are written as \n inside the Lua literal so every row stays on one source line. Three Vanilla rows (the Ahn'Qiraj war effort) carry a $<id>w world-state counter only the live server can fill (Questbook's finding); it is kept and named in the header, and a consumer renders it as a quantity word.
  • One file per locale, self-selecting. Data/<Version>/<locale>/Text.lua loads only when lib:WantsLocale(locale) says so: the client's own locale, or enUS when the client's locale ships no file (enGB counts as enUS). Data/<Version>/_core/Locales.lua declares the shipped set through lib:SetShippedLocales and loads before every text file, because the fallback reads it. Adding a language is one generated file, a regenerated manifest and one TOC line; toc_spec.lua pins the order and the gate.
  • English ships now: Vanilla 4,245 quests (3.29 MB), TBC 6,599 (4.96 MB). Every quest has at least a title. tools/build-text.py generates it; the dump's locales_quest table has 0 rows, so --locale deDE and friends exit with a message until a translation source exists. Known cost, accepted with the directive: a non-English client reads English text until then.
  • tools/questdb_common.py now holds the dump lookup, fingerprint, Lua escaping and header shared by both generators; build-rewards.py imports it.

Rewards API (MINOR 2 to 4)

  • GetQuestXP returns nil for a player at or above the level cap (MINOR 4), and GetLevelCap() reports it (60 Vanilla, 70 TBC, from the loaded data's expansion). Questbook's finding on 2026-09-18: the scaler only looked at the level DIFFERENCE, so a level-60 player on a level-58 quest was told the full XP when the server pays moneyMaxLevel instead. The column's original name in mangos was RewXpOrMoney (sql/archive/0.10/4875_mangos_quest_template.sql), and the quest-details packet comments it "used in XP calculation at client" (QuestHandler.cpp), which is the code-level basis for the XP-or-money reading; Player::RewardQuest itself was still not read. GetRewMoneyMaxLevel() returns 0 under QUEST_FLAGS_NO_MONEY_FROM_XP (0x100), a flag the enum comment marks "Not used currently" but the getter honours. Counted in the dumps: 139 Vanilla and 27 TBC quests carry it with a non-zero RewMoneyMaxLevel, so the generator ships it as a tenth field and GetRewards reports moneyMaxLevel = nil for them while xp, which the emulator reads from the raw column, is unchanged.

  • GetRewards(questId) returns the quest's rewards as a fresh table: items, choices, money, moneyMaxLevel, xp, questLevel, rep, spell, spellCast, srcItem; nil for a quest with no rewards or an unknown id. HasRewards, IsReady, GetRewardCount and GetSource alongside it. Storage is one packed \031-separated string per quest, decoded on read; the reader accepts a bare id as count 1 so a generator that ever drops :1 on single items cannot silently lose most rewards (Questbook's finding).

  • Money and reputation are SIGNED and stay signed. 69 Vanilla quests charge money (RewOrReqMoney down to -3,000,000 copper) and 69 cost reputation with an opposing faction. abs() anywhere on the path would render "you receive 300 gold" for a quest that costs it.

  • money and moneyMaxLevel are two things, not one field spelled twice. RewOrReqMoney (845 of 4,245 Vanilla quests) is paid or charged at turn-in; RewMoneyMaxLevel (3,493) is the money a character at the cap receives in place of XP, per the CMaNGOS wiki, and the base of the XP formula. money = nil therefore means the quest pays nothing at turn-in below the cap, which is most quests. The emulator's Player::RewardQuest was not read; the two columns' meaning rests on the wiki and on Quest::XPValue.

  • GetQuestXP(questId, playerLevel) and xp on the result. The dump has no RewXPId column (verified against all 131; that is Wrath's QuestXP.dbc mechanism). The emulator derives XP from RewMoneyMaxLevel and QuestLevel (Quest::XPValue, src/game/Quests/QuestDef.cpp, read from both mangos-classic and mangos-tbc on 2026-09-18 and identical): full XP is RewMoneyMaxLevel / 0.6 up to quest level 60 (1.2, 2.4, 3.6, 4.8, 6.0 for 61 to 65+), then scaled to 0.8 / 0.6 / 0.4 / 0.2 / 0.1 for a player 6 / 7 / 8 / 9 / 10+ levels over the quest, rounded up. Computed in integers because 102 / 0.6 is 170.00000000000003 in a double and ceil would pay 171 where the server's float32 pays 170. This is the emulator's arithmetic, not a measurement of the live client; consumers label it "up to N XP".

  • questLevel is shipped because it is half of that formula, not as quest metadata. Questie remains the source for levels, zones and chains; a quest whose only field would be its level is not emitted at all.

Data

  • tools/build-rewards.py <Version> reads quest_template from the CMaNGOS SQLite world database that LibItemDB already caches (ItemDB/tools/cmangos_cache/, fetched by its build-proc-rates.py) and writes Data/<Version>/_core/Rewards.lua. It never downloads; one copy of a 117 MB dump on the box is enough. It introspects the table and refuses to run against a dump missing any column it reads, rather than guessing.
  • Vanilla: 4,140 of 4,245 quests (248 KB). TBC: 6,270 of 6,599 (371 KB). Both are locale-independent: every value is an id or an integer. Quest TEXT is per-locale and ships separately (see above).
  • Every generated header records the dump's content fingerprint (classicmangos.sqlite sha256:0c908c740286, tbcmangos.sqlite sha256:f76a4e173ce5) because cmangos/<x>-db publishes only a moving latest asset with no version to record.
  • Licence: the database repos are GPL-3.0 (the server core is GPL-2.0 and is not read). Facts are extracted and re-emitted in this library's own schema, no CMaNGOS file is redistributed, and the source is credited in each header, the README and the CurseForge description, on the same footing as LibItemDB.

Tests

  • Offline suite on the WoWAPITesting harness (Tests/wowapi submodule at bb80c1b, .busted shim, Tests already in .pkgmeta ignore). Tests/libquestdb_spec.lua pins the codec and every read against a synthetic fixture, including the signed values, trimmed rows, bare ids, the XP table for quest levels 60 to 70, integer rounding, the level cap, the locale fallback (own locale, English stand-in, enGB, no GetLocale), the text codec, and library upgrade (a newer copy keeps an older copy's rows). Tests/data_spec.lua loads the SHIPPED tables for both flavours and checks the header counts, the fingerprint, that every reward and text row decodes, that signed money, signed rep and the $ markup are present in the data, and three known Vanilla rows. Tests/toc_spec.lua checks both manifests: files exist, load order, each flavour loads its own expansion's data, the locale manifest precedes every text file and lists it, each text file gates on its own locale, no dependency, identical directives. At release: 71 specs, 100% line coverage on LibQuestDB-1.0.lua, luacheck / ruff / markdownlint clean.

Project

  • Repository created. Folder under the Classic Era AddOns tree, git directory kept outside the WoW install at C:\Users\ianpl\wow-addon-gitdirs\_classic_era_\QuestDB, single-root VS Code workspace. Packaging, release workflow, lint and ignore files are modelled on TOGBankClassic, with the library-shaped .pkgmeta and .luacheckrc from ItemDB.
  • Era and TBC TOCs, and the dev sync watcher. QuestDB.toc (11509) and QuestDB_TBC.toc (20506), matching the flavours ItemDB and ProfessionDB ship for. wow-version-replication.ps1 mirrors the source into the _anniversary_ install, driven by .pkgmeta's ignore list, and .vscode/tasks.json starts it on folder open.
  • CurseForge project linked. .pkgmeta carries curseforge-project-id: 1700352 and both TOCs ## X-Curse-Project-ID: 1700352. The project's SLUG is questdb, recorded as a comment in .pkgmeta because the packager has no key for it; consumers list that slug under required-dependencies.
  • Workspace Lua analysis: the annotations path is now absolute. Every pairs, tonumber and select in the library was flagged as an undefined global. First diagnosis (wrong): the .code-workspace disables LuaLS's stock builtins, so re-enable them. The ketho.wow-api extension rewrote that block back within minutes; it manages it, and disables the stock builtins because its own Annotations\Core\Lua\basic.lua defines WoW's versions. The library path that should have loaded those annotations was written with a ~ prefix in the .code-workspace only. Making it absolute there did not clear the Problems panel; adding the absolute path to .luarc.json's workspace.library did (read back through the editor's Problems panel). LuaLS treats .luarc.json as authoritative, so the annotations path now lives there, and the workspace file's copy is kept in step with a comment saying the extension owns the builtin block.

This mod has no additional files