This commit is contained in:
Andrew6810
2022-11-05 21:19:42 -07:00
parent b79f4bd588
commit f3e579cb57
386 changed files with 93729 additions and 2 deletions
+824
View File
@@ -0,0 +1,824 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...)
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
TSMAPI.AuctionControl = {}
local private = {}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.AuctionControl_private")
LibStub("AceEvent-3.0"):Embed(private)
private.matchList = {}
private.currentPage = {}
local function GetNumInBags(baseItemString)
local num = 0
for _, _, itemString, quantity in TSMAPI:GetBagIterator() do
if TSMAPI:GetBaseItemString(itemString) == baseItemString then
num = num + quantity
end
end
return num
end
local function ValidateAuction(index, list)
if not private.currentAuction then return end
local itemString, count, buyout, data, _
if type(list) == "table" then
itemString, count, buyout = unpack(list)
elseif type(list) == "string" then
itemString = TSMAPI:GetItemString(GetAuctionItemLink(list, index))
-- _, _, count, _, _, _, _, _, _, buyout = GetAuctionItemInfo(list, index)
_, _, count, _, _, _, _, _, buyout = GetAuctionItemInfo(list, index)
data = {itemString, count, buyout}
else
return
end
return count == private.currentAuction.count and buyout == private.currentAuction.buyout and itemString == private.currentAuction.itemString, data
end
local diffFrame = CreateFrame("Frame")
diffFrame:Hide()
diffFrame.num = 0
diffFrame:RegisterEvent("CHAT_MSG_SYSTEM")
diffFrame:RegisterEvent("UI_ERROR_MESSAGE")
diffFrame:SetScript("OnEvent", function(self, event, arg)
if event == "UI_ERROR_MESSAGE" then
if arg == ERR_ITEM_NOT_FOUND then
local auctionExists
for i=1, GetNumAuctionItems("list") do
if ValidateAuction(i, "list") then
auctionExists = true
break
end
end
if not auctionExists then
self.num = self.num - 1
end
elseif arg == ERR_AUCTION_HIGHER_BID then
local auctionExists
for i=1, GetNumAuctionItems("list") do
if ValidateAuction(i, "list") then
auctionExists = true
break
end
end
if not auctionExists then
self.num = self.num - 1
end
end
elseif event == "CHAT_MSG_SYSTEM" then
if arg == ERR_AUCTION_BID_PLACED then
self.num = self.num - 1
end
end
end)
local customPriceWarned
function private:SetCurrentAuction(record)
if not record then
private.currentAuction = nil
return
end
local buyout = record.buyout
if private.confirmationMode == "Post" and not record:IsPlayer() then
local undercut = TSMAPI:ParseCustomPrice(private.postUndercut)
undercut = undercut and undercut(record.parent:GetItemString())
if not undercut and not customPriceWarned then
TSM:Print(L["Invalid custom price for undercut amount. Using 1c instead."])
customPriceWarned = true
undercut = 1
end
buyout = buyout - undercut
end
private.currentAuction = {
link = record.parent.itemLink,
itemString = record.parent:GetItemString(),
buyout = buyout,
count = record.count,
numAuctions = record.numAuctions,
seller = record.seller,
isPlayer = record:IsPlayer(),
num = 1,
destroyingNum = record.parent.destroyingNum,
}
end
local count = 0
function private:FindCurrentAuctionForBuyout(noCache, resetCount)
if not private.currentAuction then return end
if diffFrame.num > 0 then
return TSMAPI:CreateTimeDelay(0.2, private.FindCurrentAuctionForBuyout)
end
if resetCount then
count = 0
end
count = count + 1
private:UpdateMatchList(true)
if #private.matchList > 0 then
-- the next item is on the current page
private:UpdateAuctionConfirmation()
return
end
private.matchList = {}
private.currentPage = {}
if count > 3 then
-- auction no longer exists
TSM:Print(L["Skipping auction which no longer exists."])
diffFrame.num = diffFrame.num - 1
private.justBought = true
private:AUCTION_ITEM_LIST_UPDATE()
return
end
TSMAPI.AuctionScan:FindAuction(private.OnAuctionFound, {itemString=private.currentAuction.itemString, buyout=private.currentAuction.buyout, count=private.currentAuction.count, seller=private.currentAuction.seller}, not noCache)
private.isSearching = true
end
function private:DoBuyout()
if private.isSearching or not private.currentAuction or not private.confirmationFrame:IsVisible() then return end
for i=#private.matchList, 1, -1 do
local aucIndex = private.matchList[i]
tremove(private.matchList, i)
tremove(private.currentPage, aucIndex)
if ValidateAuction(aucIndex, "list") then
PlaceAuctionBid("list", aucIndex, private.currentAuction.buyout)
private.justBought = true
diffFrame.num = diffFrame.num + 1
return
end
end
private:FindCurrentAuctionForBuyout()
end
function private:DoCancel()
if private.isSearching or not private.currentAuction or not private.confirmationFrame:IsVisible() then return end
local function OnCancel()
private.justBought = true
private:AUCTION_ITEM_LIST_UPDATE()
end
for i=GetNumAuctionItems("owner"), 1, -1 do
if ValidateAuction(i, "owner") then
CancelAuction(i)
-- wait for all the events that are triggered by this action
private:RegisterMessage("TSM_AH_EVENTS", OnCancel)
TSMAPI:WaitForAuctionEvents("Cancel")
return
end
end
TSM:Print(L["Auction not found. Skipped."])
private.justBought = true
private:AUCTION_ITEM_LIST_UPDATE()
end
function private:DoPost(postInfo)
if private.isSearching or not postInfo or not private.postFrame:IsVisible() then return end
if not AuctionFrameAuctions.duration then
-- Fix in case Blizzard_AuctionUI hasn't set this value yet (which could cause an error)
AuctionFrameAuctions.duration = postInfo.duration
end
local bag, slot
for b, s, itemString in TSMAPI:GetBagIterator() do
if postInfo.itemString == itemString then
bag, slot = b, s
break
end
end
if not bag then
TSM:Print(L["Item not found in bags. Skipping"])
return
end
local function OnPost()
private.postFrame:Hide()
postInfo.duration = postInfo.duration == 1 and 3 or 4
TSM:AuctionControlCallback("OnPost", postInfo)
TSMAPI:FireEvent("TSM:AUCTIONCONTROL:ITEMPOSTED", postInfo)
end
private:RegisterMessage("TSM_AH_EVENTS", OnPost)
TSMAPI:WaitForAuctionEvents("Post", postInfo.numAuctions > 1)
PickupContainerItem(bag, slot)
ClickAuctionSellItemButton(AuctionsItemButton, "LeftButton")
StartAuction(postInfo.bid, postInfo.buyout, postInfo.duration, postInfo.stackSize, postInfo.numAuctions)
end
function private:UpdateMatchList(noPageScanning)
private.matchList = {}
if noPageScanning then
for i=1, #private.currentPage do
if ValidateAuction(i, private.currentPage[i]) then
tinsert(private.matchList, i)
end
end
else
private.currentPage = {}
for i=1, GetNumAuctionItems("list") do
local isValid, data = ValidateAuction(i, "list")
private.currentPage[i] = data
if isValid then
tinsert(private.matchList, i)
end
end
end
end
function private:OnAuctionFound(cacheIndex)
if not private.isSearching or not private.currentAuction then return end
private.isSearching = nil
private:UpdateMatchList()
if #private.matchList == 0 then
private:FindCurrentAuctionForBuyout(true)
else
private.currentCacheIndex = cacheIndex
private:UpdateAuctionConfirmation()
end
end
function private:AUCTION_ITEM_LIST_UPDATE()
if not private.currentAuction or not TSMAPI:AHTabIsVisible(private.module) then return end
if private.justBought then
private.justBought = nil
private.currentAuction.num = private.currentAuction.num + 1
local prevAuction = CopyTable(private.currentAuction)
if private.currentAuction.num > private.currentAuction.numAuctions then
TSMAPI.AuctionControl:HideConfirmation()
else
if #private.matchList > 0 then
private:UpdateAuctionConfirmation()
else
private:FindCurrentAuctionForBuyout(nil, true)
end
end
if private.currentCacheIndex then
TSMAPI.AuctionScan:CacheRemove(prevAuction.itemString, private.currentCacheIndex)
private.currentCacheIndex = nil
end
TSM:AuctionControlCallback("OnBuyout", prevAuction)
end
end
function TSM:AuctionControlCallback(...)
if not private.callback then return end
private.callback(...)
end
-- **************************************************************************
-- Utility TSMAPI Functions
-- **************************************************************************
function TSMAPI.AuctionControl:IsConfirmationVisible()
return (private.confirmationFrame and private.confirmationFrame:IsVisible()) or (private.postFrame and private.postFrame:IsVisible())
end
function TSMAPI.AuctionControl:IsBuyingComplete()
return diffFrame.num <= 0
end
-- **************************************************************************
-- GUI Show/Hide/Update Functions
-- **************************************************************************
function TSMAPI.AuctionControl:ShowControlButtons(parent, rt, callback, module, postBidPercent, postUndercut)
private.confirmationFrame = private.confirmationFrame or private:CreateConfirmationFrame(parent)
private.postFrame = private.postFrame or private:CreatePostFrame(parent)
private.controlButtons = private.controlButtons or private:CreateControlButtons(parent)
private.controlButtons:Show()
private.rt = rt
private.callback = callback
private.module = module
private.postBidPercent = postBidPercent
private.postUndercut = postUndercut
return private.controlButtons
end
function TSMAPI.AuctionControl:HideControlButtons()
private.controlButtons:Hide()
private.rt = nil
private.callback = nil
TSMAPI.AuctionControl:HideConfirmation()
end
function TSMAPI.AuctionControl:SetNoResultItem(itemString, buyout)
if not itemString or not buyout then return end
local link = select(2, TSMAPI:GetSafeItemInfo(itemString))
private.currentAuction = {
link = link,
itemString = itemString,
buyout = buyout,
count = 1,
numAuctions = 1,
num = 1,
isNoResult = true,
}
end
function private:ShowConfirmationWindow()
if private.confirmationFrame:IsVisible() then
private.confirmationFrame:UpdateStrata()
return
elseif private.postFrame:IsVisible() then
private.postFrame:UpdateStrata()
return
end
private:SetCurrentAuction(private.rt:GetSelectedAuction())
if not private.currentAuction then return end
private:RegisterEvent("AUCTION_ITEM_LIST_UPDATE")
diffFrame.num = 0
diffFrame:Show()
private.confirmationFrame:Show()
private.confirmationFrame.proceed:Disable()
private.confirmationFrame.linkText:SetText("")
private.confirmationFrame.quantityText:SetText("")
private.confirmationFrame.buyoutText:SetText("")
private.confirmationFrame.buyoutText2:SetText("")
private.confirmationFrame.purchasedText:SetText("")
private.confirmationFrame.searchingText:SetText(L["Searching for item..."])
if private.confirmationMode == "Buyout" then
private:FindCurrentAuctionForBuyout(nil, true)
else
private:UpdateAuctionConfirmation()
end
end
function private:ShowPostWindow()
if private.confirmationFrame:IsVisible() then
private.confirmationFrame:UpdateStrata()
return
elseif private.postFrame:IsVisible() then
private.postFrame:UpdateStrata()
return
end
if not private.currentAuction or not private.currentAuction.isNoResult then
private:SetCurrentAuction(private.rt:GetSelectedAuction())
end
if not private.currentAuction then return end
private:RegisterEvent("AUCTION_ITEM_LIST_UPDATE")
diffFrame.num = 0
diffFrame:Show()
private.postFrame:Show()
private:UpdatePostFrame()
TSMAPI:FireEvent("TSM:AUCTIONCONTROL:POSTSHOWN")
end
function TSMAPI.AuctionControl:HideConfirmation()
private:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
if private.confirmationFrame then private.confirmationFrame:Hide() end
if private.postFrame then private.postFrame:Hide() end
diffFrame:Hide()
private.isSearching = nil
private:SetCurrentAuction()
TSMAPI.AuctionScan:StopFindScan()
end
function private:UpdateAuctionConfirmation()
local buyoutText = TSMAPI:FormatTextMoneyIcon(private.currentAuction.buyout, nil, true)
local itemBuyoutText = TSMAPI:FormatTextMoneyIcon(floor(private.currentAuction.buyout/private.currentAuction.count), nil, true)
private.confirmationFrame.searchingText:SetText("")
private.confirmationFrame.linkText:SetText(private.currentAuction.link)
private.confirmationFrame.quantityText:SetText("x"..private.currentAuction.count)
private.confirmationFrame.buyoutText:SetText(format(L["Item Buyout: %s"], itemBuyoutText))
private.confirmationFrame.buyoutText2:SetText(format(L["Auction Buyout: %s"], buyoutText))
if private.confirmationMode == "Buyout" then
private.confirmationFrame.proceed:SetText(BUYOUT)
private.confirmationFrame.purchasedText:SetText(format(L["Purchasing Auction: %d/%d"], private.currentAuction.num, private.currentAuction.numAuctions))
elseif private.confirmationMode == "Cancel" then
private.confirmationFrame.proceed:SetText(CANCEL)
private.confirmationFrame.purchasedText:SetText(format(L["Canceling Auction: %d/%d"], private.currentAuction.num, private.currentAuction.numAuctions))
end
private.confirmationFrame.proceed:Enable()
end
function private:UpdatePostFrame()
local maxQuantity = select(8, TSMAPI:GetSafeItemInfo(private.currentAuction.link))
local numInBags = GetNumInBags(TSMAPI:GetBaseItemString(private.currentAuction.itemString))
local stackSize = min(private.currentAuction.count, numInBags)
local currentPerItem = floor(private.currentAuction.buyout/private.currentAuction.count)
local currentBuyout = stackSize == private.currentAuction.count and private.currentAuction.buyout or (currentPerItem*stackSize)
private.postFrame.numInBags = numInBags
private.postFrame.linkText:SetText(private.currentAuction.link)
private.postFrame.proceed:Enable()
private.postFrame.buyoutInputBox:SetText(TSMAPI:FormatTextMoney(currentBuyout, nil, nil, nil, true))
private.postFrame.perItemInputBox:SetText(TSMAPI:FormatTextMoney(currentPerItem, nil, nil, nil, true))
private.postFrame.numAuctionsInputBox.max = numInBags
private.postFrame.numAuctionsInputBox.btn:SetText(format(L["max %d"], floor(numInBags/stackSize)))
private.postFrame.numAuctionsInputBox:SetNumber(1)
private.postFrame.stackSizeInputBox.max = min(numInBags, maxQuantity)
private.postFrame.stackSizeInputBox.btn:SetText(format(L["max %d"], private.postFrame.stackSizeInputBox.max))
private.postFrame.stackSizeInputBox:SetNumber(stackSize)
private.postFrame.durationDropdown:SetValue(TSM.db.profile.postDuration)
end
-- **************************************************************************
-- GUI Creation Code
-- **************************************************************************
function private:CreateConfirmationFrame(parent)
local frame = CreateFrame("Frame", nil, parent)
TSMAPI.Design:SetFrameBackdropColor(frame)
frame:Hide()
-- frame:SetPoint("CENTER")
frame:SetPoint("BOTTOMRIGHT")
frame:SetFrameStrata("DIALOG")
frame:SetWidth(300)
frame:SetHeight(150)
frame.UpdateStrata = function()
frame:SetFrameStrata("DIALOG")
frame.bg:SetFrameStrata("HIGH")
end
frame:SetScript("OnShow", frame.UpdateStrata)
frame:SetScript("OnUpdate", function()
if not TSMAPI:AHTabIsVisible(private.module) then
TSMAPI.AuctionControl:HideConfirmation()
end
end)
local bg = CreateFrame("Frame", nil, frame)
bg:SetFrameStrata("HIGH")
bg:SetPoint("TOPLEFT", parent.content)
bg:SetPoint("BOTTOMRIGHT", parent.content)
bg:EnableMouse(true)
TSMAPI.Design:SetFrameBackdropColor(bg)
bg:SetAlpha(.2)
frame.bg = bg
local btn = TSMAPI.GUI:CreateButton(frame, 18, "TSMAHConfirmationActionButton")
btn:SetPoint("BOTTOMLEFT", 10, 10)
btn:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -2, 10)
btn:SetHeight(25)
btn:SetText("")
btn:SetScript("OnClick", function(self)
if not TSMAPI:AHTabIsVisible(private.module) then return end
self:Disable()
if private.confirmationMode == "Buyout" then
private:DoBuyout()
elseif private.confirmationMode == "Cancel" then
private:DoCancel()
end
end)
frame.proceed = btn
local btn = TSMAPI.GUI:CreateButton(frame, 18)
btn:SetPoint("BOTTOMLEFT", frame, "BOTTOM", 2, 10)
btn:SetPoint("BOTTOMRIGHT", -10, 10)
btn:SetHeight(25)
btn:SetText(CLOSE)
btn:SetScript("OnClick", function() frame:Hide() end)
frame.close = btn
local linkText = TSMAPI.GUI:CreateLabel(frame)
linkText:SetFontObject(GameFontNormal)
linkText:SetPoint("TOP", -10, -10)
frame.linkText = linkText
local bg = frame:CreateTexture(nil, "BACKGROUND")
bg:SetPoint("TOPLEFT", linkText, -2, 2)
bg:SetPoint("BOTTOMRIGHT", linkText, 2, -2)
TSMAPI.Design:SetContentColor(bg)
linkText.bg = bg
bg:Show()
local quantityText = TSMAPI.GUI:CreateLabel(frame)
quantityText:SetPoint("LEFT", linkText, "RIGHT")
frame.quantityText = quantityText
local buyoutText = TSMAPI.GUI:CreateLabel(frame)
buyoutText:SetPoint("TOPLEFT", 10, -41)
buyoutText:SetJustifyH("LEFT")
frame.buyoutText = buyoutText
local buyoutText2 = TSMAPI.GUI:CreateLabel(frame)
buyoutText2:SetPoint("TOPLEFT", buyoutText, "BOTTOMLEFT")
buyoutText2:SetJustifyH("LEFT")
frame.buyoutText2 = buyoutText2
local purchasedText = TSMAPI.GUI:CreateLabel(frame)
purchasedText:SetPoint("TOPLEFT", 10, -70)
frame.purchasedText = purchasedText
local searchingText = TSMAPI.GUI:CreateLabel(frame)
searchingText:SetPoint("CENTER")
frame.searchingText = searchingText
return frame
end
function private:CreatePostFrame(parent)
local frame = CreateFrame("Frame", nil, parent)
TSMAPI.Design:SetFrameBackdropColor(frame)
frame:Hide()
frame:SetPoint("CENTER")
frame:SetFrameStrata("DIALOG")
frame:SetWidth(250)
frame:SetHeight(245)
frame.UpdateStrata = function()
frame:SetFrameStrata("DIALOG")
frame.bg:SetFrameStrata("HIGH")
end
frame:SetScript("OnShow", frame.UpdateStrata)
frame:SetScript("OnUpdate", function()
if not TSMAPI:AHTabIsVisible(private.module) then
TSMAPI.AuctionControl:HideConfirmation()
end
end)
local bg = CreateFrame("Frame", nil, frame)
bg:SetFrameStrata("HIGH")
bg:SetPoint("TOPLEFT", parent.content)
bg:SetPoint("BOTTOMRIGHT", parent.content)
bg:EnableMouse(true)
TSMAPI.Design:SetFrameBackdropColor(bg)
bg:SetAlpha(0.2)
frame.bg = bg
local btn = TSMAPI.GUI:CreateButton(frame, 18, "TSMAHConfirmationPostButton")
btn:SetPoint("BOTTOMLEFT", 10, 10)
btn:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -2, 10)
btn:SetHeight(25)
btn:SetText(L["Post"])
btn:SetScript("OnClick", function(self)
if not TSMAPI:AHTabIsVisible(private.module) then return end
self:Disable()
local postInfo = {}
postInfo.itemString = private.currentAuction.itemString
postInfo.buyout = TSMAPI:UnformatTextMoney(frame.buyoutInputBox:GetText())
postInfo.bid = max(floor(postInfo.buyout*private.postBidPercent), 1)
postInfo.stackSize = frame.stackSizeInputBox:GetNumber()
postInfo.numAuctions = frame.numAuctionsInputBox:GetNumber()
postInfo.duration = TSM.db.profile.postDuration
private:DoPost(postInfo)
end)
frame.proceed = btn
local btn = TSMAPI.GUI:CreateButton(frame, 18)
btn:SetPoint("BOTTOMLEFT", frame, "BOTTOM", 2, 10)
btn:SetPoint("BOTTOMRIGHT", -10, 10)
btn:SetHeight(25)
btn:SetText(CLOSE)
btn:SetScript("OnClick", function() frame:Hide() end)
frame.close = btn
local linkText = TSMAPI.GUI:CreateLabel(frame)
linkText:SetFontObject(GameFontNormal)
linkText:SetPoint("TOP", -10, -10)
frame.linkText = linkText
local bg = frame:CreateTexture(nil, "BACKGROUND")
bg:SetPoint("TOPLEFT", linkText, -2, 2)
bg:SetPoint("BOTTOMRIGHT", linkText, 2, -2)
TSMAPI.Design:SetContentColor(bg)
linkText.bg = bg
bg:Show()
local function OnPriceInputBoxTextChanged()
local buyout = TSMAPI:UnformatTextMoney(frame.buyoutInputBox:GetText())
local perItem = TSMAPI:UnformatTextMoney(frame.perItemInputBox:GetText())
if not buyout or not perItem or buyout == 0 then
frame.proceed:Disable()
else
frame.proceed:Enable()
end
end
local function OnPriceInputBoxEditFocusLost(self)
local copper = TSMAPI:UnformatTextMoney(self:GetText())
if copper then
local stackSize = frame.stackSizeInputBox:GetNumber()
if self == frame.buyoutInputBox then
frame.perItemInputBox:SetText(TSMAPI:FormatTextMoney(floor(copper/stackSize), nil, nil, nil, true))
elseif self == frame.perItemInputBox then
frame.buyoutInputBox:SetText(TSMAPI:FormatTextMoney(copper*stackSize, nil, nil, nil, true))
end
self:SetText(TSMAPI:FormatTextMoney(copper, nil, nil, nil, true))
self:ClearFocus()
else
self:SetFocus()
end
end
local function OnInputBoxTabPressed(self)
local boxes = {"buyoutInputBox", "perItemInputBox", "numAuctionsInputBox", "stackSizeInputBox"}
self:ClearFocus()
for i=1, #boxes-1 do
if self == frame[boxes[i]] then
frame[boxes[i+1]]:SetFocus()
end
end
end
local buyoutLabel = TSMAPI.GUI:CreateLabel(frame)
buyoutLabel:SetPoint("TOPLEFT", 10, -40)
buyoutLabel:SetHeight(20)
buyoutLabel:SetJustifyH("LEFT")
buyoutLabel:SetText(L["Auction Buyout:"])
local buyoutInputBox = TSMAPI.GUI:CreateInputBox(frame)
buyoutInputBox:SetJustifyH("RIGHT")
buyoutInputBox:SetPoint("TOPRIGHT", -10, -40)
buyoutInputBox:SetPoint("TOPLEFT", buyoutLabel, "TOPRIGHT", 10, 0)
buyoutInputBox:SetHeight(20)
buyoutInputBox:SetScript("OnEnterPressed", buyoutInputBox.ClearFocus)
buyoutInputBox:SetScript("OnEscapePressed", buyoutInputBox.ClearFocus)
buyoutInputBox:SetScript("OnEditFocusLost", OnPriceInputBoxEditFocusLost)
buyoutInputBox:SetScript("OnTextChanged", OnPriceInputBoxTextChanged)
buyoutInputBox:SetScript("OnTabPressed", OnInputBoxTabPressed)
frame.buyoutInputBox = buyoutInputBox
local perItemLabel = TSMAPI.GUI:CreateLabel(frame)
perItemLabel:SetPoint("TOPLEFT", 10, -65)
perItemLabel:SetHeight(20)
perItemLabel:SetJustifyH("LEFT")
perItemLabel:SetText(L["Per Item:"])
local perItemInputBox = TSMAPI.GUI:CreateInputBox(frame)
perItemInputBox:SetJustifyH("RIGHT")
perItemInputBox:SetPoint("TOPRIGHT", -10, -65)
perItemInputBox:SetPoint("TOPLEFT", perItemLabel, "TOPRIGHT", 10, 0)
perItemInputBox:SetHeight(20)
perItemInputBox:SetScript("OnEnterPressed", perItemInputBox.ClearFocus)
perItemInputBox:SetScript("OnEscapePressed", perItemInputBox.ClearFocus)
perItemInputBox:SetScript("OnEditFocusLost", OnPriceInputBoxEditFocusLost)
perItemInputBox:SetScript("OnTextChanged", OnPriceInputBoxTextChanged)
perItemInputBox:SetScript("OnTabPressed", OnInputBoxTabPressed)
frame.perItemInputBox = perItemInputBox
local function OnCountInputBoxEditFocusLost(self)
local numAuctions = max(1, min(frame.numAuctionsInputBox:GetNumber(), frame.numAuctionsInputBox.max))
local stackSize = max(1, min(frame.stackSizeInputBox:GetNumber(), frame.stackSizeInputBox.max))
if self == frame.stackSizeInputBox then
numAuctions = min(numAuctions, floor(frame.numInBags/stackSize))
elseif self == frame.numAuctionsInputBox then
stackSize = min(stackSize, floor(frame.numInBags/numAuctions))
end
frame.numAuctionsInputBox:SetNumber(numAuctions)
frame.stackSizeInputBox:SetNumber(stackSize)
frame.numAuctionsInputBox.btn:SetText(format(L["max %d"], floor(frame.numInBags/stackSize)))
frame.stackSizeInputBox.btn:SetText(format(L["max %d"], min(frame.stackSizeInputBox.max, floor(frame.numInBags/numAuctions))))
local perItem = TSMAPI:UnformatTextMoney(frame.perItemInputBox:GetText())
frame.buyoutInputBox:SetText(TSMAPI:FormatTextMoney(perItem*stackSize, nil, nil, nil, true))
end
local function OnCountInputBoxTextChanged(self)
local numAuctions = frame.numAuctionsInputBox:GetNumber()
local stackSize = frame.stackSizeInputBox:GetNumber()
if numAuctions <= 0 or stackSize <= 0 or numAuctions*stackSize > frame.numInBags then
frame.proceed:Disable()
else
frame.proceed:Enable()
end
end
local function OnMaxButtonClicked(self)
self.inputBox:SetNumber(self.inputBox.max)
self.inputBox:SetFocus()
self.inputBox:ClearFocus()
end
local numAuctionsInputBox = TSMAPI.GUI:CreateInputBox(frame)
numAuctionsInputBox:SetJustifyH("CENTER")
numAuctionsInputBox:SetNumeric(true)
numAuctionsInputBox:SetPoint("TOPLEFT", 10, -110)
numAuctionsInputBox:SetHeight(20)
numAuctionsInputBox:SetScript("OnEnterPressed", numAuctionsInputBox.ClearFocus)
numAuctionsInputBox:SetScript("OnEscapePressed", numAuctionsInputBox.ClearFocus)
numAuctionsInputBox:SetScript("OnEditFocusLost", OnCountInputBoxEditFocusLost)
numAuctionsInputBox:SetScript("OnTextChanged", OnCountInputBoxTextChanged)
numAuctionsInputBox:SetScript("OnTabPressed", OnInputBoxTabPressed)
frame.numAuctionsInputBox = numAuctionsInputBox
local stackSizeInputBox = TSMAPI.GUI:CreateInputBox(frame)
stackSizeInputBox:SetJustifyH("CENTER")
stackSizeInputBox:SetNumeric(true)
stackSizeInputBox:SetPoint("TOPRIGHT", -10, -110)
stackSizeInputBox:SetHeight(20)
stackSizeInputBox:SetScript("OnEnterPressed", stackSizeInputBox.ClearFocus)
stackSizeInputBox:SetScript("OnEscapePressed", stackSizeInputBox.ClearFocus)
stackSizeInputBox:SetScript("OnEditFocusLost", OnCountInputBoxEditFocusLost)
stackSizeInputBox:SetScript("OnTextChanged", OnCountInputBoxTextChanged)
stackSizeInputBox:SetScript("OnTabPressed", OnInputBoxTabPressed)
frame.stackSizeInputBox = stackSizeInputBox
local countLabel = TSMAPI.GUI:CreateLabel(frame)
countLabel:SetPoint("TOPLEFT", numAuctionsInputBox, "TOPRIGHT", 10, 0)
countLabel:SetPoint("TOPRIGHT", stackSizeInputBox, "TOPLEFT", -10, 0)
countLabel:SetHeight(20)
countLabel:SetJustifyH("CENTER")
countLabel:SetText(L["stacks of"])
local editboxWidth = (frame:GetWidth() - 40 - countLabel:GetStringWidth()) / 2
numAuctionsInputBox:SetWidth(editboxWidth)
stackSizeInputBox:SetWidth(editboxWidth)
local maxStackSizeBtn = TSMAPI.GUI:CreateButton(frame, 12)
maxStackSizeBtn:SetPoint("TOPLEFT", stackSizeInputBox, "BOTTOMLEFT", 5, -3)
maxStackSizeBtn:SetPoint("TOPRIGHT", stackSizeInputBox, "BOTTOMRIGHT", -5, -3)
maxStackSizeBtn:SetHeight(14)
maxStackSizeBtn:SetText("")
maxStackSizeBtn:SetScript("OnClick", OnMaxButtonClicked)
maxStackSizeBtn.inputBox = stackSizeInputBox
stackSizeInputBox.btn = maxStackSizeBtn
local maxNumAuctionsBtn = TSMAPI.GUI:CreateButton(frame, 12)
maxNumAuctionsBtn:SetPoint("TOPLEFT", numAuctionsInputBox, "BOTTOMLEFT", 5, -3)
maxNumAuctionsBtn:SetPoint("TOPRIGHT", numAuctionsInputBox, "BOTTOMRIGHT", -5, -3)
maxNumAuctionsBtn:SetHeight(14)
maxNumAuctionsBtn:SetText("")
maxNumAuctionsBtn:SetScript("OnClick", OnMaxButtonClicked)
maxNumAuctionsBtn.inputBox = numAuctionsInputBox
numAuctionsInputBox.btn = maxNumAuctionsBtn
local durationLabel = TSMAPI.GUI:CreateLabel(frame)
durationLabel:SetPoint("TOPLEFT", 10, -165)
durationLabel:SetHeight(20)
durationLabel:SetJustifyH("LEFT")
durationLabel:SetText(L["Duration:"])
local list = {AUCTION_DURATION_ONE, AUCTION_DURATION_TWO, AUCTION_DURATION_THREE}
local durationDropdown = TSMAPI.GUI:CreateDropdown(frame, list)
durationDropdown:SetPoint("TOPLEFT", durationLabel, "TOPRIGHT", 10, 0)
durationDropdown:SetPoint("TOPRIGHT", 0, -165)
durationDropdown:SetHeight(20)
durationDropdown:SetCallback("OnValueChanged", function(self, _, value) TSM.db.profile.postDuration = value end)
frame.durationDropdown = durationDropdown
return frame
end
function private:CreateControlButtons(parent)
local frame = CreateFrame("Frame", nil, parent)
frame:SetHeight(24)
frame:SetWidth(390)
frame:SetPoint("BOTTOMRIGHT", -20, 6)
local function OnClick(self)
if not private.rt or not private.callback then return end
private.confirmationMode = self.which
if self.which == "Post" then
private:ShowPostWindow()
else
private:ShowConfirmationWindow()
end
end
local button = TSMAPI.GUI:CreateButton(frame, 18, "TSMAHTabCancelButton")
button:SetPoint("TOPLEFT", 0, 0)
button:SetWidth(100)
button:SetHeight(24)
button:SetText(CANCEL)
button.which = "Cancel"
button:SetScript("OnClick", OnClick)
frame.cancel = button
local button = TSMAPI.GUI:CreateButton(frame, 18, "TSMAHTabPostButton")
button:SetPoint("TOPLEFT", 104, 0)
button:SetWidth(100)
button:SetHeight(24)
button:SetText(L["Post"])
button.which = "Post"
button:SetScript("OnClick", OnClick)
frame.post = button
local button = TSMAPI.GUI:CreateButton(frame, 18, "TSMAHTabBuyoutButton")
button:SetPoint("TOPLEFT", 208, 0)
button:SetWidth(100)
button:SetHeight(24)
button:SetText(BUYOUT)
button.which = "Buyout"
button:SetScript("OnClick", OnClick)
frame.buyout = button
return frame
end
+298
View File
@@ -0,0 +1,298 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...)
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
local private = {auctionTabs={}, queuedTabs={}}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.AuctionFrame_private")
LibStub("AceEvent-3.0"):Embed(private)
LibStub("AceHook-3.0"):Embed(private)
local registeredModules = {}
function TSM:RegisterAuctionFunction(moduleName, callbackShow, callbackHide)
if registeredModules[moduleName] then return end
registeredModules[moduleName] = true
if AuctionFrame then
private:CreateTSMAHTab(moduleName, callbackShow, callbackHide)
else
tinsert(private.queuedTabs, {moduleName, callbackShow, callbackHide})
end
end
function private:CreateTSMAHTab(moduleName, callbackShow, callbackHide)
local auctionTab = CreateFrame("Frame", nil, AuctionFrame)
auctionTab:Hide()
auctionTab:SetAllPoints()
auctionTab:EnableMouse(true)
auctionTab:SetMovable(true)
auctionTab:SetScript("OnMouseDown", function() if AuctionFrame:IsMovable() then AuctionFrame:StartMoving() end end)
auctionTab:SetScript("OnMouseUp", function() if AuctionFrame:IsMovable() then AuctionFrame:StopMovingOrSizing() end end)
TSMAPI:CancelFrame("blizzAHLoadedDelay")
local n = AuctionFrame.numTabs + 1
local tab = CreateFrame("Button", "AuctionFrameTab"..n, AuctionFrame, "AuctionTabTemplate")
tab:Hide()
tab:SetID(n)
tab:SetText(TSMAPI.Design:GetInlineColor("link2")..moduleName.."|r")
tab:SetNormalFontObject(GameFontHighlightSmall)
tab.isTSMTab = moduleName
tab:SetPoint("LEFT", _G["AuctionFrameTab"..n-1], "RIGHT", -8, 0)
tab:Show()
PanelTemplates_SetNumTabs(AuctionFrame, n)
PanelTemplates_EnableTab(AuctionFrame, n)
auctionTab.tab = tab
local closeBtn = TSMAPI.GUI:CreateButton(auctionTab, 18)
closeBtn:SetPoint("BOTTOMRIGHT", -5, 5)
closeBtn:SetWidth(75)
closeBtn:SetHeight(24)
closeBtn:SetText(CLOSE)
closeBtn:SetScript("OnClick", CloseAuctionHouse)
local iconFrame = CreateFrame("Frame", nil, auctionTab)
iconFrame:SetPoint("CENTER", auctionTab, "TOPLEFT", 30, -30)
iconFrame:SetHeight(100)
iconFrame:SetWidth(100)
local icon = iconFrame:CreateTexture(nil, "ARTWORK")
icon:SetAllPoints()
icon:SetTexture("Interface\\Addons\\TradeSkillMaster\\Media\\TSM_Icon_Big")
local textFrame = CreateFrame("Frame", nil, auctionTab)
local iconText = textFrame:CreateFontString(nil, "OVERLAY")
iconText:SetPoint("CENTER", iconFrame)
iconText:SetHeight(15)
iconText:SetJustifyH("CENTER")
iconText:SetJustifyV("CENTER")
iconText:SetFont(TSMAPI.Design:GetContentFont("normal"))
iconText:SetTextColor(165/255, 168/255, 188/255, .7)
local version = TSM._version
iconText:SetText(version)
local ag = iconFrame:CreateAnimationGroup()
local spin = ag:CreateAnimation("Rotation")
spin:SetOrder(1)
spin:SetDuration(2)
spin:SetDegrees(90)
local spin = ag:CreateAnimation("Rotation")
spin:SetOrder(2)
spin:SetDuration(4)
spin:SetDegrees(-180)
local spin = ag:CreateAnimation("Rotation")
spin:SetOrder(3)
spin:SetDuration(2)
spin:SetDegrees(90)
ag:SetLooping("REPEAT")
iconFrame:SetScript("OnEnter", function() ag:Play() end)
iconFrame:SetScript("OnLeave", function() ag:Stop() end)
local moneyText = TSMAPI.GUI:CreateTitleLabel(auctionTab, 16)
moneyText:SetJustifyH("CENTER")
moneyText:SetJustifyV("CENTER")
moneyText:SetPoint("CENTER", auctionTab, "BOTTOMLEFT", 85, 17)
TSMAPI.Design:SetIconRegionColor(moneyText)
moneyText.SetMoney = function(self, money)
self:SetText(TSMAPI:FormatTextMoneyIcon(money))
end
auctionTab.moneyText = moneyText
local moneyTextFrame = CreateFrame("Frame", nil, auctionTab)
moneyTextFrame:SetAllPoints(moneyText)
moneyTextFrame:EnableMouse(true)
moneyTextFrame:SetScript("OnEnter", function(self)
local currentTotal = 0
local incomingTotal = 0
for i=1, GetNumAuctionItems("owner") do
-- local count, _, _, _, _, _, _, buyoutAmount = select(3, GetAuctionItemInfo("owner", i))
local count, _, _, _, _, _, buyoutAmount = select(3, GetAuctionItemInfo("owner", i))
if count == 0 then
incomingTotal = incomingTotal + buyoutAmount
else
currentTotal = currentTotal + buyoutAmount
end
end
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:AddLine("Gold Info:")
GameTooltip:AddDoubleLine("Player Gold", TSMAPI:FormatTextMoneyIcon(GetMoney()), 1, 1, 1, 1, 1, 1)
GameTooltip:AddDoubleLine("Incoming Auction Sales", TSMAPI:FormatTextMoneyIcon(incomingTotal), 1, 1, 1, 1, 1, 1)
GameTooltip:AddDoubleLine("Current Auctions Value", TSMAPI:FormatTextMoneyIcon(currentTotal), 1, 1, 1, 1, 1, 1)
GameTooltip:Show()
end)
moneyTextFrame:SetScript("OnLeave", function()
GameTooltip:ClearLines()
GameTooltip:Hide()
end)
auctionTab:SetScript("OnShow", function(self)
self:SetAllPoints()
if not self.minimized then
callbackShow(self)
end
end)
auctionTab:SetScript("OnHide", function(self)
if not self.minimized then
callbackHide()
end
end)
local contentFrame = CreateFrame("Frame", nil, auctionTab)
contentFrame:SetPoint("TOPLEFT", 4, -80)
contentFrame:SetPoint("BOTTOMRIGHT", -4, 35)
TSMAPI.Design:SetContentColor(contentFrame)
auctionTab.content = contentFrame
tinsert(private.auctionTabs, auctionTab)
end
function private:InitializeAuctionFrame(auctionTab)
-- make the AH movable if this option is enabled
AuctionFrame:SetMovable(TSM.db.profile.auctionFrameMovable)
AuctionFrame:EnableMouse(true)
AuctionFrame:SetScript("OnMouseDown", function(self) if self:IsMovable() then self:StartMoving() end end)
AuctionFrame:SetScript("OnMouseUp", function(self) if self:IsMovable() then self:StopMovingOrSizing() end end)
-- scale the auction frame according to the TSM option
if AuctionFrame:GetScale() ~= 1 and TSM.db.profile.auctionFrameScale == 1 then TSM.db.profile.auctionFrameScale = AuctionFrame:GetScale() end
AuctionFrame:SetScale(TSM.db.profile.auctionFrameScale)
local prevTab
local function TabChangeHook(self)
if self.isTSMTab then
for _, tabFrame in ipairs(private.auctionTabs) do
if tabFrame.minimized and tabFrame.tab ~= self then
tabFrame:Show()
tabFrame.minimized = nil
tabFrame:Hide()
elseif tabFrame:IsShown() then
tabFrame:Hide()
end
end
local tabAuctionFrame = private:GetAuctionFrame(self)
private:OnTabClick(tabAuctionFrame)
AuctionFrame:SetFrameLevel(1)
tabAuctionFrame:SetFrameStrata(AuctionFrame:GetFrameStrata())
tabAuctionFrame:SetFrameLevel(AuctionFrame:GetFrameLevel() + 1)
elseif prevTab and prevTab.isTSMTab then
local prevTabAuctionFrame = private:GetAuctionFrame(prevTab)
prevTabAuctionFrame.minimized = true
prevTabAuctionFrame:Hide()
private:TabHidden()
end
prevTab = self
end
private:Hook("AuctionFrameTab_OnClick", TabChangeHook, true)
-- Makes sure the TSM tab hides correctly when used with addons that hook this function to change tabs (ie Auctionator)
-- This probably doesn't have to be a SecureHook, but does need to be a Post-Hook.
private:SecureHook("ContainerFrameItemButton_OnModifiedClick", function()
if _G["AuctionFrameTab"..PanelTemplates_GetSelectedTab(AuctionFrame)].isTSMTab then return end
TabChangeHook(_G["AuctionFrameTab"..PanelTemplates_GetSelectedTab(AuctionFrame)])
end)
end
function private:GetAuctionFrame(targetTab)
for _, tabFrame in ipairs(private.auctionTabs) do
if tabFrame.tab == targetTab then
return tabFrame
end
end
end
function private:InitializeAHTab()
for _, info in ipairs(private.queuedTabs) do
private:CreateTSMAHTab(unpack(info))
end
private.queuedTabs = {}
private:InitializeAuctionFrame()
private.isInitialized = true
if AuctionHouse and AuctionHouse:IsVisible() then
private:AUCTION_HOUSE_SHOW()
end
end
function TSMAPI:AHTabIsVisible(module)
return module and _G["AuctionFrameTab"..AuctionFrame.selectedTab].isTSMTab == module
end
function private:AUCTION_HOUSE_SHOW()
if private.isInitialized then
for i = AuctionFrame.numTabs, 1, -1 do
local text = gsub(_G["AuctionFrameTab"..i]:GetText(), "|r", "")
text = gsub(text, "|c[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]", "")
if text == TSM.db.profile.defaultAuctionTab then
_G["AuctionFrameTab"..i]:Click()
return
end
end
_G["AuctionFrameTab1"]:Click()
end
end
function private:OnTabClick(tab)
AuctionFrameTopLeft:Hide()
AuctionFrameTop:Hide()
AuctionFrameTopRight:Hide()
AuctionFrameBotLeft:Hide()
AuctionFrameBot:Hide()
AuctionFrameBotRight:Hide()
AuctionFrameMoneyFrame:Hide()
AuctionFrameCloseButton:Hide()
private:RegisterEvent("PLAYER_MONEY")
if TSM.db.profile.openAllBags then
OpenAllBags(true)
end
TSMAPI:CreateTimeDelay("hideAHMoneyFrame", 0.1, function() AuctionFrameMoneyFrame:Hide() end)
TSMAPI.Design:SetFrameBackdropColor(tab)
AuctionFrameTab1:SetPoint("TOPLEFT", AuctionFrame, "BOTTOMLEFT", 15, 1)
tab:Show()
tab.minimized = nil
tab.moneyText:SetMoney(GetMoney())
end
function private:TabHidden()
AuctionFrameTopLeft:Show()
AuctionFrameTop:Show()
AuctionFrameTopRight:Show()
AuctionFrameBotLeft:Show()
AuctionFrameBot:Show()
AuctionFrameBotRight:Show()
AuctionFrameMoneyFrame:Show()
AuctionFrameCloseButton:Show()
AuctionFrameTab1:SetPoint("TOPLEFT", AuctionFrame, "BOTTOMLEFT", 15, 12)
end
function private:PLAYER_MONEY()
for _, tab in ipairs(private.auctionTabs) do
if tab:IsVisible() then
tab.moneyText:SetMoney(GetMoney())
end
end
end
function private:ADDON_LOADED(event, addonName)
if addonName == "Blizzard_AuctionUI" then
private:UnregisterEvent("ADDON_LOADED")
if TSM.db then
private:InitializeAHTab()
else
TSMAPI:CreateTimeDelay("blizzAHLoadedDelay", 0.2, private.InitializeAHTab, 0.2)
end
end
end
do
private:RegisterEvent("AUCTION_HOUSE_SHOW")
if IsAddOnLoaded("Blizzard_AuctionUI") then
private:InitializeAHTab()
else
private:RegisterEvent("ADDON_LOADED")
end
end
+433
View File
@@ -0,0 +1,433 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- This file contains code for the AuctionItem objects
local TSM = select(2, ...)
local NewRecord, sortHelpers
local AuctionRecord = {
Initialize = function(self)
self.objType = "AuctionRecord"
end,
SetData = function(self, parent, count, minBid, minIncrement, buyout, bid, highBidder, seller, timeLeft)
self.parent = parent
self.count = count
self.minBid = minBid
self.minIncrement = minIncrement
self.buyout = buyout
self.bid = bid
self.highBidder = highBidder
self.seller = seller
self.timeLeft = timeLeft
end,
IsPlayer = function(self)
return TSMAPI:IsPlayer(self.seller) or self.parent.alts[self.seller]
end,
GetPercent = function(self)
local itemBuyout = self:GetItemBuyout()
local marketValue = self.parent.marketValue
if itemBuyout and marketValue then
return (itemBuyout / marketValue) * 100
end
end,
GetDisplayedBid = function(self)
local displayedBid
if self.bid == 0 then
displayedBid = self.minBid
else
displayedBid = self.bid
end
return displayedBid
end,
GetRequiredBid = function(self)
local requiredBid
if self.bid == 0 then
requiredBid = self.minBid
else
requiredBid = self.bid + self.minIncrement
end
return requiredBid
end,
GetItemBuyout = function(self)
if not self.buyout or self.buyout == 0 then return end
return floor(self.buyout / self.count)
end,
GetItemDisplayedBid = function(self)
return floor(self:GetDisplayedBid() / self.count)
end,
GetItemDestroyingBuyout = function(self)
local itemBuyout = self:GetItemBuyout()
if itemBuyout then
return itemBuyout * self.parent.destroyingNum
end
end,
GetItemDestroyingDisplayedBid = function(self)
local itemBid = self:GetItemDisplayedBid()
if itemBid then
return itemBid * self.parent.destroyingNum
end
end,
Copy = function(self)
local o = NewRecord()
o:SetData(self.parent, self.count, self.minBid, self.minIncrement, self.buyout, self.bid, self.highBidder, self.seller, self.timeLeft)
o.uniqueID = self.uniqueID
return o
end,
Equals = function(self, other)
if self == other then
return true
end
local params = self.parent.recordParams
for _, key in ipairs(params) do
if type(self[key]) == "function" then
if self[key](self) ~= other[key](other) then
return false
end
else
if self[key] ~= other[key] then
return false
end
end
end
return true
end,
}
NewRecord = function()
local o = {}
setmetatable(o, AuctionRecord)
AuctionRecord.__index = AuctionRecord
o:Initialize()
return o
end
--- Test Documentation
-- @name AuctionItem
-- @description Test description for the Auction Item object.
local AuctionItem = {
-- @field Test field
Initialize = function(self)
self.objType = "AuctionItem"
self.itemLink = nil
self.marketValue = nil
self.playerAuctions = 0
self.records = {}
self.alts = {}
self.recordParams = {"buyout", "count", "seller"}
self.shouldCompact = true
self.texture = ""
end,
-- sets the item (or battle pet's) texture
SetTexture = function(self, texture)
self.texture = texture
end,
-- gets the item (or battle pet's) texture
GetTexture = function(self)
return self.texture
end,
-- sets the alts table used for making other players count as the current player
SetAlts = function(self, alts)
self.alts = alts
end,
-- sets the list of params we care about
SetRecordParams = function(self, params)
self.recordParams = params
end,
-- sets the itemLink
SetItemLink = function(self, itemLink)
self.itemLink = itemLink
end,
-- returns the itemString
GetItemString = function(self)
return TSMAPI:GetItemString(self.itemLink)
end,
-- returns the itemID
GetItemID = function(self)
return TSMAPI:GetItemID(self.itemLink)
end,
-- adds a record
AddAuctionRecord = function(self, ...)
local record = NewRecord()
record:SetData(self, ...)
-- if strfind(self.itemLink, "battlepet") then
-- record.uniqueID = table.concat({TSMAPI:Select({2, 3, 4, 5, 6, 7}, (":"):split(self.itemLink))}, ".")
-- else
record.uniqueID = select(9, (":"):split(self.itemLink))
-- end
self:AddRecord(record)
end,
-- adds a record
AddRecord = function(self, record)
self.shouldCompact = true
if record:IsPlayer() then
self.playerAuctions = self.playerAuctions + 1
end
tinsert(self.records, record)
end,
-- sorts the records using the passed sortFunc
SortRecords = function(self, sortFunc)
sort(self.records, sortFunc)
end,
-- sets the market value of this item
SetMarketValue = function(self, value)
self.marketValue = value
end,
-- sorts all the records in ascending order by buyout > bid > count > seller
DoDefaultSort = function(self)
self:SortRecords(function(a, b)
local aBuyout = a:GetItemBuyout()
local bBuyout = b:GetItemBuyout()
if not aBuyout or aBuyout == 0 then
return false
end
if not bBuyout or bBuyout == 0 then
return true
end
if aBuyout == bBuyout then
if a.seller == b.seller then
if a.count == b.count then
local aBid = a:GetItemDisplayedBid()
local bBid = b:GetItemDisplayedBid()
return aBid < bBid
end
return a.count < b.count
end
return a.seller < b.seller
end
return aBuyout < bBuyout
end)
end,
-- populates the compactRecords table
PopulateCompactRecords = function(self, sortParams, isAscending)
if self.shouldCompact then
self.shouldCompact = false
self.compactRecords = {}
self:DoDefaultSort()
local currentRecord
for _, record in ipairs(self.records) do
local temp = record:Copy()
if not currentRecord or not temp:Equals(currentRecord) then
currentRecord = temp
currentRecord.numAuctions = 1
currentRecord.totalQuantity = currentRecord.count
tinsert(self.compactRecords, currentRecord)
else
currentRecord.numAuctions = currentRecord.numAuctions + 1
currentRecord.totalQuantity = currentRecord.totalQuantity + temp.count
end
end
end
if sortParams then
sort(self.compactRecords, function(a, b)
for _, key in ipairs(sortParams) do
local sortVal = sortHelpers[key](a, b)
if sortVal < 0 then
return isAscending
elseif sortVal > 0 then
return not isAscending
end
end
end)
end
end,
-- removes all records for which shouldFilter(record) returns true
FilterRecords = function(self, shouldFilter)
self.shouldCompact = true
local toRemove = {}
for index, record in ipairs(self.records) do
if shouldFilter(record) then
tinsert(toRemove, index)
end
end
for i=#toRemove, 1, -1 do
self:RemoveRecord(toRemove[i])
end
end,
-- removes a record at the given index
RemoveRecord = function(self, index)
local toRemove = self.records[index]
if not toRemove then return end
self.shouldCompact = true
if self.compactRecords then
for i, record in ipairs(self.compactRecords) do
if record:Equals(toRemove) then
if record.numAuctions > 1 then
record.numAuctions = record.numAuctions - 1
else
tremove(self.compactRecords, i)
end
break
end
end
end
if toRemove:IsPlayer() then
self.playerAuctions = self.playerAuctions - 1
end
tremove(self.records, index)
end,
-- adds up all the counts from all the records
GetTotalItemQuantity = function(self)
local totalQuantity = 0
for _, record in ipairs(self.records) do
totalQuantity = totalQuantity + record.count
end
return totalQuantity
end,
-- counts up the number of items (not auctions) the player has
GetPlayerItemQuantity = function(self)
local totalQuantity = 0
for _, record in ipairs(self.records) do
if record:IsPlayer() then
totalQuantity = totalQuantity + record.count
end
end
return totalQuantity
end,
IsPlayerOnly = function(self)
for _, record in ipairs(self.records) do
if not record:IsPlayer() then
return false
end
end
return true
end,
SetDestroyingNum = function(self, num)
self.destroyingNum = num
end,
}
function TSMAPI.AuctionScan:NewAuctionItem()
local o = {}
setmetatable(o, AuctionItem)
AuctionItem.__index = AuctionItem
o:Initialize()
return o
end
local function CompareStrings(a, b)
if a < b then
return -1
elseif a > b then
return 1
else
return 0
end
end
-- bunch of helper functions for AuctionItem sorting
-- negative return means a < b
-- possitive return means a > b
-- zero return means a == b
sortHelpers = {
Percent = function(a, b)
return (a:GetPercent() or math.huge) - (b:GetPercent() or math.huge)
end,
Buyout = function(a, b)
return (a.buyout or math.huge) - (b.buyout or math.huge)
end,
DisplayedBid = function(a, b)
return a:GetDisplayedBid() - b:GetDisplayedBid()
end,
ItemBuyout = function(a, b)
return (a:GetItemBuyout() or math.huge) - (b:GetItemBuyout() or math.huge)
end,
ItemDisplayedBid = function(a, b)
return a:GetItemDisplayedBid() - b:GetItemDisplayedBid()
end,
Count = function(a, b)
return a.count - b.count
end,
Seller = function(a, b)
return CompareStrings(a.seller, b.seller)
end,
TimeLeft = function(a, b)
return a.timeLeft - b.timeLeft
end,
NumAuctions = function(a, b)
return a.numAuctions - b.numAuctions
end,
Name = function(a, b)
local aName = TSMAPI:GetSafeItemInfo(a.parent.itemLink)
local bName = TSMAPI:GetSafeItemInfo(a.parent.itemLink)
return CompareStrings(aName, bName)
end,
DestroyingBuyout = function(a, b)
return (a:GetItemDestroyingBuyout() or math.huge) - (b:GetItemDestroyingBuyout() or math.huge)
end,
}
function TSMAPI.AuctionScan:SortAuctions(data, sortParams, useCompactRecords, isAscending)
local function compareSort(a, b)
for _, key in ipairs(sortParams) do
local sortVal
if useCompactRecords then
sortVal = sortHelpers[key](a.compactRecords[1], b.compactRecords[1])
else
sortVal = sortHelpers[key](a.records[1], b.records[1])
end
if sortVal < 0 then
return isAscending
elseif sortVal > 0 then
return not isAscending
end
end
end
sort(data, compareSort)
end
@@ -0,0 +1,314 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- This file contains code for scanning the auction house
local TSM = select(2, ...)
local private = {}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.AuctionQueryUtil_private")
local ITEM_CLASS_LOOKUP = {}
for i, class in ipairs({GetAuctionItemClasses()}) do
ITEM_CLASS_LOOKUP[class] = {}
ITEM_CLASS_LOOKUP[class].index = i
for j, subclass in pairs({GetAuctionItemSubClasses(i)}) do
ITEM_CLASS_LOOKUP[class][subclass] = j
end
end
local function GetItemClasses(itemString)
local class, subClass = select(6, TSMAPI:GetSafeItemInfo(itemString))
if not class or not ITEM_CLASS_LOOKUP[class] then return end
return ITEM_CLASS_LOOKUP[class].index, ITEM_CLASS_LOOKUP[class][subClass]
end
function TSMAPI:GetAuctionQueryInfo(itemString)
local name, _, rarity, _, minLevel, class, subClass, _, equipLoc = TSMAPI:GetSafeItemInfo(itemString)
local class, subClass = GetItemClasses(itemString)
if not name then return end
return {name=name, minLevel=minLevel, maxLevel=minLevel, invType=0, class=class, subClass=subClass, quality=rarity}
end
local function GetCommonQueryInfo(name, items)
local queries = {}
for _, itemString in ipairs(items) do
local itemQuery = TSMAPI:GetAuctionQueryInfo(itemString)
local existingQuery
for _, query in ipairs(queries) do
if query.class == itemQuery.class then
existingQuery = query
break
end
end
if existingQuery then
existingQuery.minLevel = min(existingQuery.minLevel, itemQuery.minLevel)
existingQuery.maxLevel = max(existingQuery.maxLevel, itemQuery.maxLevel)
existingQuery.quality = min(existingQuery.quality, itemQuery.quality)
if existingQuery.subClass ~= itemQuery.subClass then
existingQuery.subClass = nil
end
tinsert(existingQuery.items, itemString)
else
itemQuery.name = name
itemQuery.items = {itemString}
tinsert(queries, itemQuery)
end
end
return queries
end
local function GetCommonQueryInfoClass(class, items)
local resultQuery = TSMAPI:GetAuctionQueryInfo(items[1])
resultQuery.name = ""
resultQuery.class = class
for i=2, #items do
local itemQuery = TSMAPI:GetAuctionQueryInfo(items[i])
resultQuery.minLevel = min(resultQuery.minLevel, itemQuery.minLevel)
resultQuery.maxLevel = max(resultQuery.maxLevel, itemQuery.maxLevel)
resultQuery.quality = min(resultQuery.quality, itemQuery.quality)
if resultQuery.subClass ~= itemQuery.subClass then resultQuery.subClass = nil end
end
resultQuery.items = items
return {resultQuery}
end
local function GreatestSubstring(str1, str2)
local parts1 = {(" "):split(str1)}
local parts2 = {(" "):split(str2)}
for i=1, #parts1 do
if parts1[i] ~= parts2[i] then
local subStr = table.concat(parts1, " ", 1, i-1)
return subStr ~= "" and subStr
end
end
return table.concat(parts1, " ")
end
local function ReduceStrings(strList)
local didReduction = true
while didReduction do
didReduction = false
for i=1, #strList-1 do
if i > #strList-1 then break end
local subStr = GreatestSubstring(strList[i], strList[i+1])
if subStr then
strList[i] = subStr
tremove(strList, i+1)
didReduction = true
end
end
if not private.thread then return end
private.thread:Yield()
end
return true
end
local function NumPagesCallback(event, numPages)
if event == "NUM_PAGES" then
local skippedItems = {}
local score = max(#private.combinedQueries[1].items-numPages, 0)
if private.combinedQueries[1].name == "" then
-- This is a common class term so determine if we should use this or not.
local cost = 0
for _, query in ipairs(private.queries) do
if query.score and query.class == private.combinedQueries[1].class then
cost = cost + query.score
end
end
if score >= cost and score > 0 then
-- use the common class term
for i=#private.queries, 1, -1 do
local query = private.queries[i]
local shouldRemove = (query.class == private.combinedQueries[1].class)
if shouldRemove then
tremove(private.queries, i)
end
end
tinsert(private.queries, private.combinedQueries[1])
end
else
if numPages > #private.combinedQueries[1].items then
for _, itemString in ipairs(private.combinedQueries[1].items) do
local query = TSMAPI:GetAuctionQueryInfo(itemString)
query.items = {itemString}
query.score = 0
tinsert(private.queries, query)
end
elseif numPages == 0 then
for _, itemString in ipairs(private.combinedQueries[1].items) do
tinsert(skippedItems, itemString)
end
else
-- use the common search term
private.combinedQueries[1].score = score
tinsert(private.queries, private.combinedQueries[1])
end
end
tremove(private.combinedQueries, 1)
private.callback("QUERY_UPDATE", private.totalQueries-#private.combinedQueries, private.totalQueries, skippedItems)
end
private:CheckNextCombinedQuery()
end
function private:CheckNextCombinedQuery()
if not private.isScanning then return end
if #private.combinedQueries == 0 then
-- we're done
sort(private.queries, function(a, b) return a.name < b.name end)
TSM:StopGeneratingQueries()
TSMAPI:CreateTimeDelay("queryUtilCallbackDelay", 0.05, function() private.callback("QUERY_COMPLETE", private.queries) end)
return
end
for _, itemString in ipairs(private.combinedQueries[1].items) do
if strlower(private.combinedQueries[1].name) == strlower(TSMAPI:GetSafeItemInfo(itemString)) then
-- One of the items in this combined query is the same as the common search term,
-- so it's always worth using this common search term.
NumPagesCallback("NUM_PAGES", 1)
return
end
end
TSMAPI.AuctionScan:GetNumPages(private.combinedQueries[1], NumPagesCallback)
end
local function GenerateSearchTerms(names, itemList, isReversed)
sort(names)
if not ReduceStrings(names) then return end -- run the reduction
-- create a table associating all the reduced names to a list of items
local temp = {}
for i, filterName in ipairs(names) do
for j, itemString in ipairs(itemList) do
local itemName = TSMAPI:GetSafeItemInfo(itemString)
itemName = itemName and isReversed and strrev(itemName) or itemName -- reverse item name if necessary
if itemName and strfind(itemName, "^"..TSMAPI:StrEscape(filterName)) then
temp[filterName] = temp[filterName] or {}
tinsert(temp[filterName], itemString)
end
end
if not private.thread then return end
private.thread:Yield()
end
return temp
end
local function GenerateQueriesThread(self)
private.thread = self
local function GenerateFilters(reverse)
-- create a list of all item names
local names = {}
for _, itemString in ipairs(private.itemList) do
local name = TSMAPI:GetSafeItemInfo(itemString)
if type(name) == "string" and name ~= "" then
tinsert(names, reverse and strrev(name) or name)
end
end
if not private.thread then return end
local filters, tempFilters, tempItems = {}, {}, {}
local numFilters = 0
local tbl = GenerateSearchTerms(names, private.itemList, reverse)
if not tbl then return end
for filterName, items in pairs(tbl) do
if #items > 1 then
filters[reverse and strrev(filterName) or filterName] = items
numFilters = numFilters + 1
else
tinsert(tempFilters, strrev(filterName)) -- reverse name for second pass
for _, itemString in ipairs(items) do
tinsert(tempItems, itemString)
end
end
end
-- try to find common search terms of reversed item names
local tbl = GenerateSearchTerms(tempFilters, tempItems, not reverse)
if not tbl then return end
for filterName, items in pairs(tbl) do
filters[reverse and filterName or strrev(filterName)] = items
numFilters = numFilters + 1
end
return filters, numFilters
end
local endTime = debugprofilestop() + 5000
while debugprofilestop() < endTime do
-- request all the item info
local tryAgain = false
for _, itemString in ipairs(private.itemList) do
if not TSMAPI:GetSafeItemInfo(itemString) then
tryAgain = true
end
end
if not tryAgain then break end
self:Sleep(0.1)
end
local filters1, num1 = GenerateFilters()
local filters2, num2 = GenerateFilters(true)
if not filters1 or not filters2 then return end
local filters = num2 < num1 and filters2 or filters1
-- generate class filters
local itemClasses = {}
local classes = {GetAuctionItemClasses()}
for _, itemString in ipairs(private.itemList) do
local classIndex = GetItemClasses(itemString)
if classIndex then
itemClasses[classIndex] = itemClasses[classIndex] or {}
tinsert(itemClasses[classIndex], itemString)
end
end
-- create the actual queries
local queries, combinedQueries = {}, {}
for filterName, items in pairs(filters) do
for _, query in ipairs(GetCommonQueryInfo(filterName, items)) do
if #query.items > 1 then
tinsert(combinedQueries, query)
else
tinsert(queries, query)
end
end
end
for class, items in pairs(itemClasses) do
for _, query in ipairs(GetCommonQueryInfoClass(class, items)) do
if #query.items > 1 then
tinsert(combinedQueries, query)
end
end
end
private.isScanning = true
private.queries = queries
private.combinedQueries = combinedQueries
private.totalQueries = #combinedQueries
end
function TSMAPI:GenerateQueries(itemList, callback)
if private.thread then return end
private.itemList = itemList
private.callback = callback
local function ThreadDone()
if private.thread then
private.thread = nil
private:CheckNextCombinedQuery()
end
end
TSMAPI.Threading:Start(GenerateQueriesThread, 0.5, ThreadDone)
end
function TSM:StopGeneratingQueries()
private.thread = nil
private.isScanning = nil
end
@@ -0,0 +1,730 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...)
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table
local RT_COUNT = 1
local HEAD_HEIGHT = 27
local HEAD_SPACE = 2
local purchaseCache = {}
local resultsTables = {}
local function OnSizeChanged(rt, width)
for i, col in ipairs(rt.headCols) do
col:SetWidth(col.info.width*width)
end
for _, row in ipairs(rt.rows) do
for i, col in ipairs(row.cols) do
col:SetWidth(rt.headCols[i].info.width*width)
end
end
end
local rowTextFunctions = {
GetPriceText = function(buyout, displayBid)
local bidLine = TSMAPI:FormatTextMoney(displayBid, "|cff999999", true) or "|cff999999---|r"
local buyoutLine = buyout and buyout > 0 and TSMAPI:FormatTextMoney(buyout, nil, true) or "---"
if TSM.db.profile.showBids then
return bidLine.."\n"..buyoutLine
else
return buyoutLine
end
end,
GetTimeLeftText = function(timeLeft)
return _G["AUCTION_TIME_LEFT"..(timeLeft or "")] or ""
end,
GetNameText = function(_, link)
return gsub(gsub(link, "%[", ""), "%]", "")
end,
GetAuctionsText = function(num, player, isExpandable, totalNum)
num = totalNum or num
local playerText = player and (" |cffffff00("..player..")|r") or ""
if isExpandable then
return TSMAPI.Design:GetInlineColor("link2")..num.."|r"..playerText
else
return num..playerText
end
end,
GetSellerText = function(seller)
if TSMAPI:IsPlayer(seller) then
return "|cffffff00"..seller.."|r"
else
return seller or ""
end
end,
GetPercentText = function(pct)
if not pct then return "---" end
return TSMAPI:GetAuctionPercentColor(pct)..floor(pct+0.5).."%|r"
end
}
local function GetRowTable(rt, auction, isExpandable)
if not auction then return end
local bid, buyout
if TSM.db.profile.pricePerUnit then
bid = auction:GetItemDisplayedBid()
buyout = auction:GetItemBuyout()
else
bid = auction:GetDisplayedBid()
buyout = auction.buyout
end
local auctionsData, rowTable
local itemString = auction.parent:GetItemString()
if rt.expanded[itemString] then
auctionsData = {#auction.parent.records, nil, nil, auction.numAuctions}
else
auctionsData = {#auction.parent.records, auction.parent.records[1].playerAuctions, isExpandable}
end
local name, iLvl
-- if strmatch(auction.parent.itemLink, "battlepet") then
-- local _, speciesID, itemLvl = strsplit(":", auction.parent.itemLink)
-- local itemName = C_PetJournal.GetPetInfoBySpeciesID(speciesID)
-- name, iLvl = itemName, itemLvl
-- else
local itemName, _, _, itemLvl = GetItemInfo(auction.parent.itemLink)
name, iLvl = itemName, itemLvl
-- end
local pct = auction:GetPercent()
if not pct or pct < 0 or pct == math.huge then
pct = nil
end
if rt.isDestroying then
local destroyingBid = auction:GetItemDestroyingDisplayedBid()
local destroyingBuyout = auction:GetItemDestroyingBuyout()
rowTable = {
{value=rowTextFunctions.GetNameText, args={name, auction.parent.itemLink}},
{value=rowTextFunctions.GetAuctionsText, args=auctionsData},
{value=auction.count, args={auction.count}},
{value=rowTextFunctions.GetSellerText, args={auction.seller}},
{value=rowTextFunctions.GetPriceText, args={destroyingBuyout, destroyingBid}},
{value=rowTextFunctions.GetPriceText, args={buyout, bid}},
{value=rowTextFunctions.GetPercentText, args={pct}},
}
else
rowTable = {
{value=rowTextFunctions.GetNameText, args={name, auction.parent.itemLink}},
{value=(iLvl or "---"), args={iLvl or 0}},
{value=rowTextFunctions.GetAuctionsText, args=auctionsData},
{value=auction.count, args={auction.count}},
{value=rowTextFunctions.GetTimeLeftText, args={auction.timeLeft}},
{value=rowTextFunctions.GetSellerText, args={auction.seller}},
{value=rowTextFunctions.GetPriceText, args={buyout, bid}},
{value=rowTextFunctions.GetPercentText, args={pct}},
}
end
rowTable.itemString = itemString
rowTable.auctionRecord = auction
rowTable.expandable = isExpandable
rowTable.texture = auction.parent:GetTexture()
rowTable.link = auction.parent.itemLink
return rowTable
end
local function GetTableIndex(tbl, value)
for i, v in pairs(tbl) do
if value == v then
return i
end
end
end
local function OnColumnClick(self, ...)
local button = ...
local rt = self.rt
local column = GetTableIndex(rt.headCols, self)
if button == "RightButton" and column == #rt.headCols-1 then
TSM.db.profile.pricePerUnit = not TSM.db.profile.pricePerUnit
local priceColName = TSM.db.profile.pricePerUnit and L["Price Per Item"] or L["Price Per Stack"]
self:SetText(priceColName)
rt:RefreshRowData()
return
end
local ascending = rt.sortInfo.ascending
rt:SetSort(column, rt.sortInfo.column ~= column or not ascending)
local handler = self.rt.handlers.OnColumnClick
if handler then
handler(self.rt, self.row.data, self, ...)
end
end
local methods = {
DrawRows = function(rt)
if not rt.auctionData then return end
for i=1, rt.NUM_ROWS do
rt.rows[i]:Hide()
end
wipe(rt.displayRows)
local itemsUsed = {}
for i, data in ipairs(rt.data) do
local itemString = data.itemString
if not itemsUsed[itemString] or rt.expanded[itemString] then
tinsert(rt.displayRows, data)
itemsUsed[itemString] = true
elseif i == rt.selected then
rt.selected = nil
end
end
FauxScrollFrame_Update(rt.scrollFrame, #rt.displayRows, rt.NUM_ROWS, rt.ROW_HEIGHT)
local offset = FauxScrollFrame_GetOffset(rt.scrollFrame)
rt.offset = offset
for i=1, min(rt.NUM_ROWS, #rt.displayRows) do
rt.rows[i]:Show()
local data = rt.displayRows[i+offset]
local cols = rt.rows[i].cols
rt.rows[i].data = data
if rt.selected == GetTableIndex(rt.data, data) then
rt.rows[i].highlight:Show()
else
rt.rows[i].highlight:Hide()
end
for j, col in ipairs(rt.rows[i].cols) do
local colData = data[j]
if j == 1 then
col.icon:SetTexture(data.texture)
if data.indented then
col.spacer:SetWidth(10)
col.icon:SetAlpha(0.5)
col:GetFontString():SetAlpha(0.7)
else
col.spacer:SetWidth(1)
col.icon:SetAlpha(1)
col:GetFontString():SetAlpha(1)
end
end
if type(colData.value) == "function" then
col:SetText(colData.value(unpack(colData.args)))
else
col:SetText(colData.value)
end
end
end
rt:UpdateActiveRows()
end,
RefreshRowData = function(rt)
if not rt.auctionData then return end
wipe(rt.data)
wipe(rt.displayRows)
local function RowSort(a, b)
if a[rt.sortInfo.column].args[1] == nil then return end
local aVal
local bVal
if getn(a[rt.sortInfo.column].args) == 4 then
aVal = a[rt.sortInfo.column].args[4]
bVal = b[rt.sortInfo.column].args[4]
else
aVal = a[rt.sortInfo.column].args[1]
bVal = b[rt.sortInfo.column].args[1]
end
-- local aVal = a[rt.sortInfo.column].args[1]
-- local bVal = b[rt.sortInfo.column].args[1]
if type(aVal) ~= "string" or type(bVal) ~= "string" then
aVal = tonumber(aVal) or 0
bVal = tonumber(bVal) or 0
end
if aVal == bVal then
-- make this a stable sort (abitrarily) by using table reference strings
return tostring(a) < tostring(b)
end
if rt.sortInfo.ascending then
return aVal < bVal
else
return aVal > bVal
end
end
local tmp = {}
for _, auction in ipairs(rt.auctionData) do
local itemString = auction:GetItemString()
local itemRowData = {}
for i, data in ipairs(auction.compactRecords) do
local rowTbl = GetRowTable(rt, data, #auction.compactRecords > 1)
rowTbl.indented = true
tinsert(itemRowData, rowTbl)
end
sort(itemRowData, RowSort)
if itemRowData[1] then
itemRowData[1].indented = false
end
tinsert(tmp, itemRowData)
end
sort(tmp, function(a, b) return RowSort(a[1], b[1]) end)
for _, itemRows in ipairs(tmp) do
for _, row in ipairs(itemRows) do
tinsert(rt.data, row)
end
end
rt:DrawRows()
end,
SetData = function(rt, auctionData)
rt.auctionData = auctionData
rt:RefreshRowData()
end,
ClearSelection = function(rt)
rt.selected = nil
rt:DrawRows()
end,
SetSelectedAuction = function(rt, auction)
rt.selected = nil
for i, data in ipairs(rt.data) do
if type(auction) == "table" then
if data.auctionRecord == auction or data.auctionRecord:Equals(auction) then
rt.selected = i
break
end
elseif type(auction) == "string" then
if data.itemString == auction then
rt.selected = i
break
end
end
end
rt:DrawRows()
end,
GetSelectedAuction = function(rt)
if not rt.selected or not rt.data[rt.selected] then return end
return rt.data[rt.selected].auctionRecord
end,
SetExpanded = function(rt, itemString, expanded)
rt.expanded[itemString] = expanded
rt:RefreshRowData()
end,
ToggleExpanded = function(rt, itemString)
rt.expanded[itemString] = not rt.expanded[itemString]
rt:RefreshRowData()
end,
SetSort = function(rt, column, ascending)
if not rt.headCols[column or 0] then return end
rt.sortInfo.column = column
rt.sortInfo.ascending = ascending
for _, col in ipairs(rt.headCols) do
local tex = col:GetNormalTexture()
tex:SetTexture("Interface\\WorldStateFrame\\WorldStateFinalScore-Highlight")
tex:SetTexCoord(0.017, 1, 0.083, 0.909)
tex:SetAlpha(0.5)
end
if ascending then
rt.headCols[column]:GetNormalTexture():SetTexture(0.6, 0.8, 1, 0.8)
else
rt.headCols[column]:GetNormalTexture():SetTexture(0.8, 0.6, 1, 0.8)
end
rt:RefreshRowData()
end,
SetDisabled = function(rt, disabled)
rt.disabled = disabled
end,
SetColHeadText = function(rt, column, text)
rt.headCols[column]:SetText(text)
end,
UpdateActiveRows = function(rt)
if not rt.quickBuyout then return end
for _, row in ipairs(rt.rows) do
row:HideActiveBorder()
if row.data then
local rowRecord = row.data.auctionRecord
for i=1, GetNumAuctionItems("list") do
local itemString = TSMAPI:GetItemString(GetAuctionItemLink("list", i))
-- local _, _, count, _, _, _, _, _, _, buyout, _, _, _, seller = GetAuctionItemInfo("list", i)
local _, _, count, _, _, _, _, _, buyout, _, _, seller = GetAuctionItemInfo("list", i)
if itemString == row.data.itemString and rowRecord.count == count and rowRecord.buyout == buyout and rowRecord.seller == seller then
row:ShowActiveBorder()
break
end
end
end
end
wipe(purchaseCache)
end,
}
local defaultColScripts = {
OnEnter = function(self, ...)
if self.rt.disabled then return end
if self ~= self.row.cols[1] or not self.rt.isShowingItemTooltip then
GameTooltip:SetOwner(self, "ANCHOR_NONE")
GameTooltip:SetPoint("BOTTOMLEFT", self, "TOPLEFT")
local data = self.row.data
local extra = ""
if self.row.isActive then
extra = TSMAPI.Design:GetInlineColor("link").."\n\n"..L["Alt-Click to immediately buyout this auction."].."|r"
end
if self.rt.expanded[data.itemString] then
GameTooltip:AddLine(L["Double-click to collapse this item and show only the cheapest auction."]..extra, 1, 1, 1, true)
elseif data.expandable then
GameTooltip:AddLine(L["Double-click to expand this item and show all the auctions."]..extra, 1, 1, 1, true)
else
GameTooltip:AddLine(L["There is only one price level and seller for this item."]..extra, 1, 1, 1, true)
end
GameTooltip:Show()
end
self.row.highlight:Show()
local handler = self.rt.handlers.OnEnter
if handler then
handler(self.rt, self.row.data, self, ...)
end
end,
OnLeave = function(self, ...)
if self.rt.disabled then return end
if self ~= self.row.cols[1] or not self.rt.isShowingItemTooltip then
GameTooltip:Hide()
end
if not self.rt.selected or self.rt.selected ~= GetTableIndex(self.rt.data, self.row.data) then
self.row.highlight:Hide()
end
local handler = self.rt.handlers.OnLeave
if handler then
handler(self.rt, self.row.data, self, ...)
end
end,
OnClick = function(self, button, ...)
if self.rt.disabled then return end
self.rt:ClearSelection()
self.rt.selected = GetTableIndex(self.rt.data, self.row.data)
self.row.highlight:Show()
if self.rt.quickBuyout and IsAltKeyDown() then
local rowRecord = self.row.data.auctionRecord
for i=GetNumAuctionItems("list"), 1, -1 do
local link = GetAuctionItemLink("list", i)
if not purchaseCache[link] then
local itemString = TSMAPI:GetItemString(link)
-- local _, _, count, _, _, _, _, _, _, buyout, _, _, _, seller = GetAuctionItemInfo("list", i)
local _, _, count, _, _, _, _, _, buyout, _, _, seller = GetAuctionItemInfo("list", i)
if itemString == self.row.data.itemString and rowRecord.count == count and rowRecord.buyout == buyout and rowRecord.seller == seller then
PlaceAuctionBid("list", i, rowRecord.buyout)
TSM:AuctionControlCallback("OnBuyout", {itemString=TSMAPI:GetItemString(rowRecord.parent.itemLink), link=rowRecord.parent.itemLink, count=rowRecord.count, seller=rowRecord.seller, buyout=rowRecord.buyout, destroyingNum=rowRecord.parent.destroyingNum})
purchaseCache[link] = true
return
end
end
end
end
local handler = self.rt.handlers.OnClick
if handler then
handler(self.rt, self.row.data, self, button, ...)
end
end,
OnDoubleClick = function(self, ...)
if self.rt.disabled then return end
local data = self.row.data
if data.expandable then
self.rt:ToggleExpanded(data.itemString)
end
local handler = self.rt.handlers.OnDoubleClick
if handler then
handler(self.rt, self.row.data, self, ...)
end
end,
}
function TSMAPI:CreateAuctionResultsTable(parent, handlers, quickBuyout, isDestroying)
local priceColName = TSM.db.profile.pricePerUnit and "Price Per Item" or "Price Per Stack"
local colInfo = isDestroying and {
{name=L["Item"], width=0.43},
{name=L["Auctions"], width=0.07, align="CENTER"},
{name=L["Stack Size"], width=0.05, align="CENTER"},
{name=L["Seller"], width=0.11, align="CENTER"},
{name=L["Price Per Target Item"], width=0.13, align="RIGHT", isPrice=true},
{name=priceColName, width=0.13, align="RIGHT", isPrice=true},
{name=L["% Market Value"], width=0.08, align="CENTER"},
} or {
{name=L["Item"], width=0.42},
{name=L["Item Level"], width=0.05, align="CENTER"},
{name=L["Auctions"], width=0.07, align="CENTER"},
{name=L["Stack Size"], width=0.05, align="CENTER"},
{name=L["Time Left"], width=0.09, align="CENTER"},
{name=L["Seller"], width=0.11, align="CENTER"},
{name=priceColName, width=0.13, align="RIGHT", isPrice=true},
{name=L["% Market Value"], width=0.08, align="CENTER"},
}
local rtName = "TSMAuctionResultsTable"..RT_COUNT
RT_COUNT = RT_COUNT + 1
local rt = CreateFrame("Frame", rtName, parent)
rt.NUM_ROWS = TSM.db.profile.auctionResultRows
rt.ROW_HEIGHT = (parent:GetHeight()-HEAD_HEIGHT-HEAD_SPACE)/rt.NUM_ROWS
rt:SetScript("OnShow", function()
local priceColName = TSM.db.profile.pricePerUnit and L["Price Per Item"] or L["Price Per Stack"]
rt:SetColHeadText(#rt.headCols-1, priceColName)
rt:RefreshRowData()
end)
local contentFrame = CreateFrame("Frame", rtName.."Content", rt)
contentFrame:SetPoint("TOPLEFT")
contentFrame:SetPoint("BOTTOMRIGHT", -15, 0)
contentFrame:SetScript("OnSizeChanged", function(_, width) OnSizeChanged(rt, width) end)
rt.contentFrame = contentFrame
-- frame to hold the header columns and the rows
local scrollFrame = CreateFrame("ScrollFrame", rtName.."ScrollFrame", rt, "FauxScrollFrameTemplate")
scrollFrame:SetScript("OnVerticalScroll", function(self, offset)
FauxScrollFrame_OnVerticalScroll(self, offset, rt.ROW_HEIGHT, function() rt:DrawRows() end)
end)
scrollFrame:SetAllPoints(contentFrame)
rt.scrollFrame = scrollFrame
-- make the scroll bar consistent with the TSM theme
local scrollBar = _G[scrollFrame:GetName().."ScrollBar"]
scrollBar:ClearAllPoints()
scrollBar:SetPoint("BOTTOMRIGHT", rt, -1, 1)
scrollBar:SetPoint("TOPRIGHT", rt, -1, -HEAD_HEIGHT)
scrollBar:SetWidth(12)
local thumbTex = scrollBar:GetThumbTexture()
thumbTex:SetPoint("CENTER")
TSMAPI.Design:SetFrameColor(thumbTex)
thumbTex:SetHeight(150)
thumbTex:SetWidth(scrollBar:GetWidth())
_G[scrollBar:GetName().."ScrollUpButton"]:Hide()
_G[scrollBar:GetName().."ScrollDownButton"]:Hide()
-- create the header columns
rt.headCols = {}
for i, info in ipairs(colInfo) do
local col = CreateFrame("Button", rtName.."HeadCol"..i, rt.contentFrame)
col:SetHeight(HEAD_HEIGHT)
if i == 1 then
col:SetPoint("TOPLEFT")
else
col:SetPoint("TOPLEFT", rt.headCols[i-1], "TOPRIGHT")
end
col.info = info
col.rt = rt
col:RegisterForClicks("AnyUp")
col:SetScript("OnClick", OnColumnClick)
local text = col:CreateFontString()
text:SetJustifyH("CENTER")
text:SetJustifyV("CENTER")
text:SetFont(TSMAPI.Design:GetContentFont("small"))
TSMAPI.Design:SetWidgetTextColor(text)
col:SetFontString(text)
col:SetText(info.name or "")
text:SetAllPoints()
local tex = col:CreateTexture()
tex:SetAllPoints()
tex:SetTexture("Interface\\WorldStateFrame\\WorldStateFinalScore-Highlight")
tex:SetTexCoord(0.017, 1, 0.083, 0.909)
tex:SetAlpha(0.5)
col:SetNormalTexture(tex)
local tex = col:CreateTexture()
tex:SetAllPoints()
tex:SetTexture("Interface\\Buttons\\UI-Listbox-Highlight")
tex:SetTexCoord(0.025, 0.957, 0.087, 0.931)
tex:SetAlpha(0.2)
col:SetHighlightTexture(tex)
tinsert(rt.headCols, col)
end
-- create the rows
rt.rows = {}
for i=1, rt.NUM_ROWS do
local row = CreateFrame("Frame", rtName.."Row"..i, rt.contentFrame)
row:SetHeight(rt.ROW_HEIGHT)
if i == 1 then
row:SetPoint("TOPLEFT", 0, -(HEAD_HEIGHT+HEAD_SPACE))
row:SetPoint("TOPRIGHT", 0, -(HEAD_HEIGHT+HEAD_SPACE))
else
row:SetPoint("TOPLEFT", rt.rows[i-1], "BOTTOMLEFT")
row:SetPoint("TOPRIGHT", rt.rows[i-1], "BOTTOMRIGHT")
end
local highlight = row:CreateTexture()
highlight:SetAllPoints()
highlight:SetTexture(1, .9, 0, .5)
highlight:Hide()
row.highlight = highlight
row.rt = rt
row.cols = {}
for j=1, #colInfo do
local col = CreateFrame("Button", nil, row)
local text = col:CreateFontString()
if TSM.db.profile.showBids and colInfo[j].isPrice then
text:SetFont(TSMAPI.Design:GetContentFont(), min(13, rt.ROW_HEIGHT/2 - 2))
else
text:SetFont(TSMAPI.Design:GetContentFont(), min(14, rt.ROW_HEIGHT))
end
text:SetJustifyH(colInfo[j].align or "LEFT")
text:SetJustifyV("CENTER")
text:SetPoint("TOPLEFT", 1, -1)
text:SetPoint("BOTTOMRIGHT", -1, 1)
col:SetFontString(text)
col:SetHeight(rt.ROW_HEIGHT)
col:RegisterForClicks("AnyUp")
for name, func in pairs(defaultColScripts) do
col:SetScript(name, func)
end
col.rt = rt
col.row = row
col.rowNum = i
if j == 1 then
col:SetPoint("TOPLEFT")
else
col:SetPoint("TOPLEFT", row.cols[j-1], "TOPRIGHT")
end
if j%2 == 1 then
local tex = col:CreateTexture()
tex:SetAllPoints()
tex:SetTexture(1, 1, 1, .03)
col:SetNormalTexture(tex)
end
-- special first column to hold spacer / item name / item icon
if j == 1 then
local spacer = CreateFrame("Frame", nil, col)
spacer:SetPoint("TOPLEFT")
spacer:SetHeight(rt.ROW_HEIGHT)
spacer:SetWidth(1)
col.spacer = spacer
local iconBtn = CreateFrame("Button", nil, col)
iconBtn:SetBackdrop({edgeFile="Interface\\Buttons\\WHITE8X8", edgeSize=1.5})
iconBtn:SetBackdropBorderColor(0, 1, 0, 0)
iconBtn:SetPoint("TOPLEFT", spacer, "TOPRIGHT")
iconBtn:SetHeight(rt.ROW_HEIGHT)
iconBtn:SetWidth(rt.ROW_HEIGHT)
iconBtn:SetScript("OnEnter", function(self)
if row.data.link then
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
TSMAPI:SafeTooltipLink(row.data.link)
GameTooltip:Show()
rt.isShowingItemTooltip = true
end
end)
iconBtn:SetScript("OnLeave", function(self)
-- BattlePetTooltip:Hide()
GameTooltip:ClearLines()
GameTooltip:Hide()
rt.isShowingItemTooltip = false
end)
iconBtn:SetScript("OnClick", function(_, ...)
if IsModifiedClick() then
HandleModifiedItemClick(row.data.auctionRecord.parent.itemLink)
else
col:GetScript("OnClick")(col, ...)
end
end)
iconBtn:SetScript("OnDoubleClick", function(_, ...)
col:GetScript("OnDoubleClick")(col, ...)
end)
local icon = iconBtn:CreateTexture(nil, "ARTWORK")
icon:SetPoint("TOPLEFT", 2, -2)
icon:SetPoint("BOTTOMRIGHT", -2, 2)
col.iconBtn = iconBtn
col.icon = icon
row.ShowActiveBorder = function()
if rt.quickBuyout then
row.isActive = true
iconBtn:SetBackdropBorderColor(0, 1, 0, .7)
end
end
row.HideActiveBorder = function()
row.isActive = nil
iconBtn:SetBackdropBorderColor(0, 0, 0, 0)
end
text:ClearAllPoints()
text:SetPoint("TOPLEFT", iconBtn, "TOPRIGHT", 2, 0)
text:SetPoint("BOTTOMRIGHT")
end
tinsert(row.cols, col)
end
if i%2 == 0 then
local tex = row:CreateTexture()
tex:SetAllPoints()
tex:SetTexture("Interface\\WorldStateFrame\\WorldStateFinalScore-Highlight")
tex:SetTexCoord(0.017, 1, 0.083, 0.909)
tex:SetAlpha(0.3)
end
tinsert(rt.rows, row)
end
rt:SetAllPoints()
rt.data = {}
rt.expanded = {}
rt.displayRows = {}
rt.handlers = handlers or {}
rt.sortInfo = {}
rt.quickBuyout = quickBuyout
rt.isDestroying = isDestroying
tinsert(resultsTables, rt)
for name, func in pairs(methods) do
rt[name] = func
end
LibStub("AceEvent-3.0").RegisterEvent(rt, "AUCTION_ITEM_LIST_UPDATE", "UpdateActiveRows")
return rt
end
@@ -0,0 +1,628 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
-- This file contains code for scanning the auction house
local TSM = select(2, ...)
local AuctionScanning = TSM:NewModule("AuctionScanning", "AceEvent-3.0")
TSMAPI.AuctionScan = {}
local RETRY_DELAY = 2
local MAX_RETRIES = 4
local BASE_DELAY = 0.10 -- time to delay for before trying to scan a page again when it isn't fully loaded
local private = { callbackHandler = nil, query = {}, options = {}, data = {}, isScanning = nil }
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.AuctionScanning_private")
local scanCache = {}
local CACHE_DECAY_PER_DAY = 5
local CACHE_AUTO_HIT_TIME = 10 * 60
local SECONDS_PER_DAY = 60 * 60 * 24
local function DoCallback(...)
if type(private.callbackHandler) == "function" then
private.callbackHandler(...)
end
end
local function eventHandler(event)
if event == "AUCTION_HOUSE_CLOSED" then
-- auction house was closed, make sure all scanning is stopped
AuctionScanning:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
private.auctionHouseShown = false
DoCallback("INTERRUPTED")
private:StopScanning()
elseif event == "AUCTION_ITEM_LIST_UPDATE" then
-- gets called whenever the AH window is updated (something is shown in the results section)
AuctionScanning:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
TSMAPI:CancelFrame("updateDelay")
-- now that our query was successful, we can get our data
private:ScanAuctions()
end
end
function AuctionScanning:OnEnable()
AuctionScanning:RegisterEvent("AUCTION_HOUSE_CLOSED", eventHandler)
end
function private:ScanAuctionPage(resolveSellers)
local shown = GetNumAuctionItems("list")
local badData = false
local auctions = {}
for i = 1, shown do
-- checks to make sure all the data has been sent to the client
-- if not, the data is bad and we'll wait / try again
-- local count, _, _, _, _, _, _, buyout, _, _, _, seller = select(3, GetAuctionItemInfo("list", i))
local count, _, _, _, _, _, buyout, _, _, seller = select(3, GetAuctionItemInfo("list", i))
local itemString = TSMAPI:GetItemString(GetAuctionItemLink("list", i))
auctions[i] = { itemString = itemString, index = i, count = count, buyout = buyout, seller = seller }
if not (itemString and buyout and count and (seller or not resolveSellers or buyout == 0)) then
badData = true
end
end
return badData, auctions
end
function IsDuplicatePage()
if not private.pageTemp or GetNumAuctionItems("list") == 0 then return false end
local numLinks, prevLink = 0, nil
for i = 1, GetNumAuctionItems("list") do
-- local _, _, count, _, _, _, _, minBid, minInc, buyout, bid, _, _, seller = GetAuctionItemInfo("list", i)
local _, _, count, _, _, _, minBid, minInc, buyout, bid, _, seller = GetAuctionItemInfo("list", i)
local link = GetAuctionItemLink("list", i)
local temp = private.pageTemp[i]
if not prevLink then
prevLink = link
elseif prevLink ~= link then
prevLink = link
numLinks = numLinks + 1
end
if not temp or temp.count ~= count or temp.minBid ~= minBid or temp.minInc ~= minInc or temp.buyout ~= buyout or temp.bid ~= bid or temp.seller ~= seller or temp.link ~= link then
return false
end
end
if numLinks > 1 and private.pageTemp.shown == GetNumAuctionItems("list") then
return false
end
return true
end
local function PopulatePageTemp()
local shown = GetNumAuctionItems("list")
private.pageTemp = { numShown = shown }
for i = 1, shown do
-- checks to make sure all the data has been sent to the client
-- if not, the data is bad and we'll wait / try again
-- local _, _, count, _, _, _, _, minBid, minInc, buyout, bid, _, seller = GetAuctionItemInfo("list", i)
local _, _, count, _, _, _, minBid, minInc, buyout, bid, _, seller = GetAuctionItemInfo("list", i)
local link = GetAuctionItemLink("list", i)
private.pageTemp[i] = { count = count, minBid = minBid, minInc = minInc, buyout = buyout, bid = bid, seller = seller, link = link }
end
end
-- Starts a scan of the auction house.
-- query - A single query containing QueryAuctionItem paramters:
-- name, minLevel, maxLevel, invType, class, subClass, usable, quality
-- resolveSellers - whether or not to resolve seller names
-- maxPrice - stop scanning when prices go above this price
function TSMAPI.AuctionScan:RunQuery(query, callbackHandler, resolveSellers, maxPrice, doCache)
TSMAPI.AuctionScan:StopScan() -- stop any scan in progress
if not AuctionFrame:IsVisible() then
return -1 -- the auction house isn't open (return code -1)
elseif type(query) ~= "table" then
return -2 -- the scan queue is not a table (return code -2)
elseif not CanSendAuctionQuery() then
TSMAPI:CreateTimeDelay("cantSendAuctionQueryDelay", 0.1, function() TSMAPI.AuctionScan:RunQuery(query, callbackHandler, resolveSellers, maxPrice, doCache) end)
return 0 -- the query will start as soon as it can but did not start immediately (return code 0)
end
-- sort by buyout
SortAuctionItems("list", "buyout")
if IsAuctionSortReversed("list", "buyout") then
SortAuctionItems("list", "buyout")
end
-- setup the query
private.query = CopyTable(query)
private.query.page = 0 -- the current page of this query we're scanning
private.query.timeDelay = 0 -- a delay used to wait for information to show up
private.query.retries = 0 -- how many times we've done a hard retry so far
private.query.hardRetry = nil -- if a page hasn't loaded after we've tried a delay, we'll do a hard retry and re-send the query
private.cache = doCache and { query = CopyTable(query), items = {} } or nil
-- setup other stuff
wipe(private.data)
private.isScanning = true
private.callbackHandler = callbackHandler
private.resolveSellers = resolveSellers
private.scanType = "query"
private.maxPrice = maxPrice or math.huge
--starts scanning
private:SendQuery()
return 1 -- scan started successfully (return code 1)
end
function TSMAPI.AuctionScan:ScanLastPage(callbackHandler)
private:StopScanning() -- stop any scan in progress
if not AuctionFrame:IsVisible() then
return -1 -- the auction house isn't open (return code -1)
elseif not CanSendAuctionQuery() then
TSMAPI:CreateTimeDelay("cantSendAuctionQueryDelay", 0.1, function() TSMAPI.AuctionScan:ScanLastPage(callbackHandler) end)
return 0 -- the query will start as soon as it can but did not start immediately (return code 0)
end
-- clear the auction sort
SortAuctionClearSort("list")
-- setup the query
private.query = {name="", page=0}
private.query.timeDelay = 0 -- a delay used to wait for information to show up
private.query.retries = 0 -- how many times we've done a hard retry so far
private.query.hardRetry = nil -- if a page hasn't loaded after we've tried a delay, we'll do a hard retry and re-send the query
-- setup other stuff
wipe(private.data)
private.isScanning = true
private.callbackHandler = callbackHandler
private.scanType = "lastPage"
--starts scanning
private:SendQuery()
return 1 -- scan started successfully (return code 1)
end
-- sends a query to the AH frame once it is ready to be queried (uses frame as a delay)
function private:SendQuery()
if not private.isScanning then return end
if CanSendAuctionQuery() then
-- stop delay timer
TSMAPI:CancelFrame("queryDelay")
-- Query the auction house (then waits for AUCTION_ITEM_LIST_UPDATE to fire)
AuctionScanning:RegisterEvent("AUCTION_ITEM_LIST_UPDATE", eventHandler)
-- [exact] cardinal ruby 0 0 nil 0 0 0 0 0
-- [normal] cardinal ruby nil nil nil nil nil 0 nil nil
QueryAuctionItems(private.query.name, private.query.minLevel, private.query.maxLevel, private.query.invType, private.query.class, private.query.subClass, private.query.page, private.query.usable, private.query.quality)
else
-- run delay timer then try again to scan
TSMAPI:CreateTimeDelay("queryDelay", 0.05, private.SendQuery)
end
end
--scans the currently shown page of auctions and collects all the data
function private:ScanAuctions()
if not private.isScanning then return end
local shown, total = GetNumAuctionItems("list")
local totalPages = ceil(total / NUM_AUCTION_ITEMS_PER_PAGE)
if private.scanType == "numPages" then
local cacheData = TSM.db.factionrealm.numPagesCache[private.query.cacheKey]
cacheData.lastScan = time()
local confidence = (120 - cacheData.confidence) / (CACHE_DECAY_PER_DAY * 2)
local diff = abs(cacheData.avg - totalPages)
if diff <= 1 and diff > 0.5 then
confidence = floor(confidence * (1.5 - diff))
elseif diff > 1 then
confidence = floor(confidence - CACHE_DECAY_PER_DAY * diff)
end
cacheData.confidence = max(floor(cacheData.confidence + confidence), 0)
cacheData.avg = (cacheData.avg * cacheData.numScans + totalPages) / (cacheData.numScans + 1)
cacheData.numScans = cacheData.numScans + 1
private:StopScanning()
return DoCallback("NUM_PAGES", totalPages)
elseif private.scanType == "lastPage" then
local lastPage = floor(total / NUM_AUCTION_ITEMS_PER_PAGE)
if private.query.page ~= lastPage then
private.query.page = lastPage
return private:SendQuery()
end
end
local dataIsBad, auctions = private:ScanAuctionPage(private.resolveSellers)
-- check that we have good data
if dataIsBad or IsDuplicatePage() then
if private.query.retries < MAX_RETRIES then
if private.query.hardRetry then
-- Hard retry
-- re-sends the entire query
private.query.retries = private.query.retries + 1
private.query.timeDelay = 0
private.query.hardRetry = nil
private:SendQuery()
else
-- Soft retry
-- runs a delay and then tries to scan the query again
private.query.timeDelay = private.query.timeDelay + BASE_DELAY
TSMAPI:CreateTimeDelay("updateDelay", BASE_DELAY, private.ScanAuctions)
-- If after 2 seconds of retrying we still don't have data, will go and requery to try and solve the issue
-- if we still don't have data, we try to scan it anyway and move on.
if private.query.timeDelay >= RETRY_DELAY then
private.query.hardRetry = true
end
end
return
end
end
if private.cache then
-- store info in cache
for i, v in ipairs(auctions) do
local cacheTmp = CopyTable(v)
cacheTmp.index = private.query.page * 50 + i
tinsert(private.cache, cacheTmp)
private.cache.items[cacheTmp.itemString] = true
end
end
private.query.hardRetry = nil
private.query.retries = 0
private.query.timeDelay = 0
if private.scanType ~= "lastPage" then
private.query.page = private.query.page + 1 -- increment current page
if totalPages > 0 then
DoCallback("SCAN_PAGE_UPDATE", private.query.page, totalPages)
end
end
PopulatePageTemp()
-- now that we know our query is good, time to verify and then store our data
for _, v in ipairs(auctions) do
if private:AddAuctionRecord(v.index) then
-- we've hit the max price so we're done scanning
private:StopScanning()
return DoCallback("SCAN_COMPLETE", private.data)
end
end
if private.scanType == "lastPage" then
return DoCallback("SCAN_LAST_PAGE_COMPLETE", private.data)
elseif private.query.page >= totalPages then
-- we have finished scanning this query
private:StopScanning()
return DoCallback("SCAN_COMPLETE", private.data)
end
-- query the next page and continue scanning
private:SendQuery()
end
-- Add a new record to the private.data table
function private:AddAuctionRecord(index)
-- local name, texture, count, _, _, _, _, minBid, minIncrement, buyout, bid, highBidder, highBidder_full, seller, seller_full = GetAuctionItemInfo("list", index)
local name, texture, count, _, _, _, minBid, minIncrement, buyout, bid, highBidder, seller = GetAuctionItemInfo("list", index)
seller = TSM:GetAuctionPlayer(seller, null)
highBidder = TSM:GetAuctionPlayer(highBidder, null)
local timeLeft = GetAuctionItemTimeLeft("list", index)
local link = GetAuctionItemLink("list", index)
local itemString = TSMAPI:GetItemString(link)
if not itemString then return end
-- Create a new entry in the table
if not private.data[itemString] then
private.data[itemString] = TSMAPI.AuctionScan:NewAuctionItem()
private.data[itemString]:SetItemLink(link)
private.data[itemString]:SetTexture(texture)
end
private.data[itemString]:AddAuctionRecord(count, minBid, minIncrement, buyout, bid, highBidder, seller or "?", timeLeft)
-- add the base item if necessary
local baseItemString = TSMAPI:GetBaseItemString(itemString)
if baseItemString ~= itemString then
-- Create a new entry in the table
if not private.data[baseItemString] then
private.data[baseItemString] = TSMAPI.AuctionScan:NewAuctionItem()
private.data[baseItemString]:SetItemLink(link)
private.data[baseItemString]:SetTexture(texture)
end
private.data[baseItemString]:AddAuctionRecord(count, minBid, minIncrement, buyout, bid, highBidder, seller or "?", timeLeft)
private.data[baseItemString].isBaseItem = true
end
if select(8, TSMAPI:GetSafeItemInfo(link)) == count then
return (buyout or 0) / count > (private.maxPrice or math.huge)
end
end
-- stops the scan when we are finished scanning, it was interrupted, or somebody stopped it
function private:StopScanning()
TSMAPI:CancelFrame("cantSendAuctionQueryDelay")
if not private.isScanning then return end
if private.cache then
-- store the cache info
sort(private.cache, function(a, b) return a.index < b.index end)
for itemString in pairs(private.cache.items) do
scanCache[itemString] = private.cache
end
wipe(private.cache.items)
private.cache = nil
end
-- cancel any delays that might still be running
TSMAPI:CancelFrame("queryDelay")
TSMAPI:CancelFrame("updateDelay")
AuctionScanning:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
private.isScanning = nil
private.pageTemp = nil
end
-- API for stopping the scan
-- returns true/false if we were/weren't actually scanning
function TSMAPI.AuctionScan:StopScan()
private:StopScanning()
TSM:StopGeneratingQueries()
end
-- Gets the number of pages for a given query
function TSMAPI.AuctionScan:GetNumPages(query, callbackHandler)
private:StopScanning() -- stop any scan in progress
if not AuctionFrame:IsVisible() then
return -1 -- the auction house isn't open (return code -1)
elseif type(query) ~= "table" then
return -2 -- the scan queue is not a table (return code -2)
elseif not CanSendAuctionQuery() then
TSMAPI:CreateTimeDelay("cantSendAuctionQueryDelay", 0.1, function() TSMAPI.AuctionScan:GetNumPages(query, callbackHandler) end)
return 0 -- the query will start as soon as it can but did not start immediately (return code 0)
end
-- fancy caching
local temp = {}
for i, field in ipairs({ "name", "minLevel", "maxLevel", "invType", "class", "subClass", "usable", "quality" }) do
temp[i] = tostring(query[field])
end
local cacheKey = table.concat(temp, "~")
local cacheData = TSM.db.factionrealm.numPagesCache[cacheKey]
if cacheData then
local cacheHit
if time() - cacheData.lastScan < CACHE_AUTO_HIT_TIME then
-- auto cache hit
cacheHit = true
elseif random(1, 100) <= cacheData.confidence then
-- cache hit
cacheData.confidence = cacheData.confidence - floor(((time() - cacheData.lastScan) / SECONDS_PER_DAY) * CACHE_DECAY_PER_DAY + 0.5)
cacheData.confidence = max(cacheData.confidence, 0) -- ensure >= 0
cacheHit = true
end
if cacheHit then
local numPages = max(ceil(cacheData.avg), 1) -- round avg num of pages up and ensure >= 1
TSMAPI:CreateTimeDelay("numPagesCacheDelay", 0, function() callbackHandler("NUM_PAGES", numPages) end)
return 2
end
else
TSM.db.factionrealm.numPagesCache[cacheKey] = { avg = 0, confidence = 0, numScans = 0, lastScan = 0 }
end
-- setup the query
private.query = CopyTable(query)
private.query.cacheKey = cacheKey
-- setup other stuff
wipe(private.data)
private.isScanning = true
private.callbackHandler = callbackHandler
private.scanType = "numPages"
--starts scanning
private:SendQuery()
return 1 -- scan started successfully (return code 1)
end
function TSMAPI.AuctionScan:CacheRemove(itemString, index)
if scanCache[itemString] then
tremove(scanCache[itemString], index)
end
end
function TSMAPI.AuctionScan:ClearCache()
wipe(scanCache)
end
local findPrivate = {}
findPrivate.findFrame = findPrivate.findFrame or CreateFrame("Frame")
local function eventHandler(frame, event)
if event == "AUCTION_HOUSE_SHOW" then
-- auction house was opened
elseif event == "AUCTION_HOUSE_CLOSED" then
frame:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
if findPrivate.isScanning then -- stop scanning if we were scanning (pass true to specify it was interrupted)
TSMAPI.AuctionScan:StopFindScan()
end
elseif event == "AUCTION_ITEM_LIST_UPDATE" then
frame:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
if findPrivate.isScanning then
findPrivate.timeDelay = 0
TSMAPI:CancelFrame("auctionFindScanDelay")
-- now that our query was successful we can get our data
findPrivate:ScanAuctions()
end
end
end
findPrivate.findFrame:SetScript("OnEvent", eventHandler)
findPrivate.findFrame:RegisterEvent("AUCTION_HOUSE_CLOSED")
findPrivate.findFrame:RegisterEvent("AUCTION_HOUSE_SHOW")
local function CompareTableKeys(tbl1, tbl2)
for _, key in ipairs(findPrivate.keys) do
if tbl1[key] ~= tbl2[key] then
return
end
end
return true
end
local function IsTargetAuction(index)
local itemString = TSMAPI:GetItemString(GetAuctionItemLink("list", index))
-- local _, _, count, _, _, _, _, minBid, bidIncrement, buyout, bidAmount, _, _, seller, seller_full = GetAuctionItemInfo("list", index)
local _, _, count, _, _, _, minBid, bidIncrement, buyout, bidAmount, _, _, seller = GetAuctionItemInfo("list", index)
seller = TSM:GetAuctionPlayer(seller, nil)
local bid = bidAmount == 0 and minBid or bidAmount
local tmp = { itemString = itemString, count = count, bid = bid, buyout = buyout, seller = seller }
return CompareTableKeys(tmp, findPrivate.targetInfo)
end
-- valid targetInfo keys: itemString, count, bid, buyout, seller
function TSMAPI.AuctionScan:FindAuction(callback, targetInfo, useCache)
if findPrivate.isScanning then TSMAPI.AuctionScan:StopFindScan() end
findPrivate.keys = { "itemString", "count", "bid", "buyout", "seller" }
for i = #findPrivate.keys, 1, -1 do
if not targetInfo[findPrivate.keys[i]] then
tremove(findPrivate.keys, i)
end
end
local cacheIndex
if useCache and scanCache[targetInfo.itemString] then
for i, v in ipairs(scanCache[targetInfo.itemString]) do
if CompareTableKeys(v, targetInfo) then
cacheIndex = i
break
end
end
end
if cacheIndex then
findPrivate.page = floor((cacheIndex - 1) / 50)
findPrivate.query = scanCache[targetInfo.itemString].query
else
local name, _, rarity, _, minLevel, class, subClass = TSMAPI:GetSafeItemInfo(targetInfo.itemString)
findPrivate.query = { name = name, minLevel = minLevel, maxLevel = minLevel, class = class, subClass = subClass, rarity = rarity }
findPrivate.page = 0
end
findPrivate.targetInfo = targetInfo
findPrivate.callback = callback
findPrivate.cacheIndex = cacheIndex
findPrivate.isScanning = targetInfo.itemString
findPrivate.retries = 0
findPrivate.hardRetry = nil
-- check if the item is on the current page
for i = 1, GetNumAuctionItems("list") do
if IsTargetAuction(i) then
TSMAPI.AuctionScan:StopFindScan()
TSMAPI:CreateTimeDelay("queryFoundDelay", 0.1, function() findPrivate.callback(i) end)
return
end
end
findPrivate:SendQuery()
end
-- sends a query to the AH frame once it is ready to be queried (uses frame as a delay)
function findPrivate:SendQuery()
if not findPrivate.isScanning then return end
if CanSendAuctionQuery() then
-- stop delay timer
TSMAPI:CancelFrame("auctionFindQueryDelay")
-- query the auction house (then waits for AUCTION_ITEM_LIST_UPDATE to fire)
findPrivate.findFrame:RegisterEvent("AUCTION_ITEM_LIST_UPDATE")
local q = findPrivate.query
QueryAuctionItems(q.name, q.minLevel, q.maxLevel, q.invType, q.class, q.subClass, findPrivate.page, 0, q.rarity)
else
-- run delay timer then try again to scan
TSMAPI:CreateTimeDelay("auctionFindQueryDelay", 0.05, function() findPrivate:SendQuery() end)
end
end
-- scans the currently shown page of auctions and collects all the data
function findPrivate:ScanAuctions()
if not findPrivate.isScanning then return end
-- collects data on the query:
-- # of auctions on current page
-- # of pages total
local shown, total = GetNumAuctionItems("list")
local totalPages = math.ceil(total / 50)
local dataIsBad, temp = private:ScanAuctionPage(findPrivate.targetInfo.seller)
-- Check for bad data
if findPrivate.retries < 3 then
if dataIsBad then
if findPrivate.hardRetry then
-- Hard retry
-- re-sends the entire query
findPrivate.retries = findPrivate.retries + 1
findPrivate:SendQuery()
else
-- Soft retry
-- runs a delay and then tries to scan the query again
findPrivate.timeDelay = findPrivate.timeDelay + BASE_DELAY
TSMAPI:CreateTimeDelay("auctionFindScanDelay", BASE_DELAY, findPrivate.ScanAuctions)
-- If after 4 seconds of retrying we still don't have data, will go and requery to try and solve the issue
-- if we still don't have data, we try to scan it anyway and move on.
if findPrivate.timeDelay >= 4 then
findPrivate.hardRetry = true
findPrivate.retries = 0
end
end
return
end
end
findPrivate.hardRetry = nil
findPrivate.retries = 0
-- now that we know our query is good, time to verify and then store our data
for i = 1, shown do
if IsTargetAuction(temp[i].index) then
TSMAPI.AuctionScan:StopFindScan()
return findPrivate.callback(temp[i].index, findPrivate.cacheIndex == findPrivate.page and findPrivate.page * 50 + temp[i].index)
end
end
-- This query has more pages to scan
-- increment the page # and send the new query
if not findPrivate.cacheIndex and totalPages > (findPrivate.page + 1) then
findPrivate.page = findPrivate.page + 1
findPrivate:SendQuery()
return
end
-- we are done scanning!
TSMAPI.AuctionScan:StopFindScan()
return findPrivate.callback()
end
-- returns whether or not we're currently doing a find scan
function TSMAPI.AuctionScan:IsFindScanning()
return findPrivate.isScanning
end
-- stops the scan because it was either interrupted or it was completed successfully
function TSMAPI.AuctionScan:StopFindScan()
findPrivate.findFrame:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE")
findPrivate.isScanning = nil
TSMAPI:CancelFrame("auctionFindQueryDelay")
TSMAPI:CancelFrame("auctionFindScanDelay")
end
+116
View File
@@ -0,0 +1,116 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- http://www.curse.com/addons/wow/tradeskill-master --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select(2, ...)
local private = {}
TSMAPI:RegisterForTracing(private, "TradeSkillMaster.AuctionUtil_private")
LibStub("AceEvent-3.0"):Embed(private)
local eventFrame = CreateFrame("Frame")
eventFrame:Hide()
eventFrame.data = {}
eventFrame.callback = function() end
eventFrame:SetScript("OnEvent", function(self, event, ...)
if self.interrupt and event == self.interrupt.event and self.interrupt.callback() then
self:UnregisterAllEvents()
self.data = {}
end
for i=1, #self.data do
if self.data[i].event == event then
if self.data[i].callback then
if self.data[i].callback(event, ...) then
tremove(self.data, i)
self:UnregisterEvent(event)
end
else
tremove(self.data, i)
self:UnregisterEvent(event)
end
break
end
end
if #self.data == 0 then
self:Hide()
self.callback()
end
end)
local function WaitForEvents(data, callback, interrupt)
eventFrame.data = data
eventFrame.callback = callback
for i=1, #data do
eventFrame:RegisterEvent(data[i].event)
end
if interrupt then
eventFrame.interrupt = interrupt
eventFrame:RegisterEvent(interrupt.event)
end
eventFrame:Show()
end
function TSMAPI:CreateEventDelay(event, callback, timeout, validator)
if not event then return end
local eventName = "eventDelay"..random()
if timeout then
TSMAPI:CreateTimeDelay(eventName, timeout, function() eventFrame:Hide() end)
callback()
end
WaitForEvents({event=event, callback=validator}, function() callback() TSMAPI:CancelFrame(eventName) end)
end
-- Sends the "TSM_AH_EVENTS" message once the action (buyout/bid/cancel/post)
-- has been acknowledged by the server and the client has been notified
function TSMAPI:WaitForAuctionEvents(mode, isMultiPost)
local function ValidateEvent(_, msg)
if mode == "Buyout" then
return msg:match(gsub(ERR_AUCTION_BID_PLACED, "%%s", ""))
elseif mode == "Cancel" then
return msg == ERR_AUCTION_REMOVED
elseif mode == "Post" then
return msg == ERR_AUCTION_STARTED
end
end
local events, interrupt
if mode == "Buyout" then
events = {{event="AUCTION_ITEM_LIST_UPDATE"}, {event="CHAT_MSG_SYSTEM", callback=ValidateEvent}}
interrupt = {event="UI_ERROR_MESSAGE", callback=function(_,msg) return msg == ERR_AUCTION_HIGHER_BID end}
elseif mode == "Cancel" then
events = {{event="CHAT_MSG_SYSTEM", callback=ValidateEvent}, {event="AUCTION_OWNED_LIST_UPDATE"}}
elseif mode == "Post" then
if isMultiPost then
events = {{event="AUCTION_MULTISELL_UPDATE", callback=function(_,arg1,arg2) return arg1 == arg2 end}}
else
events = {{event="CHAT_MSG_SYSTEM", callback=ValidateEvent}}
end
end
if events then
WaitForEvents(events, function() private:SendMessage("TSM_AH_EVENTS", mode) end, interrupt)
end
end
function TSMAPI:GetAuctionPercentColor(percent)
local colors = {
{color="|cff2992ff", value=50}, -- blue
{color="|cff16ff16", value=80}, -- green
{color="|cffffff00", value=110}, -- yellow
{color="|cffff9218", value=135}, -- orange
{color="|cffff0000", value=math.huge}, -- red
}
for i=1, #colors do
if percent < colors[i].value then
return colors[i].color
end
end
return "|cffffffff"
end