------------------------------------------------------ -- MetaHunt: Tooltips Module -- Standalone beast tooltip augmentation from MetaHunt data ------------------------------------------------------ local MTH_Tooltips = { name = "tooltips", enabled = true, events = { "UPDATE_MOUSEOVER_UNIT", "UNIT_NAME_UPDATE", }, initialized = false, beastIndex = nil, vendorIndex = nil, debugState = { events = 0, pollTicks = 0, pollTriggers = 0, injectAttempts = 0, added = 0, lookupMiss = 0, skipNoUnit = 0, skipPlayerControlled = 0, skipTooltipApi = 0, skipNoName = 0, skipExistingLine = 0, lastUnitName = "", lastNormalized = "", lastOutcome = "", }, hookInstalled = false, injectFrame = nil, pollFrame = nil, pollElapsed = 0, hintElapsed = 0, lastMouseoverKey = "", } local function MTH_TT_CopyValue(value) if type(value) ~= "table" then return value end local copy = {} for key, entry in pairs(value) do copy[key] = MTH_TT_CopyValue(entry) end return copy end local function MTH_TT_GetOptionsStore() if MTH and MTH.GetModuleCharSavedVariables then local store = MTH:GetModuleCharSavedVariables("tooltips") if type(store) == "table" then if MTH.GetModuleSavedVariables and next(store) == nil then local accountStore = MTH:GetModuleSavedVariables("tooltips") if type(accountStore) == "table" and next(accountStore) ~= nil then for key, value in pairs(accountStore) do store[key] = MTH_TT_CopyValue(value) end end end if store.beastTooltips == nil then store.beastTooltips = true end if store.ammoVendorTooltips == nil then store.ammoVendorTooltips = true end if store.foodItemTooltips == nil then if type(FOM_Config) == "table" and FOM_Config.Tooltip ~= nil then store.foodItemTooltips = FOM_Config.Tooltip and true or false else store.foodItemTooltips = true end end if store.ownPetTooltips == nil then store.ownPetTooltips = false end return store end end return nil end local function MTH_TT_IsBeastTooltipsEnabled() local store = MTH_TT_GetOptionsStore() if type(store) == "table" then return store.beastTooltips and true or false end return true end local function MTH_TT_IsAmmoVendorTooltipsEnabled() local store = MTH_TT_GetOptionsStore() if type(store) == "table" then return store.ammoVendorTooltips and true or false end return true end local function MTH_TT_IsFoodItemTooltipsEnabled() local store = MTH_TT_GetOptionsStore() if type(store) == "table" then return store.foodItemTooltips and true or false end return true end local function MTH_TT_IsOwnPetTooltipsEnabled() local store = MTH_TT_GetOptionsStore() if type(store) == "table" then return store.ownPetTooltips and true or false end return false end local function MTH_TT_GetAutoQuestStore() local store = nil if MTH and MTH.GetModuleCharSavedVariables then store = MTH:GetModuleCharSavedVariables("autoquest") end if type(store) ~= "table" then return nil end if type(store.settings) ~= "table" then store.settings = {} end local settings = store.settings if settings.scorpokDrazial == nil then settings.scorpokDrazial = false end if settings.scorpokTooltip == nil then settings.scorpokTooltip = false end return settings end local function MTH_TT_IsScorpokAutomationEnabled() local store = MTH_TT_GetAutoQuestStore() if type(store) ~= "table" then return false end return store.scorpokDrazial and true or false end local function MTH_TT_IsScorpokTooltipEnabled() local store = MTH_TT_GetAutoQuestStore() if type(store) ~= "table" then return false end return store.scorpokTooltip and true or false end local function MTH_TT_IsAutoQuestModuleEnabled() if MTH and type(MTH.GetModule) == "function" then local module = MTH:GetModule("autoquest") if type(module) == "table" and module.enabled ~= nil then return module.enabled and true or false end end if MTH and type(MTH.IsModuleEnabled) == "function" then return MTH:IsModuleEnabled("autoquest", false) and true or false end return false end local function MTH_TT_Loc(key, fallback) if MTH and MTH.GetLocalization then return MTH:GetLocalization(key, fallback) end return fallback end local function MTH_TT_Lower(value) if value == nil then return "" end return string.lower(tostring(value)) end local function MTH_TT_Trim(value) if value == nil then return "" end local text = tostring(value) text = string.gsub(text, "^%s+", "") text = string.gsub(text, "%s+$", "") return text end local function MTH_TT_NormalizeName(value) local text = MTH_TT_Trim(value) text = MTH_TT_Lower(text) text = string.gsub(text, "[^%w%s]", " ") text = string.gsub(text, "%s+", " ") text = MTH_TT_Trim(text) return text end local function MTH_TT_Log(line) return end local function MTH_TT_StateSet(outcome, unitName, normalized) local state = MTH_Tooltips and MTH_Tooltips.debugState if not state then return end state.lastOutcome = tostring(outcome or "") state.lastUnitName = tostring(unitName or "") state.lastNormalized = tostring(normalized or "") end local function MTH_TT_BuildIndex() local index = { byName = {} } if type(MTH_DS_Beasts) ~= "table" then MTH_TT_Log("build index skipped: MTH_DS_Beasts missing") return index end local count = 0 for _, row in pairs(MTH_DS_Beasts) do if type(row) == "table" then local beastName = MTH_TT_Trim(row.name) if beastName ~= "" then local key = MTH_TT_NormalizeName(beastName) if not index.byName[key] then index.byName[key] = row count = count + 1 end end end end MTH_TT_Log("build index complete: uniqueNames=" .. tostring(count)) return index end local function MTH_TT_FindBeastRow(name) if not MTH_Tooltips.beastIndex then MTH_Tooltips.beastIndex = MTH_TT_BuildIndex() end local lookup = MTH_TT_NormalizeName(name) if lookup == "" then return nil end return MTH_Tooltips.beastIndex.byName[lookup] end local function MTH_TT_BuildVendorIndex() local index = { byName = {} } if type(MTH_DS_AmmoItems) ~= "table" or type(MTH_DS_Vendors) ~= "table" then MTH_TT_Log("build vendor index skipped: ammo items or vendors missing") return index end for _, item in pairs(MTH_DS_AmmoItems) do if type(item) == "table" and type(item.vendors) == "table" then local subtype = MTH_TT_Lower(item.subtype) local hasArrows = subtype == "arrow" local hasBullets = subtype == "bullet" if hasArrows or hasBullets then for vendorId in pairs(item.vendors) do local id = tonumber(vendorId) or vendorId local vendor = MTH_DS_Vendors[id] if type(vendor) == "table" then local vendorName = MTH_TT_Trim(vendor.name) local key = MTH_TT_NormalizeName(vendorName) if key ~= "" then local entry = index.byName[key] if not entry then entry = { name = vendorName, arrows = false, bullets = false, } index.byName[key] = entry end if hasArrows then entry.arrows = true end if hasBullets then entry.bullets = true end end end end end end end return index end local function MTH_TT_FindVendorInfo(name) if not MTH_Tooltips.vendorIndex then MTH_Tooltips.vendorIndex = MTH_TT_BuildVendorIndex() end local lookup = MTH_TT_NormalizeName(name) if lookup == "" then return nil end return MTH_Tooltips.vendorIndex.byName[lookup] end local function MTH_TT_HasExistingTooltipLine() if not GameTooltip or not GameTooltip.NumLines then return false end for i = 1, GameTooltip:NumLines() do local fontString = getglobal("GameTooltipTextLeft" .. i) if fontString and fontString.GetText then local text = fontString:GetText() if text and (string.find(text, "Pet Abilities", 1, true) or string.find(text, "MetaHunt Abilities", 1, true)) then return true end end end return false end local function MTH_TT_HasExistingVendorTooltipLine() if not GameTooltip or not GameTooltip.NumLines then return false end for i = 1, GameTooltip:NumLines() do local fontString = getglobal("GameTooltipTextLeft" .. i) if fontString and fontString.GetText then local text = fontString:GetText() if text and ( string.find(text, "Sells Arrows and Bullets", 1, true) or string.find(text, "Sells Arrows", 1, true) or string.find(text, "Sells Bullets", 1, true) ) then return true end end end return false end local function MTH_TT_HasExistingFoodTooltipLine() if not GameTooltip or not GameTooltip.NumLines then return false end local markers = { MTH_TT_Loc("TOOLTIPS_FOOD_MARKER_UNKNOWN", "can eat this, but hasn't tried it yet."), MTH_TT_Loc("TOOLTIPS_FOOD_MARKER_UNDER", "doesn't like this anymore."), MTH_TT_Loc("TOOLTIPS_FOOD_MARKER_MIGHT", "might eat this."), MTH_TT_Loc("TOOLTIPS_FOOD_MARKER_WILL", "will eat this."), MTH_TT_Loc("TOOLTIPS_FOOD_MARKER_LIKE", "likes to eat this."), MTH_TT_Loc("TOOLTIPS_FOOD_MARKER_LOVE", "loves to eat this."), } for i = 1, GameTooltip:NumLines() do local fontString = getglobal("GameTooltipTextLeft" .. i) if fontString and fontString.GetText then local text = fontString:GetText() if text then for j = 1, table.getn(markers) do local marker = markers[j] if marker and marker ~= "" and string.find(text, marker, 1, true) then return true end end end end end return false end local function MTH_TT_ExtractItemIdFromLink(link) if type(link) ~= "string" then return nil end local _, _, itemIdText = string.find(link, "item:(%d+):") if itemIdText then return tonumber(itemIdText) end return nil end local function MTH_TT_GetQuestColor(name, defaultR, defaultG, defaultB) if type(QuestDifficultyColor) == "table" then local color = QuestDifficultyColor[name] if type(color) == "table" and color.r and color.g and color.b then return color.r, color.g, color.b end end return defaultR, defaultG, defaultB end local function MTH_TT_FormatElapsedStableStyle(timestamp) local value = tonumber(timestamp) if not value or value <= 0 then return nil end local now = tonumber(time()) or 0 local elapsed = now - value if elapsed < 0 then elapsed = 0 end local days = math.floor(elapsed / 86400) local remAfterDays = elapsed - (days * 86400) local hours = math.floor(remAfterDays / 3600) local remAfterHours = remAfterDays - (hours * 3600) local mins = math.floor(remAfterHours / 60) if days > 0 then if hours > 0 then return tostring(days) .. " days " .. tostring(hours) .. " hours" end return tostring(days) .. " days" end if hours > 0 then if mins > 0 then return tostring(hours) .. " hours " .. tostring(mins) .. " minutes" end return tostring(hours) .. " hours" end return tostring(mins) .. " minutes" end local function MTH_TT_GetTooltipUnitToken() if not GameTooltip or type(GameTooltip.GetUnit) ~= "function" then return nil end local ok, unitA, unitB = pcall(function() return GameTooltip:GetUnit() end) if not ok then return nil end if type(unitB) == "string" and unitB ~= "" then return unitB end if type(unitA) == "string" and unitA ~= "" then return unitA end return nil end local function MTH_TT_IsOwnerUnderPetFrame(owner) if not owner then return false end local petFrame = (_G and _G["PetFrame"]) or (type(getglobal) == "function" and getglobal("PetFrame")) local petFrameHappiness = (_G and _G["PetFrameHappiness"]) or (type(getglobal) == "function" and getglobal("PetFrameHappiness")) local cursor = owner local guard = 0 while cursor and guard < 20 do if cursor == petFrame or cursor == petFrameHappiness then return true end if type(cursor.GetParent) == "function" then cursor = cursor:GetParent() else break end guard = guard + 1 end return false end local function MTH_TT_IsMouseOverPetFrame() if type(MouseIsOver) ~= "function" then return false end local petFrame = (_G and _G["PetFrame"]) or (type(getglobal) == "function" and getglobal("PetFrame")) local petFrameHappiness = (_G and _G["PetFrameHappiness"]) or (type(getglobal) == "function" and getglobal("PetFrameHappiness")) if petFrame and MouseIsOver(petFrame) then return true end if petFrameHappiness and MouseIsOver(petFrameHappiness) then return true end return false end local function MTH_TT_IsTooltipOnOwnPet() if type(UnitExists) ~= "function" or type(UnitIsUnit) ~= "function" then return false end if not UnitExists("pet") then return false end local owner = nil local ownerName = "" if GameTooltip and type(GameTooltip.GetOwner) == "function" then owner = GameTooltip:GetOwner() if owner and owner.GetName then ownerName = tostring(owner:GetName() or "") end end if ownerName ~= "" then if string.find(ownerName, "PetActionButton", 1, true) or string.find(ownerName, "PetActionBar", 1, true) or string.find(ownerName, "BT4PetButton", 1, true) then return false end end local token = MTH_TT_GetTooltipUnitToken() if token and token ~= "" then local ok, same = pcall(UnitIsUnit, token, "pet") if ok and same then return true end end if UnitExists("mouseover") then local ok, same = pcall(UnitIsUnit, "mouseover", "pet") if ok and same then return true end end if owner then if MTH_TT_IsOwnerUnderPetFrame(owner) then return true end if ownerName ~= "" and string.find(ownerName, "PetFrame", 1, true) then return true end end if MTH_TT_IsMouseOverPetFrame() then return true end local hasMouseover = UnitExists("mouseover") and true or false if (not hasMouseover) and (token == nil or token == "") then local petName = (type(UnitName) == "function") and tostring(UnitName("pet") or "") or "" if petName ~= "" then local titleRegion = (type(getglobal) == "function" and getglobal("GameTooltipTextLeft1")) or (_G and _G["GameTooltipTextLeft1"]) if titleRegion and type(titleRegion.GetText) == "function" then local titleText = tostring(titleRegion:GetText() or "") if titleText ~= "" and MTH_TT_NormalizeName(titleText) == MTH_TT_NormalizeName(petName) then return true end end end end return false end local function MTH_TT_HasExistingOwnPetTooltipLines() if not GameTooltip or not GameTooltip.NumLines then return false end local function hasMarker(text) text = tostring(text or "") if text == "" then return false end if string.find(text, "Mood :", 1, true) or string.find(text, "Loyalty :", 1, true) or string.find(text, "XP :", 1, true) or string.find(text, "By my side for:", 1, true) or string.find(text, "Has eaten:", 1, true) or string.find(text, "times this session", 1, true) then return true end return false end for i = 1, GameTooltip:NumLines() do local left = getglobal("GameTooltipTextLeft" .. i) if left and left.GetText then if hasMarker(left:GetText()) then return true end end local right = getglobal("GameTooltipTextRight" .. i) if right and right.GetText then if hasMarker(right:GetText()) then return true end end end return false end local function MTH_TT_GetHeatColorByRatio(ratio) ratio = tonumber(ratio) or 0 if ratio < 0 then ratio = 0 end if ratio > 1 then ratio = 1 end local red = 1 - ratio local green = ratio local blue = 0.15 return red, green, blue end local function MTH_TT_GetLoyaltyHeatColor(level) local numeric = tonumber(level) if not numeric then return 0.90, 0.90, 0.90 end if numeric < 1 then numeric = 1 end if numeric > 6 then numeric = 6 end return MTH_TT_GetHeatColorByRatio((numeric - 1) / 5) end local function MTH_TT_FormatPercentCompact(percentValue) local numeric = tonumber(percentValue) if not numeric then return "?" end local rounded = math.floor((numeric * 10) + 0.5) / 10 local integerPart = math.floor(rounded) if math.abs(rounded - integerPart) < 0.001 then return tostring(integerPart) end return string.format("%.1f", rounded) end local function MTH_TT_ColorizeDigitsBlue(text) local source = tostring(text or "") if source == "" then return "" end return string.gsub(source, "(%d+)", "|cff73bfff%1|r") end local MTH_TT_SCORPOK_NPC_NAME = "Bloodmage Drazial" local MTH_TT_SCORPOK_TARGET_NPC_IDS = { [7505] = true, [5988] = true, [5982] = true, [5992] = true, [5993] = true, } local MTH_TT_SCORPOK_TARGET_NPC_NAMES = { [MTH_TT_NormalizeName("Bloodmage Drazial")] = true, [MTH_TT_NormalizeName("Scorpok Stinger")] = true, [MTH_TT_NormalizeName("Black Slayer")] = true, [MTH_TT_NormalizeName("Ashmane Boar")] = true, [MTH_TT_NormalizeName("Helboar")] = true, } local MTH_TT_ITEM_SCORPOK_PINCER_ID = 8393 local MTH_TT_ITEM_VULTURE_GIZZARD_ID = 8396 local MTH_TT_ITEM_BLASTED_BOAR_LUNG_ID = 8392 local MTH_TT_ITEM_GROUND_SCORPOK_ASSAY_ID = 8412 local function MTH_TT_ParseNpcIdFromGuid(guid) if type(guid) ~= "string" or guid == "" then return nil end local _, _, _, _, _, npcId = string.find(guid, "^Creature%-%d+%-%d+%-%d+%-%d+%-(%d+)%-%x+$") if npcId then return tonumber(npcId) end if string.sub(guid, 1, 2) == "0x" and string.len(guid) >= 12 then local hex = string.sub(guid, 9, 12) local parsed = tonumber(hex, 16) if parsed and parsed > 0 then return parsed end end return nil end local function MTH_TT_GetBagItemCount(itemId) local getContainerNumSlots = _G and _G["GetContainerNumSlots"] or nil local getContainerItemLink = _G and _G["GetContainerItemLink"] or nil local getContainerItemInfo = _G and _G["GetContainerItemInfo"] or nil if type(getContainerNumSlots) ~= "function" or type(getContainerItemLink) ~= "function" then return 0 end local total = 0 for bag = 0, 4 do local slots = math.floor(tonumber(getContainerNumSlots(bag)) or 0) for slot = 1, slots do local link = getContainerItemLink(bag, slot) if type(link) == "string" and link ~= "" then local _, _, foundText = string.find(link, "item:(%d+)") local found = tonumber(foundText) if found and tonumber(found) == tonumber(itemId) then local count = 1 if type(getContainerItemInfo) == "function" then local _, stackCount = getContainerItemInfo(bag, slot) count = math.floor(tonumber(stackCount) or 1) end total = total + count end end end end return total end local function MTH_TT_ColorCountByThreshold(value, threshold) local n = math.floor(tonumber(value) or 0) if n <= 0 then return "|cffff0000" .. tostring(n) .. "|r" end local modFn = nil if type(rawget) == "function" and type(math) == "table" then modFn = rawget(math, "mod") or rawget(math, "fmod") elseif type(math) == "table" then modFn = math.fmod end if threshold > 0 and type(modFn) == "function" and modFn(n, threshold) == 0 then return "|cff00ff00" .. tostring(n) .. "|r" end return "|cffffa500" .. tostring(n) .. "|r" end local function MTH_TT_ColorAssayCount(value) local n = math.floor(tonumber(value) or 0) if n > 0 then return "|cff73bfff" .. tostring(n) .. "|r" end return tostring(n) end local function MTH_TT_HasScorpokTooltipLine() if not GameTooltip or not GameTooltip.NumLines then return false end for i = 1, GameTooltip:NumLines() do local left = getglobal("GameTooltipTextLeft" .. i) if left and left.GetText then local text = tostring(left:GetText() or "") if string.find(text, "MTH Auto Quest", 1, true) or string.find(text, "Ground Scorpok Assay", 1, true) then return true end end end return false end local function MTH_TT_IsScorpokTooltipZoneAllowed() local getRealZoneText = _G and _G["GetRealZoneText"] or nil local getZoneText = _G and _G["GetZoneText"] or nil local zoneName = "" if type(getRealZoneText) == "function" then zoneName = tostring(getRealZoneText() or "") end if zoneName == "" and type(getZoneText) == "function" then zoneName = tostring(getZoneText() or "") end if zoneName == "" then return true end return MTH_TT_NormalizeName(zoneName) == MTH_TT_NormalizeName("Blasted Lands") end local function MTH_TT_AddScorpokTargetsTooltip(unit, unitName) if not MTH_TT_IsAutoQuestModuleEnabled() then return false end if not MTH_TT_IsScorpokAutomationEnabled() or not MTH_TT_IsScorpokTooltipEnabled() then return false end if not MTH_TT_IsScorpokTooltipZoneAllowed() then return false end if MTH_TT_HasScorpokTooltipLine() then return false end local npcId = nil if type(UnitGUID) == "function" then npcId = MTH_TT_ParseNpcIdFromGuid(UnitGUID(unit)) end local isDrazial = false if tonumber(npcId) == 7505 then isDrazial = true elseif MTH_TT_Lower(unitName) == MTH_TT_Lower(MTH_TT_SCORPOK_NPC_NAME) then isDrazial = true end local isTargetNpc = false if npcId and MTH_TT_SCORPOK_TARGET_NPC_IDS[tonumber(npcId)] then isTargetNpc = true end if not isTargetNpc then local normalizedName = MTH_TT_NormalizeName(unitName) isTargetNpc = MTH_TT_SCORPOK_TARGET_NPC_NAMES[normalizedName] and true or false if not isTargetNpc then isTargetNpc = MTH_TT_Lower(unitName) == MTH_TT_Lower(MTH_TT_SCORPOK_NPC_NAME) end end if not isTargetNpc then return false end local pincer = MTH_TT_GetBagItemCount(MTH_TT_ITEM_SCORPOK_PINCER_ID) local gizzard = MTH_TT_GetBagItemCount(MTH_TT_ITEM_VULTURE_GIZZARD_ID) local lung = MTH_TT_GetBagItemCount(MTH_TT_ITEM_BLASTED_BOAR_LUNG_ID) local assayInBag = MTH_TT_GetBagItemCount(MTH_TT_ITEM_GROUND_SCORPOK_ASSAY_ID) local assaysToGet = math.floor(math.min((pincer / 3), (gizzard / 2), lung)) GameTooltip:AddLine(" ") GameTooltip:AddLine("|cffffff00MTH Auto Quest|r") GameTooltip:AddLine(" - Scorpok Pincer - " .. MTH_TT_ColorCountByThreshold(pincer, 3), 1, 1, 1) GameTooltip:AddLine(" - Vulture Gizzard - " .. MTH_TT_ColorCountByThreshold(gizzard, 2), 1, 1, 1) GameTooltip:AddLine(" - Blasted Boar Lung - " .. MTH_TT_ColorCountByThreshold(lung, 1), 1, 1, 1) local assaysToGetText = "|cff73bfff" .. tostring(assaysToGet) .. "|r" GameTooltip:AddLine( "Assays to get : " .. assaysToGetText .. " | Assays in bag : " .. MTH_TT_ColorAssayCount(assayInBag), 1, 1, 1 ) if isDrazial then GameTooltip:AddLine("Spam SHIFT-click!", 0.7, 0.9, 1) end GameTooltip:AddLine("Recipes: 3x Pincer, 2x Gizzard, 1x Lung", 0.7, 0.7, 0.7) return true end local function MTH_TT_AddOwnPetTooltipHint() if not (MTH_Tooltips and MTH_Tooltips.enabled) then return end if not MTH_TT_IsOwnPetTooltipsEnabled() then return end if not GameTooltip or not GameTooltip.AddLine then return end if MTH_TT_HasExistingOwnPetTooltipLines() then return end if not MTH_TT_IsTooltipOnOwnPet() then return end if type(MTH_GetCurrentPetInfo) ~= "function" then return end local petInfo = MTH_GetCurrentPetInfo() if type(petInfo) ~= "table" then return end local happinessValue = tonumber(petInfo.happiness) local happinessText = "?" local happinessR, happinessG, happinessB = 0.90, 0.90, 0.90 if type(GetPetHappiness) == "function" then local h = GetPetHappiness() if h ~= nil then happinessValue = tonumber(h) or happinessValue end end if happinessValue == 1 then happinessText = "Unhappy" happinessR, happinessG, happinessB = 1.00, 0.25, 0.25 elseif happinessValue == 2 then happinessText = "Content" happinessR, happinessG, happinessB = 1.00, 0.90, 0.20 elseif happinessValue == 3 then happinessText = "Happy" happinessR, happinessG, happinessB = 0.25, 1.00, 0.25 end if type(GameTooltip.AddDoubleLine) == "function" then GameTooltip:AddDoubleLine("Mood :", tostring(happinessText), 0.90, 0.90, 0.90, happinessR, happinessG, happinessB) else GameTooltip:AddLine("Mood : " .. tostring(happinessText), happinessR, happinessG, happinessB) end local loyaltyDisplay = tostring(petInfo.loyaltyDisplay or "") if loyaltyDisplay == "" then local loyaltyText = tostring(petInfo.loyaltyText or "") if loyaltyText ~= "" then loyaltyDisplay = loyaltyText end end if loyaltyDisplay ~= "" then local loyaltyR, loyaltyG, loyaltyB = MTH_TT_GetLoyaltyHeatColor(petInfo.loyaltyLevel) if type(GameTooltip.AddDoubleLine) == "function" then GameTooltip:AddDoubleLine("Loyalty :", tostring(loyaltyDisplay), 0.90, 0.90, 0.90, loyaltyR, loyaltyG, loyaltyB) else GameTooltip:AddLine("Loyalty : " .. tostring(loyaltyDisplay), loyaltyR, loyaltyG, loyaltyB) end end local petLevel = tonumber(petInfo.level) local xpCur = tonumber(petInfo.xp) local xpTot = tonumber(petInfo.xpMax) if (petLevel == nil or petLevel < 60) and xpCur ~= nil and xpTot ~= nil and xpTot > 0 then local xpPercent = tonumber(petInfo.xpPercent) if xpPercent == nil then xpPercent = math.floor((xpCur / xpTot) * 1000 + 0.5) / 10 end local xpRatio = xpCur / xpTot local xpR, xpG, xpB = MTH_TT_GetHeatColorByRatio(xpRatio) local xpCurInt = math.floor(xpCur + 0.5) local xpTotInt = math.floor(xpTot + 0.5) local xpValue = MTH_TT_FormatPercentCompact(xpPercent) .. "% (" .. tostring(xpCurInt) .. "/" .. tostring(xpTotInt) .. ")" if type(GameTooltip.AddDoubleLine) == "function" then GameTooltip:AddDoubleLine("XP :", xpValue, 0.90, 0.90, 0.90, xpR, xpG, xpB) else GameTooltip:AddLine("XP : " .. xpValue, xpR, xpG, xpB) end end local withMeAt = tonumber(petInfo.withMeSinceAt) or nil local withMeText = MTH_TT_FormatElapsedStableStyle(withMeAt) if withMeText and withMeText ~= "" then local withMeValue = MTH_TT_ColorizeDigitsBlue(tostring(withMeText)) if type(GameTooltip.AddDoubleLine) == "function" then GameTooltip:AddDoubleLine("By my side for:", withMeValue, 0.90, 0.90, 0.90, 0.90, 0.90, 0.90) else GameTooltip:AddLine("By my side for: " .. withMeValue, 0.90, 0.90, 0.90) end end local sessionFeeds = 0 if type(MTH_FEED_GetSessionFeedCount) == "function" then sessionFeeds = tonumber(MTH_FEED_GetSessionFeedCount(petInfo.id)) or 0 end local feedValue = MTH_TT_ColorizeDigitsBlue(tostring(sessionFeeds) .. " times this session") if type(GameTooltip.AddDoubleLine) == "function" then GameTooltip:AddDoubleLine("Has eaten:", feedValue, 0.90, 0.90, 0.90, 0.90, 0.90, 0.90) else GameTooltip:AddLine("Has eaten: " .. feedValue, 0.90, 0.90, 0.90) end GameTooltip:Show() end local function MTH_TT_BuildFoodOpinionLine(itemLink) if type(itemLink) ~= "string" then return nil end local pet = type(MTH_FEED_GetCurrentPetContext) == "function" and MTH_FEED_GetCurrentPetContext() or nil if type(pet) ~= "table" or not pet.liveExists then return nil end local petName = MTH_TT_Trim(pet.name) if petName == "" then return nil end local itemId = MTH_TT_ExtractItemIdFromLink(itemLink) if not itemId then return nil end if type(FOM_IsInDiet) == "function" then local ok, inDiet = pcall(FOM_IsInDiet, itemId) if not ok or not inDiet then return nil end elseif type(MTH_FEED_GetFoodMeta) == "function" then local foodMeta = MTH_FEED_GetFoodMeta(itemId) if type(foodMeta) ~= "table" or not foodMeta.diet then return nil end end local realmPlayer = tostring(FOM_RealmPlayer or "") if realmPlayer == "" and type(GetCVar) == "function" and type(UnitName) == "function" then local realmName = tostring(GetCVar("realmName") or "") local playerName = tostring(UnitName("player") or "") if realmName ~= "" and playerName ~= "" then realmPlayer = realmName .. "." .. playerName end end local absoluteQuality = nil if realmPlayer ~= "" and type(FOM_FoodQuality) == "table" then local realmMap = FOM_FoodQuality[realmPlayer] if type(realmMap) == "table" then local petMap = realmMap[petName] if type(petMap) == "table" then absoluteQuality = petMap[itemId] end end end if absoluteQuality == nil then local text = string.format(MTH_TT_Loc("TOOLTIPS_FOOD_QUALITY_UNKNOWN", "%s can eat this, but hasn't tried it yet."), petName) local color = HIGHLIGHT_FONT_COLOR if color and color.r and color.g and color.b then return text, color.r, color.g, color.b end return text, 1.0, 0.82, 0.0 end local petLevel = tonumber(pet.level) or 0 if petLevel <= 0 then return nil end local currentQuality = tonumber(absoluteQuality) / petLevel if currentQuality < 0 then local r, g, b = MTH_TT_GetQuestColor("trivial", 0.5, 0.5, 0.5) return string.format(MTH_TT_Loc("TOOLTIPS_FOOD_QUALITY_UNDER", "%s doesn't like this anymore."), petName), r, g, b elseif currentQuality == 0 then local r, g, b = MTH_TT_GetQuestColor("trivial", 0.5, 0.5, 0.5) return string.format(MTH_TT_Loc("TOOLTIPS_FOOD_QUALITY_MIGHT", "%s might eat this."), petName), r, g, b elseif currentQuality <= 8 then local r, g, b = MTH_TT_GetQuestColor("standard", 1.0, 1.0, 0.0) return string.format(MTH_TT_Loc("TOOLTIPS_FOOD_QUALITY_WILL", "%s will eat this."), petName), r, g, b elseif currentQuality <= 17 then local r, g, b = MTH_TT_GetQuestColor("difficult", 1.0, 0.5, 0.0) return string.format(MTH_TT_Loc("TOOLTIPS_FOOD_QUALITY_LIKE", "%s likes to eat this."), petName), r, g, b elseif currentQuality <= 35 then local r, g, b = MTH_TT_GetQuestColor("verydifficult", 1.0, 0.1, 0.1) return string.format(MTH_TT_Loc("TOOLTIPS_FOOD_QUALITY_LOVE", "%s loves to eat this."), petName), r, g, b end return nil end local function MTH_TT_AddFoodTooltipHint() if not (MTH_Tooltips and MTH_Tooltips.enabled) then return end if not MTH_TT_IsFoodItemTooltipsEnabled() then return end if not GameTooltip or not GameTooltip.GetItem or not GameTooltip.AddLine then return end if MTH_TT_HasExistingFoodTooltipLine() then return end local _, itemLink = GameTooltip:GetItem() if not itemLink then return end local text, r, g, b = MTH_TT_BuildFoodOpinionLine(itemLink) if text and text ~= "" then GameTooltip:AddLine(text, r or 1.0, g or 1.0, b or 1.0) GameTooltip:Show() end end local function MTH_TT_HasExistingNotLearnedLine() if not GameTooltip or not GameTooltip.NumLines then return false end for i = 1, GameTooltip:NumLines() do local fontString = getglobal("GameTooltipTextLeft" .. i) if fontString and fontString.GetText then local text = fontString:GetText() if text and string.find(text, "Not learned yet, keep going !", 1, true) then return true end end end return false end local function MTH_TT_GetKnownSpellMap() if type(MTH_GetKnownPetSpellMap) == "function" then return MTH_GetKnownPetSpellMap() end if not MTH_CharSavedVariables or not MTH_CharSavedVariables.petTraining then return nil end return MTH_CharSavedVariables.petTraining.spellMap end local function MTH_TT_ParseAbilityToken(token) local text = MTH_TT_Trim(token) if text == "" then return "", "", nil end local _, _, abilityName, rankText = string.find(text, "^(.-)%s+(%d+)$") if abilityName and abilityName ~= "" then abilityName = MTH_TT_Trim(abilityName) return abilityName, MTH_TT_Lower(abilityName), tonumber(rankText) end return text, MTH_TT_Lower(text), nil end local function MTH_TT_HunterKnowsAbility(abilityLower, rankNumber) if abilityLower == "" then return false end -- Prefer the authoritative pet-training check: it is rank-strict and also consults the -- hunter's learned-ability map, so a freshly learned rank counts even before a full -- Beast Training rescan has rebuilt the spell-map rows the fallback below relies on. if type(MTH_IsPetAbilityKnownByHunter) == "function" then if MTH_IsPetAbilityKnownByHunter(abilityLower, rankNumber) then return true end end local spellMap = MTH_TT_GetKnownSpellMap() if type(spellMap) ~= "table" then return false end for _, row in pairs(spellMap) do if row and row.name and row.isKnown ~= false then if MTH_TT_Lower(row.name) == abilityLower then if rankNumber then if tonumber(row.rankNumber) == tonumber(rankNumber) then return true end else return true end end end end return false end local function MTH_TT_FindAbilityCanonicalName(abilityName) local name = MTH_TT_Trim(abilityName) if name == "" then return nil end if not (MTH_DS_PetSpells and type(MTH_DS_PetSpells.byAbility) == "table") then return nil end if MTH_DS_PetSpells.byAbility[name] then return name end local key = MTH_TT_Lower(name) for abilityKey in pairs(MTH_DS_PetSpells.byAbility) do if MTH_TT_Lower(abilityKey) == key then return abilityKey end end return nil end local function MTH_TT_AbilityHasPositiveRanks(canonicalAbilityName) if canonicalAbilityName == nil or canonicalAbilityName == "" then return false end if not (MTH_DS_PetSpells and type(MTH_DS_PetSpells.byAbility) == "table") then return false end local bucket = MTH_DS_PetSpells.byAbility[canonicalAbilityName] if not (bucket and type(bucket.spells) == "table") then return false end for i = 1, table.getn(bucket.spells) do local spell = bucket.spells[i] if spell then local rank = tonumber(spell.rankNumber) if not rank and spell.rank then local _, _, parsed = string.find(tostring(spell.rank), "(%d+)") rank = tonumber(parsed) end if rank and rank > 0 then return true end end end return false end local function MTH_TT_ParseRankFromText(value, allowTrailingNumber) local text = MTH_TT_Trim(value) if text == "" then return nil end local _, _, rankText = string.find(text, "[Rr]ank%s*(%d+)") if rankText then return tonumber(rankText) end if allowTrailingNumber == true then local _, _, trailing = string.find(text, "(%d+)$") if trailing then return tonumber(trailing) end end return nil end local function MTH_TT_CleanSpellName(value) local text = MTH_TT_Trim(value) if text == "" then return "" end text = string.gsub(text, "%s*%([Rr]ank%s*%d+%)", "") text = string.gsub(text, "%s+[Rr]ank%s*%d+$", "") text = string.gsub(text, "%s+%d+$", "") text = MTH_TT_Trim(text) return text end local function MTH_TT_GetTooltipSpellNameAndRank() local spellName = nil local rankNumber = nil if GameTooltip and type(GameTooltip.GetSpell) == "function" then local ok, a, b = pcall(function() return GameTooltip:GetSpell() end) if ok then if type(a) == "string" then spellName = a rankNumber = MTH_TT_ParseRankFromText(b, true) elseif type(b) == "string" then spellName = b rankNumber = MTH_TT_ParseRankFromText(a, true) end end end if not spellName or spellName == "" then local left1 = getglobal("GameTooltipTextLeft1") if left1 and left1.GetText then spellName = MTH_TT_Trim(left1:GetText()) end end if not rankNumber then rankNumber = MTH_TT_ParseRankFromText(spellName, true) end if not rankNumber then local right1 = getglobal("GameTooltipTextRight1") if right1 and right1.GetText then rankNumber = MTH_TT_ParseRankFromText(right1:GetText(), false) end end if not rankNumber then local left2 = getglobal("GameTooltipTextLeft2") if left2 and left2.GetText then rankNumber = MTH_TT_ParseRankFromText(left2:GetText(), false) end end if not rankNumber then local right2 = getglobal("GameTooltipTextRight2") if right2 and right2.GetText then rankNumber = MTH_TT_ParseRankFromText(right2:GetText(), false) end end if (not rankNumber) and GameTooltip and GameTooltip.NumLines then for i = 2, GameTooltip:NumLines() do local left = getglobal("GameTooltipTextLeft" .. i) if left and left.GetText then local text = MTH_TT_Trim(left:GetText()) local parsed = MTH_TT_ParseRankFromText(text, false) if parsed and parsed > 0 then rankNumber = parsed break end end local right = getglobal("GameTooltipTextRight" .. i) if right and right.GetText then local parsedRight = MTH_TT_ParseRankFromText(right:GetText(), false) if parsedRight and parsedRight > 0 then rankNumber = parsedRight break end end end end return MTH_TT_CleanSpellName(spellName), rankNumber end local function MTH_TT_GetCurrentPetRowForTooltip() local pets = nil if type(MTH_PETS_GetRootStore) == "function" then pets = MTH_PETS_GetRootStore() elseif type(MTH_CharSavedVariables) == "table" then pets = MTH_CharSavedVariables.MTH_Pets end if type(pets) ~= "table" then return nil end local currentId = pets.currentPetId if (currentId == nil or tostring(currentId) == "") and type(pets.currentPet) == "table" and pets.currentPet.exists == true then currentId = pets.currentPet.id end if currentId == nil or tostring(currentId) == "" then return nil end local activeById = type(pets.petStore) == "table" and pets.petStore.activeById or nil if type(activeById) ~= "table" then return nil end return activeById[currentId] or activeById[tostring(currentId)] end local function MTH_TT_CurrentPetKnowsAbility(abilityLower, rankNumber) if abilityLower == "" then return false end local row = MTH_TT_GetCurrentPetRowForTooltip() if type(row) ~= "table" then return false end local wantedRank = tonumber(rankNumber) local spellbook = row.petSpellbook if type(spellbook) == "table" and type(spellbook.spells) == "table" then for i = 1, table.getn(spellbook.spells) do local spell = spellbook.spells[i] if type(spell) == "table" then local spellLower = MTH_TT_Lower(MTH_TT_CleanSpellName(spell.name)) if spellLower == abilityLower then if wantedRank then local spellRank = tonumber(spell.rank) if not spellRank then spellRank = MTH_TT_ParseRankFromText(spell.name, true) end if spellRank and tonumber(spellRank) == wantedRank then return true end else return true end end end end end if type(row.abilities) == "table" then for _, ability in pairs(row.abilities) do if type(ability) == "table" then local abilityLowerName = MTH_TT_Lower(MTH_TT_CleanSpellName(ability.name)) if abilityLowerName == abilityLower then if wantedRank then local abilityRank = tonumber(ability.rank) if abilityRank and abilityRank == wantedRank then return true end else return true end end end end end return false end local function MTH_TT_IsPetActionTooltipOwner() if not GameTooltip or type(GameTooltip.GetOwner) ~= "function" then return false end local owner = GameTooltip:GetOwner() if not owner then return false end local ownerName = owner.GetName and owner:GetName() or "" ownerName = tostring(ownerName or "") if ownerName ~= "" and ( string.find(ownerName, "PetActionButton", 1, true) or string.find(ownerName, "PetActionBar", 1, true) or string.find(ownerName, "BT4PetButton", 1, true) ) then return true end return false end local function MTH_TT_IsSpellOnPetActionBar(spellName) local wanted = MTH_TT_Lower(MTH_TT_CleanSpellName(spellName)) if wanted == "" then return false end if type(GetPetActionInfo) ~= "function" then return false end for slot = 1, 12 do local name = GetPetActionInfo(slot) if type(name) == "string" and name ~= "" then local normalized = MTH_TT_Lower(MTH_TT_CleanSpellName(name)) if normalized == wanted then return true end end end return false end local function MTH_TT_GetTooltipOwnerName() if not GameTooltip or type(GameTooltip.GetOwner) ~= "function" then return "" end local owner = GameTooltip:GetOwner() if not owner then return "" end if owner.GetName then local name = tostring(owner:GetName() or "") if name ~= "" then return name end end return "" end local function MTH_TT_AddPetActionNotLearnedHint() if not (MTH_Tooltips and MTH_Tooltips.enabled) then return end if not GameTooltip or not GameTooltip.AddLine then return end if MTH_TT_HasExistingNotLearnedLine() then return end local spellName, rankNumber = MTH_TT_GetTooltipSpellNameAndRank() if not spellName or spellName == "" then return end local ownerMatch = MTH_TT_IsPetActionTooltipOwner() local barMatch = MTH_TT_IsSpellOnPetActionBar(spellName) if not ownerMatch and not barMatch then return end local canonical = MTH_TT_FindAbilityCanonicalName(spellName) if not canonical then return end if rankNumber and rankNumber > 0 and not MTH_TT_AbilityHasPositiveRanks(canonical) then rankNumber = nil end local canonicalLower = MTH_TT_Lower(canonical) local hunterKnown = MTH_TT_HunterKnowsAbility(canonicalLower, rankNumber) -- The hint reflects whether the HUNTER has learned this ability rank. We must NOT -- suppress it just because the current pet knows it: taming a new beast is exactly -- when you want to see which of its abilities you still have to learn. if hunterKnown then return end GameTooltip:AddLine("Not learned yet, keep going ! ", 1.0, 0.25, 0.25) GameTooltip:Show() end local function MTH_TT_ParseAbilities(abilitiesText) local text = MTH_TT_Trim(abilitiesText) local parsed = {} if text == "" or text == "None" then return parsed end local startPos = 1 local len = string.len(text) while startPos <= len do local commaPos = string.find(text, ",", startPos, true) local token if commaPos then token = string.sub(text, startPos, commaPos - 1) startPos = commaPos + 1 else token = string.sub(text, startPos) startPos = len + 1 end token = MTH_TT_Trim(token) if token ~= "" then local _, abilityLower, rankNumber = MTH_TT_ParseAbilityToken(token) local known = MTH_TT_HunterKnowsAbility(abilityLower, rankNumber) table.insert(parsed, { text = token, known = known }) end end return parsed end local function MTH_TT_AddTooltip(unit) local state = MTH_Tooltips.debugState state.injectAttempts = (state.injectAttempts or 0) + 1 if not UnitExists(unit) then state.skipNoUnit = (state.skipNoUnit or 0) + 1 MTH_TT_StateSet("skip-no-unit") return end if UnitPlayerControlled(unit) then state.skipPlayerControlled = (state.skipPlayerControlled or 0) + 1 MTH_TT_StateSet("skip-player-controlled") return end if not GameTooltip or not GameTooltip.AddLine then state.skipTooltipApi = (state.skipTooltipApi or 0) + 1 MTH_TT_StateSet("skip-tooltip-api") MTH_TT_Log("skip: GameTooltip API unavailable") return end local name = UnitName(unit) if not name or name == "" then state.skipNoName = (state.skipNoName or 0) + 1 MTH_TT_StateSet("skip-no-name") MTH_TT_Log("skip: mouseover has no unit name") return end local normalized = MTH_TT_NormalizeName(name) MTH_TT_StateSet("lookup", name, normalized) local beastTooltipsEnabled = MTH_TT_IsBeastTooltipsEnabled() local ammoVendorTooltipsEnabled = MTH_TT_IsAmmoVendorTooltipsEnabled() local vendorInfo = ammoVendorTooltipsEnabled and MTH_TT_FindVendorInfo(name) or nil local row = beastTooltipsEnabled and MTH_TT_FindBeastRow(name) or nil local scorpokAdded = MTH_TT_AddScorpokTargetsTooltip(unit, name) if not row and not vendorInfo and not scorpokAdded then state.lookupMiss = (state.lookupMiss or 0) + 1 MTH_TT_StateSet("lookup-miss", name, normalized) MTH_TT_Log("lookup miss: unit='" .. tostring(name) .. "' normalized='" .. tostring(normalized) .. "'") return end local addedAnything = nil if scorpokAdded then addedAnything = 1 end if vendorInfo and (vendorInfo.arrows or vendorInfo.bullets) and not MTH_TT_HasExistingVendorTooltipLine() then if vendorInfo.arrows and vendorInfo.bullets then GameTooltip:AddLine("Sells Arrows and Bullets", 0.45, 0.90, 1.00) elseif vendorInfo.arrows then GameTooltip:AddLine("Sells Arrows", 0.45, 0.90, 1.00) elseif vendorInfo.bullets then GameTooltip:AddLine("Sells Bullets", 0.45, 0.90, 1.00) end addedAnything = 1 MTH_TT_Log("vendor tooltip added: unit='" .. tostring(name) .. "'") end local abilitiesSource = row and row.abilities or nil if abilitiesSource then local abilities = MTH_TT_ParseAbilities(abilitiesSource) if table.getn(abilities) == 0 then MTH_TT_StateSet("skip-no-abilities", name, normalized) MTH_TT_Log("skip: beast has no abilities ('" .. tostring(name) .. "')") elseif MTH_TT_HasExistingTooltipLine() then state.skipExistingLine = (state.skipExistingLine or 0) + 1 MTH_TT_StateSet("skip-existing-line", name, normalized) MTH_TT_Log("skip: tooltip line already present for '" .. tostring(name) .. "'") else GameTooltip:AddLine("Pet Abilities:", 1.0, 0.95, 0.45) for i = 1, table.getn(abilities) do local entry = abilities[i] if entry and entry.known then GameTooltip:AddLine(" " .. tostring(entry.text or ""), 0.2, 1.0, 0.2) else GameTooltip:AddLine(" " .. tostring(entry and entry.text or ""), 1.0, 0.25, 0.25) end end addedAnything = 1 MTH_TT_Log("beast tooltip added: unit='" .. tostring(name) .. "' entries='" .. tostring(table.getn(abilities)) .. "'") end end if addedAnything then GameTooltip:Show() state.added = (state.added or 0) + 1 MTH_TT_StateSet("added", name, normalized) end end local function MTH_TT_EnsureInjectFrame() if MTH_Tooltips.injectFrame then return MTH_Tooltips.injectFrame end local frame = CreateFrame("Frame", "MTH_TooltipsInject") frame.pending = false frame.onUpdate = function() if not this then return end if not this.pending then this:SetScript("OnUpdate", nil) return end this.pending = false this:SetScript("OnUpdate", nil) if MTH_Tooltips and MTH_Tooltips.enabled then MTH_TT_AddTooltip("mouseover") end end MTH_Tooltips.injectFrame = frame return frame end local function MTH_TT_RequestInject() local frame = MTH_TT_EnsureInjectFrame() if frame then frame.pending = true if not frame:GetScript("OnUpdate") then frame:SetScript("OnUpdate", frame.onUpdate) end end end local function MTH_TT_EnsurePollFrame() if MTH_Tooltips.pollFrame then return MTH_Tooltips.pollFrame end local frame = CreateFrame("Frame", "MTH_TooltipsPoll") frame:SetScript("OnUpdate", function() if not (MTH_Tooltips and MTH_Tooltips.enabled) then return end MTH_Tooltips.hintElapsed = (MTH_Tooltips.hintElapsed or 0) + (arg1 or 0) if MTH_Tooltips.hintElapsed >= 0.25 then MTH_Tooltips.hintElapsed = 0 if GameTooltip and GameTooltip.IsShown and GameTooltip:IsShown() then MTH_TT_AddOwnPetTooltipHint() if not MTH_TT_HasExistingNotLearnedLine() then MTH_TT_AddPetActionNotLearnedHint() end end end MTH_Tooltips.pollElapsed = (MTH_Tooltips.pollElapsed or 0) + (elapsed or 0) if MTH_Tooltips.pollElapsed < 0.15 then return end MTH_Tooltips.pollElapsed = 0 local state = MTH_Tooltips.debugState state.pollTicks = (state.pollTicks or 0) + 1 if not UnitExists("mouseover") then MTH_Tooltips.lastMouseoverKey = "" return end local name = UnitName("mouseover") if not name or name == "" then return end local lvl = UnitLevel and UnitLevel("mouseover") or 0 local key = tostring(name) .. "#" .. tostring(lvl) if key ~= MTH_Tooltips.lastMouseoverKey then MTH_Tooltips.lastMouseoverKey = key state.pollTriggers = (state.pollTriggers or 0) + 1 MTH_TT_RequestInject() return end if not MTH_TT_HasExistingTooltipLine() then MTH_TT_RequestInject() end end) MTH_Tooltips.pollFrame = frame return frame end local function MTH_TT_SetRuntimeActive(active) local injectFrame = MTH_Tooltips.injectFrame if injectFrame then if not active then injectFrame.pending = false injectFrame:SetScript("OnUpdate", nil) end end local pollFrame = MTH_Tooltips.pollFrame if pollFrame then if active then pollFrame:SetScript("OnUpdate", pollFrame:GetScript("OnUpdate") or function() if not (MTH_Tooltips and MTH_Tooltips.enabled) then return end MTH_Tooltips.hintElapsed = (MTH_Tooltips.hintElapsed or 0) + (arg1 or 0) if MTH_Tooltips.hintElapsed >= 0.25 then MTH_Tooltips.hintElapsed = 0 if GameTooltip and GameTooltip.IsShown and GameTooltip:IsShown() then MTH_TT_AddOwnPetTooltipHint() if not MTH_TT_HasExistingNotLearnedLine() then MTH_TT_AddPetActionNotLearnedHint() end end end MTH_Tooltips.pollElapsed = (MTH_Tooltips.pollElapsed or 0) + (elapsed or 0) if MTH_Tooltips.pollElapsed < 0.15 then return end MTH_Tooltips.pollElapsed = 0 local state = MTH_Tooltips.debugState state.pollTicks = (state.pollTicks or 0) + 1 if not UnitExists("mouseover") then MTH_Tooltips.lastMouseoverKey = "" return end local name = UnitName("mouseover") if not name or name == "" then return end local lvl = UnitLevel and UnitLevel("mouseover") or 0 local key = tostring(name) .. "#" .. tostring(lvl) if key ~= MTH_Tooltips.lastMouseoverKey then MTH_Tooltips.lastMouseoverKey = key state.pollTriggers = (state.pollTriggers or 0) + 1 MTH_TT_RequestInject() return end if not MTH_TT_HasExistingTooltipLine() then MTH_TT_RequestInject() end end) else MTH_Tooltips.pollElapsed = 0 MTH_Tooltips.hintElapsed = 0 pollFrame:SetScript("OnUpdate", nil) end end end local function MTH_TT_InstallTooltipHook() if MTH_Tooltips.hookInstalled then return end if not GameTooltip then MTH_TT_Log("hook skipped: GameTooltip missing") return end if type(GameTooltip.HookScript) == "function" then local ok, err = pcall(function() GameTooltip:HookScript("OnTooltipSetUnit", function() if not (MTH_Tooltips and MTH_Tooltips.enabled) then return end MTH_TT_AddOwnPetTooltipHint() MTH_TT_RequestInject() end) GameTooltip:HookScript("OnTooltipSetItem", function() if not (MTH_Tooltips and MTH_Tooltips.enabled) then return end MTH_TT_AddFoodTooltipHint() end) GameTooltip:HookScript("OnTooltipSetSpell", function() if not (MTH_Tooltips and MTH_Tooltips.enabled) then return end MTH_TT_AddPetActionNotLearnedHint() end) end) if ok then MTH_Tooltips.hookInstalled = true MTH_TT_Log("hook installed: GameTooltip OnTooltipSetUnit+OnTooltipSetItem+OnTooltipSetSpell") else MTH_TT_Log("hook skipped: OnTooltipSetUnit unavailable (" .. tostring(err or "unknown") .. ")") end else MTH_TT_Log("hook skipped: HookScript API unavailable") end end function MTH_Tooltips:init() self.beastIndex = MTH_TT_BuildIndex() self.initialized = true MTH_TT_InstallTooltipHook() MTH_TT_EnsureInjectFrame() MTH_TT_EnsurePollFrame() MTH_TT_SetRuntimeActive(self.enabled) MTH_TT_Log("module init complete") end function MTH_Tooltips:onEvent(evt, arg1) if evt ~= "UPDATE_MOUSEOVER_UNIT" and evt ~= "UNIT_NAME_UPDATE" then return end if not self.enabled then return end if evt == "UNIT_NAME_UPDATE" and arg1 ~= "mouseover" then return end self.debugState.events = (self.debugState.events or 0) + 1 MTH_TT_Log("event: UPDATE_MOUSEOVER_UNIT") if not self.initialized then self:init() end MTH_TT_RequestInject() end function MTH_Tooltips:setEnabled(enabled) self.enabled = enabled and true or false MTH_TT_Log("setEnabled: " .. tostring(self.enabled)) if self.enabled and not self.initialized then self:init() elseif self.enabled then MTH_TT_EnsurePollFrame() MTH_TT_EnsureInjectFrame() MTH_TT_SetRuntimeActive(true) else MTH_TT_SetRuntimeActive(false) end end function MTH_Tooltips:SetBeastTooltipsEnabled(enabled) local store = MTH_TT_GetOptionsStore() if type(store) == "table" then store.beastTooltips = enabled and true or false end end function MTH_Tooltips:SetAmmoVendorTooltipsEnabled(enabled) local store = MTH_TT_GetOptionsStore() if type(store) == "table" then store.ammoVendorTooltips = enabled and true or false end self.vendorIndex = nil end function MTH_Tooltips:SetFoodItemTooltipsEnabled(enabled) local store = MTH_TT_GetOptionsStore() if type(store) == "table" then store.foodItemTooltips = enabled and true or false end end function MTH_Tooltips:SetOwnPetTooltipsEnabled(enabled) local store = MTH_TT_GetOptionsStore() if type(store) == "table" then store.ownPetTooltips = enabled and true or false end end function MTH_Tooltips:GetBeastTooltipsEnabled() return MTH_TT_IsBeastTooltipsEnabled() end function MTH_Tooltips:GetAmmoVendorTooltipsEnabled() return MTH_TT_IsAmmoVendorTooltipsEnabled() end function MTH_Tooltips:GetFoodItemTooltipsEnabled() return MTH_TT_IsFoodItemTooltipsEnabled() end function MTH_Tooltips:GetOwnPetTooltipsEnabled() return MTH_TT_IsOwnPetTooltipsEnabled() end function MTH_Tooltips:cleanup() self.enabled = false MTH_TT_SetRuntimeActive(false) end function MTH_Tooltips:GetDebugSnapshot() return self.debugState end MTH:RegisterModule("tooltips", MTH_Tooltips)