Files
AtlasLoot/Core/Tooltip.lua
T
roby-brok 1ad4c1e2a6 Tooltip: don't crash on tooltips longer than 30 lines
AddSourceLine's line buffer is pre-built for exactly 30 entries, so any
item tooltip that exceeds that dies with

  Core\Tooltip.lua:56: attempt to index field '?' (a nil value)

on every hover, and the source line is never added. Easy to hit on
servers with long set lists in the tooltip plus a tooltip-extending
addon (StatCompare and friends) -- a 6-piece set block alone adds ~10
lines. Grow the buffer on demand instead; the clear/rebuild loops
already iterate whatever size the table has.
2026-08-14 13:28:10 +03:00

340 lines
9.5 KiB
Lua

-- extra info on GameTooltip and ItemRefTooltip
local AtlasLootTip = CreateFrame("Frame", "AtlasLootTip", GameTooltip)
local strfind = string.find
local GetItemInfo = GetItemInfo
local GREY = "|cff999999"
local _G = _G or getfenv(0)
local insideHook = false
local tooltipMoney = 0
local original_SetTooltipMoney = SetTooltipMoney
function SetTooltipMoney(frame, money)
if insideHook then
tooltipMoney = money or 0
else
original_SetTooltipMoney(frame, money)
end
end
local WrappingLines = {
["^Set:"] = gsub("^"..ITEM_SET_BONUS, "%%s", ""),
["^%(%d%) Set:"] = gsub(gsub(ITEM_SET_BONUS_GRAY, "%(%%d%)", "^%%(%%d%%)"), "%%s", ""),
["^Effect:"] = gsub("^"..ITEM_SPELL_EFFECT, "%%s", ""),
["^Equip:"] = "^"..ITEM_SPELL_TRIGGER_ONEQUIP,
["^Chance on hit:"] = "^"..ITEM_SPELL_TRIGGER_ONPROC,
["^Use:"] = "^"..ITEM_SPELL_TRIGGER_ONUSE,
["^\nRequires"] = "^\n"..gsub(ITEM_REQ_SKILL, "%%s", "")
}
local lines = {}
for i = 1, 30 do
lines[i] = {}
end
local function AddSourceLine(tooltip, sourceStr)
local name = tooltip:GetName()
local numLines = tooltip:NumLines()
local left, right
local leftText, rightText
local leftR, leftG, leftB
local rightR, rightG, rightB
local wrap
for i in pairs(lines) do
for j in pairs(lines[i]) do
lines[i][j] = nil
end
end
for i = 1, numLines do
-- the buffer is pre-built for 30 lines, but long set tooltips plus
-- tooltip-extending addons (StatCompare etc.) can exceed that
lines[i] = lines[i] or {}
left = _G[name.."TextLeft"..i]
right = _G[name.."TextRight"..i]
leftText = left:GetText()
rightText = right:IsShown() and right:GetText()
leftR, leftG, leftB = left:GetTextColor()
rightR, rightG, rightB = right:GetTextColor()
lines[i][1] = leftText
lines[i][2] = rightText
lines[i][3] = leftR
lines[i][4] = leftG
lines[i][5] = leftB
lines[i][6] = rightR
lines[i][7] = rightG
lines[i][8] = rightB
end
if not lines[1][1] then
return
end
tooltip:SetText(lines[1][1], lines[1][3], lines[1][4], lines[1][5], 1, false)
if numLines < 28 then
tooltip:AddLine(sourceStr)
elseif lines[2][1] then
lines[2][1] = sourceStr.."\n"..lines[2][1]
end
for i = 2, getn(lines) do
if lines[i][2] then
tooltip:AddDoubleLine(lines[i][1], lines[i][2], lines[i][3], lines[i][4], lines[i][5], lines[i][6], lines[i][7], lines[i][8])
else
wrap = false
if strsub(lines[i][1] or "", 1, 1) == "\"" then
wrap = true
else
for _, pattern in pairs(WrappingLines) do
if strfind(lines[i][1] or "", pattern) then
wrap = true
break
end
end
end
tooltip:AddLine(lines[i][1], lines[i][3], lines[i][4], lines[i][5], wrap)
end
end
end
local lastItemID, lastSourceStr
local function ExtendTooltip(tooltip)
if AtlasLootCharDB.ShowSource then
local itemID = tonumber(tooltip.itemID)
if itemID and itemID ~= 51217 then -- 51217 Fashion Coin
if itemID ~= lastItemID then
lastItemID = itemID
lastSourceStr = nil
local source = AtlasLoot_Data["AtlasLootSources"][itemID]
if source then
local str = GREY..source.."|r"
lastSourceStr = str
end
end
if lastSourceStr then
AddSourceLine(tooltip, lastSourceStr)
tooltip:Show()
end
end
end
if tooltipMoney > 0 then
original_SetTooltipMoney(tooltip, tooltipMoney)
tooltip:Show()
end
end
local IDCache = {}
local function GetItemIDByName(name)
if not name then return nil end
if IDCache[name] then return IDCache[name] ~= 0 and IDCache[name] or nil end
for itemID = 1, 99999 do
if GetItemInfo(itemID) == name then
IDCache[name] = itemID
return itemID
end
end
IDCache[name] = 0
return nil
end
local function IDFromLink(link)
if not link then return nil end
local _, _, id = strfind(link, "item:(%d+)")
return tonumber(id)
end
local function HookTooltip(tooltip)
local original_SetLootRollItem = tooltip.SetLootRollItem
local original_SetLootItem = tooltip.SetLootItem
local original_SetMerchantItem = tooltip.SetMerchantItem
local original_SetQuestLogItem = tooltip.SetQuestLogItem
local original_SetQuestItem = tooltip.SetQuestItem
local original_SetHyperlink = tooltip.SetHyperlink
local original_SetBagItem = tooltip.SetBagItem
local original_SetInboxItem = tooltip.SetInboxItem
local original_SetInventoryItem = tooltip.SetInventoryItem
local original_SetCraftItem = tooltip.SetCraftItem
local original_SetCraftSpell = tooltip.SetCraftSpell
local original_SetTradeSkillItem = tooltip.SetTradeSkillItem
local original_SetAuctionItem = tooltip.SetAuctionItem
local original_SetAuctionSellItem = tooltip.SetAuctionSellItem
local original_SetTradePlayerItem = tooltip.SetTradePlayerItem
local original_SetTradeTargetItem = tooltip.SetTradeTargetItem
local original_OnHide = tooltip:GetScript("OnHide")
tooltip:SetScript("OnHide", function()
if original_OnHide then original_OnHide() end
this.itemID = nil
tooltipMoney = 0
end)
function tooltip.SetLootRollItem(self, rollID)
insideHook = true
original_SetLootRollItem(self, rollID)
insideHook = false
self.itemID = IDFromLink(GetLootRollItemLink(rollID))
ExtendTooltip(self)
end
function tooltip.SetLootItem(self, slot)
insideHook = true
original_SetLootItem(self, slot)
insideHook = false
self.itemID = IDFromLink(GetLootSlotLink(slot))
ExtendTooltip(self)
end
function tooltip.SetMerchantItem(self, merchantIndex)
insideHook = true
original_SetMerchantItem(self, merchantIndex)
insideHook = false
self.itemID = IDFromLink(GetMerchantItemLink(merchantIndex))
ExtendTooltip(self)
end
function tooltip.SetQuestLogItem(self, itemType, index)
insideHook = true
original_SetQuestLogItem(self, itemType, index)
insideHook = false
self.itemID = IDFromLink(GetQuestLogItemLink(itemType, index))
ExtendTooltip(self)
end
function tooltip.SetQuestItem(self, itemType, index)
insideHook = true
original_SetQuestItem(self, itemType, index)
insideHook = false
self.itemID = IDFromLink(GetQuestItemLink(itemType, index))
ExtendTooltip(self)
end
function tooltip.SetHyperlink(self, arg1)
insideHook = true
original_SetHyperlink(self, arg1)
insideHook = false
self.itemID = IDFromLink(arg1)
ExtendTooltip(self)
end
function tooltip.SetBagItem(self, container, slot)
insideHook = true
local hasCooldown, repairCost = original_SetBagItem(self, container, slot)
insideHook = false
self.itemID = IDFromLink(GetContainerItemLink(container, slot))
ExtendTooltip(self)
return hasCooldown, repairCost
end
function tooltip.SetInboxItem(self, mailID, attachmentIndex)
insideHook = true
original_SetInboxItem(self, mailID, attachmentIndex)
insideHook = false
self.itemID = GetItemIDByName(GetInboxItem(mailID))
ExtendTooltip(self)
end
function tooltip.SetInventoryItem(self, unit, slot)
insideHook = true
local hasItem, hasCooldown, repairCost = original_SetInventoryItem(self, unit, slot)
insideHook = false
self.itemID = IDFromLink(GetInventoryItemLink(unit, slot))
ExtendTooltip(self)
return hasItem, hasCooldown, repairCost
end
function tooltip.SetCraftItem(self, skill, slot)
insideHook = true
original_SetCraftItem(self, skill, slot)
insideHook = false
self.itemID = IDFromLink(GetCraftReagentItemLink(skill, slot))
ExtendTooltip(self)
end
function tooltip.SetCraftSpell(self, slot)
insideHook = true
original_SetCraftSpell(self, slot)
insideHook = false
self.itemID = IDFromLink(GetCraftItemLink(slot))
ExtendTooltip(self)
end
function tooltip.SetTradeSkillItem(self, skillIndex, reagentIndex)
insideHook = true
original_SetTradeSkillItem(self, skillIndex, reagentIndex)
insideHook = false
if reagentIndex then
self.itemID = IDFromLink(GetTradeSkillReagentItemLink(skillIndex, reagentIndex))
else
self.itemID = IDFromLink(GetTradeSkillItemLink(skillIndex))
end
ExtendTooltip(self)
end
function tooltip.SetAuctionItem(self, atype, index)
insideHook = true
original_SetAuctionItem(self, atype, index)
insideHook = false
self.itemID = IDFromLink(GetAuctionItemLink(atype, index))
ExtendTooltip(self)
end
function tooltip.SetAuctionSellItem(self)
insideHook = true
original_SetAuctionSellItem(self)
insideHook = false
self.itemID = tonumber(GetItemIDByName(GetAuctionSellItemInfo()))
ExtendTooltip(self)
end
function tooltip.SetTradePlayerItem(self, index)
insideHook = true
original_SetTradePlayerItem(self, index)
insideHook = false
self.itemID = IDFromLink(GetTradePlayerItemLink(index))
ExtendTooltip(self)
end
function tooltip.SetTradeTargetItem(self, index)
insideHook = true
original_SetTradeTargetItem(self, index)
insideHook = false
self.itemID = IDFromLink(GetTradeTargetItemLink(index))
ExtendTooltip(self)
end
end
AtlasLootTip:SetScript("OnShow", function()
if not (aux_frame and aux_frame:IsShown()) then return end
local focus = GetMouseFocus()
if not focus then return end
local parent = focus:GetParent()
if not (parent and parent.row and parent.row.record) then return end
GameTooltip.itemID = tonumber(parent.row.record.item_id)
ExtendTooltip(GameTooltip)
end)
-- adapted from http://shagu.org/ShaguTweaks/
AtlasLootTip.HookAddonOrVariable = function(addon, func)
local lurker = CreateFrame("Frame", nil)
lurker.func = func
lurker:RegisterEvent("ADDON_LOADED")
lurker:RegisterEvent("VARIABLES_LOADED")
lurker:RegisterEvent("PLAYER_ENTERING_WORLD")
lurker:SetScript("OnEvent", function()
if IsAddOnLoaded(addon) or _G[addon] then
this:func()
this:UnregisterAllEvents()
end
end)
end
AtlasLootTip.HookAddonOrVariable("Tmog", function()
HookTooltip(TmogTooltip)
end)
HookTooltip(GameTooltip)
HookTooltip(ItemRefTooltip)