Expose QuestieLearner:GetIdAndTypeFromGUID / GetObjectIdFromGUID /
GetNpcIdFromGUID (they handle modern dash GUIDs AND legacy 0x hex /
GameObject GUIDs). Tooltips now fall back to these when strsplit('-',guid)
can't parse the GUID, so NPC IDs no longer silently fail to write on
hex-GUID units; the Object ID line resolves from the object's GUID when
its name isn't in the lookup. IDs deduped per tooltip.
The ID lines were gated by lastItemId/lastGuid (only added when the
hovered item/unit changed). WoW clears and re-fires OnTooltipSetItem/
OnTooltipSetUnit for the same item/unit and rebuilds the tooltip, so the
gate skipped re-adding the line on the rebuilt tooltip and the ID
disappeared. Add the ID line on every render instead, deduped per
tooltip via _TooltipHasLeftLine so it shows exactly once.
- Critters are never quest-relevant. Added a static CRITTER_NPC_SET (common
classic critters) plus runtime detection via UnitCreatureType on
mouseover/target (covers Ascension custom critters). LearnNPC and the kill
handler skip critters; InjectLearnedData purges already-recorded ones; and
critters are purged on sight when a unit token is available.
- Object/NPC spawns mis-stored under Eversong parent areaId 3430 (e.g. object
180516 'Shrine of Dath'Remar' on Sunstrider) now migrate to uiMapId 1241,
along with the [9]/[5] home-zone field. Legit Eversong data is keyed 1941,
so any 3430 key is mis-stored Sunstrider data.
Verified in Tests/QuestieLearnerCritterPurge_spec.lua.
Two bugs made saved learner spawns collapse to one pin on every login:
1. The spawn zone-key migration was inverted: it ran
GetAreaIdByUiMapId(1241) -> Eversong parent areaId 3430 and moved
Sunstrider coords there (wrong map / NE corner), deduping distinct
coords via InsertIfNewBucket along the way. Now it converts only
legacy areaId keys FORWARD to the canonical uiMapId and leaves uiMapId
keys untouched. Same for the [9]/[5] home-zone fields.
2. Sanitize de-duplicated coords with the flat COORD_GRID (2.0) instead
of the per-zone grid, collapsing Sunstrider's tightly-packed spawns
(grid 0.5). Now uses GetCoordGridForZone(zoneId).
Both rewrote learned.npcs in place, persisting the damage to
SavedVariables. Regression test in QuestieLearnerMultiSpawn_spec.lua.
A prior change (dd630fd) kept clustering enabled on Sunstrider Isle in
learner mode to avoid pin fan-out, but that collapsed several distinct
learned coordinates (e.g. 7 Arcane Wraith spots) into a single pin --
breaking the DB-building workflow where every learned spawn must be
visible. The SavedVariables data was correct the whole time; this was a
display regression. Sunstrider (1241) now shows every distinct pin by
default in all data-source modes; consolidation only happens when the
player explicitly raises the Dense Pin Clustering Aggressiveness knob.
The Ascension server appends a new progress line on every objective
update instead of replacing it, so tooltips stacked 0/8 1/8 2/8 3/8 for
one objective. New always-on DedupeAscensionProgressLines collapses each
stack to its most-progressed line. It only hides a line when the same
tooltip holds another progress line with the same objective text AND
denominator, so other addons' lines and Questie's own single-line
objectives are never touched. Separate from the opt-in full-strip option
(#16). Unit-tested in Tests/QuestieTooltipDedupe_spec.lua.
Draw-queue race: ProcessQueue processes the world-map and minimap queues
independently. When a frame was unloaded while still queued, its
minimap-queue entry could trigger the deferred Unload first (removing
both icons and clearing _needsUnload), then a later map-queue entry
re-added the world-map icon -- orphaned, since Unload had nil'd the
questIdFrames/_G reference, so it lingered until /reload while the
minimap cleared. Unload now drops the frame's pending draw-queue entries
(new QuestieMap:DequeueFrameDrawCalls) so an unloaded frame can never be
re-added to a map.
- Key the kill spawn under the same map space the coords were captured in
(GetCurrentPlayerPosition mapId), not a separately-derived GetZoneId()
that can disagree on subzones and place pins on the wrong map.
- Throttled debug warning when a credited kill yields no usable position,
so a systematic capture failure is diagnosable.
- Add Tests/QuestieLearnerKillCapture_spec.lua: drives the real kill
handler end-to-end and asserts capture, the 0,0 guard, and the per-mode
display contract (learner shows learned spawn, static never leaks it).
The kill handler's position capture ran inside 'if credited then', but
credited was read before its 'local credited = ...' assignment a few
lines below, so it was always nil and px,py were never captured -- every
killed NPC stayed spawnSource='fallback' with no [7] and drew no learner
pins regardless of kill count. Move the credited computation above the
capture and nil-guard a 0,0 position. GetPlayerCoords now uses the robust
GetCurrentPlayerPosition (Sunstrider-corrected), returning nil when
invalid so no 0,0 pins are recorded. Reverts the earlier HBD GetPlayerCoords
approach which treated the wrong symptom and caused spurious pins.
FadeLogic only re-checked ShouldBeHidden when deciding whether to
re-show an already-hidden icon, so a filtered quest (e.g. a dungeon
quest) that was already visible -- or that HBD's pin renderer showed on
coming into range -- was never hidden on the minimap while the world map
hid it. FadeLogic now proactively FakeHides any in-range icon whose
ShouldBeHidden is true, in both minimap fade paths; the second path also
gains the #17 minimap-radius cutoff gating it was missing.
GetPlayerCoords used raw GetPlayerMapPosition('player'), which returns
0,0 when the world map isn't on the player's zone (the usual case) and
mis-reports on Ascension subzones like Sunstrider. Learn events captured
no position, so NPCs were saved spawnSource='fallback' with no [7]
spawns and their learner-only pins never persisted -- showing briefly
after a live kill then vanishing. Read from HBD:GetPlayerZonePosition()
(robust SetMapToCurrentZone/Sunstrider-corrected, cached), falling back
to the old API only if HBD is unavailable.
HideAscensionQuestLines ran on every tooltip and removed any line
matching a quest-objective pattern (N/M, [N] ...), clobbering other
tooltip addons' lines (durability, stack counts, etc.). Gate it behind a
new General-tab option 'Hide Ascension quest progress lines' (default
off) so Questie no longer modifies tooltip lines it does not own by
default. Existing installs read the unset option as off automatically.
At fractional UI scales (e.g. 0.71 game scale + Windows display scaling)
the raw fractional pin offset landed icons between physical pixels,
rendering blurry and offset from the engine-drawn native quest blips.
Snap the offset to a whole physical pixel via the minimap's effective
scale so Questie's icons sit on the same grid as native markers.
Ascension board bounties (e.g. NPC 24 'Outlaw's Contract Board') aren't
reliably flagged repeatable, so IsRepeatable missed them and their ! kept
showing below 60. Add QuestieDB.IsBoardQuest (detects starter NPC/object
names containing 'board', cached per quest); the hide-below-60 option now
hides repeatable OR board quests in both the draw path and ShouldBeHidden.
The ElvUI tooltip style shipped on by default, so Questie restyled every
default WoW tooltip (stripping the border) for users who never asked for
it and don't run ElvUI. Default is now false, plus migration [8] resets
it off once for existing profiles.
Some Ascension turn-ins (crafting/auto-complete) fire QUEST_REMOVED with
no preceding QUEST_TURNED_IN, so the 1s abandon timer runs after
QuestLogCache.RemoveQuest has cleared the quest, making IsComplete return
0 and the turned-in quest get marked abandoned -- its objective pins and
turn-in ? then linger. Capture IsComplete at QUEST_REMOVED time and use
that snapshot in MarkQuestAsAbandoned.
The cutoff hid icons beyond its yard value before checking the minimap's
visible radius, so the default 100 hid icons that were clearly on the
minimap (view radius is 133-466 yd by zoom). Now it only clips icons
outside the visible circle: HBD's pin renderer gates the cutoff on
dist > 1, and QuestieMap FadeLogic raises the effective cutoff to at
least the current minimap view radius via new HBDPins:GetMinimapRadius().
HideAscensionQuestLines kept a questBlockActive flag and hid every
following non-indented line after an objective line until an indented
one. Questie's own Item/NPC/Object ID lines and other addons' additions
(e.g. an item-count overlay) are appended at the bottom of the tooltip,
so they were wiped too. Now only lines matching an Ascension objective
pattern are hidden; trailing lines are left intact.
Changelog entries for the import/export work landed in 2c695c4: Export
Current Zone, live map+stats refresh after import (no reload), and the
200k-entry import guard.
QuestieLearnerExport:
- Extract _Encode helper to consolidate serialize+deflate+encode
- Add ExportZone(serverKey, zoneId) to export only the learned data
for a specific zone (NPCs/objects with spawns in that zone, plus
their dropped items and related quests) for smaller, faster exports
- Cap ValidateImport at 200k entries to guard against pathological
payloads that could stall the client during merge
- Call SmoothReset after MergeImport so newly imported spawns appear
on the map without requiring /reload
QuestieOptionsDatabase:
- Refresh AceConfigRegistry after import so Database tab stats update
immediately (previously required /reload or opening Options)
- Add Export Current Zone button tied to ExportZone()
Hardens the learner data-ingestion paths against malformed/partial data
across NPC/object/item/quest types:
- _ValidateLearnedSpawnData now validates object spawn coordinates (key 4)
in addition to NPC spawns (key 7); object coords were previously
unvalidated. Extracted a shared _ValidateCoordTable helper.
- _ApplyIncomingNetworkMerge coordinate merge skips malformed zone keys and
coordinates (non-number / out-of-range) per entry instead of erroring,
and guards the item drop-list merge against non-number NPC ids.
- The live comms-merge flush isolates each broadcast in a pcall so one
malformed entry can't abort the batch or break the live-update loop;
the post-merge InjectLearnedData is also guarded.
Live Learn* entry points already validate id/type. selene 0 errors;
busted 145 successes / same 7 pre-existing failures.
Importing learned data merged from several players must not corrupt the
store. Fixes:
- ValidateImport prefix check was a no-op: parsed
as (always false), so non-Questie strings were
never rejected. Now a proper inequality check.
- MergeImport now ensures the learner stores exist (fresh profile can
import), merges each entry synchronously via _ApplyIncomingNetworkMerge
(validates structure, only adopts missing fields, never overwrites good
local data), wraps each entry in pcall so one malformed entry is
skipped/counted rather than aborting the whole import, and returns
accurate merged/skipped/rejected counts.
- Synchronous merge also fixes InjectLearnedData previously running before
the async-queued merges landed.
selene 0 errors; busted 145 successes / same 7 pre-existing failures.
Players who run fully zoomed-out minimaps need a larger visibility radius
since quest objective spawns can be far apart. Raise the slider max from
200 to 1000 (yards). Default (100) and minimum (25) unchanged.
Reverts the previous approach of stripping char.complete (which mutated
completed-quest data). Correct rule: a quest in the player's log is active
and should show its turn-in location regardless of the completed flag.
- AddFinisher now trusts the live quest log: if the quest is in
currentQuestlog and not failed, the '?' finisher draws regardless of the
(possibly stale, e.g. prior-prestige) char.complete flag. No completion
data is modified. Reverts the AcceptQuest and QUEST_QUERY_COMPLETE strips.
- Learner: OnTargetChanged now learns quest-giver/turn-in NPC spawns
(it previously only cached the GUID), so targeting a turn-in NPC records
its location. OnQuestComplete/OnQuestTurnedIn fall back to the 'target'
unit when the 'npc' gossip unit is already cleared, so the finisher NPC
is reliably learned on turn-in.
selene 0 errors; busted 145 successes / same 7 pre-existing failures.
A quest completed in a prior Ascension prestige stays in char.complete.
On re-accept it is active in the log again, but the stale completion flag
was never cleared — and AddFinisher only draws the turn-in '?' when
not char.complete[questId]. So the finisher was suppressed and the quest
showed '(Complete)' with stale objective text while standing at the
turn-in NPC (e.g. Aggression 8334 at Lanthan Perilon 15281).
An active in-log quest is now kept out of char.complete:
- QuestieQuest:AcceptQuest clears char.complete[questId] on accept.
- The async QUEST_QUERY_COMPLETE handler also strips any quest currently
in QuestiePlayer.currentQuestlog, so the server's completed list can't
re-flag a re-accepted quest.
Restores the turn-in '?' and normal objective tracking. selene 0 errors;
busted 145 successes / same 7 pre-existing failures.
#7: Completed quests kept showing as available '!'. The server completed-
quest list arrives async via QUEST_QUERY_COMPLETE, often after available
quests were first drawn. Recalculate available quests once char.complete is
populated by that event so completed quests are removed (also clears the
already-completed subset of #8).
#9: An accepted quest's available '!' could linger on the minimap until
/reload. UnloadQuestFramesByDataType unloaded the frame but left its name in
questIdFrames and _G; it now removes those references so the icon is torn
down immediately on both map and minimap.
#10: Added 'Hide repeatable quests below level 60' (Icons tab, off by
default) to hide repeatable available quests (e.g. the Ascension Callboard)
from map/minimap until level 60. Enforced in _DrawQuestIfAvailable and
ShouldBeHidden so existing minimap pins are removed too; reappear at 60.
Lint: selene 0 errors. Tests: 145 successes / same 7 pre-existing failures.
The previous ElvUI-style border was too thick and uneven (thicker at the
top): it used Interface\ChatFrame\ChatFrameBackground with edgeSize = 1 UI
*unit*, which renders several physical pixels thick at the user's UI scale,
and a 1x1 texture that samples unevenly along edges.
Now copies ElvUI/Core/Toolkit.lua SetTemplate exactly: the blank texture
E.media.blankTex (Interface\Buttons\WHITE8X8) for bg + edge, and edgeSize =
E.mult (one physical pixel = (768 / screenHeight) / uiScale) so the border
is a true even 1px. Colors unchanged (ElvUI defaults the user runs):
backdropfadecolor {0.06,0.06,0.06} @ colorAlpha 0.8, bordercolor black.
Factored into QuestieTooltips:ApplyElvUISkin so the secondary learner frame
and the default tooltips share one implementation.
The InjectLearnedData purge loop was deleting entire NPC entries from
Questie.dbLearner.global.npcs whenever data[7] (spawns) was empty,
even if the entry still had a name, home zone, recorded kills, or
quest references. This caused learner data to silently disappear on
/reload for NPCs that had been learned but never killed in a position
the learner could record (e.g. party-kill position attribution fix
now passes nil coords for uncredited kills).
Now only purges when ALL of name, zone, kills, and quest references
are missing. Entries with other useful state get data[7] set to nil
instead so the row stays.
- ElvUI tooltip style: new 'ElvUI tooltip style' option (General tab, on by
default) skins GameTooltip/WorldMapTooltip/ItemRefTooltip/shopping tooltips
and the secondary learner frame with ElvUI's transparent flat look (dark bg
+ thin 1px border) when ElvUI is not installed. Corrected the secondary
frame fallback that used the chunky WoW border. No-op when ElvUI is loaded.
- Source attribution now shows ONLY inside the secondary learner tooltip when
'Use secondary learner tooltip' is enabled; removed from the main NPC/object/
item tooltip and gated off entirely when the secondary tooltip is disabled
(map-pin source gated the same way).
- Fixed learner-learned pins mislabelled 'AscensionDB': GetPinDataSource is now
mode-aware and returns 'Learner' in learner mode when the entity has a learner
record, even if AscensionDB also curates it (curated coords are discarded by
GetNPC/GetObject in learner mode anyway).
Test-neutral (145 successes / same 7 pre-existing failures + 1 error).
In learner-only mode the map/minimap drew curated AscensionDB (and static)
spawns for every quest NPC/object the player had not personally recorded.
GetNPC/GetObject fall back to the npcDataOverrides/objectDataOverrides
entry for metadata when there is no learner record, and that entry's
spawns (AscensionDB-curated for un-recorded entities) were drawn as pins.
Both now track whether a real learner record exists and, in learner mode,
strip spawns when it does not -- so learner-only shows exclusively what the
learner recorded (metadata fallback for names/tooltips kept). GetObject
also now nils the override in learner mode to match GetNPC. Recorded
entities still show their learner spawns; auto/static/none unchanged.
Verified test-neutral on the full suite (144 successes / same 7 pre-existing
failures) with a local regression test that passes with the fix and fails
without it.
Quest-type filters (Available Dungeon/Raid/PvP/Repeatable/Event quests and
other ShouldBeHidden rules) were honored on the world map but not the
minimap. At toggle time HideQuestIcons FakeHides both map and minimap
filtered icons, but the minimap icon's per-frame FadeLogic re-showed any
hidden icon once in range via 'elseif self.hidden then self:FakeShow()',
undoing the filter on the minimap only (the world icon has no FadeLogic).
FadeLogic now re-checks ShouldBeHidden before re-showing, in both the
world-icon and manual-icon minimap fade paths, so filters apply to both.
A third-party addon (e.g. AtlasLoot) registers an AceComm prefix longer
than the client's 16-character limit. Because Questie's bundled AceComm is
the LibStub winner and its xpcall polyfill wraps AceAddon's OnEnable, the
upstream hard error() in RegisterComm surfaced as a recurring Lua error at
every login (seen on ChromieCraft).
The prefix can never work on the client regardless, so RegisterComm now
warns once and skips the registration for over-long prefixes instead of
throwing, leaving the calling addon's OnEnable intact and removing the
error popup.
Two crashes reported in #15 on Elune (1.6.3):
- TrackerUtils:IsVoiceOverLoaded only verified the VoiceOver addons were
loaded, not that VoiceOver.QuestOverlayUI.questPlayButtons exists. Some
VoiceOver builds expose a QuestOverlayUI without that table, so
UpdateVoiceOverPlayButtons and SetAllPlayButtonAlpha crashed with
'attempt to index field questPlayButtons (a nil value)'. Now verified in
IsVoiceOverLoaded, which all play-button call sites gate on.
- AceGUIContainer-TreeGroup crashed indexing a nil AceGUI.tooltip when a
conflicting addon registered a broken AceGUI-3.0 core (version 1.#INF)
that won LibStub but never created the shared tooltip frame. The tree
button handlers now lazily recreate it; widget version bumped 47->48 so
the fixed widget wins registration.
The 'Use secondary learner tooltip' option's off-path appended learner
spawn/kill lines directly to the main GameTooltip. The toggle now gates
the data entirely: on = shown in the separate secondary frame, off = not
shown anywhere. Learner spawn/kill data is never inline in the main NPC
tooltip under any setting.
Removed the now-unused _AddTooltipSeparator helper and updated the option
description.
Re-adds the 'Show data source' tooltip option (General tab, default off),
but accurate this time. The previous version was removed because it
guessed the source from the global data-source mode (showing Learner when
data was AscensionDB, etc.). This tags each pin with its real provenance
at creation and reads that tag.
- QuestieDB.GetPinDataSource(entityType, id, spawnData): resolves
Learner (per-spawn isLearned or learner record), AscensionDB (curated
ascensionOverrideKeys override), or base Questie DB. Never guesses.
- Objective pins tagged per-spawn in _DetermineIconsToDraw; available/
finisher pins defaulted by quest in DrawWorldIcon; manual notes tagged
Townsfolk in DrawManualIcon.
- World-map pins read the per-pin tag (MapIconTooltip); unit/object/item
hovers derive per-id at the render layer (TooltipHandler via
QuestieTooltips:GetDataSourceLine). Comms appended via KeyExists.
- No line shown when source is genuinely unknown (never misleading).
Hovering an NPC right after turning in one of its associated quests
spammed a debugstack trace in DEVELOP mode: the learner objective-
correlation block in GetTooltip called QuestLogCache.GetQuestObjectives
for a quest no longer in QuestLogCache (which gracefully returns {} but
logs the stack at line 306).
Guard the NPC and object correlation loops on
QuestiePlayer.currentQuestlog[questId] so live objective progress is only
looked up for quests the player is currently on. Not a crash and no
functional change for active quests.
Two parallel learner-tooltip systems both rendered on unit hover, so with
the 'Use secondary learner tooltip' option on, learner spawn/kill lines
still leaked into the main NPC tooltip instead of going solely to the
secondary frame.
QuestieLearner's OnTooltipSetUnit hook (toggle-aware, secondary-capable)
already owns unit-hover learner display, routing it to the main tooltip
(secondary off) or the separate secondary frame (secondary on). But
Tooltip.lua's _GetLearnerTooltipLines also injected learner lines into the
m_<npcId> tooltip data, redundantly.
Add a suppressLearnerLines flag to QuestieTooltips:GetTooltip and pass it
from the NPC unit-hover call in TooltipHandler so the inline lines are
omitted there. Map-pin and object tooltips (no secondary frame) keep their
learner lines. Also removes the latent duplicate present even with the
secondary frame disabled.
When a quest object node is opened/looted, that specific node's
map/minimap pin now disappears immediately even before the objective is
fully collected, instead of all node pins staying until turn-in.
On LOOT_OPENED, QuestieQuest:RemoveLootedObjectivePins finds the active
object-objective spawn nearest the player's world position (<=12 yd),
marks it consumed in Questie.db.char.lootedObjectSpawns, and repopulates
just that objective so clustering recomputes with the node skipped.
_DetermineIconsToDraw skips consumed object spawns, so removal persists
across redraws and /reload. ClearLootedSpawns runs on AcceptQuest, so
abandoning and re-doing the quest (including after an Ascension prestige)
shows every node again.
Works in every data source mode (auto/learner/static/none): suppression
is centralized in the shared _DetermineIconsToDraw that all redraw routes
funnel through, operating on the mode-resolved objective.spawnList.
Matching is radius-based (1.5 zone units) so a learner spawn re-added at
the player's position for the just-looted node is suppressed too.
Scoped to object-type objectives; monster/kill pins are unchanged.
Completed objectives could leave their map/minimap pins on screen until
turn-in. Pin removal relied solely on _UnloadAlreadySpawnedIcons, which
walks objective.AlreadySpawned; that table desyncs from the live frames
after learner spawn-list invalidation or complete->abandon->reaccept
cycles, so when reset to {} the frames leaked.
Add QuestieMap:UnloadQuestFramesForObjective(questId, objectiveIndex)
which unloads frames off the map's own questIdFrames registry by matching
frame.data.ObjectiveIndex, and call it from every completion path in
PopulateObjective. Restricted to positive standard-objective indices;
SpecialObjectives (sentinel index 0) keep using AlreadySpawned.