Files

323 lines
10 KiB
Lua

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