---@class QuestieTooltips local QuestieTooltips = QuestieLoader:CreateModule("QuestieTooltips"); local _QuestieTooltips = QuestieTooltips.private ------------------------- --Import modules. ------------------------- ---@type QuestieComms local QuestieComms = QuestieLoader:ImportModule("QuestieComms"); ---@type QuestieLib local QuestieLib = QuestieLoader:ImportModule("QuestieLib"); ---@type QuestiePlayer local QuestiePlayer = QuestieLoader:ImportModule("QuestiePlayer"); ---@type QuestieDB local QuestieDB = QuestieLoader:ImportModule("QuestieDB"); ---@type l10n local l10n = QuestieLoader:ImportModule("l10n") --- COMPATIBILITY --- local UnitInParty = QuestieCompat.UnitInParty local IsInGroup = QuestieCompat.IsInGroup local GetClassColor = QuestieCompat.GetClassColor local tinsert = table.insert QuestieTooltips.lastGametooltip = "" QuestieTooltips.lastGametooltipCount = -1; QuestieTooltips.lastGametooltipType = ""; QuestieTooltips.lastFrameName = ""; QuestieTooltips.lookupByKey = { --["u_Grell"] = {questid, {"Line 1", "Line 2"}} } QuestieTooltips.lookupKeysByQuestId = { --["questId"] = {"u_Grell", ... } } local MAX_GROUP_MEMBER_COUNT = 6 local _InitObjectiveTexts ---@param questId number ---@param key string monster: m_, items: i_, objects: o_ + string name of the objective ---@param objective table function QuestieTooltips:RegisterObjectiveTooltip(questId, key, objective) if not QuestieTooltips.lookupByKey[key] then QuestieTooltips.lookupByKey[key] = {}; end if not QuestieTooltips.lookupKeysByQuestId[questId] then QuestieTooltips.lookupKeysByQuestId[questId] = {} end local tooltip = { questId = questId, objective = objective, }; QuestieTooltips.lookupByKey[key][tostring(questId) .. " " .. objective.Index] = tooltip tinsert(QuestieTooltips.lookupKeysByQuestId[questId], key) end ---@param questId number ---@param name string The name of the object or NPC the tooltip should show on ---@param starterId number The ID of the object or NPC the tooltip should show on ---@param key string @Either m_ or o_ function QuestieTooltips:RegisterQuestStartTooltip(questId, name, starterId, key) if not name then return end if not QuestieTooltips.lookupByKey[key] then QuestieTooltips.lookupByKey[key] = {}; end if not QuestieTooltips.lookupKeysByQuestId[questId] then QuestieTooltips.lookupKeysByQuestId[questId] = {} end local tooltip = { questId = questId, name = name, starterId = starterId, }; QuestieTooltips.lookupByKey[key][tostring(questId) .. " " .. name .. " " .. starterId] = tooltip tinsert(QuestieTooltips.lookupKeysByQuestId[questId], key) end ---@param questId number function QuestieTooltips:RemoveQuest(questId) if (not QuestieTooltips.lookupKeysByQuestId[questId]) then -- Tooltip has already been removed return end -- Remove tooltip related keys from quest table so that -- it can be readded/registered by other quest functions. local quest = QuestieDB.GetQuest(questId) if quest then for _, objective in next, quest.Objectives do objective.AlreadySpawned = {} objective.hasRegisteredTooltips = false objective.registeredItemTooltips = false end for _, objective in next, quest.SpecialObjectives do objective.AlreadySpawned = {} objective.hasRegisteredTooltips = false objective.registeredItemTooltips = false end end Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieTooltips:RemoveQuest]", questId) for _, key in next, QuestieTooltips.lookupKeysByQuestId[questId] or {} do --Count to see if we should remove the main object local totalCount = 0 local totalRemoved = 0 for _, tooltipData in next, QuestieTooltips.lookupByKey[key] or {} do --Remove specific quest if (tooltipData.questId == questId and tooltipData.objective) then QuestieTooltips.lookupByKey[key][tostring(tooltipData.questId) .. " " .. tooltipData.objective.Index] = nil totalRemoved = totalRemoved + 1 elseif (tooltipData.questId == questId and tooltipData.name) then QuestieTooltips.lookupByKey[key][tostring(tooltipData.questId) .. " " .. tooltipData.name .. " " .. tooltipData.starterId] = nil totalRemoved = totalRemoved + 1 end totalCount = totalCount + 1 end if (totalCount == totalRemoved) then QuestieTooltips.lookupByKey[key] = nil end end QuestieTooltips.lookupKeysByQuestId[questId] = nil end -- This code is related to QuestieComms, here we fetch all the tooltip data that exist in QuestieCommsData -- It uses a similar system like here with i_ID etc as keys. local function _FetchTooltipsForGroupMembers(key, tooltipData) local anotherPlayer = false; if QuestieComms and QuestieComms.data:KeyExists(key) then ---@tooltipData @tooltipData[questId][playerName][objectiveIndex].text local tooltipDataExternal = QuestieComms.data:GetTooltip(key); for questId, playerList in next, tooltipDataExternal do if (not tooltipData[questId]) then tooltipData[questId] = { title = QuestieLib:GetColoredQuestName(questId, Questie.db.profile.enableTooltipsQuestLevel, true, true) } end for playerName, _ in next, playerList do local playerInfo = QuestiePlayer:GetPartyMemberByName(playerName); if playerInfo or QuestieComms.remotePlayerEnabled[playerName] then anotherPlayer = true break end end if anotherPlayer then break end end end if QuestieComms.data:KeyExists(key) and anotherPlayer then ---@tooltipData @tooltipData[questId][playerName][objectiveIndex].text local tooltipDataExternal = QuestieComms.data:GetTooltip(key); for questId, playerList in next, tooltipDataExternal do if (not tooltipData[questId]) then tooltipData[questId] = { title = QuestieLib:GetColoredQuestName(questId, Questie.db.profile.enableTooltipsQuestLevel, true, true) } end for playerName, objectives in next, playerList do local playerInfo = QuestiePlayer:GetPartyMemberByName(playerName); if playerInfo or QuestieComms.remotePlayerEnabled[playerName] then anotherPlayer = true; for objectiveIndex, objective in next, objectives do if (not objective) then objective = {} end tooltipData[questId].objectivesText = _InitObjectiveTexts(tooltipData[questId].objectivesText, objectiveIndex, playerName) local text; local color = QuestieLib:GetRGBForObjective(objective) if objective.required then text = " " .. color .. tostring(objective.fulfilled) .. "/" .. tostring(objective.required) .. " " .. objective.text; else text = " " .. color .. objective.text; end tooltipData[questId].objectivesText[objectiveIndex][playerName] = { ["color"] = color, ["text"] = text }; end end end end end return anotherPlayer end ---@param key string function QuestieTooltips:GetTooltip(key) Questie:Debug(Questie.DEBUG_SPAM, "[QuestieTooltips:GetTooltip]", key) if (not key) then return nil end if QuestiePlayer.numberOfGroupMembers > MAX_GROUP_MEMBER_COUNT then return nil -- temporary disable tooltips in raids, we should make a proper fix end --Do not remove! This is the datastrucutre for tooltipData! --[[tooltipdata[questId] = { title = coloredTitle, objectivesText = { [objectiveIndex] = { [playerName] = { [color] = color, [text] = text } } } }]] -- local tooltipData = {} local tooltipLines = {} if (not QuestieTooltips.lookupByKey[key]) then local QuestieLearner = QuestieLoader:ImportModule("QuestieLearner") local QuestLogCache = QuestieLoader:ImportModule("QuestLogCache") if QuestieLearner and QuestieLearner.data then -- Try to find in learned NPCs or objects local id = tonumber(key:sub(3)) if id then if key:sub(1,2) == "m_" then local learnedNpc = QuestieLearner.data.npcs[id] if learnedNpc and learnedNpc[10] then -- check questObjectives for questId, objList in next, learnedNpc[10] do local oIndex = 1 while objList[oIndex] do local objText = objList[oIndex] local needed, collected local objectives = QuestLogCache.GetQuestObjectives(questId) if objectives then for _, obj in next, objectives do if obj.text and objText and (obj.text == objText or string.find(obj.text, objText, 1, true) or string.find(objText, obj.text, 1, true)) then needed = obj.numRequired collected = obj.numFulfilled break end end end QuestieTooltips:RegisterObjectiveTooltip(questId, key, { Index = 0, Description = objText, Needed = needed, Collected = collected, Update = function(self) local objs = QuestLogCache.GetQuestObjectives(questId) if objs then for _, o in next, objs do if o.text and self.Description and (o.text == self.Description or string.find(o.text, self.Description, 1, true) or string.find(self.Description, o.text, 1, true)) then self.Needed = o.numRequired self.Collected = o.numFulfilled break end end end end }) oIndex = oIndex + 1 end end if learnedNpc.mc then tinsert(tooltipLines, "|cFF5EBAF3(Learned - Confidence: " .. tostring(learnedNpc.mc) .. ")|r") end end elseif key:sub(1,2) == "o_" then local learnedObj = QuestieLearner.data.objects[id] if learnedObj and learnedObj[10] then for questId, objList in next, learnedObj[10] do local oIndex = 1 while objList[oIndex] do local objText = objList[oIndex] local needed, collected local objectives = QuestLogCache.GetQuestObjectives(questId) if objectives then for _, obj in next, objectives do if obj.text and objText and (obj.text == objText or string.find(obj.text, objText, 1, true) or string.find(objText, obj.text, 1, true)) then needed = obj.numRequired collected = obj.numFulfilled break end end end QuestieTooltips:RegisterObjectiveTooltip(questId, key, { Index = 0, Description = objText, Needed = needed, Collected = collected, Update = function(self) local objs = QuestLogCache.GetQuestObjectives(questId) if objs then for _, o in next, objs do if o.text and self.Description and (o.text == self.Description or string.find(o.text, self.Description, 1, true) or string.find(self.Description, o.text, 1, true)) then self.Needed = o.numRequired self.Collected = o.numFulfilled break end end end end }) oIndex = oIndex + 1 end end if learnedObj.mc then tinsert(tooltipLines, "|cFF5EBAF3(Learned - Confidence: " .. tostring(learnedObj.mc) .. ")|r") end end end end end end if QuestieTooltips.lookupByKey[key] then local playerName = UnitName("player") for k, tooltip in next, QuestieTooltips.lookupByKey[key] do if tooltip.name then if Questie.db.profile.showQuestsInNpcTooltip then local questString = QuestieLib:GetColoredQuestName(tooltip.questId, Questie.db.profile.enableTooltipsQuestLevel, true, true) tinsert(tooltipLines, questString) end else local objective = tooltip.objective if objective and not (objective.IsSourceItem or objective.IsRequiredSourceItem) and objective.Update then -- Tooltip was registered for a sourceItem or requiredSourceItem and not a real "objective" objective:Update() end local questId = tooltip.questId local objectiveIndex = objective.Index; if (not tooltipData[questId]) then tooltipData[questId] = { title = QuestieLib:GetColoredQuestName(questId, Questie.db.profile.enableTooltipsQuestLevel, true, true) } end if not QuestiePlayer.currentQuestlog[questId] then -- TODO: Is this still required? QuestieTooltips.lookupByKey[key][k] = nil else tooltipData[questId].objectivesText = _InitObjectiveTexts(tooltipData[questId].objectivesText, objectiveIndex, playerName) -- Try to get the name of the NPC/Object from the spawnList if possible local creatureName local idFromKey = tonumber(key:sub(3)) if idFromKey and objective.spawnList and objective.spawnList[idFromKey] then creatureName = objective.spawnList[idFromKey].Name end local text; local color = QuestieLib:GetRGBForObjective(objective) if objective.Type == "spell" and objective.spawnList[idFromKey] and objective.spawnList[idFromKey].ItemId then text = " " .. color .. tostring(QuestieDB.QueryItemSingle(objective.spawnList[idFromKey].ItemId, "name")); elseif objective.Needed then text = " " .. color .. tostring(objective.Collected) .. "/" .. tostring(objective.Needed) .. " " .. tostring(objective.Description); else text = " " .. color .. tostring(objective.Description); end -- If we found a creature name, prepend it to the text if creatureName then text = " |cFFDDDDDD" .. creatureName .. "|r\n " .. text end tooltipData[questId].objectivesText[objectiveIndex][playerName] = { ["color"] = color, ["text"] = text }; end end end end local anotherPlayer = false if IsInGroup() then anotherPlayer = _FetchTooltipsForGroupMembers(key, tooltipData) end local playerName = UnitName("player") for questId, questData in next, tooltipData do local hasObjective = false local tempObjectives = {} for _, playerList in next, questData.objectivesText or {} do for objectivePlayerName, objectiveInfo in next, playerList do local playerInfo = QuestiePlayer:GetPartyMemberByName(objectivePlayerName) local playerColor local playerType = "" if playerInfo then playerColor = "|c" .. playerInfo.colorHex elseif QuestieComms.remotePlayerEnabled[objectivePlayerName] and QuestieComms.remoteQuestLogs[questId] and QuestieComms.remoteQuestLogs[questId][objectivePlayerName] and (not Questie.db.profile.onlyPartyShared or UnitInParty(objectivePlayerName)) then playerColor = QuestieComms.remotePlayerClasses[playerName] if playerColor then playerColor = Questie:GetClassColor(playerColor) playerType = " (" .. l10n("Nearby") .. ")" end end if objectivePlayerName == playerName and anotherPlayer then -- why did we have this case local _, classFilename = UnitClass("player"); local _, _, _, argbHex = GetClassColor(classFilename) objectiveInfo.text = objectiveInfo.text .. " (|c" .. argbHex .. objectivePlayerName .. "|r" .. objectiveInfo.color .. ")|r" elseif playerColor and objectivePlayerName ~= playerName then objectiveInfo.text = objectiveInfo.text .. " (" .. playerColor .. objectivePlayerName .. "|r" .. objectiveInfo.color .. ")|r" .. playerType end -- We want the player to be on top. if objectivePlayerName == playerName then tinsert(tempObjectives, 1, objectiveInfo.text); hasObjective = true elseif playerColor then tinsert(tempObjectives, objectiveInfo.text); hasObjective = true end end end if hasObjective then tinsert(tooltipLines, questData.title); for _, text in next, tempObjectives do tinsert(tooltipLines, text); end end end return tooltipLines end _InitObjectiveTexts = function(objectivesText, objectiveIndex, playerName) if (not objectivesText) then objectivesText = {} end if (not objectivesText[objectiveIndex]) then objectivesText[objectiveIndex] = {} end if (not objectivesText[objectiveIndex][playerName]) then objectivesText[objectiveIndex][playerName] = {} end return objectivesText end function QuestieTooltips:Initialize() -- For the clicked item frame. ItemRefTooltip:HookScript("OnTooltipSetItem", _QuestieTooltips.AddItemDataToTooltip) ItemRefTooltip:HookScript("OnHide", function(self) if (not self.IsForbidden) or (not self:IsForbidden()) then -- do we need this here also QuestieTooltips.lastGametooltip = "" QuestieTooltips.lastItemRefTooltip = "" QuestieTooltips.lastGametooltipItem = nil QuestieTooltips.lastGametooltipUnit = nil QuestieTooltips.lastGametooltipCount = 0 QuestieTooltips.lastFrameName = ""; end end) -- For the hover frame. GameTooltip:HookScript("OnTooltipSetUnit", function(self) if QuestiePlayer.numberOfGroupMembers > MAX_GROUP_MEMBER_COUNT then -- When in a raid, we want as little code running as possible return end _QuestieTooltips.AddUnitDataToTooltip(self) end) GameTooltip:HookScript("OnTooltipSetItem", _QuestieTooltips.AddItemDataToTooltip) GameTooltip:HookScript("OnShow", function(self) if QuestiePlayer.numberOfGroupMembers > MAX_GROUP_MEMBER_COUNT then -- When in a raid, we want as little code running as possible return end if (not self.IsForbidden) or (not self:IsForbidden()) then -- do we need this here also QuestieTooltips.lastGametooltipItem = nil QuestieTooltips.lastGametooltipUnit = nil QuestieTooltips.lastGametooltipCount = 0 QuestieTooltips.lastFrameName = ""; end end) GameTooltip:HookScript("OnHide", function(self) if QuestiePlayer.numberOfGroupMembers > MAX_GROUP_MEMBER_COUNT then -- When in a raid, we want as little code running as possible return end if (not self.IsForbidden) or (not self:IsForbidden()) then -- do we need this here also QuestieTooltips.lastGametooltip = "" QuestieTooltips.lastItemRefTooltip = "" QuestieTooltips.lastGametooltipItem = nil QuestieTooltips.lastGametooltipUnit = nil QuestieTooltips.lastGametooltipCount = 0 end end) -- Fired whenever the cursor hovers something with a tooltip. And then on every frame GameTooltip:HookScript("OnUpdate", function(self) if QuestiePlayer.numberOfGroupMembers > MAX_GROUP_MEMBER_COUNT then -- When in a raid, we want as little code running as possible return end if (not self.IsForbidden) or (not self:IsForbidden()) then --Because this is an OnUpdate we need to check that it is actually not a Unit or Item to think its a local uName, unit = self:GetUnit() local iName, link = self:GetItem() local sName, spell = self:GetSpell() if (uName == nil and unit == nil and iName == nil and link == nil and sName == nil and spell == nil) and ( QuestieTooltips.lastGametooltip ~= GameTooltipTextLeft1:GetText() or (not QuestieTooltips.lastGametooltipCount) or _QuestieTooltips:CountTooltip() < QuestieTooltips.lastGametooltipCount or QuestieTooltips.lastGametooltipType ~= "object" ) and (not self.ShownAsMapIcon) then -- We are hovering over a Questie map icon which adds it's own tooltip _QuestieTooltips:AddObjectDataToTooltip(GameTooltipTextLeft1:GetText()) QuestieTooltips.lastGametooltipCount = _QuestieTooltips:CountTooltip() end QuestieTooltips.lastGametooltip = GameTooltipTextLeft1:GetText() end end) end return QuestieTooltips