forked from John-The-Baptist/OctoAuctionator
568 lines
18 KiB
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
|