Files
OctoAuctionator/Compat.lua

568 lines
18 KiB
Lua

-- Compat.lua: bridges the WoW 3.3 / Lua 5.1 constructs used by Auctionator 2.6.2 to the 1.12 client
-- (Lua 5.0, vanilla auction API). Loaded first. Everything here is prefixed Atr_ unless noted.
AtrNS = AtrNS or {}
local G = getfenv(0)
----------------------------------------------------------------------
-- small language helpers
----------------------------------------------------------------------
-- strsplit(delim, str): pieces as multiple return values; delim is matched literally
function Atr_strsplit(delim, str, pieces)
if str == nil then return nil end
local t, pos = {}, 1
while true do
local s, e = string.find(str, delim, pos, true)
if (not s) or (pieces and getn(t) >= pieces - 1) then
tinsert(t, string.sub(str, pos))
break
end
tinsert(t, string.sub(str, pos, s - 1))
pos = e + 1
end
return unpack(t)
end
-- Atr_hooksecurefunc(table, "Method", fn) / Atr_hooksecurefunc("Global", fn): runs fn after the original.
-- Auctionator always uses its own copy: 1.12 has no hooksecurefunc, and addons loaded earlier define
-- incompatible globals (Atlas-CFM's only knows the ("Global", fn) form, so every tooltip hook was a no-op).
function Atr_hooksecurefunc(a1, a2, a3)
local tbl, name, fn
if type(a1) == "string" then
tbl, name, fn = G, a1, a2
else
tbl, name, fn = a1, a2, a3
end
local orig = tbl[name]
if type(orig) ~= "function" then return end
tbl[name] = function(p1, p2, p3, p4, p5, p6, p7, p8, p9)
local r1, r2, r3, r4 = orig(p1, p2, p3, p4, p5, p6, p7, p8, p9)
fn(p1, p2, p3, p4, p5, p6, p7, p8, p9)
return r1, r2, r3, r4
end
end
-- Lua errors coming from this addon are also echoed to the chat frame (once per message), so they show up
-- in screenshots even when the error popup is off. The previous handler still runs.
do
local seen = {}
-- errors caught by pcall (tooltip hooks) are reported here, once per message
function Atr_ReportError(msg)
msg = tostring(msg)
if seen[msg] then return end
seen[msg] = true
if DEFAULT_CHAT_FRAME then
DEFAULT_CHAT_FRAME:AddMessage("|cffff3333Auctionator error:|r " .. msg)
end
end
local prev = (G.geterrorhandler and geterrorhandler()) or G._ERRORMESSAGE
if G.seterrorhandler and prev then
seterrorhandler(function(msg)
if type(msg) == "string" and string.find(msg, "Auctionator") and not seen[msg] then
seen[msg] = true
if DEFAULT_CHAT_FRAME then
DEFAULT_CHAT_FRAME:AddMessage("|cffff3333Auctionator error:|r " .. msg)
end
end
return prev(msg)
end)
end
end
function Atr_GetVersion()
return "1.0.0"
end
----------------------------------------------------------------------
-- item info (3.x layout: name, link, rarity, ilevel, minLevel, type, subType, stack, equipLoc, texture, sellPrice)
----------------------------------------------------------------------
local linkByName = {} -- lower(name) -> item link, filled from everything we see
function Atr_RememberItemLink(name, link)
if name and link then linkByName[string.lower(name)] = link end
end
local function ItemIdOf(x)
if type(x) == "number" then return x end
if type(x) ~= "string" then return nil end
local _, _, id = string.find(x, "item:(%d+)")
if id then return tonumber(id) end
local l = linkByName[string.lower(x)]
if l then
local _, _, id2 = string.find(l, "item:(%d+)")
if id2 then return tonumber(id2) end
end
return nil
end
function Atr_ItemIdFromAnything(x)
return ItemIdOf(x)
end
function Atr_GetSellValueById(id)
if id and G.SellValues then
local v = G.SellValues["item:" .. id]
if v then return v end
end
return nil
end
function Atr_GetItemInfo(x)
local id = ItemIdOf(x)
if not id then return nil end
local a, b, c, d, e, f, g, h, i, j = GetItemInfo("item:" .. id .. ":0:0:0")
if not a then return nil end
local name, link, rarity, ilevel, minLevel, itemType, subType, stack, equip, texture
if type(e) == "string" then
-- 1.12 layout: name, link, rarity, minLevel, type, subType, stack, equipLoc, texture
name, link, rarity, minLevel, itemType, subType, stack, equip, texture = a, b, c, d, e, f, g, h, i
else
-- 2.x+ layout (extended clients)
name, link, rarity, ilevel, minLevel, itemType, subType, stack, equip, texture = a, b, c, d, e, f, g, h, i, j
end
if link then Atr_RememberItemLink(name, link) end
return name, link, rarity, ilevel, minLevel, itemType, subType, stack, equip, texture, Atr_GetSellValueById(id)
end
function Atr_GetItemIcon(x)
local _, _, _, _, _, _, _, _, _, texture = Atr_GetItemInfo(x)
return texture
end
----------------------------------------------------------------------
-- cursor / inventory
----------------------------------------------------------------------
function Atr_CursorIsItem()
return CursorHasItem() and true or false
end
local function DropCursorAnywhere()
if not CursorHasItem() then return end
for bag = 0, 4 do
for slot = 1, (GetContainerNumSlots(bag) or 0) do
if not GetContainerItemLink(bag, slot) then
PickupContainerItem(bag, slot)
if not CursorHasItem() then return end
end
end
end
if G.ClearCursor then ClearCursor() end
end
Atr_DropCursorAnywhere = DropCursorAnywhere
local function LinkId(link)
if not link then return nil end
local _, _, id = string.find(link, "item:(%d+)")
return id and tonumber(id) or nil
end
-- link of the item currently sitting in the auction sell slot
function Atr_SellSlotLink()
local name, texture, count, quality, canUse, price, maxStack, link = GetAuctionSellItemInfo()
if not name then return nil end
if link then
Atr_RememberItemLink(name, link)
return link, name, count, maxStack, price
end
local l = linkByName[string.lower(name)]
return l, name, count, maxStack, price
end
-- total of an item (by name) in bags; a stack inside the auction slot is counted once
function Atr_CountItemInBags(itemName)
local total, lockedCounts = 0, {}
local lname = itemName and string.lower(itemName)
for bag = 0, 4 do
for slot = 1, (GetContainerNumSlots(bag) or 0) do
local link = GetContainerItemLink(bag, slot)
if link then
local _, _, nm = string.find(link, "%[(.-)%]")
if nm and string.lower(nm) == lname then
local _, count, locked = GetContainerItemInfo(bag, slot)
total = total + (count or 1)
if locked then lockedCounts[count or 1] = true end
end
end
end
end
local slotLink, slotName, slotCount = Atr_SellSlotLink()
if slotName and string.lower(slotName) == lname and slotCount and not lockedCounts[slotCount] then
total = total + slotCount
end
return total
end
----------------------------------------------------------------------
-- auction house call adapters
----------------------------------------------------------------------
-- 1.12 has no getAll; class/subclass/slot indexes of 0 mean "any" and must be nil
function Atr_QueryAuctionItems(name, minLevel, maxLevel, invType, itemClass, itemSubclass, page, usable, quality)
if minLevel == "" then minLevel = nil end
if maxLevel == "" then maxLevel = nil end
if invType == 0 then invType = nil end
if itemClass == 0 then itemClass = nil end
if itemSubclass == 0 then itemSubclass = nil end
if usable == 0 then usable = nil end
if quality == 0 or quality == -1 then quality = nil end
QueryAuctionItems(name or "", minLevel, maxLevel, invType, itemClass, itemSubclass, page or 0, usable, quality)
end
function Atr_CanSendAuctionQuery()
local can = CanSendAuctionQuery()
return can, false
end
-- durations: 1,2,3 as Auctionator uses them -> minutes understood by StartAuction on this server
AtrNS.durationMinutes = { 120, 480, 1440 }
AtrNS.durationText = { "6 hours", "24 hours", "72 hours" }
----------------------------------------------------------------------
-- multi stack posting (the 3.3 client did this in StartAuction itself)
----------------------------------------------------------------------
local post = nil
local driver = CreateFrame("Frame", "AtrCompatDriver")
local acc = 0
local function FindStack(id, size)
local best, bestCount
for bag = 0, 4 do
for slot = 1, (GetContainerNumSlots(bag) or 0) do
local link = GetContainerItemLink(bag, slot)
if link and LinkId(link) == id then
local _, count, locked = GetContainerItemInfo(bag, slot)
if count and not locked and count >= size then
if count == size then return bag, slot, count end
if not bestCount or count < bestCount then
best = { bag, slot }
bestCount = count
end
end
end
end
end
if best then return best[1], best[2], bestCount end
return nil
end
local function ClearSlot()
if not GetAuctionSellItemInfo() then return end
if CursorHasItem() and G.ClearCursor then ClearCursor() end
ClickAuctionSellItemButton()
DropCursorAnywhere()
end
local function Finish(msg)
local p = post
post = nil
if p and msg and G.Atr_OnAuctionMultiSellFailureMsg then
Atr_OnAuctionMultiSellFailureMsg(msg)
elseif msg and DEFAULT_CHAT_FRAME then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ffffAuctionator:|r " .. msg)
end
ClearSlot()
if p and p.multi and p.done < p.n and G.Atr_OnAuctionMultiSellFailure then
Atr_OnAuctionMultiSellFailure()
end
end
local function Step()
local p = post
if not p then return end
local now = GetTime()
if not AtrNS.ahOpen and AuctionFrame and not AuctionFrame:IsVisible() then
Finish("Auction house closed, posting stopped.")
return
end
if p.state == "prep" then
if now < p.t then return end
local link, name, count = Atr_SellSlotLink()
if name then
if LinkId(link) == p.id and count == p.size then
p.state = "ready"
return
end
ClearSlot()
p.t = now + 0.4
return
end
local bag, slot, count2 = FindStack(p.id, p.size)
if not bag then
Finish("Not enough items to finish posting (" .. p.done .. " of " .. p.n .. " posted).")
return
end
if CursorHasItem() and G.ClearCursor then ClearCursor() end
if count2 == p.size then
PickupContainerItem(bag, slot)
else
SplitContainerItem(bag, slot, p.size)
end
ClickAuctionSellItemButton()
p.state = "placing"
p.t = now
elseif p.state == "placing" then
local link, name, count = Atr_SellSlotLink()
if name and count == p.size then
p.state = "ready"
elseif now - p.t > 3 then
if CursorHasItem() then DropCursorAnywhere() end
Finish("Could not place the item in the auction slot (" .. p.done .. " of " .. p.n .. " posted).")
end
elseif p.state == "ready" then
StartAuction(p.bid, p.buyout, p.minutes)
p.state = "posting"
p.t = now
elseif p.state == "posting" then
local name = GetAuctionSellItemInfo()
if (not name) and now - p.t > 0.3 then
p.done = p.done + 1
if p.multi and G.Atr_OnAuctionMultiSellUpdate then
local sv1, sv2 = G.arg1, G.arg2
G.arg1, G.arg2 = p.done, p.n
Atr_OnAuctionMultiSellUpdate()
G.arg1, G.arg2 = sv1, sv2
end
if p.done >= p.n then
post = nil
else
p.state = "prep"
p.t = now + 0.3
end
elseif now - p.t > 6 then
Finish("The server did not confirm the auction (" .. p.done .. " of " .. p.n .. " posted).")
end
end
end
driver:SetScript("OnUpdate", function()
if not post then return end
acc = acc + arg1
if acc < 0.1 then return end
acc = 0
Step()
end)
driver:RegisterEvent("UI_ERROR_MESSAGE")
driver:RegisterEvent("AUCTION_HOUSE_CLOSED")
driver:SetScript("OnEvent", function()
if not post then return end
if event == "AUCTION_HOUSE_CLOSED" then
post = nil
elseif event == "UI_ERROR_MESSAGE" and (post.state == "placing" or post.state == "ready" or post.state == "posting") then
Finish("Posting stopped: " .. tostring(arg1))
end
end)
function Atr_PostingInProgress()
return post ~= nil
end
-- startingBid / buyout are per stack; duration is 1,2,3
function Atr_StartAuction(startingBid, buyout, duration, stackSize, numStacks)
if post then return end
local link, name, count = Atr_SellSlotLink()
local id = LinkId(link)
stackSize = stackSize or count or 1
numStacks = numStacks or 1
if not name then return end
local minutes = AtrNS.durationMinutes[duration] or duration or 1440
if numStacks == 1 and count == stackSize then
StartAuction(startingBid, buyout, minutes)
return
end
if not id then
StartAuction(startingBid, buyout, minutes)
return
end
post = { id = id, size = stackSize, n = numStacks, done = 0, bid = startingBid, buyout = buyout,
minutes = minutes, state = "prep", t = GetTime(), multi = (numStacks > 1) }
if post.multi and G.Atr_OnAuctionMultiSellStart then Atr_OnAuctionMultiSellStart() end
end
----------------------------------------------------------------------
-- selected row in the lists. The 3.x code locked the button in its PUSHED state; the list buttons have
-- no NormalTexture and the 1.12 client never hid the pushed texture again, so every clicked row stayed
-- yellow. The rows now carry their own selection texture instead (the XML PushedTextures are removed).
----------------------------------------------------------------------
function Atr_SetRowSelected(row, on)
local sel = row.atrSel
if not sel then
sel = row:CreateTexture(nil, "BACKGROUND")
sel:SetTexture("Interface\\HelpFrame\\HelpFrameButton-Highlight")
sel:SetAllPoints(row)
local name = row:GetName() or ""
if string.find(name, "^AuctionatorEntry") then
sel:SetTexCoord(0.25, 0.35, 0.0, 0.6)
else
sel:SetTexCoord(0.15, 0.25, 0.0, 0.6)
if sel.SetBlendMode then sel:SetBlendMode("ADD") end
end
row.atrSel = sel
end
if on then sel:Show() else sel:Hide() end
end
----------------------------------------------------------------------
-- Blizzard coins instead of g/s/c letters (1.12 text cannot hold inline textures, so prices use small
-- money frames: tooltips, the Stack Price column, the deposit line)
----------------------------------------------------------------------
local moneyFrames = 0
function Atr_CreateMoneyFrame(parent, moneyType)
moneyFrames = moneyFrames + 1
local f = CreateFrame("Frame", "AtrMoneyFrame" .. moneyFrames, parent, "SmallMoneyFrameTemplate")
local saved = G.this
G.this = f
f.small = 1
MoneyFrame_SetType(moneyType or "STATIC")
G.this = saved
return f
end
function Atr_SetMoney(frame, money)
MoneyFrame_Update(frame:GetName(), money or 0)
end
-- tooltips: one pooled money frame per priced line, hidden whenever the tooltip is reused or hidden
local function TipHideMoney(tip, resetWidth)
if tip.atrMoney then
for i = 1, getn(tip.atrMoney) do tip.atrMoney[i]:Hide() end
end
tip.atrMoneyUsed = 0
if resetWidth and tip.atrMinWidth then
tip.atrMinWidth = nil
tip:SetMinimumWidth(0)
end
end
local function TipHook(tip)
if tip.atrMoney then return end
tip.atrMoney = {}
local origHide = tip:GetScript("OnHide")
tip:SetScript("OnHide", function()
TipHideMoney(tip, true)
if origHide then origHide() end
end)
Atr_hooksecurefunc(tip, "SetOwner", function(t) TipHideMoney(tip, true) end)
Atr_hooksecurefunc(tip, "ClearLines", function(t) TipHideMoney(tip, true) end)
end
function Atr_TipBeginMoney(tip)
TipHideMoney(tip, false)
end
function Atr_TipAddMoneyLine(tip, label, money)
TipHook(tip)
tip:AddDoubleLine(label, " ")
local n = tip:NumLines()
local used = (tip.atrMoneyUsed or 0) + 1
tip.atrMoneyUsed = used
local mf = tip.atrMoney[used]
if not mf then
mf = Atr_CreateMoneyFrame(tip, "STATIC")
tip.atrMoney[used] = mf
end
Atr_SetMoney(mf, money)
mf:ClearAllPoints()
local tname = tip:GetName()
local left = getglobal(tname .. "TextLeft" .. n)
local right = getglobal(tname .. "TextRight" .. n)
if right then
mf:SetPoint("RIGHT", right, "RIGHT", 13, 0) -- the coins sit 13px inside the frame's right edge
elseif left then
mf:SetPoint("LEFT", left, "RIGHT", 8, 0)
end
mf:Show()
local need = (left and left:GetStringWidth() or 80) + mf:GetWidth() + 30
if need > (tip.atrMinWidth or 0) then
tip.atrMinWidth = need
tip:SetMinimumWidth(need)
end
end
-- Stack Price column of the auction list rows
function Atr_ShowRowStackMoney(row, money)
local mf = row.atrStackMoney
if not money then
if mf then mf:Hide() end
return
end
if not mf then
mf = Atr_CreateMoneyFrame(row, "STATIC")
mf:SetPoint("RIGHT", row, "LEFT", 573, 0) -- right edge of the 120px column at x=440
row.atrStackMoney = mf
end
Atr_SetMoney(mf, money)
mf:Show()
end
-- "Deposit:" line of the Sell tab
function Atr_ShowDepositMoney(money, suffix)
local mf = AtrNS.depositMoney
if not money then
if mf then mf:Hide() end
return
end
if not mf then
mf = Atr_CreateMoneyFrame(Atr_Deposit_Text:GetParent(), "STATIC")
mf:SetPoint("LEFT", Atr_Deposit_Text, "RIGHT", 2, 0)
local fs = mf:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
fs:SetPoint("LEFT", mf, "RIGHT", -8, 0)
mf.atrSuffix = fs
AtrNS.depositMoney = mf
end
Atr_SetMoney(mf, money)
mf.atrSuffix:SetText(suffix or "")
mf:Show()
end
----------------------------------------------------------------------
-- odds and ends the 3.x code takes for granted
----------------------------------------------------------------------
function Atr_ItemStackSize(link)
if not link then return 0 end
local _, _, _, _, _, _, _, stack = Atr_GetItemInfo(link)
return stack or 0
end
-- no sort-clear call on 1.12; Auctionator only used it to start from a known state
function Atr_SortAuctionClearSort(kind)
end
-- the stock helper returns nil until the first scroll
do
local origGetOffset = G.FauxScrollFrame_GetOffset
if origGetOffset then
function FauxScrollFrame_GetOffset(frame)
return origGetOffset(frame) or 0
end
end
end
if not G.ClearCursor then
function ClearCursor()
if CursorHasItem() then Atr_DropCursorAnywhere() end
end
end
-- addon memory reporting does not exist on 1.12 (only used by /atr mem)
if not G.UpdateAddOnMemoryUsage then
function UpdateAddOnMemoryUsage() end
end
if not G.GetAddOnMemoryUsage then
function GetAddOnMemoryUsage() return 0 end
end
if not G.GetAddOnMetadata then
function GetAddOnMetadata() return Atr_GetVersion() end
end