5a9dce0f13
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.
471 lines
16 KiB
Lua
471 lines
16 KiB
Lua
---@type QuestieTooltips
|
|
local QuestieTooltips = QuestieLoader:ImportModule("QuestieTooltips");
|
|
local _QuestieTooltips = QuestieTooltips.private
|
|
|
|
---@type l10n
|
|
local l10n = QuestieLoader:ImportModule("l10n")
|
|
|
|
---@type QuestieDB
|
|
local QuestieDB = QuestieLoader:ImportModule("QuestieDB")
|
|
---@type QuestiePlayer
|
|
local QuestiePlayer = QuestieLoader:ImportModule("QuestiePlayer")
|
|
---@type QuestieLib
|
|
local QuestieLib = QuestieLoader:ImportModule("QuestieLib")
|
|
|
|
--- COMPATIBILITY ---
|
|
local UnitGUID = QuestieCompat.UnitGUID
|
|
|
|
local lastGuid
|
|
|
|
-- ============================================================
|
|
-- NPCs that DROP an item which STARTS a quest (quest-starter drops)
|
|
-- Shows in tooltip like:
|
|
-- (yellow quest icon) Drops quest item !
|
|
-- (item icon) [ItemLink (quality colored)] [ID: <QuestId> (light blue)]
|
|
-- Works with Questie-335 compiled databases (binary + pointers).
|
|
-- ============================================================
|
|
|
|
local QUEST_START_LINE = "|TInterface\\GossipFrame\\AvailableQuestIcon:18:18:0:0|t |cFFFFD200Drops a quest !|r"
|
|
local QUEST_ID_COLOR = "|cFF80C8FF" -- light blue
|
|
local RESET_COLOR = "|r"
|
|
|
|
local _npcQuestStarterDrops = nil
|
|
local _npcQuestStarterDropsBuilt = false
|
|
|
|
-- ============================================================
|
|
-- Quest state cache (so we don't scan the log for every tooltip)
|
|
-- Hide lines if:
|
|
-- - quest is in quest log
|
|
-- - quest is flagged completed (turned in)
|
|
-- ============================================================
|
|
|
|
local _questInLogCache = {}
|
|
local _questInLogCacheBuilt = false
|
|
|
|
local function _WipeTable(t)
|
|
if wipe then
|
|
wipe(t)
|
|
else
|
|
for k in next, t do
|
|
t[k] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
local function _RebuildQuestInLogCache()
|
|
_WipeTable(_questInLogCache)
|
|
_questInLogCacheBuilt = true
|
|
|
|
if not GetNumQuestLogEntries or not GetQuestLogTitle then
|
|
return
|
|
end
|
|
|
|
local n = GetNumQuestLogEntries()
|
|
for i = 1, n do
|
|
-- WotLK: title, level, suggestedGroup, isHeader, isCollapsed, isComplete, frequency, questID
|
|
local _, _, _, isHeader, _, _, _, questID = GetQuestLogTitle(i)
|
|
if not isHeader and questID then
|
|
questID = tonumber(questID)
|
|
if questID then
|
|
_questInLogCache[questID] = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function _QuestInLog(questId)
|
|
if not questId then return false end
|
|
if not _questInLogCacheBuilt then
|
|
_RebuildQuestInLogCache()
|
|
end
|
|
return _questInLogCache[questId] == true
|
|
end
|
|
|
|
-- Invalidate cache on quest log changes
|
|
do
|
|
local f = CreateFrame("Frame")
|
|
f:RegisterEvent("QUEST_LOG_UPDATE")
|
|
f:RegisterEvent("QUEST_ACCEPTED")
|
|
f:RegisterEvent("QUEST_REMOVED")
|
|
f:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
f:SetScript("OnEvent", function()
|
|
_questInLogCacheBuilt = false
|
|
end)
|
|
end
|
|
|
|
local function _GetItemPointers()
|
|
-- QuestieDB exposes these after DB init:
|
|
-- QuestieDB.ItemPointers = QuestieDB.QueryItem.pointers
|
|
if QuestieDB and type(QuestieDB.ItemPointers) == "table" then
|
|
return QuestieDB.ItemPointers
|
|
end
|
|
if QuestieDB and QuestieDB.QueryItem and type(QuestieDB.QueryItem.pointers) == "table" then
|
|
return QuestieDB.QueryItem.pointers
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- Check if player already has the quest (in log or turned in)
|
|
local function _PlayerHasQuest(questId)
|
|
questId = tonumber(questId)
|
|
if not questId then return false end
|
|
|
|
-- 1) Active in log
|
|
if _QuestInLog(questId) then
|
|
return true
|
|
end
|
|
|
|
-- 2) Turned in / completed
|
|
if IsQuestFlaggedCompleted and IsQuestFlaggedCompleted(questId) and not QuestiePlayer.currentQuestlog[questId] then
|
|
return true
|
|
end
|
|
|
|
-- Optional fallback (some cores implement this reliably)
|
|
if GetQuestLogIndexByID then
|
|
local idx = GetQuestLogIndexByID(questId)
|
|
if idx and idx > 0 then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local function _TryBuildNpcQuestStarterDrops()
|
|
if _npcQuestStarterDropsBuilt and _npcQuestStarterDrops then
|
|
return true
|
|
end
|
|
|
|
-- DB not ready yet? Try again later.
|
|
if not QuestieDB or type(QuestieDB.QueryItemSingle) ~= "function" then
|
|
return false
|
|
end
|
|
|
|
local pointers = _GetItemPointers()
|
|
if type(pointers) ~= "table" then
|
|
return false
|
|
end
|
|
|
|
_npcQuestStarterDrops = {}
|
|
|
|
-- Helper function to process an item and add it to the lookup table
|
|
local function processItem(itemId)
|
|
local questId = QuestieDB.QueryItemSingle(itemId, "startQuest")
|
|
if questId and questId ~= 0 then
|
|
local npcDrops = QuestieDB.QueryItemSingle(itemId, "npcDrops")
|
|
if npcDrops and type(npcDrops) == "table" then
|
|
local itemName = QuestieDB.QueryItemSingle(itemId, "name")
|
|
for _, npcId in next, npcDrops do
|
|
local list = _npcQuestStarterDrops[npcId]
|
|
if not list then
|
|
list = {}
|
|
_npcQuestStarterDrops[npcId] = list
|
|
end
|
|
list[table.getn(list) + 1] = { itemId = itemId, questId = tonumber(questId), name = itemName }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Iterate all items from compiled database
|
|
for itemId, _ in next, pointers do
|
|
processItem(itemId)
|
|
end
|
|
|
|
-- Also iterate Ascension override items (they don't have pointers)
|
|
if QuestieDB.itemDataOverrides and type(QuestieDB.itemDataOverrides) == "table" then
|
|
for itemId, _ in next, QuestieDB.itemDataOverrides do
|
|
processItem(itemId)
|
|
end
|
|
end
|
|
|
|
_npcQuestStarterDropsBuilt = true
|
|
return true
|
|
end
|
|
|
|
local function _TooltipHasQuestStarterLine(tooltip)
|
|
local n = tooltip:NumLines()
|
|
local base = tooltip:GetName() .. "TextLeft"
|
|
for i = 1, n do
|
|
local left = _G[base .. i]
|
|
if left then
|
|
local t = left:GetText()
|
|
if t and t:find("Drops quest item", 1, true) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function _AddQuestStarterDropsToTooltip(npcId)
|
|
if not _TryBuildNpcQuestStarterDrops() then return end
|
|
if not _npcQuestStarterDrops then return end
|
|
|
|
local drops = _npcQuestStarterDrops[npcId]
|
|
if not drops or table.getn(drops) == 0 then return end
|
|
|
|
if _TooltipHasQuestStarterLine(GameTooltip) then
|
|
return
|
|
end
|
|
|
|
-- Filter drops to only show items where player doesn't have the quest yet
|
|
local filteredDrops = {}
|
|
local dropsCount = table.getn(drops)
|
|
for i = 1, dropsCount do
|
|
local info = drops[i]
|
|
if info.questId and (not _PlayerHasQuest(info.questId)) then
|
|
filteredDrops[table.getn(filteredDrops) + 1] = info
|
|
end
|
|
end
|
|
|
|
if table.getn(filteredDrops) == 0 then
|
|
return
|
|
end
|
|
|
|
GameTooltip:AddLine(QUEST_START_LINE)
|
|
|
|
local filteredCount = table.getn(filteredDrops)
|
|
for i = 1, filteredCount do
|
|
local info = filteredDrops[i]
|
|
local itemId = info.itemId
|
|
local questId = info.questId
|
|
|
|
-- Item link with correct quality color when cached by client
|
|
local itemLink = select(2, GetItemInfo(itemId))
|
|
if not itemLink then
|
|
local itemName = info.name
|
|
if not itemName or itemName == "" then
|
|
itemName = "Item " .. tostring(itemId)
|
|
end
|
|
itemLink = ("|Hitem:%d:::::::::|h[%s]|h"):format(itemId, itemName)
|
|
end
|
|
|
|
local icon = GetItemIcon and GetItemIcon(itemId)
|
|
local qid = ("%s[ID: %d]%s"):format(QUEST_ID_COLOR, questId, RESET_COLOR)
|
|
|
|
if icon then
|
|
GameTooltip:AddLine(("|T%s:14:14:0:0|t %s %s"):format(icon, itemLink, qid))
|
|
else
|
|
GameTooltip:AddLine(("%s %s"):format(itemLink, qid))
|
|
end
|
|
end
|
|
end
|
|
|
|
function _QuestieTooltips:HideAscensionQuestLines(tooltip)
|
|
if not Questie.db.profile.enableTooltips then return end
|
|
local numLines = tooltip:NumLines()
|
|
if not numLines or numLines < 1 then return end
|
|
|
|
local questBlockActive = false
|
|
|
|
for i = 2, numLines do
|
|
local fontString = _G[tooltip:GetName() .. "TextLeft" .. i]
|
|
if fontString then
|
|
local text = fontString:GetText()
|
|
if text then
|
|
local cleanText = string.gsub(text, "|[cC]%x%x%x%x%x%x%x%x", "")
|
|
cleanText = string.gsub(cleanText, "|[rR]", "")
|
|
cleanText = string.match(cleanText, "^%s*(.-)%s*$") or cleanText
|
|
|
|
-- Optional [-] or bullets in front of objective lines
|
|
if string.match(cleanText, "^%[%d+.-%]") or string.match(cleanText, "^%d+/%d+") or string.match(cleanText, "^%-.-%d+/%d+") then
|
|
fontString:SetText("")
|
|
fontString:Hide()
|
|
questBlockActive = true
|
|
elseif questBlockActive then
|
|
if string.match(text, "^%s+") then
|
|
questBlockActive = false
|
|
else
|
|
fontString:SetText("")
|
|
fontString:Hide()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function _QuestieTooltips:AddUnitDataToTooltip()
|
|
if (self.IsForbidden and self:IsForbidden()) or (not Questie.db.profile.enableTooltips) then
|
|
return
|
|
end
|
|
|
|
_QuestieTooltips:HideAscensionQuestLines(self)
|
|
|
|
local name, unitToken = self:GetUnit();
|
|
if not unitToken then return end
|
|
local guid = UnitGUID(unitToken);
|
|
if (not guid) then
|
|
guid = UnitGUID("mouseover");
|
|
end
|
|
|
|
local guidType, _, _, _, _, npcId, _ = strsplit("-", guid or "");
|
|
if name and (guidType == "Creature" or guidType == "Vehicle") and (
|
|
name ~= QuestieTooltips.lastGametooltipUnit or
|
|
(not QuestieTooltips.lastGametooltipCount) or
|
|
_QuestieTooltips:CountTooltip() < QuestieTooltips.lastGametooltipCount or
|
|
QuestieTooltips.lastGametooltipType ~= "monster" or
|
|
lastGuid ~= guid
|
|
) then
|
|
QuestieTooltips.lastGametooltipUnit = name
|
|
|
|
-- Suppress System A's inline learner lines here: QuestieLearner's OnTooltipSetUnit
|
|
-- hook already renders learner spawn/kill data for the hovered unit (into the main
|
|
-- tooltip, or the separate secondary learner frame when that option is enabled).
|
|
local tooltipData = QuestieTooltips:GetTooltip("m_" .. npcId, true);
|
|
|
|
if tooltipData then
|
|
if Questie.db.profile.enableTooltipsNPCID == true then
|
|
GameTooltip:AddDoubleLine("NPC ID", "|cFFFFFFFF" .. npcId .. "|r")
|
|
end
|
|
for _, v in next, tooltipData do
|
|
GameTooltip:AddLine(v)
|
|
end
|
|
else
|
|
-- Even if Questie has no objective tooltip for this NPC, we still want to show quest-starter drops.
|
|
if Questie.db.profile.enableTooltipsNPCID == true then
|
|
GameTooltip:AddDoubleLine("NPC ID", "|cFFFFFFFF" .. npcId .. "|r")
|
|
end
|
|
end
|
|
|
|
local npcNum = tonumber(npcId)
|
|
if npcNum then
|
|
_AddQuestStarterDropsToTooltip(npcNum)
|
|
end
|
|
|
|
if QuestieTooltips.ResizeTooltip then
|
|
QuestieTooltips:ResizeTooltip(GameTooltip)
|
|
end
|
|
QuestieTooltips.lastGametooltipCount = _QuestieTooltips:CountTooltip()
|
|
end
|
|
lastGuid = guid;
|
|
QuestieTooltips.lastGametooltipType = "monster";
|
|
end
|
|
|
|
-- =======================
|
|
-- Rest of original file
|
|
-- =======================
|
|
|
|
local lastItemId = 0;
|
|
function _QuestieTooltips:AddItemDataToTooltip()
|
|
if (self.IsForbidden and self:IsForbidden()) or (not Questie.db.profile.enableTooltips) then
|
|
return
|
|
end
|
|
|
|
_QuestieTooltips:HideAscensionQuestLines(self)
|
|
|
|
local name, link = self:GetItem()
|
|
local itemId
|
|
if link then
|
|
itemId = select(3,
|
|
string.match(link,
|
|
"|?c?f?f?(%x*)|?H?([^:]*):?(%d+):?(%d*):?(%d*):?(%d*):?(%d*):?(%d*):?(%-?%d*):?(%-?%d*):?(%d*):?(%d*):?(%-?%d*)|?h?%[?([^%[%]]*)%]?|?h?|?r?"))
|
|
end
|
|
if name and itemId and (lastItemId ~= itemId) then
|
|
QuestieTooltips.lastGametooltipItem = name
|
|
local tooltipData = QuestieTooltips:GetTooltip("i_" .. (itemId or 0));
|
|
-- Item ID is shown unconditionally for every item hover (matches the
|
|
-- behavior of NPC/Object tooltips), so users can always copy/paste
|
|
-- the ID even for items that have no quest objective or starter
|
|
-- data attached. The line is always added when the itemId changes
|
|
-- regardless of whether tooltipData is non-nil.
|
|
if Questie.db.profile.enableTooltipsItemID == true then
|
|
self:AddDoubleLine("Item ID", "|cFFFFFFFF" .. itemId .. "|r")
|
|
end
|
|
-- If the item starts a quest (the player must right-click/use it,
|
|
-- or talk to an NPC while it's in their bag), surface that so the
|
|
-- player can see which quest the item unlocks without first
|
|
-- having to look it up. We never knew the item's startQuest
|
|
-- until runtime, so this is read directly from the static DB /
|
|
-- Ascension override table.
|
|
local startQuestId = QuestieDB and QuestieDB.QueryItemSingle
|
|
and tonumber(QuestieDB.QueryItemSingle(itemId, "startQuest") or 0) or 0
|
|
if startQuestId and startQuestId > 0 and (not _PlayerHasQuest(startQuestId)) then
|
|
local questTitle = QuestieLib and QuestieLib.GetColoredQuestName
|
|
and QuestieLib:GetColoredQuestName(startQuestId, Questie.db.profile.enableTooltipsQuestLevel, true, true)
|
|
or nil
|
|
if questTitle and questTitle ~= "" then
|
|
self:AddDoubleLine(QUEST_START_LINE, questTitle)
|
|
end
|
|
end
|
|
if tooltipData then
|
|
for _, v in next, tooltipData do
|
|
self:AddLine(v)
|
|
end
|
|
end
|
|
QuestieTooltips.lastGametooltipCount = _QuestieTooltips:CountTooltip()
|
|
end
|
|
lastItemId = itemId;
|
|
QuestieTooltips.lastGametooltipType = "item";
|
|
QuestieTooltips.lastFrameName = self:GetName();
|
|
end
|
|
|
|
function _QuestieTooltips:AddObjectDataToTooltip(name)
|
|
if (not Questie.db.profile.enableTooltips) then
|
|
return
|
|
end
|
|
|
|
_QuestieTooltips:HideAscensionQuestLines(GameTooltip)
|
|
|
|
if name then
|
|
local titleAdded = false
|
|
local lookup = l10n.objectNameLookup[name] or {}
|
|
local count = table.getn(lookup)
|
|
|
|
if Questie.db.profile.enableTooltipsObjectID == true and count ~= 0 then
|
|
if count == 1 then
|
|
GameTooltip:AddDoubleLine("Object ID", "|cFFFFFFFF" .. lookup[1] .. "|r")
|
|
else
|
|
GameTooltip:AddDoubleLine("Object ID", "|cFFFFFFFF" .. lookup[1] .. " (" .. count .. ")|r")
|
|
end
|
|
end
|
|
|
|
local alreadyAddedObjectiveLines = {}
|
|
for _, gameObjectId in next, lookup do
|
|
local tooltipData = QuestieTooltips:GetTooltip("o_" .. gameObjectId);
|
|
|
|
if type(gameObjectId) == "number" and tooltipData then
|
|
if (not titleAdded) then
|
|
GameTooltip:AddLine(tooltipData[1])
|
|
titleAdded = true
|
|
end
|
|
|
|
if tooltipData[2] then
|
|
-- Quest has objectives
|
|
for index, line in next, tooltipData do
|
|
if index > 1 and (not alreadyAddedObjectiveLines[line]) then -- skip the first entry, it's the title
|
|
local _, _, acquired, needed = string.find(line, "(%d+)/(%d+)")
|
|
-- We need "tonumber", because acquired can contain parts of the color string
|
|
if acquired and tonumber(acquired) == tonumber(needed) then
|
|
-- We don't want to show completed objectives on game objects
|
|
break;
|
|
end
|
|
alreadyAddedObjectiveLines[line] = true
|
|
GameTooltip:AddLine(line)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if QuestieTooltips.ResizeTooltip then
|
|
QuestieTooltips:ResizeTooltip(GameTooltip)
|
|
end
|
|
GameTooltip:Show()
|
|
end
|
|
QuestieTooltips.lastGametooltipType = "object";
|
|
end
|
|
|
|
function _QuestieTooltips:CountTooltip()
|
|
local tooltipCount = 0
|
|
for i = 1, GameTooltip:NumLines() do
|
|
local frame = _G["GameTooltipTextLeft" .. i]
|
|
if frame and frame:GetText() then
|
|
tooltipCount = tooltipCount + 1
|
|
else
|
|
return tooltipCount
|
|
end
|
|
end
|
|
return tooltipCount
|
|
end
|