Compare commits

...

10 Commits

Author SHA1 Message Date
Narcasung 9da949dd66 feat(tracker): add "By Zone + % Complete" sort options
Groups quests by zone with headers, then orders each zone's quests by
completion percent (completes on top / bottom in the reversed variant).
2026-07-19 17:22:53 +02:00
Narcasung 190d2b0d5e docs: soften CoA-specific wording to reflect general 3.3.5 client support 2026-07-18 00:05:56 +02:00
Narcasung 460718e313 fix(tracker): show quest item shortcut buttons for custom quests
Item buttons only checked QuestieDB static data (sourceItemId /
requiredSourceItems), which custom server quests never have. Fall back
to GetQuestLogSpecialItemInfo to resolve the item live from the quest
log when the DB has no data for the quest.
2026-07-17 16:13:08 +02:00
Narcasung b4e0894c3f fix(map): remove redundant scale-mismatched icon cull on world map
The manual overflow cull in HandleWorldMapPin compared icon:GetCenter()
against WorldMapScrollFrame's bounds without normalizing effective scale.
Magnify's zoom drags the icon's effective scale via WorldMapDetailFrame,
while the scroll frame's stays constant, so at high zoom ratios the
comparison failed for every icon and hid the whole map. Native
WorldMapScrollFrame:SetScrollChild() clipping (now reachable on all 4
edges since SetDrawOrder correctly detects bundled Magnify) already
handles this without the buggy manual math.
2026-07-15 17:34:38 +02:00
Narcasung 8089b5f08c fix(tracker): show (Complete) and correct level for DB-less custom quests
Quests with no static DB entry (e.g. Ascension custom quest 175206) made
GetColoredQuestName bail out early on a nil name lookup, before it ever
reached the (Complete)/(Failed) suffix logic. The tracker's existing
fallback for that case never added the suffix either, and it computed
level via QuestieLib.GetTbcLevel (static-DB-only), which silently defaults
to level 1 and then gets scaled from that wrong base by Ascension's level
scaling. Since these quests also auto-collapse once complete (hiding the
tracker's other "Quest Complete!" indicator line), the title was the only
place that could show completion status, and it was silently swallowing it.

- QuestieTracker.lua: the "no DB name" fallback branch now appends
  (Complete) under the same collapseCompletedQuests/isMinimizable rule as
  the normal path, and uses the live quest object's own level instead of
  the static-only lookup.
- QuestieQuest.lua (PopulateQuestLogInfo): re-syncs quest.level from a live
  GetQuestLogTitle scan every update, since QuestieDB.GetQuest caches quest
  objects permanently and QuestLogCache never tracked level at all.
- TrackerUtils.lua (GetCompletionText): guards the Description[1] fallback
  against quests that have no top-level Description array, which was
  throwing and aborting that quest's tracker render entirely.
2026-07-15 17:20:42 +02:00
Narcasung 75bebafaa9 fix(quest-tag): prefer live quest log tag over static QuestTag.lua data
Ascension sometimes reuses a Blizzard questId for custom content whose
group/tag status differs from the original (e.g. 253 "Bride of the
Embalmer" is a real group quest here but zeroed by QuestTag.lua for
TBC+/WotLK clients). Once a quest is accepted, QuestieDB.GetQuestTagInfo
now checks the live questLog entry's questTag before falling back to
questTagCorrections and the static QuestTag.lua table. Compat.lua gains
QuestTagNameToId, a reverse lookup built from questTagToName.
2026-07-15 16:20:51 +02:00
Narcasung 6fad92244c docs: tighten README wording and quest-tag fix summary 2026-07-15 15:57:35 +02:00
Narcasung 62956abb8e fix(map): keep world-map icons inside Magnify's native scroll clip
SetDrawOrder only recognized a standalone "Magnify-WotLK" addon, but this
setup bundles Magnify inside LootCollector (no separate .toc), so the check
always failed and every icon got reparented onto WorldMapFrame -- outside
WorldMapScrollFrame's clipped subtree -- and strata-forced to WorldMapFrame
instead of WorldMapButton, regardless of HBD's earlier correct parenting.
Detect the bundled Magnify via the _G.LootCollectorMagnify global and match
WorldMapButton's strata instead, mirroring LootCollector's own pins. Fixes
icon culling on all 4 edges during both zoom and pan, in normal and
objective-panel map modes.
2026-07-15 13:42:34 +02:00
Narcasung 13cddced16 Tidy README title and force line breaks between credit links
Markdown collapses single newlines into the same line; add explicit
<br> tags so each credit renders on its own line.
2026-07-15 01:16:20 +02:00
Narcasung 93c5e56c1d Replace upstream README with short fork summary and credits
Personal private fork, so the upstream marketing README (badges,
Patreon/PayPal, docs links) isn't relevant here. Keep just the list
of local fixes and credit the fork chain: aron-w/Questie-X,
Xurkon/Questie-X, and the original Questie.
2026-07-15 01:14:20 +02:00
11 changed files with 171 additions and 432 deletions
+8
View File
@@ -1090,6 +1090,14 @@ local questTagToName = {
[85] = "Heroic",
}
-- Reverse lookup (tag name -> tag id), exposed so callers can convert the live
-- questTag string returned by the game's own GetQuestLogTitle back into the
-- numeric tag id used throughout Questie.
QuestieCompat.QuestTagNameToId = {}
for id, name in pairs(questTagToName) do
QuestieCompat.QuestTagNameToId[name] = id
end
-- Retrieves tag information about the quest.
-- https://wowpedia.fandom.com/wiki/API_GetQuestTagInfo
function QuestieCompat.GetQuestTagInfo(questId)
+10 -20
View File
@@ -879,26 +879,16 @@ local function HandleWorldMapPin(icon, data)
icon:SetPoint("CENTER", WorldMapButton, "TOPLEFT", (x * worldmapWidth) / iconEffScale, -(y * worldmapHeight) / iconEffScale)
icon:Show()
-- Questie fix: when zoomed in (e.g. via Magnify/LootCollector), WorldMapButton is
-- drawn far larger than the visible window and only WorldMapScrollFrame clips it.
-- Pins positioned past the visible edge still render on top of the surrounding UI
-- instead of being clipped, so hide them manually once they're outside the
-- scroll frame's actual on-screen bounds. GetLeft/Right/Top/Bottom/Center are all
-- in a shared scale-independent coordinate space, so no extra scale math needed.
-- Only do this when the map is actually drawn larger than its viewport (the zoom
-- case) -- at normal size (e.g. just the "show objective" panel active, no zoom)
-- the whole map already fits on-screen, so this check should never fire there;
-- skip it entirely rather than risk false positives hiding valid icons.
if WorldMapScrollFrame and WorldMapScrollFrame:IsVisible() then
local scrollWidth, scrollHeight = WorldMapScrollFrame:GetWidth(), WorldMapScrollFrame:GetHeight()
if scrollWidth and scrollHeight and (worldmapWidth > scrollWidth + 1 or worldmapHeight > scrollHeight + 1) then
local iconCenterX, iconCenterY = icon:GetCenter()
local vLeft, vRight, vTop, vBottom = WorldMapScrollFrame:GetLeft(), WorldMapScrollFrame:GetRight(), WorldMapScrollFrame:GetTop(), WorldMapScrollFrame:GetBottom()
if iconCenterX and vLeft and (iconCenterX < vLeft or iconCenterX > vRight or iconCenterY > vTop or iconCenterY < vBottom) then
icon:Hide()
end
end
end
-- Manual GetLeft/Right/Top/Bottom-vs-GetCenter overflow culling used to live here
-- (Questie fix for Magnify/LootCollector zoom). Removed: it compared Get*() values
-- across frames with different effective scales (icon's scale is dragged around by
-- Magnify's WorldMapDetailFrame:SetScale() zoom, WorldMapScrollFrame's isn't), which
-- are only directly comparable at equal effective scale -- at high zoom ratios the
-- mismatch made the check fail for every icon, hiding the entire map. Icons are
-- already parented inside WorldMapButton, itself inside Magnify's real
-- WorldMapScrollFrame:SetScrollChild() subtree once SetDrawOrder correctly detects
-- Magnify (see Modules/Map/QuestieMapUtils.lua), so the engine's native scroll-child
-- clip now handles this on all 4 edges, during pan, with no Lua-side math needed.
else
icon:Hide()
end
+19
View File
@@ -999,6 +999,25 @@ end
---@param questId number
---@return number|nil questType, string|nil questTag
function QuestieDB.GetQuestTagInfo(questId)
-- Prefer the server's own live tag over static data whenever the quest is currently in
-- the quest log. Custom servers can reuse a Blizzard quest ID for different content (e.g.
-- Ascension's "Bride of the Embalmer" reusing ID 253, which Questie's static QuestTag.lua
-- correctly marks as non-group for retail's real quest 253), which the static tables below
-- have no way to know about. The live tag is authoritative once available, whether that
-- means it HAS a tag the static data misses, or has NO tag the static data wrongly assumes.
local cachedQuest = QuestLogCache.questLog_DO_NOT_MODIFY[questId]
if cachedQuest then
local liveTag = cachedQuest.questTag
if liveTag then
local liveTagId = QuestieCompat.QuestTagNameToId[liveTag]
if liveTagId then
return liveTagId, liveTag
end
else
return nil, nil
end
end
if questTagCorrections[questId] then
return questTagCorrections[questId][1], questTagCorrections[questId][2]
end
@@ -864,6 +864,12 @@ local trackerOptionsLocales = {
["esES"] = "Por zona",
["frFR"] = "Par zone",
},
["By Zone + %% Complete"] = {
["enUS"] = true,
},
["By Zone + %% Complete (Reversed)"] = {
["enUS"] = true,
},
["By Zone Prox"] = {
["ptBR"] = "Por proximidade de zona",
["ruRU"] = "По дальности зоны",
+10 -3
View File
@@ -47,13 +47,20 @@ function QuestieMap.utils:SetDrawOrder(frame)
frame:SetFrameStrata(frameStrata)
frame:SetFrameLevel(frameLevel)
else
-- If Magnify-WotLK is loaded, parent to WorldMapButton instead of WorldMapFrame
-- If Magnify is loaded (standalone Magnify-WotLK, or bundled inside LootCollector,
-- which has no separate .toc so IsAddOnLoaded("Magnify-WotLK") never sees it),
-- parent to WorldMapButton and match ITS strata instead of WorldMapFrame's, so the
-- icon stays inside Magnify's native WorldMapScrollFrame clip subtree (mirrors how
-- LootCollector's own map pins are parented/strata'd in Modules/Map.lua)
local magnifyLoaded = (IsAddOnLoaded and IsAddOnLoaded("Magnify-WotLK")) or (_G.LootCollectorMagnify ~= nil)
local parent = WorldMapFrame
if IsAddOnLoaded and IsAddOnLoaded("Magnify-WotLK") and _G.WorldMapButton then
local strataSource = WorldMapFrame
if magnifyLoaded and _G.WorldMapButton then
parent = WorldMapButton
strataSource = WorldMapButton
end
local frameLevel = WorldMapFrame:GetFrameLevel() + 7
local frameStrata = WorldMapFrame:GetFrameStrata()
local frameStrata = strataSource:GetFrameStrata()
frame:SetParent(parent)
frame:SetFrameStrata(frameStrata)
frame:SetFrameLevel(frameLevel)
@@ -402,6 +402,8 @@ function QuestieOptions.tabs.tracker:Initialize()
['byProximity'] = l10n('By Proximity'),
['byProximityReversed'] = l10n('By Proximity (Reversed)'),
['byZone'] = l10n('By Zone'),
['byZoneComplete'] = l10n('By Zone + %% Complete'),
['byZoneCompleteReversed'] = l10n('By Zone + %% Complete (Reversed)'),
['byZonePlayerProximity'] = l10n('By Zone Prox'),
['byZonePlayerProximityReversed'] = l10n('By Zone Prox (Reversed)'),
}
+19
View File
@@ -1,5 +1,6 @@
--- COMPATIBILITY ---
local IsQuestFlaggedCompleted = QuestieCompat.IsQuestFlaggedCompleted or C_QuestLog.IsQuestFlaggedCompleted
local GetQuestLogTitle = QuestieCompat.GetQuestLogTitle
---@class QuestieQuest
local QuestieQuest = QuestieLoader:CreateModule("QuestieQuest")
@@ -2305,6 +2306,24 @@ function QuestieQuest:PopulateQuestLogInfo(quest)
end
end
-- Sync level with the live quest log. QuestieDB.GetQuest permanently caches quest
-- objects (Database/QuestieDB.lua:1752 `if _QuestieDB.questCache[questId] then return
-- ... end`), so quest.level is otherwise frozen at whatever value was computed the
-- first time this quest was cached. QuestLogCache.questLog_DO_NOT_MODIFY doesn't carry
-- a level field at all, so read it straight from GetQuestLogTitle. On servers with
-- dynamic quest level scaling (e.g. Ascension), the effective level can change after
-- caching (player level-up, scaling option toggled), so re-sync it here every time.
if GetQuestLogTitle then
local questIndex = GetQuestLogIndexByID and GetQuestLogIndexByID(quest.Id)
if questIndex and questIndex > 0 then
local _, liveLevel = GetQuestLogTitle(questIndex)
if liveLevel and liveLevel > 0 and liveLevel ~= quest.level then
quest.level = liveLevel
quest.questLevel = liveLevel
end
end
end
-- Live fallback quests (no static DB entry) manage their own Objectives.
-- Their per-objective Update() functions read directly from QuestLogCache.
if quest._isLogFallback then
+41 -6
View File
@@ -46,6 +46,7 @@ local QuestieDebugOffer = QuestieLoader:ImportModule("QuestieDebugOffer")
local C_Timer = QuestieCompat.C_Timer
local GetQuestLogTitle = QuestieCompat.GetQuestLogTitle
local GetQuestLogIndexByID = QuestieCompat.GetQuestLogIndexByID
local GetQuestLogSpecialItemInfo = QuestieCompat.GetQuestLogSpecialItemInfo
local GetItemInfo = QuestieCompat.GetItemInfo
local function GetNumLines(label)
if label.GetNumLines then
@@ -1030,6 +1031,13 @@ function QuestieTracker:Update()
questName = questName .. " (" .. quest.Id .. ")"
end
coloredQuestName = "|cFFFFFF00" .. questName .. "|r"
-- Fallback quests (e.g. custom server quests not in QuestieDB) skip
-- GetColoredQuestName entirely since it needs a DB name lookup, so they
-- never got the (Complete) suffix DB quests get under the same setting.
if Questie.db.profile.collapseCompletedQuests and isMinimizable then
coloredQuestName = coloredQuestName .. " " ..
Questie:Colorize("(" .. l10n("Complete") .. ")", "green")
end
elseif timedQuest then
coloredQuestName = QuestieLib:GetColoredQuestName(quest.Id,
Questie.db.profile.trackerShowQuestLevel, false, false)
@@ -1044,18 +1052,25 @@ function QuestieTracker:Update()
-- server quest not yet in the static DB), so GetColoredQuestName
-- returned nil -- fall back instead of SetText(nil) blanking the
-- title line while its objectives still render normally below it.
-- Level/tag lookups don't require a DB "name" entry, so reuse the
-- same formatting helper as the normal path to keep the "[level]"
-- prefix consistent with quests that do have a DB name.
-- Use the live quest object's own level (kept in sync by
-- QuestieQuest:PopulateQuestLogInfo) instead of QuestieLib.GetTbcLevel,
-- which queries the static DB only: for a quest with no DB entry it
-- silently defaults to level 1, and Ascension's scaling then scales
-- that wrong base instead of the quest's real level.
local fallbackName = quest.name or tostring(quest.Id)
if Questie.db.profile.trackerShowQuestLevel then
local level = QuestieLib.GetTbcLevel(quest.Id)
fallbackName = QuestieLib:GetQuestString(quest.Id, fallbackName, level, false)
if Questie.db.profile.trackerShowQuestLevel and quest.level and quest.level > 0 then
fallbackName = QuestieLib:GetQuestString(quest.Id, fallbackName, quest.level, false)
end
if Questie.db.profile.enableTooltipsQuestID then
fallbackName = fallbackName .. " (" .. quest.Id .. ")"
end
coloredQuestName = "|cFFFFFF00" .. fallbackName .. "|r"
-- Same suffix rule as the normal (DB) branch -- GetColoredQuestName
-- never got a chance to run since it bailed out on the missing name.
if Questie.db.profile.collapseCompletedQuests and isMinimizable then
coloredQuestName = coloredQuestName .. " " ..
Questie:Colorize("(" .. l10n("Complete") .. ")", "green")
end
end
line.label:SetText(coloredQuestName)
@@ -1080,6 +1095,26 @@ function QuestieTracker:Update()
local usableQIB = false
local sourceItemId = QuestieDB.QueryQuestSingle(quest.Id, "sourceItemId")
local isLiveSourceItem = false
if not sourceItemId or sourceItemId == 0 then
-- QuestieDB has no data for this quest (e.g. a custom server quest).
-- Ask the client directly which item, if any, it considers this quest's
-- usable "special item" instead of relying on static DB data that
-- simply doesn't exist for quests Questie doesn't know about.
local questLogIndex = GetQuestLogIndexByID and GetQuestLogIndexByID(quest.Id)
if questLogIndex and questLogIndex > 0 and GetQuestLogSpecialItemInfo then
local link = GetQuestLogSpecialItemInfo(questLogIndex)
local liveItemId = link and tonumber(link:match("item:(%d+)"))
if liveItemId then
sourceItemId = liveItemId
isLiveSourceItem = true
quest.sourceItemId = liveItemId
end
end
end
quest._liveSourceItem = isLiveSourceItem
local sourceItem = sourceItemId and TrackerUtils:IsQuestItemUsable(sourceItemId)
local requiredItems = quest.requiredSourceItems
local requiredItem = requiredItems and TrackerUtils:IsQuestItemUsable(requiredItems[1])
+5 -1
View File
@@ -504,7 +504,11 @@ function TrackerLinePool.Initialize(questFrame)
-- Check primary source item
if quest.sourceItemId and quest.sourceItemId ~= 0 and buttonType == "primary" then
if QuestieDB.QueryItemSingle(quest.sourceItemId, "class") == 12 and GetItemCount(quest.sourceItemId, false, false) > 0 then
-- For quests QuestieDB has no data on, sourceItemId was resolved live via
-- GetQuestLogSpecialItemInfo -- the client already vetted it as the quest's
-- special item, so the static-DB item class isn't known/needed here.
local isQuestItem = quest._liveSourceItem or QuestieDB.QueryItemSingle(quest.sourceItemId, "class") == 12
if isQuestItem and GetItemCount(quest.sourceItemId, false, false) > 0 then
foundItemId = quest.sourceItemId
end
end
+36 -2
View File
@@ -379,8 +379,14 @@ function TrackerUtils:GetCompletionText(quest)
if completionText then
return completionText
else
elseif quest.Description and quest.Description[1] then
return quest.Description[1]:gsub("%.", "")
else
-- Fallback/custom quests (e.g. server quests not in QuestieDB) have no
-- top-level Description array. Without this, indexing quest.Description[1]
-- throws and aborts the whole tracker render for that quest via the
-- caller's pcall, freezing its line on the last successful render.
return nil
end
end
@@ -700,7 +706,7 @@ local function _GetZoneName(zoneOrSort, questId, zoneNameOverride)
if not zoneOrSort then return "Unknown Zone" end
local zoneName
local sortObj = Questie.db.profile.trackerSortObjectives
if sortObj == "byZone" or sortObj == "byZonePlayerProximity" or sortObj == "byZonePlayerProximityReversed" then
if sortObj == "byZone" or sortObj == "byZoneComplete" or sortObj == "byZoneCompleteReversed" or sortObj == "byZonePlayerProximity" or sortObj == "byZonePlayerProximityReversed" then
if (zoneOrSort) > 0 then
zoneName = TrackerUtils:GetZoneNameByID(zoneOrSort)
if not zoneName or zoneName == "Unknown Zone" then
@@ -958,6 +964,34 @@ function TrackerUtils:GetSortedQuestIds()
end
end
end)
elseif sortObj == "byZoneComplete" or sortObj == "byZoneCompleteReversed" then
table.sort(sortedQuestIds, function(a, b)
local qAZone = questDetails[a].zoneName
local qBZone = questDetails[b].zoneName
-- Sort by Zone first, then by % Complete within each zone
-- (byZoneComplete: completes at top, 0% at bottom; reversed: opposite)
if qAZone == qBZone then
local vA, vB = questDetails[a].questCompletePercent, questDetails[b].questCompletePercent
if vA == vB then
local qA = questDetails[a].quest
local qB = questDetails[b].quest
return qA and qB and qA.level < qB.level
end
if sortObj == "byZoneComplete" then
return vA > vB
else
return vA < vB
end
else
if qAZone ~= nil and qBZone ~= nil then
return qAZone < qBZone
else
return qAZone and qBZone
end
end
end)
elseif sortObj == "byZonePlayerProximity" or sortObj == "byZonePlayerProximityReversed" then
local toSort = {}
local continent = _GetContinent(C_Map.GetBestMapForUnit("player"))
+15 -400
View File
@@ -1,408 +1,23 @@
> **Notice:** This is a maintenance release, not the completed performance refactor. The performance refactor is still in progress on the `phase2-lua50-sweep` branch. This release (v1.6.4) contains bug fixes and improvements over v1.6.3 and should be more stable, but the full performance work will land in a future release. If you are currently on v1.6.3, this release is a recommended upgrade.
# Questie-X
> Current refactor focus: reducing QuestieLearner pin redraws during heavy kill activity, adding live Advanced performance controls for QuestieLearner and QuestieComms, adding Arrow throttles for low-end systems, and suppressing non-fatal error spam outside Questie debug modes. These changes are being staged on feature branches first and still need in-game validation before they become the next stable release.
Fork of [aron-w/Questie-X](https://github.com/aron-w/Questie-X) with bug fixes and personal changes.
Made for and tested on CoA, but it should also work on other 3.3.5 clients.
<div align="center">
## Fixes
<img src="docs/QuestieXlogo.png" alt="Questie-X Logo" width="320" />
- fixed world map icons offset due to Ascension's objective frame and zoom addons like Magnify.
- fixed tracker objectives not updating correctly.
- fixed tracker handle not moving the frame.
- fixed custom quests having no name in the tracker.
- fixed custom quests not showing (complete) in the tracker.
- fixed custom quest items not showing.
![Version](https://img.shields.io/badge/Questie--X-v1.6.4-blue.svg?style=for-the-badge)
[![Downloads](https://img.shields.io/github/downloads/Xurkon/Questie-X/total?style=for-the-badge&color=e67e22)](https://github.com/Xurkon/Questie-X/releases)
[![Documentation](https://img.shields.io/badge/Documentation-View%20Docs-58a6ff?style=for-the-badge)](https://xurkon.github.io/Questie-X/)
[![Patreon](https://img.shields.io/badge/Patreon-F96854?style=for-the-badge&logo=patreon&logoColor=white)](https://www.patreon.com/Xurkon)
[![PayPal](https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white)](https://www.paypal.me/Xurkon)
![License](https://img.shields.io/github/license/Xurkon/Questie-X?style=for-the-badge&color=2980b9)
## Changes
<br/>
**A universal WoW quest-helper with a plugin architecture for any private server.**
[Download Latest](https://github.com/Xurkon/Questie-X/releases/latest) &nbsp;&bull;&nbsp; [View Source](https://github.com/Xurkon/Questie-X) &nbsp;&bull;&nbsp; [Documentation](https://xurkon.github.io/Questie-X/)
</div>
---
## About
Questie-X is a fork of the original [Questie](https://github.com/Questie/Questie) addon, rebuilt to run reliably on any private server regardless of realm type or custom content. It fixes longstanding Lua errors, corrects API incompatibilities introduced by custom server emulators, and introduces a plugin system so server-specific quest databases can be distributed and maintained separately from the core addon.
---
## Current Performance Refactor Status
The active performance work is split across review branches so each phase can be reverted independently if needed.
| Area | Current Status |
|------|----------------|
| QuestieLearner | Kill-triggered map-pin refreshes are now debounced with a maximum wait cap, bystander `UNIT_DIED` events no longer force learner pin redraws, and `PARTY_KILL` events are protected from being suppressed by earlier `UNIT_DIED` debounce entries. |
| QuestieComms | Advanced options include a full comms disable switch plus throttles for processing, quest-state broadcasts, and bulk sync behavior. |
| Arrow | Advanced options include update throttles that apply live and are intended to reduce repeated target/coordinate work on lower-end PCs. |
| Error noise | Missing quest and non-fatal database messages are being routed to Questie debug-critical/developer output instead of normal chat spam. |
| Phase 3 hot paths | A measured branch contains additional localization, tooltip/map, quest eligibility, and cache-allocation improvements that still need integration with the learner/comms branch. |
Before the next stable release, the combined branch needs in-game testing in heavy kill zones with the minimap open, nearby players killing mobs, comms disabled/low/normal/fast, and Arrow throttles adjusted live from the options menu.
---
## Installation
> **⚠️ Two addons are always required — even on supported servers**
>
> Questie-X **cannot run alone**. It has no quest, NPC, item, or object data bundled inside it — that data lives entirely in a separate database plugin. You must install **both** the core engine **and** a server-specific database plugin for Questie-X to display anything.
>
> **Which database plugin you need depends on your server** — see the table in Step 2 below.
> **⚠️ Upgrading from the old architecture (Questie-335 / PE-Questie / any pre-v1.1.4 Questie fork)?**
> The old addon loaded its database from files inside the core folder. Questie-X loads data from a separate plugin addon. These two systems are **not compatible** — you must fully remove the old installation before installing Questie-X, or you will get conflicts, duplicate modules, and data errors.
>
> **Before you install:**
> 1. Open `Interface/AddOns/` and **delete** any folder named `Questie`, `Questie-335`, `PE-Questie`, `Questie-X` (if updating), or any other Questie variant.
> 2. Delete any associated saved-variable files: `WTF/Account/<name>/SavedVariables/Questie*.lua`.
> 3. Then follow the fresh install steps below.
### Step 1 — Install Questie-X Core
1. [Download](https://github.com/Xurkon/Questie-X/releases/latest) the latest `Questie-X` release `.zip`.
2. Extract the archive — you will get a folder named `Questie-X`.
3. Move that folder into your `Interface/AddOns/` directory:
```
World of Warcraft/
└── Interface/
└── AddOns/
└── Questie-X/ ← place it here
```
### Step 2 — Install Your Server's Database Plugin
> **⚠️ This step is not optional. Questie-X will not work without a database plugin loaded.**
Questie-X **requires** a database plugin to function. The plugin provides quest, NPC, object, and item data specific to your private server. Without it, Questie-X has no data to display.
**Two categories of servers:**
| Your Server Type | What to Install |
|-----------------|----------------|
| **Supported server (see table below)** | Download that server's plugin |
| **Custom/unlisted server** | Use QuestieLearner to crowdsource data; see Note at bottom of this section |
**Download and install the plugin for your server from the table below:**
> **💡 All server plugins also require Questie-X-WotLKDB as the base layer.** Your server-specific plugin (AscensionDB, ClassicDB, etc.) provides the quest data overrides and custom content — but Questie-X-WotLKDB is still needed underneath it for the core WotLK quest data that your server's plugin extends. Install both.
| Your Server | Plugin to Download | Repository |
|-------------|------------------|------------|
| WotLK 3.3.5 (most private servers) | **Questie-X-WotLKDB** | [Xurkon/Questie-X-WotLKDB](https://github.com/Xurkon/Questie-X-WotLKDB) |
| Classic Era / Vanilla 1.14.x | **Questie-X-ClassicDB** | [Xurkon/Questie-X-ClassicDB](https://github.com/Xurkon/Questie-X-ClassicDB) |
| TBC 2.5.x | **Questie-X-TBCDB** | [Xurkon/Questie-X-TBCDB](https://github.com/Xurkon/Questie-X-TBCDB) |
| Project Ascension | **Questie-X-AscensionDB** | [Xurkon/Questie-X-AscensionDB](https://github.com/Xurkon/Questie-X-AscensionDB) |
| Project Ebonhold | **Questie-X-EbonholdDB** | [Xurkon/Questie-X-EbonholdDB](https://github.com/Xurkon/Questie-X-EbonholdDB) |
| Other / Unknown | Use WotLKDB as a starting baseline, then use QuestieLearner to fill gaps | — |
#### How to Install the Plugin
1. Click the repository link for your server in the table above.
2. Go to that repo's **Releases** page.
3. Download the latest release `.zip`.
4. Extract the archive — you will get a folder named `Questie-X-<ServerName>DB` (e.g., `Questie-X-WotLKDB`).
5. Move that folder into your `Interface/AddOns/` directory, **alongside** the `Questie-X` folder you installed in Step 1.
Your final folder structure should look like one of these:
**For WotLK / Classic / TBC servers:**
```
World of Warcraft/
└── Interface/
└── AddOns/
├── Questie-X/ ← Step 1 — core addon (REQUIRED)
└── Questie-X-WotLKDB/ ← Step 2 — WotLKDB baseline (REQUIRED)
```
**For Ascension servers:**
```
World of Warcraft/
└── Interface/
└── AddOns/
├── Questie-X/ ← Step 1 — core addon (REQUIRED)
├── Questie-X-WotLKDB/ ← WotLKDB baseline (REQUIRED)
└── Questie-X-AscensionDB/ ← AscensionDB overrides (REQUIRED)
```
**For Ebonhold servers:**
```
World of Warcraft/
└── Interface/
└── AddOns/
├── Questie-X/ ← Step 1 — core addon (REQUIRED)
├── Questie-X-WotLKDB/ ← WotLKDB baseline (REQUIRED)
└── Questie-X-EbonholdDB/ ← EbonholdDB overrides (REQUIRED)
```
> **⚠️ Important:** All folders must be present and at the same level inside `Interface/AddOns/`. The server-specific DB (AscensionDB, EbonholdDB, etc.) goes **alongside** `Questie-X-WotLKDB` — it does not replace it. If any folder is missing, Questie-X will show an error message in chat telling you what's missing.
#### After Installation
1. Launch WoW and log in to your character.
2. If Questie-X loads successfully, you will see the minimap icon and no error messages.
3. If no plugin is detected, Questie-X will print a message in chat telling you exactly which plugin to install.
#### Note for Unsupported Servers
If your server is not listed above, install **Questie-X-WotLKDB** as a baseline (it covers general WoW quest data). Then use **QuestieLearner** to record quest and NPC data as you play — gaps will be filled in automatically as you and other players on your server interact with the world. See the [QuestieLearner](#questielearner) section for details.
---
## Writing a Plugin
Questie-X exposes a public `QuestiePluginAPI` that any addon can use to register custom server data without touching core files. This makes it possible to maintain server-specific databases as independent repositories that update on their own release schedule.
### How It Works
A plugin calls `QuestiePluginAPI:RegisterPlugin` during addon load and passes its database tables. Questie-X merges these into its runtime database before the first quest scan, so all features — map pins, tooltips, tracker, arrow — work transparently for custom content.
---
### Plugin Architecture
A minimal plugin needs a `.toc` file declaring `Questie-X` as a dependency and a loader script. The `.toc` must list `Questie-X` under `## Dependencies` so the WoW client loads it in the correct order.
**`MyServer-QuestieDB.toc`**
```
## Interface: 30300
## Title: MyServer QuestieDB
## Notes: Quest database plugin for MyServer
## Dependencies: Questie-X
## Version: 1.0.0
MyServerLoader.lua
MyServerQuestDB.lua
MyServerNpcDB.lua
MyServerObjectDB.lua
MyServerItemDB.lua
```
**`MyServerLoader.lua`**
```lua
local plugin = QuestiePluginAPI:RegisterPlugin("MyServer")
-- Inject each database type (tables follow the same schema as Questie's built-in DBs)
plugin:InjectDatabase("QUEST", MyServerQuestDB)
plugin:InjectDatabase("NPC", MyServerNpcDB)
plugin:InjectDatabase("OBJECT", MyServerObjectDB)
plugin:InjectDatabase("ITEM", MyServerItemDB)
-- Optional: inject custom zone/map routing tables
plugin:InjectZoneTables(MyServerZoneTables)
-- Optional: inject fallback UiMapData for non-standard boundary maps
plugin:InjectUiMapData(MyServerUiMapData)
-- Always call this last — clears Questie's internal zone/quest caches
-- so freshly injected data is picked up on the next scan
plugin:FinishLoading()
```
The database tables follow the same schema as Questie's built-in databases. See [`Modules/Libs/QuestiePluginAPI.lua`](Modules/Libs/QuestiePluginAPI.lua) for the full API reference.
If your server uses non-standard map data, enable **Options → Advanced → Use WotLK map data** after logging in.
---
## Arrow Styles
Questie-X includes a configurable arrow style picker with preview swatches, plus independent controls for arrow scale, transparency, font size, and objective attachment.
Bundled arrow assets are detected automatically as either sprite sheets or regular textures, and custom `.tga` files can be dropped into `Icons/Arrows` for use in the dropdown.
### Arrow Redesign
- `Arrow1` through `Arrow4` are the bundled image styles, while `arrowold` remains the only bundled sprite sheet.
- `Horde Arrow` and `Alliance Arrow` add faction-themed styles with their insignia built into the art.
- The dropdown now uses generated preview swatches from `Icons/Arrows`, so each bundled style shows a live thumbnail instead of a text-only entry.
- Image arrows rotate as a single texture and keep their native art, while sprite sheets only use sheet-cell logic when the style is explicitly `arrowold` or a custom sheet is marked as such.
- The arrow and objective text can be detached or reattached independently, locked separately, and reset with one-click actions.
- The attached gap, objective transparency, arrow transparency, font size, and distance unit are all configurable directly from the Arrow tab.
- Custom `.tga` files can still be dropped into `Icons/Arrows` and picked from the same dropdown without editing core files.
---
## Fixes & Compatibility
### Quest Log & Tracker
- Corrected `GetQuestLogTitle` return value indices to match the client API. The client returns `suggestedGroup` at index 4, shifting `isHeader` to index 5 and `questId` to index 9. Previously, modules were using indices 4/8, causing quest headers to be misidentified and `isDaily` to be assigned the wrong value.
- Removed premature `break` on `nil` title in quest log iteration loops. Quest log slots on private servers can be non-contiguous; the loop now uses a nil guard instead of aborting, preventing silently skipped quests.
- Quest objective counters now update correctly when items are deposited by automated systems that bypass the standard loot frame, using a multi-stage `BAG_UPDATE_DELAYED` strategy.
- Fixed `QuestEventHandler` crash on auto-completing quests caused by a missing `QuestiePlayer` module import.
- Fixed re-accepted repeatable quests not showing objective icons after the second acceptance.
### Map & Minimap
- Fixed `WorldMapFrame` compatibility for servers that render the world map in minimized mode.
- Fixed "ghost icon" bug where completed quest icons remained on the map after turn-in.
- Fixed `RequestMapUpdate` logic that caused completed quest icons to persist across zone transitions.
- Downgraded spurious `[CRITICAL] No AreaId found for UiMapId` log spam to debug level. On some servers, `C_Map.GetBestMapForUnit` returns a continent-level UiMapId for capital cities; the nil return was already handled gracefully but was incorrectly logged as critical.
- Fixed map pins for `killCredit`-type objectives not resolving spawn locations correctly.
- Added a configurable minimap icon range cutoff so players can choose how far away quest icons remain visible before fading out.
### Tooltips
- Fixed `attempt to concatenate nil` error when a quest starter or finisher has no name in the database.
- Added support for `killcredit` and `spell` objective types in `MapIconTooltip`.
- Tooltip now displays if an NPC drops an item that starts a quest.
- Fixed `attempt to concatenate local 'minLevel' (a nil value)` crash in `MapIconTooltip` when hovering over creatures whose `creatureLevels` entry was an empty table instead of the expected `{minLevel, maxLevel, rank}` tuple. Added early-return guard in `_GetLevelString`.
- Fixed tooltip crash when hovering over NPC/object keys (`m_<id>`, `o_<id>`) where `learnedNpc[10]` or `learnedObj[10]` is unexpectedly a string instead of a table. Added type guard before iterating the objective list array.
### Quest Arrow
- Refactored distance calculations and target prioritisation; arrow now correctly filters targets by zone and instance.
- Fixed arrow pointing to previously completed objective locations instead of the current finisher.
- Fixed nil error in `_CollectObjective` when processing incomplete quests.
- Fixed arrow direction for quests that require speaking to an NPC as a prerequisite step.
- **Sunstrider Isle (Ascension starting zone)**: Resolved arrow distance, direction, and map pin issues on Sunstrider Isle. `GetCurrentZoneId()` can return 3430 OR 3431 when the player is on uiMap 1241 — all 4 Sunstrider detection checks now accept both zoneIds plus uiMapId 1241. Fixed arrow rotation direction (`SetRotation` is CW-positive, not CCW). Fixed collection function distance mismatch where targets were converted through 1941 (Eversong) bounds while player coords were in 1241 bounds. NPC 15281 spawn zone corrected from 3430 to 1241 so coords land in Sunstrider's normalized space. Removed the 1241→1941 redirect in `_ResolveMapUiMapId()`; pins on 1241 now render natively via `areaIdToUiMapId[1241] = 1241`.
### Nameplates
- Questie nameplate hooks are skipped when a conflicting nameplate addon is detected, preventing taint and UI errors.
### Databases & Custom IDs
- Full support for large integer NPC, quest, object, and item IDs used by custom server emulators.
- Fixed `ZoneDB` crash when encountering maps with no AreaId mapping (e.g. continent-level maps on Kalimdor).
- Fixed `GetObject` returning nil for Item Finishers misidentified as GameObject Finishers on custom servers.
- Fixed `NPC 30514` (Thorim listen bunny) missing fallback spawn data for Sibling Rivalry turn-in.
---
## QuestieLearner
QuestieLearner is Questie-X's built-in crowdsourced database system. When you interact with the world — accepting quests, killing objectives, looting items, interacting with objects — Questie-X silently records any data it doesn't already have (spawn locations, NPC IDs, item IDs, coordinates). That data is saved locally per-realm and can be shared with other players or submitted back to improve the database for everyone.
This is the primary tool for filling in gaps on **custom or lightly-documented servers** where the base database plugin doesn't have complete coverage.
### What It Learns Automatically
QuestieLearner hooks into several in-game events and records data passively without any user action:
| Event | What is learned |
|-------|----------------|
| Quest accept / turn-in | Quest ID → quest giver/finisher NPC position and ID |
| Kill objective progress | NPC ID → spawn zone and coordinates at time of kill |
| Object interaction | Object ID → spawn zone and coordinates |
| Item loot | Item ID → which NPC/object it dropped from |
| Mouseover | NPC/object name resolved from server for any entity you hover |
Data for quest IDs that exist in the base database is merged into the existing entry. Data for unknown quest IDs (custom server content not yet in the plugin) is stored separately under a per-realm key so it doesn't contaminate the base data.
### Sharing Data with Nearby Players
If other players nearby are also running Questie-X, learned entries are broadcast automatically via the `QUESTIE_LEARNER` addon message channel. You receive their data and they receive yours — no configuration required. This means a group running the same zone will collectively fill in the map faster than any single player could alone.
Received data is validated before merging: entries missing zone ID, coordinates, or NPC name are discarded.
### Exporting Your Data
Once you've accumulated learned data you want to share (e.g. to contribute back to the plugin repository or send to another player), export it from the **Options → Database** tab:
1. Open Questie-X options: `/questie` or click the minimap button → **Options**.
2. Go to the **Database** tab.
3. Click **Export**. A compressed, base64-encoded string is generated and shown in the text box.
4. Copy the entire string (Ctrl+A → Ctrl+C).
The export string encodes your full `QuestieLearnerDB` for the current realm using LibDeflate. It is safe to paste into a Discord message, GitHub issue, or pastebin.
### Importing Data
To load data exported by another player or provided by the community:
1. Open Questie-X options → **Database** tab.
2. Paste the export string into the import text box.
3. Click **Import**. Questie-X decodes and merges the data into your local database and immediately injects it into the active override tables — map pins and tracker entries update without a full reload. A `/reload` is only required if you want newly imported quest starters/finishers to appear on the world map for quests already in your log.
Imported entries follow the same validation rules as received broadcast entries. Conflicts (same NPC ID with different coordinates) are resolved by keeping the entry with the most data fields populated.
### Cleaning Up Stale Data
Over time, the learned database can accumulate entries from old patches, removed NPCs, or incorrect data from unreliable sources. To prune it:
1. Open Questie-X options → **Database** tab.
2. Click **Cleanup**. This removes entries where the NPC/object/item no longer exists in the current loaded database or has coordinates that fall outside any known zone boundary.
### Contributing Learned Data to a Plugin
If you've accumulated significant data for a custom server that doesn't yet have a plugin (or has an incomplete one):
1. Export your data as above.
2. Open an issue or pull request on the relevant plugin repository (e.g. [Questie-X-AscensionDB](https://github.com/Xurkon/Questie-X-AscensionDB)) and paste your export string.
3. The maintainer can decode it with the same Import function and integrate confirmed entries into the next release.
### Slash Commands
| Command | Description |
|---------|-------------|
| `/questie` | Open the options panel (navigate to Database tab) |
| `/ql export` | Print the export string directly to chat for quick copy |
| `/ql import <string>` | Import a data string without opening the options panel |
| `/ql clear` | Clear all learned data for the current realm |
| `/ql status` | Print a summary of how many entries have been learned per data type |
---
## Features
### Visual Map Objectives
Quest starters, turn-ins, and all objective types are drawn as icons directly on the minimap and world map.
<div align="center">
<img src="https://i.imgur.com/4abi5yu.png" height="200" alt="Quest Givers" />
<img src="https://i.imgur.com/DgvBHyh.png" height="200" alt="Quest Complete" />
<img src="https://i.imgur.com/uPykHKC.png" height="200" alt="Quest Tooltip" />
</div>
### Quest Tracker
- Tracks quests automatically on acceptance.
- Displays up to 20 quests simultaneously (original limit: 5).
- Left-click opens the quest log; right-click provides focus mode and TomTom arrow integration.
- Headers persist correctly across all session events.
<div align="center">
<img src="https://user-images.githubusercontent.com/8838573/67285596-24dbab00-f4d8-11e9-9ae1-7dd6206b5e48.png" width="400" alt="Tracker" />
</div>
### Quest Arrow
Directional arrow pointing toward the nearest active objective or quest finisher, with zone and instance awareness.
### My Journey & Quests by Zone
- **Journey Log** — records every quest accepted, completed, and abandoned during a session.
- **Quests by Zone** — lists all available and completed quests in a given zone for completionists.
<div align="center">
<img src="https://user-images.githubusercontent.com/8838573/67285651-3cb32f00-f4d8-11e9-95d8-e8ceb2a8d871.png" height="200" alt="Journey" />
<img src="https://user-images.githubusercontent.com/8838573/67285665-450b6a00-f4d8-11e9-9283-325d26c7c70d.png" height="200" alt="Zone Quests" />
</div>
### Database Search & Configuration
- Search the full Questie database for any NPC, object, or quest by name or ID.
- Extensive options: icon scale, tracking behaviour, nameplate display, tracker layout, and more.
<div align="center">
<img src="https://user-images.githubusercontent.com/8838573/67285691-4f2d6880-f4d8-11e9-8656-b3e37dce2f05.png" height="200" alt="Search" />
<img src="https://user-images.githubusercontent.com/8838573/67285731-61a7a200-f4d8-11e9-9026-b1eeaad0d721.png" height="200" alt="Config" />
</div>
---
- added "By Zone + %% Completed" and "By Zone + %% Completed (Reversed)" tracker sorting options.
## Credits
- **Questie Team** — Original addon developers.
- **Xurkon**Questie-X fork and ongoing maintenance.
- **[Majed (3majed)](https://github.com/3majed/Questie-335)** — Ascension server dataset.
## License
MIT License — see [LICENSE](LICENSE) for details.
[aron-w/Questie-X](https://github.com/aron-w/Questie-X)<br>
[Xurkon/Questie-X](https://github.com/Xurkon/Questie-X)<br>
[Questie](https://github.com/Questie/Questie)