local _G = ShaguTweaks.GetGlobalEnv() local T = ShaguTweaks.T local GetItemIDFromLink = ShaguTweaks.GetItemIDFromLink local API = ShaguTweaks.API local module = ShaguTweaks:register({ title = T["Vendor Values"], description = T["Shows the vendor sell values on all item tooltips."], expansions = { ["vanilla"] = true }, category = T["Tooltip & Items"], enabled = true, }) local function GetVendorPrice(id) if not id then return end if API and API.GetItemSellPriceByID then return API.GetItemSellPriceByID(id) end end -- Keep the historical public SellValueDB contract for addons such as Aux, -- without restoring the large embedded vendor-price database. Missing entries -- are resolved lazily through ClassicAPI and cached only after they are known. local sellValueDB = ShaguTweaks.SellValueDB if type(sellValueDB) ~= "table" then sellValueDB = {} end setmetatable(sellValueDB, { __index = function(tbl, itemID) local price = GetVendorPrice(itemID) -- The old embedded DB omitted unsellable 0-price items, so keep nil for -- those entries instead of changing the historical SellValueDB contract. if price and price > 0 then rawset(tbl, itemID, price) return price end end, }) ShaguTweaks.SellValueDB = sellValueDB local pendingPrices = {} local function ClearPending(frame) if frame then pendingPrices[frame] = nil end end local function AddVendorPrices(frame, id, count, ignoreMerchant) if not frame or not id then return end if not ignoreMerchant and MerchantFrame:IsShown() then ClearPending(frame) return end local price = GetVendorPrice(id) count = math.max(tonumber(count) or 1, 1) if price ~= nil then ClearPending(frame) if price > 0 then SetTooltipMoney(frame, price * count) frame:Show() return true end return end -- ClassicAPI warms uncached item records in the background. Remember the -- currently displayed item so GET_ITEM_INFO_RECEIVED can add its price once -- the record arrives, without polling or a permanent OnUpdate. if API and API.itemprice then pendingPrices[frame] = { itemID = id, count = count, ignoreMerchant = ignoreMerchant, } end end local function AddVendorPriceFromLink(frame, link, count, ignoreMerchant) if not link then return end AddVendorPrices(frame, GetItemIDFromLink(link), count, ignoreMerchant) end module.enable = function(self) -- Refresh a still-visible tooltip when ClassicAPI finishes warming an item -- record. The handler does no work unless a tooltip actually has a pending -- uncached item. if API and API.eventutils and _G.C_EventUtils and _G.C_EventUtils.IsEventValid("GET_ITEM_INFO_RECEIVED") then local priceEvents = CreateFrame("Frame") priceEvents:RegisterEvent("GET_ITEM_INFO_RECEIVED") priceEvents:SetScript("OnEvent", function() for frame, pending in pairs(pendingPrices) do if not frame:IsShown() then pendingPrices[frame] = nil elseif pending.itemID == arg1 then AddVendorPrices(frame, pending.itemID, pending.count, pending.ignoreMerchant) end end end) end -- Every tooltip setter is described once here. Prefer ClassicAPI's direct -- item IDs; link parsing remains only as a cold compatibility path inside -- api.lua or for SetHyperlink/SetCraftSpell where a link is the natural API. local fill = { SetHyperlink = function(self, link) return nil, 1, true, link end, SetBagItem = function(self, container, slot) if container == -1 and BankButtonIDToInvSlotID then local invSlot = BankButtonIDToInvSlotID(slot) local itemID = invSlot and API.GetInventoryItemID("player", invSlot) local count = invSlot and GetInventoryItemCount("player", invSlot) return itemID, count, false end return API.GetContainerItemID(container, slot), API.GetContainerItemStackCount(container, slot), false end, SetQuestLogItem = function(self, itemType, index) return API.GetQuestLogItemID(itemType, index), 1, true end, SetQuestItem = function(self, itemType, index) return API.GetQuestItemID(itemType, index), 1, true end, SetLootItem = function(self, slot) return API.GetLootSlotItemID(slot), 1, true end, SetInboxItem = function(self, mailID, attachmentIndex) local _, _, count = GetInboxItem(mailID, attachmentIndex) return API.GetInboxItemID(mailID), count, true end, SetInventoryItem = function(self, unit, slot) return API.GetInventoryItemID(unit, slot), 1, true end, SetLootRollItem = function(self, rollID) return API.GetLootRollItemID(rollID), 1, true end, SetMerchantItem = function(self, merchantIndex) return API.GetMerchantItemID(merchantIndex), 1, false end, SetCraftItem = function(self, skill, slot) return API.GetCraftReagentItemID(skill, slot), 1, true end, SetCraftSpell = function(self, slot) return nil, 1, true, GetCraftItemLink(slot) end, SetTradeSkillItem = function(self, skillIndex, reagentIndex) if reagentIndex then return API.GetTradeSkillReagentItemID(skillIndex, reagentIndex), 1, true end return API.GetTradeSkillItemID(skillIndex), 1, true end, SetAuctionItem = function(self, atype, index) local _, _, count = GetAuctionItemInfo(atype, index) return API.GetAuctionItemID(atype, index), count, true end, SetAuctionSellItem = function(self) local _, _, count = GetAuctionSellItemInfo() return API.GetAuctionSellItemID(), count, true end, SetTradePlayerItem = function(self, index) return API.GetTradePlayerItemID(index), 1, true end, SetTradeTargetItem = function(self, index) return API.GetTradeTargetItemID(index), 1, true end, } local installed = {} local fillDepth = 0 local context local function Capture(prepare, frame, a1, a2, a3) local itemID, count, ignoreMerchant, link = prepare(frame, a1, a2, a3) context = context or {} if itemID and not context.itemID then context.itemID = itemID end if link and not context.link then context.link = link end if context.count == nil and count ~= nil then context.count = count end if context.ignoreMerchant == nil and ignoreMerchant ~= nil then context.ignoreMerchant = ignoreMerchant end end local function InstallTooltipHooks() for method, prepare in pairs(fill) do -- Lua 5.0 shares generic-for control variables between closures. Keep -- body locals so each wrapper captures the correct method and callback. local method = method local prepare = prepare local original = GameTooltip[method] -- A load-on-demand bag/UI addon may replace a setter after ShaguTweaks -- initialized. Wrap that new outer setter while keeping the old captured -- call chain intact. fillDepth prevents duplicate vendor-price lines. if type(original) == "function" and original ~= installed[method] then local wrapper = function(frame, a1, a2, a3) local outer = fillDepth == 0 if outer then context = {} ClearPending(frame) end -- If an outer virtual bag method cannot identify an item, allow a -- nested real SetInventoryItem/SetHyperlink call to provide it. if outer or (context and not context.itemID and not context.link) then Capture(prepare, frame, a1, a2, a3) end fillDepth = fillDepth + 1 local ok, r1, r2, r3, r4 = pcall(original, frame, a1, a2, a3) fillDepth = fillDepth - 1 if outer then local captured = context context = nil if ok and captured then if captured.itemID then AddVendorPrices(frame, captured.itemID, captured.count, captured.ignoreMerchant) elseif captured.link then AddVendorPriceFromLink(frame, captured.link, captured.count, captured.ignoreMerchant) end end end if not ok then error(r1) end return r1, r2, r3, r4 end installed[method] = wrapper GameTooltip[method] = wrapper end end end -- SetItemRef is global rather than a GameTooltip method. Keep the same -- late-replacement protection without introducing a second shared helper. local installedItemRef local itemRefDepth = 0 local function InstallItemRefHook() local original = _G.SetItemRef if type(original) ~= "function" or original == installedItemRef then return end local wrapper = function(link, text, button) local outer = itemRefDepth == 0 if outer then ClearPending(ItemRefTooltip) end local itemID = outer and GetItemIDFromLink(link) or nil itemRefDepth = itemRefDepth + 1 local ok, r1, r2, r3, r4 = pcall(original, link, text, button) itemRefDepth = itemRefDepth - 1 if ok and outer and itemID and not API.IsAltKeyDown() and not API.IsShiftKeyDown() and not API.IsControlKeyDown() then AddVendorPrices(ItemRefTooltip, itemID, 1, true) end if not ok then error(r1) end return r1, r2, r3, r4 end installedItemRef = wrapper _G.SetItemRef = wrapper end InstallTooltipHooks() InstallItemRefHook() -- Re-check after load-on-demand UI addons install their own tooltip wrappers. -- The one-shot next-frame pass handles event registration order without a -- permanent OnUpdate. local hookwatch = CreateFrame("Frame") local rehook = CreateFrame("Frame") rehook:SetScript("OnUpdate", nil) local function ScheduleRehook() if rehook:GetScript("OnUpdate") then return end rehook:SetScript("OnUpdate", function() this:SetScript("OnUpdate", nil) InstallTooltipHooks() InstallItemRefHook() end) end hookwatch:RegisterEvent("ADDON_LOADED") hookwatch:RegisterEvent("PLAYER_LOGIN") hookwatch:RegisterEvent("PLAYER_ENTERING_WORLD") hookwatch:SetScript("OnEvent", function() InstallTooltipHooks() InstallItemRefHook() ScheduleRehook() end) end