Files
ShaguTweaks-ClassicAPI/api.lua
T

314 lines
9.8 KiB
Lua

local _G = ShaguTweaks.GetGlobalEnv()
-- Central capability layer for ClassicAPI / SuperWoW integration.
-- Keep feature checks here so individual modules don't scatter DLL-specific
-- detection logic all over the codebase.
ShaguTweaks.API = ShaguTweaks.API or {}
local API = ShaguTweaks.API
API.classicapi_version = tonumber(_G.CLASSIC_API_VERSION) or 0
API.classicapi = type(_G.C_NamePlate) == "table"
or type(_G.C_Spell) == "table"
or type(_G.C_UnitAuras) == "table"
or type(_G.UnitGUID) == "function"
API.nameplates = type(_G.C_NamePlate) == "table"
and type(_G.C_NamePlate.GetNamePlateForUnit) == "function"
API.casts = type(_G.C_Spell) == "table"
and type(_G.C_Spell.UnitCastingInfo) == "function"
and type(_G.C_Spell.UnitChannelInfo) == "function"
API.auras = type(_G.C_UnitAuras) == "table"
and type(_G.C_UnitAuras.GetAuraDataByIndex) == "function"
and type(_G.C_UnitAuras.GetDebuffDataByIndex) == "function"
API.spellinfo = type(_G.GetSpellInfo) == "function"
API.inventory = type(_G.C_Container) == "table"
and type(_G.C_Container.GetContainerNumFreeSlots) == "function"
API.itemprice = type(_G.C_Item) == "table"
and type(_G.C_Item.GetItemSellPriceByID) == "function"
API.merchant = type(_G.C_MerchantFrame) == "table"
and type(_G.C_MerchantFrame.GetNumJunkItems) == "function"
and type(_G.C_MerchantFrame.SellAllJunkItems) == "function"
API.playerstate = type(_G.IsMounted) == "function"
and type(_G.Dismount) == "function"
and type(_G.GetShapeshiftFormID) == "function"
and type(_G.CancelShapeshiftForm) == "function"
API.unitguid = type(_G.UnitGUID) == "function"
API.chatidentity = type(_G.GetCurrentChatGUID) == "function"
and type(_G.GetPlayerInfoByGUID) == "function"
API.playercache = type(_G.C_PlayerCache) == "table"
and type(_G.C_PlayerCache.GetPlayerInfoByName) == "function"
-- SuperWoW remains optional. It can still provide useful extra cast/GUID
-- information, but ClassicAPI is the primary compatibility layer.
API.superwow = type(_G.SpellInfo) == "function"
and type(_G.CombatLogAdd) == "function"
API.GetNamePlateForUnit = function(unit)
if API.nameplates then
return _G.C_NamePlate.GetNamePlateForUnit(unit)
end
end
API.UnitCastingInfo = function(unit)
if API.casts then
return _G.C_Spell.UnitCastingInfo(unit)
end
end
API.UnitChannelInfo = function(unit)
if API.casts then
return _G.C_Spell.UnitChannelInfo(unit)
end
end
-- ShaguTweaks' castbar modules use the old 1.12-compatible seven-value shape.
-- Keep that public shape while sourcing the data from ClassicAPI first.
API.GetCastInfo = function(unit)
if not API.casts then return end
local name, displayName, texture, startTime, endTime, isTradeSkill,
castID, notInterruptible, spellID = _G.C_Spell.UnitCastingInfo(unit)
-- Remote channels seen after they started can have metadata but no timing.
-- Castbars require start/end times, so only expose complete timing data here.
if not name or not startTime or not endTime then return end
return name, "", displayName or "", texture, startTime, endTime,
isTradeSkill, castID, notInterruptible, spellID
end
API.GetChannelInfo = function(unit)
if not API.casts then return end
local name, displayName, texture, startTime, endTime, isTradeSkill,
notInterruptible, spellID = _G.C_Spell.UnitChannelInfo(unit)
if not name or not startTime or not endTime then return end
return name, "", displayName or "", texture, startTime, endTime,
isTradeSkill, notInterruptible, spellID
end
API.GetAuraDataByIndex = function(unit, index, filter)
if API.auras then
return _G.C_UnitAuras.GetAuraDataByIndex(unit, index, filter)
end
end
API.GetDebuffDataByIndex = function(unit, index)
if API.auras then
return _G.C_UnitAuras.GetDebuffDataByIndex(unit, index)
end
end
API.GetCurrentChatGUID = function()
if API.chatidentity then
return _G.GetCurrentChatGUID()
end
end
API.GetPlayerInfoByGUID = function(guid)
if API.chatidentity and guid then
return _G.GetPlayerInfoByGUID(guid)
end
end
-- Read-only bridge to ClassicAPI's optional persistent player cache.
-- ShaguTweaks never enables that cache or its visible-object scanner itself.
API.GetCachedPlayerInfoByName = function(name)
if API.playercache and name then
return _G.C_PlayerCache.GetPlayerInfoByName(name)
end
end
API.GetSpellInfo = function(spell, bookType)
if API.spellinfo then
return _G.GetSpellInfo(spell, bookType)
end
end
API.GetContainerNumFreeSlots = function(bag)
if API.inventory then
return _G.C_Container.GetContainerNumFreeSlots(bag)
end
return 0, 0
end
-- Returns the live vendor sell price in copper when the client has the item
-- cached. ClassicAPI warms uncached item data itself and returns nil until the
-- cache fill completes.
API.GetItemSellPriceByID = function(itemID)
if API.itemprice and itemID then
return _G.C_Item.GetItemSellPriceByID(itemID)
end
end
local function GetVendorPriceCache()
ShaguTweaks_cache = ShaguTweaks_cache or {}
ShaguTweaks_cache["vendor_prices"] = ShaguTweaks_cache["vendor_prices"] or {}
return ShaguTweaks_cache["vendor_prices"]
end
-- Store only prices that add something to the original static database:
-- new Turtle/custom items or prices changed by Turtle WoW. This keeps the
-- per-character SavedVariables cache very small.
local function StoreVendorPrice(itemID, price)
if type(itemID) ~= "number" or type(price) ~= "number" or price <= 0 then return end
local learned = GetVendorPriceCache()
local legacy = ShaguTweaks.SellValueLegacyDB or ShaguTweaks.SellValueDB
local legacyPrice = legacy and legacy[itemID]
if not legacyPrice or legacyPrice ~= price then
learned[itemID] = price
else
-- Drop a now-redundant learned value if the bundled database caught up.
learned[itemID] = nil
end
-- If the ClassicAPI proxy is already active, make this confirmed value
-- immediately available without another lookup during the current session.
if ShaguTweaks.SellValueLegacyDB and ShaguTweaks.SellValueDB then
rawset(ShaguTweaks.SellValueDB, itemID, price)
end
return price
end
API.StoreVendorPrice = StoreVendorPrice
API.RememberVendorPrice = function(itemID)
if not API.itemprice or type(itemID) ~= "number" then return end
local price = API.GetItemSellPriceByID(itemID)
if price and price > 0 then
return StoreVendorPrice(itemID, price)
end
end
-- Vendor Values keeps its original static database as the final fallback.
-- Resolution order is: current ClassicAPI value -> learned persistent value ->
-- bundled ShaguTweaks database.
API.PrepareVendorValues = function()
if not API.itemprice or not ShaguTweaks.SellValueDB or ShaguTweaks.SellValueLegacyDB then
return
end
local legacy = ShaguTweaks.SellValueDB
local learned = GetVendorPriceCache()
local live = {}
setmetatable(live, {
__index = function(tab, itemID)
if type(itemID) ~= "number" then
return legacy[itemID]
end
local price = API.GetItemSellPriceByID(itemID)
if price and price > 0 then
if not legacy[itemID] or legacy[itemID] ~= price then
learned[itemID] = price
else
learned[itemID] = nil
end
rawset(tab, itemID, price)
return price
end
local remembered = learned[itemID]
if remembered and remembered > 0 then
return remembered
end
return legacy[itemID]
end
})
ShaguTweaks.SellValueLegacyDB = legacy
ShaguTweaks.SellValueDB = live
-- Vendor Values intentionally suppresses its own price while a merchant is
-- open because vanilla already displays the real sell value there. If
-- ClassicAPI's item record still lacks a new Turtle item's sell price, read
-- the native money frame *after* SetBagItem built the merchant tooltip and
-- persist that value. The merchant tooltip reports the current stack value,
-- so convert it back to the per-item price used by SellValueDB.
if not API.vendorprice_tooltip_hooks then
API.vendorprice_tooltip_hooks = true
ShaguTweaks.hooksecurefunc(GameTooltip, "SetBagItem", function(tooltip, container, slot)
if not MerchantFrame or not MerchantFrame:IsShown() then return end
local link = GetContainerItemLink(container, slot)
local itemID = link and ShaguTweaks.GetItemIDFromLink(link)
if not itemID then return end
-- Prefer ClassicAPI whenever it has a direct item-record price.
if API.RememberVendorPrice(itemID) then return end
local _, count = GetContainerItemInfo(container, slot)
count = tonumber(count) or 1
if count < 1 then count = 1 end
local tooltipName = tooltip and tooltip:GetName()
local moneyFrame = tooltipName and _G[tooltipName .. "MoneyFrame1"]
local stackPrice = moneyFrame and moneyFrame:IsShown() and tonumber(moneyFrame.staticMoney)
if not stackPrice or stackPrice <= 0 then return end
local unitPrice = floor(stackPrice / count)
-- A vendor stack value must be an exact multiple of its per-item value.
-- Refuse ambiguous values rather than persist a potentially wrong price.
if unitPrice > 0 and unitPrice * count == stackPrice then
StoreVendorPrice(itemID, unitPrice)
end
end)
end
end
API.GetNumJunkItems = function()
if API.merchant then
return _G.C_MerchantFrame.GetNumJunkItems()
end
return 0
end
API.SellAllJunkItems = function()
if API.merchant then
return _G.C_MerchantFrame.SellAllJunkItems()
end
end
API.IsMounted = function()
return API.playerstate and _G.IsMounted() or false
end
API.Dismount = function()
if API.playerstate then return _G.Dismount() end
end
API.GetShapeshiftFormID = function()
if API.playerstate then return _G.GetShapeshiftFormID() end
return 0
end
API.CancelShapeshiftForm = function()
if API.playerstate then return _G.CancelShapeshiftForm() end
end
API.UnitGUID = function(unit)
if API.unitguid then
return _G.UnitGUID(unit)
end
end