---@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 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 pairs(quest.Objectives) do objective.AlreadySpawned = {} objective.hasRegisteredTooltips = false objective.registeredItemTooltips = false end for _, objective in pairs(quest.SpecialObjectives) do objective.AlreadySpawned = {} objective.hasRegisteredTooltips = false objective.registeredItemTooltips = false end end Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieTooltips:RemoveQuest]", questId) for _, key in pairs(QuestieTooltips.lookupKeysByQuestId[questId] or {}) do --Count to see if we should remove the main object local totalCount = 0 local totalRemoved = 0 for _, tooltipData in pairs(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 pairs(tooltipDataExternal) do if (not tooltipData[questId]) then tooltipData[questId] = { title = QuestieLib:GetColoredQuestName(questId, Questie.db.profile.enableTooltipsQuestLevel, true, true) } end for playerName, _ in pairs(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 pairs(tooltipDataExternal) do if (not tooltipData[questId]) then tooltipData[questId] = { title = QuestieLib:GetColoredQuestName(questId, Questie.db.profile.enableTooltipsQuestLevel, true, true) } end for playerName, objectives in pairs(playerList) do local playerInfo = QuestiePlayer:GetPartyMemberByName(playerName); if playerInfo or QuestieComms.remotePlayerEnabled[playerName] then anotherPlayer = true; for objectiveIndex, objective in pairs(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 QuestieTooltips.lookupByKey[key] then local playerName = UnitName("player") for k, tooltip in pairs(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 not (objective.IsSourceItem or objective.IsRequiredSourceItem) 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) local text; local color = QuestieLib:GetRGBForObjective(objective) if objective.Type == "spell" and objective.spawnList[tonumber(key:sub(3))].ItemId then text = " " .. color .. tostring(QuestieDB.QueryItemSingle(objective.spawnList[tonumber(key:sub(3))].ItemId, "name")); tooltipData[questId].objectivesText[objectiveIndex][playerName] = { ["color"] = color, ["text"] = text }; elseif objective.Needed then text = " " .. color .. tostring(objective.Collected) .. "/" .. tostring(objective.Needed) .. " " .. tostring(objective.Description); tooltipData[questId].objectivesText[objectiveIndex][playerName] = { ["color"] = color, ["text"] = text }; else text = " " .. color .. tostring(objective.Description); tooltipData[questId].objectivesText[objectiveIndex][playerName] = { ["color"] = color, ["text"] = text }; end end end end end local anotherPlayer = false if IsInGroup() then anotherPlayer = _FetchTooltipsForGroupMembers(key, tooltipData) end local playerName = UnitName("player") for questId, questData in pairs(tooltipData) do local hasObjective = false local tempObjectives = {} for _, playerList in pairs(questData.objectivesText or {}) do for objectivePlayerName, objectiveInfo in pairs(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 pairs(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