From 4949bfdd89a022e1e86f3f4835148f97e1422e0c Mon Sep 17 00:00:00 2001 From: John-The-Baptist <921+john-the-baptist@noreply.octowow.st> Date: Wed, 7 Oct 2026 19:10:24 +0000 Subject: [PATCH] Add Vanilla compatibility layer and installation guide --- Compat.lua | 567 +++++++++++++++++++++++++++++ OctoAuctionator.toc | 34 ++ README.md | 52 ++- zcUtils.lua | 857 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1507 insertions(+), 3 deletions(-) create mode 100644 Compat.lua create mode 100644 OctoAuctionator.toc create mode 100644 zcUtils.lua diff --git a/Compat.lua b/Compat.lua new file mode 100644 index 0000000..8b8ae9f --- /dev/null +++ b/Compat.lua @@ -0,0 +1,567 @@ +-- 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 diff --git a/OctoAuctionator.toc b/OctoAuctionator.toc new file mode 100644 index 0000000..cf255e8 --- /dev/null +++ b/OctoAuctionator.toc @@ -0,0 +1,34 @@ +## Interface: 11200 +## Title: OctoAuctionator +## Version: 1.0.0 +## Author: John The Baptist (port of Auctionator 2.6.2 by Zirco) +## Notes: Auctionator 2.6.2 ported to the 1.12 client: Buy, Sell and More tabs, shopping lists, Full Scan, tooltip prices +## OptionalDeps: SellValue +## SavedVariablesPerCharacter: AUCTIONATOR_SHOW_ST_PRICE, AUCTIONATOR_ENABLE_ALT, AUCTIONATOR_OPEN_FIRST, AUCTIONATOR_OPEN_BUY, AUCTIONATOR_OPEN_ALL_BAGS, AUCTIONATOR_DEF_DURATION, AUCTIONATOR_SHOW_TIPS, AUCTIONATOR_V_TIPS, AUCTIONATOR_A_TIPS, AUCTIONATOR_D_TIPS, AUCTIONATOR_SHIFT_TIPS, AUCTIONATOR_DE_DETAILS_TIPS, AUCTIONATOR_DEFTAB +## SavedVariables: AUCTIONATOR_SAVEDVARS, AUCTIONATOR_PRICING_HISTORY, AUCTIONATOR_SHOPPING_LISTS, AUCTIONATOR_PRICE_DATABASE, AUCTIONATOR_LAST_SCAN_TIME, AUCTIONATOR_TOONS, AUCTIONATOR_STACKING_PREFS, AUCTIONATOR_SCAN_MINLEVEL + +Compat.lua + +Locales\enUS.lua + +zcUtils.lua + +Auctionator.lua +AuctionatorShop.lua + +Auctionator.xml + +AuctionatorLocalize.lua +AuctionatorPane.lua +AuctionatorScan.lua +AuctionatorQuery.lua +AuctionatorHints.lua +AuctionatorVendor.lua +AuctionatorConflicts.lua +AuctionatorAPI.lua +AuctionatorConfig.lua +AuctionatorBuy.lua + +AuctionatorConfig.xml + +AuctionatorOptions.lua diff --git a/README.md b/README.md index 66cdd1e..8233715 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,49 @@ -# OctoAuctionator - -Classic Auctionator 2.6.2 port for OctoWoW 1.18.1. Buy, Sell and More tabs. Made by John The Baptist; original addon by Zirco. \ No newline at end of file +# OctoAuctionator 1.0.0 + +The classic **Auctionator 2.6.2** auction house (Buy / Sell / More... tabs) ported to **OctoWoW 1.18.1** (1.12.1 client). +A free addon. + +**Made by John The Baptist.** + +## What it does + +- **Buy:** type a name and press Search. Results fill in while the pages are still coming in, so you can already pick + a row and press Buy (the scan pauses for the purchase and then carries on). Click **Item Price** to flip + cheapest-first / most-expensive-first. Recent searches and your own shopping lists sit on the left. +- **Sell:** drop an item in the slot and it searches that item, shows the current auctions and recommends a buyout + just under the cheapest one. Set the stack size and number of stacks, pick 6 / 24 / 72 hours, Create Auction. +- **More...:** your own items: what you have on the AH right now (check for undercuts) and everything you posted before. +- **Full Scan:** optional. Scans the whole auction house and stores the lowest prices for the tooltips. +- **Tooltips:** auction price of an item (and the vendor price if you use SellValue), shown with the game's own coins. + +**About speed:** the server sends 50 auctions per page and only while the auction window is open; the 1.12 client has +no "get all". A normal search is a page or a few; a Full Scan of a big auction house takes a while. + +## Install + +1. Download **OctoAuctionator-1.0.0.zip** from Releases or the forum attachment. +2. Extract it into your WoW `Interface/AddOns` folder. +3. Restart WoW and enable **OctoAuctionator** at character selection. + +The folder must contain `Interface/AddOns/OctoAuctionator/OctoAuctionator.toc`. +Turn off other auction house addons (aux, other Auctionator versions) to avoid two UIs fighting over the window. +**SellValue** is optional (vendor prices in tooltips). + +## Usage + +- Open the auction house: the **Buy**, **Sell** and **More...** tabs are next to Browse / Bids / Auctions. +- The **Auctionator** button (top right) opens the options; **Full Scan...** starts a full scan. +- `/atr clear fullscandb` clears the stored scan prices, `/atr clear posthistory` clears your posting history. + +## Screenshots + +![Buy tab](screenshots/octoauctionator-buy.jpg) + +![Sell tab](screenshots/octoauctionator-sell.jpg) + +## Credits + +Original addon: **Auctionator 2.6.2 by Zirco** (WoWInterface). This version keeps his interface and code and adapts +them to the 1.12 client and this server. + +*It's not much, but it's honest work.* diff --git a/zcUtils.lua b/zcUtils.lua new file mode 100644 index 0000000..f53bdd4 --- /dev/null +++ b/zcUtils.lua @@ -0,0 +1,857 @@ + +-- Zirco's utilities + +-- This module should contain no globals as it is intended to be "linked" in to each of Zirco's addons + + +local addonName, addonTable = "Auctionator", AtrNS; +local zc = {}; + +addonTable.zc = zc; + +----------------------------------------- + +function zc.RGBtoHEX (r,g,b) + + local hex = ""; + + return string.format ("%02x%02x%02x", r * 255, g * 255, b * 255); + +end + +----------------------------------------- + +function zc.EnableDisable (elem, b) + + if (b) then + elem:Enable(); + else + elem:Disable(); + end +end + +----------------------------------------- + +function zc.ShowHide (elem, b) + + if (b) then + elem:Show(); + else + elem:Hide(); + end +end + +----------------------------------------- + +function zc.SetTextIf (elem, b, t1, t2) + + if (b) then + elem:SetText(t1); + else + elem:SetText(t2); + end +end + +----------------------------------------- + +function zc.Val (val, ifNilVal) + + if (val == nil) then + return ifNilVal; + end + + return val; +end + +----------------------------------------- + +function zc.Min (a, b) + + if (a == nil) then + return b; + end + + if (b == nil) then + return a; + end + + return math.min (tonumber (a), tonumber (b)); +end + +----------------------------------------- + +function zc.Max (a, b) + + if (a == nil) then + return b; + end + + if (b == nil) then + return a; + end + + return math.max (tonumber (a), tonumber (b)); +end + +----------------------------------------- + +function zc.If (b, x, y) + + if (b ~= nil and b ~= false) then + return x; + end + + return y; +end + +----------------------------------------- + +function zc.PrintKeysSorted (t) + + local ta = {}; + + for a,v in pairs (t) do + table.insert (ta, a); + end + + table.sort (ta, function (a,b) return (string.lower(a) < string.lower(b)); end); + + for x = 1, getn(ta) do + zc.msg_pink (x.." "..ta[x]); + end + +end + +----------------------------------------- + +function zc.UTF8_Truncate (s, newlen) + + if (string.len (s) <= newlen) then + return s; + end + + local x, c; + + for x = newlen, 1, -1 do + c = string.byte (s, x+1); + if (c ~= nil and (c < 128 or c >= 192)) then -- the next byte starts a character, so cut here + return string.sub (s, 1, x); + end + end + + return string.sub (s, 1, newlen); +end + +----------------------------------------- + +function zc.GetArrayElemOrFirst (a, x) + + if (a and getn(a) > 0) then + if (x == nil or x < 1 or x > getn(a)) then + x = 1; + end + + return a[x]; + end + + return nil; +end + +----------------------------------------- + +function zc.GetArrayElemOrNil (a, x) + + if (a and getn(a) > 0) then + if (x == nil or x < 1 or x > getn(a)) then + return nil; + end + + return a[x]; + end + + return nil; +end + +----------------------------------------- + +function zc.padstring (s, n, c) + while (string.len (s) < n) do + s = c..s; + end + + return s; +end + + +----------------------------------------- + +local encTable = {"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z", + "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z", + "0","1","2","3","4","5","6","7","8","9", + "-", "_" }; + + +local decTable; + +----------------------------------------- + +local function BuildDecTable() + + if (decTable == nil) then + decTable = {}; + local i; + for i = 1,64 do + decTable[encTable[i]] = i-1; + end + end + +end + +----------------------------------------- + +function zc.enc64 (n) + + if (n == 0) then + return encTable[1]; + end + + local k = n; + local result = ""; + + while k > 0 do + local x = mod (k, 64); + result = encTable[x+1]..result; + k = floor (k / 64); + end + + return result; +end + +----------------------------------------- + +function zc.dec64 (s) + + if (s == nil or s == "") then + return 0; + end + + BuildDecTable(); + + local result = 0; + local len = string.len (s); + local x; + + for x = 1, len do + result = result * 64; + result = result + decTable[string.sub(s,x,x)]; + end + + return result; +end + +----------------------------------------- + +function ZF (s) + BuildDecTable(); + + local s2 = ""; + local n; + + for n = 1, string.len(s) do + local c = string.sub(s, n,n); + local x = decTable[c]; + + if (x == nil) then + s2 = s2..c; + else + if (x < 32) then x = x + 32; + else x = x - 32; + end; + s2 = s2..encTable[x+1]; + end + end + + return s2; + +end + +----------------------------------------- + +function zc.words(str) + local t = {} + local function helper(word) table.insert(t, word) return "" end + local rest = string.gsub (str, "%w+", helper); + if (not string.find (rest, "%S")) then + if (getn(t) == 1) then return t[1]; end; + if (getn(t) == 2) then return t[1],t[2]; end; + if (getn(t) == 3) then return t[1],t[2],t[3]; end; + if (getn(t) == 4) then return t[1],t[2],t[3],t[4]; end; + if (getn(t) == 5) then return t[1],t[2],t[3],t[4],t[5]; end; + return t; + end +end + +----------------------------------------- + +local gDeferredCalls = {}; + +----------------------------------------- + +function zc.AddDeferredCall (seconds, funcname, param1, param2, tag) -- tag is optional. if present used to overwrite prior call with same tag + + local now = time(); + + local cfdEntry = {}; + + cfdEntry.funcname = funcname; + cfdEntry.param1 = param1; + cfdEntry.param2 = param2; + cfdEntry.when = now + seconds; + cfdEntry.tag = ""; + + if (tag) then + cfdEntry.tag = tag; + + for i = 1, getn(gDeferredCalls) do + if (gDeferredCalls[i].tag == tag) then + gDeferredCalls[i] = cfdEntry; -- overwrite + return; + end + end + end + + table.insert (gDeferredCalls, cfdEntry); +end + + +----------------------------------------- + +function zc.CheckDeferredCall () + + local now = time(); + local i; + + for i = 1, getn(gDeferredCalls) do + if (gDeferredCalls[i].when < now) then + local fcn = getglobal(gDeferredCalls[i].funcname); + local p1 = gDeferredCalls[i].param1; + local p2 = gDeferredCalls[i].param2; + table.remove (gDeferredCalls, i); + if (type(fcn) == 'function') then + fcn(p1, p2); + end + + return; -- only do one + end + end + +end + +----------------------------------------- + +function zc.periodic (elem, name, period, elapsed) + + local t = elem[name] or 0; + + t = t + elapsed; + + if (t > period) then + elem[name] = 0; + return true; + end + + elem[name] = t; + return false; +end + + +----------------------------------------- + +function zc.tableIsEmpty (t) + + local n, v; + for n, v in pairs (t) do + return false; + end + + return true; +end + +----------------------------------------- + +function zc.PrintTable (t, indent) + + if (not indent) then + indent = 0; + end + + local x + local padding = ""; + for x = 1,indent do + padding = padding.." "; + end + + zc.msg ("-------"); + + for n, v in pairs (t) do + if (type(v) == "table") then + zc.msg (padding..n, "TABLE"); + zc.PrintTable(v, indent+1); + elseif (type(v) == "userdata") then + zc.msg (padding..n, "userdata"); + else + zc.msg (padding..n, v); + end + end + +end + +----------------------------------------- + +function zc.ItemIDfromLink (itemLink) + + if (itemLink == nil) then + return 0,0,0; + end + + -- 1.12 item strings are item:id:enchant:suffix:unique + local found, _, itemString = string.find(itemLink, "^|c%x+|H(.+)|h%[.*%]") + + if (itemString == nil) then + found, _, itemString = string.find (itemLink, "(item:[%-%d:]+)"); + end + + if (itemString == nil) then + return 0,0,0; + end + + local _, itemId, _, suffixId, uniqueId = Atr_strsplit (":", itemString) + + return itemId, suffixId or 0, uniqueId or 0; + +end + +----------------------------------------- + +function zc.BoolToString (b) + if (b) then + return "true"; + end + + return "false"; +end + +----------------------------------------- + +function zc.BoolToNum (b) + if (b) then + return 1; + end + + return 0; +end + +----------------------------------------- + +function zc.NumToBool (n) + if (n == 0) then + return false; + end + + return true; +end + +----------------------------------------- + +function zc.pluralizeIf (word, count) + + if (count and count == 1) then + return word; + else + return zc.pluralize(word); + end +end + +----------------------------------------- + +function zc.pluralize (word) + + return word.."s"; + +end + +----------------------------------------- + +function zc.round (v) + return math.floor (v + 0.5); +end + +----------------------------------------- + +function zc.msg_red (...) zc.msg_color (1, 0, 0, unpack(arg)); end +function zc.msg_pink (...) zc.msg_color (1, .6, .6, unpack(arg)); end +function zc.msg_yellow (...) zc.msg_color (1, 1, 0, unpack(arg)); end + +----------------------------------------- + +function zc.msg_color (r, g, b, ...) + + local options = {}; + options.r = r; + options.g = g; + options.b = b; + + zc.msg_ex (options, unpack(arg)); +end + + +----------------------------------------- + +function zc.msg_str (...) + + local options = {}; + options.str = true; + + return zc.msg_ex (options, unpack(arg)); +end + +----------------------------------------- + +function zc.msg_atr (...) + + zc.msg_yellow ("|cff00ffff|r", unpack(arg)); +end + + +----------------------------------------- + +function zc.HSV2RGB (h, s, v) + + local r, g, b; + + local hi = mod (math.floor(h/60), 6); + local f = h/60 - math.floor(h/60); + local p = v * (1-s); + local q = v * (1-(f*s)); + local t = v * (1-((1-f)*s)); + + if (hi == 0) then return v, t, p; end + if (hi == 1) then return q, v, p; end + if (hi == 2) then return p, v, t; end + if (hi == 3) then return p, q, v; end + if (hi == 4) then return t, p, v; end + if (hi == 5) then return v, p, q; end + + return +end + +----------------------------------------- + +function zc.md (...) + -- developer trace output: disabled in the 1.12 port +end + +----------------------------------------- + +function zc.msg (...) + + local options = {}; + + zc.msg_ex (options, unpack(arg)); +end + +----------------------------------------- + +function zc.msg_ex (options, ...) + + if (not DEFAULT_CHAT_FRAME) then + return; + end + + local msg = ""; + + local i; + local m; + + for i = 1, arg.n do + + local v = arg[i]; + + if (type(v) == "boolean") then m = zc.BoolToString(v); + elseif (type(v) == "table") then m = ""; + elseif (v == nil) then m = ""; + else m = v; + end + + msg = msg.." "..m; + + end + + if (options.str) then + return msg; + end + + if (options.r ~= nil) then + DEFAULT_CHAT_FRAME:AddMessage (msg, options.r, options.g, options.b); + else + DEFAULT_CHAT_FRAME:AddMessage (msg); + end +end + + + +----------------------------------------- + +function zc.val2gsc (v) + local rv = zc.round(v) + + local g = math.floor (rv/10000); + + rv = rv - g*10000; + + local s = math.floor (rv/100); + + rv = rv - s*100; + + local c = rv; + + return g, s, c +end + +----------------------------------------- + +function zc.priceToString (val) + + local gold, silver, copper = zc.val2gsc(val); + + local st = ""; + + + if (gold ~= 0) then + st = gold.."g "; + end + + + if (st ~= "") then + st = st..format("%02is ", silver); + elseif (silver ~= 0) then + st = st..silver.."s "; + end + + + if (st ~= "") then + st = st..format("%02ic", copper); + elseif (copper ~= 0) then + st = st..copper.."c"; + end + + return st; +end + +----------------------------------------- + +local goldicon = "|cffffd100g|r" +local silvericon = "|cffc7c7cfs|r" +local coppericon = "|cffeda55fc|r" + +----------------------------------------- + +function zc.priceToMoneyString (val, noZeroCoppers) + + local gold, silver, copper = zc.val2gsc(val); + + local st = ""; + + if (gold ~= 0) then + st = gold..goldicon.." "; + end + + + if (st ~= "") then + st = st..format("%02i%s ", silver, silvericon); + elseif (silver ~= 0) then + st = st..silver..silvericon.." "; + end + + if (noZeroCoppers and copper == 0) then + return st; + end + + if (st ~= "") then + st = st..format("%02i%s", copper, coppericon); + elseif (copper ~= 0) then + st = st..copper..coppericon; + end + + return st; + +end + +----------------------------------------- + +function zc.StringSame (s1, s2) + if (s1 == nil and s2 == nil) then + return true; + end + + if (s1 == nil or s2 == nil) then + return false; + end + + if (s1 == s2) then -- maybe will fix german umlaut problem? + return true; + end + + return (string.lower (s1) == string.lower (s2)); +end + +----------------------------------------- + +function zc.StringContains (s, sub) + if (sub == nil or sub == "") then + return false; + end + + local start, stop = string.find (string.lower(s), string.lower(sub), 1, true); + + return (start ~= nil); +end + +----------------------------------------- + +function zc.StringEndsWith (s, sub) + + if (sub == nil or sub == "") then + return false; + end + + local i = string.len(s) - string.len(sub); + + if (i < 0) then + return false; + end + + local sEnd = string.sub (s, i+1); + + return (string.lower (sEnd) == string.lower (sub)); + +end + +----------------------------------------- + +function zc.StringStartsWith (s, sub) + + if (s == nil or sub == nil or sub == "") then + return false; + end + + local sublen = string.len (sub); + + if (string.len (s) < sublen) then + return false; + end + + return (string.lower (string.sub(s, 1, sublen)) == string.lower(sub)); + +end + +----------------------------------------- + +function zc.CopyDeep (src) + + local result = {}; + + for n, v in pairs (src) do + if (type(v) == "table") then + result[n] = zc.CopyDeep(v); + else + result[n] = v; + end + end + + return result; + +end + +----------------------------------------- + +function zc.printableLink (link) + + if (link == nil) then + return "nil"; + end + + local printable = gsub(link, "\124", "\124\124"); + + return printable; +end + +----------------------------------------- + +function zc.printmem () + + local cmem = math.floor(collectgarbage ("count")) + + UpdateAddOnMemoryUsage(); + local mem = GetAddOnMemoryUsage("OctoAuctionator"); + zc.msg_atr (math.floor(mem).." KB (total LUA: "..cmem.." KB)"); +end + +----------------------------------------- + +function zc.printstack (options) + + return {}; -- needs debugstack(), which the 1.12 client does not have + +end + + + +----------------------------------------- + +function zc.tallyAdd (ttable, value) + + if (ttable[value]) then + ttable[value] = ttable[value] + 1; + else + ttable[value] = 1; + end +end + + +----------------------------------------- + +function zc.tallyPrint (ttable, options) + + local sortedTable = {}; + local total = 0; + + local n = 1; + for value,count in pairs(ttable) do + + sortedTable[n] = {}; + sortedTable[n].value = value; + sortedTable[n].count = count; + + total = total + count; + + n = n + 1; + end + + + if (options.sortByValue and options.sortDesc) then table.sort (sortedTable, function(x,y) return x.value > y.value; end); + elseif (options.sortByValue and not options.sortDesc) then table.sort (sortedTable, function(x,y) return x.value < y.value; end); + elseif (options.sortDesc) then table.sort (sortedTable, function(x,y) return x.count > y.count; end); + else table.sort (sortedTable, function(x,y) return x.count < y.count; end); + end + + + for n = 1, getn(sortedTable) do + + if (not options.printCount or n < options.printCount) then + zc.msg_pink (sortedTable[n].count.." "..sortedTable[n].value); + end + end + + zc.msg_yellow ("Total: "..total); +end