initial commit

This commit is contained in:
Gitea
2020-11-13 14:13:12 -05:00
commit 05df49ff60
368 changed files with 128754 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,441 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
local BuyUtil = TSM.UI.AuctionUI:NewPackage("BuyUtil")
local L = TSM.Include("Locale").GetTable()
local Money = TSM.Include("Util.Money")
local Log = TSM.Include("Util.Log")
local Math = TSM.Include("Util.Math")
local Theme = TSM.Include("Util.Theme")
local ItemInfo = TSM.Include("Service.ItemInfo")
local CustomPrice = TSM.Include("Service.CustomPrice")
local UIElements = TSM.Include("UI.UIElements")
local private = {
totalBuyout = nil,
isBuy = nil,
auctionScan = nil,
subRow = nil,
index = nil,
noSeller = nil,
baseFrame = nil,
dialogFrame = nil,
future = nil,
prepareQuantity = nil,
prepareSuccess = false,
marketValueFunc = nil,
}
-- ============================================================================
-- Module Functions
-- ============================================================================
function BuyUtil.ShowConfirmation(baseFrame, subRow, isBuy, auctionNum, numFound, maxQuantity, callback, auctionScan, index, noSeller, marketValueFunc)
auctionNum = min(auctionNum, numFound)
local buyout = subRow:GetBuyouts()
if not isBuy then
buyout = subRow:GetRequiredBid(subRow)
end
local quantity = subRow:GetQuantities()
local itemString = subRow:GetItemString()
local _, _, _, isHighBidder = subRow:GetBidInfo()
local isCommodity = not TSM.IsWowClassic() and subRow:IsCommodity()
local shouldConfirm = false
if isCommodity then
shouldConfirm = true
elseif isBuy and isHighBidder then
shouldConfirm = true
elseif TSM.db.global.shoppingOptions.buyoutConfirm then
shouldConfirm = ceil(buyout / quantity) >= (CustomPrice.GetValue(TSM.db.global.shoppingOptions.buyoutAlertSource, itemString) or 0)
end
if not shouldConfirm then
return false
end
baseFrame = baseFrame:GetBaseElement()
private.isBuy = isBuy
private.auctionScan = auctionScan
private.subRow = subRow
private.index = index
private.noSeller = noSeller
private.baseFrame = baseFrame
private.marketValueFunc = marketValueFunc
if private.dialogFrame then
return true
end
local defaultQuantity = isCommodity and numFound or 1
assert(not isCommodity or isBuy)
local displayItemBuyout, displayTotalBuyout = nil, nil
if isCommodity then
displayTotalBuyout = private.CommodityResultsByQuantity(itemString, defaultQuantity)
displayItemBuyout = Math.Ceil(displayTotalBuyout / defaultQuantity, COPPER_PER_SILVER)
else
displayItemBuyout = ceil(buyout / quantity)
displayTotalBuyout = TSM.IsWowClassic() and buyout or ceil(buyout / quantity)
end
private.dialogFrame = UIElements.New("Frame", "frame")
:SetLayout("VERTICAL")
:SetSize(isCommodity and 600 or 326, isCommodity and 272 or 262)
:SetPadding(12)
:AddAnchor("CENTER")
:SetContext(callback)
:SetMouseEnabled(true)
:SetBackgroundColor("FRAME_BG", true)
:AddChild(UIElements.New("Frame", "header")
:SetLayout("HORIZONTAL")
:SetHeight(24)
:SetMargin(0, 0, -4, 10)
:AddChild(UIElements.New("Spacer", "spacer")
:SetWidth(20)
)
:AddChild(UIElements.New("Text", "title")
:SetJustifyH("CENTER")
:SetFont("BODY_BODY1_BOLD")
:SetText(isCommodity and L["Order Confirmation"] or L["Buyout"])
)
:AddChild(UIElements.New("Button", "closeBtn")
:SetMargin(0, -4, 0, 0)
:SetBackgroundAndSize("iconPack.24x24/Close/Default")
:SetScript("OnClick", private.BuyoutConfirmCloseBtnOnClick)
)
)
:AddChild(UIElements.New("Frame", "content")
:SetLayout("HORIZONTAL")
:AddChild(UIElements.New("Frame", "left")
:SetLayout("VERTICAL")
:AddChild(UIElements.New("Frame", "item")
:SetLayout("HORIZONTAL")
:SetPadding(6)
:SetMargin(0, 0, 0, 16)
:SetBackgroundColor("PRIMARY_BG_ALT", true)
:AddChild(UIElements.New("Button", "icon")
:SetSize(36, 36)
:SetMargin(0, 8, 0, 0)
:SetBackground(ItemInfo.GetTexture(itemString))
:SetTooltip(itemString)
)
:AddChild(UIElements.New("Text", "name")
:SetHeight(36)
:SetFont("ITEM_BODY1")
:SetText(TSM.UI.GetColoredItemName(itemString))
)
)
:AddChildIf(isCommodity, UIElements.New("Frame", "quantity")
:SetLayout("HORIZONTAL")
:SetHeight(24)
:AddChild(UIElements.New("Text", "desc")
:SetFont("BODY_BODY2")
:SetText(L["Quantity"]..":")
)
:AddChild(UIElements.New("Input", "input")
:SetWidth(140)
:SetJustifyH("RIGHT")
:SetBackgroundColor("PRIMARY_BG_ALT")
:SetValidateFunc("NUMBER", "1:"..maxQuantity)
:SetValue(tostring(defaultQuantity))
:SetContext(maxQuantity)
:SetScript("OnValueChanged", private.InputQtyOnValueChanged)
)
)
:AddChildIf(not isCommodity, UIElements.New("Frame", "stacks")
:SetLayout("HORIZONTAL")
:SetHeight(20)
:SetMargin(0, 0, 0, 10)
:AddChild(UIElements.New("Text", "desc")
:SetWidth("AUTO")
:SetFont("BODY_BODY2")
:SetText(isBuy and L["Purchasing Auction"]..":" or L["Bidding Auction"]..":")
)
:AddChild(UIElements.New("Text", "number")
:SetJustifyH("RIGHT")
:SetFont("TABLE_TABLE1")
:SetText(auctionNum.."/"..numFound)
)
)
:AddChild(UIElements.New("Spacer", "spacer"))
:AddChild(UIElements.New("Frame", "price")
:SetLayout("HORIZONTAL")
:SetHeight(20)
:SetMargin(0, 0, 0, 10)
:AddChild(UIElements.New("Text", "desc")
:SetWidth("AUTO")
:SetFont("BODY_BODY2")
:SetText(L["Unit Price"]..":")
)
:AddChild(UIElements.New("Text", "money")
:SetJustifyH("RIGHT")
:SetFont("TABLE_TABLE1")
:SetContext(displayItemBuyout)
:SetText(private.GetUnitPriceMoneyStr(displayItemBuyout))
)
)
:AddChild(UIElements.New("Frame", "total")
:SetLayout("HORIZONTAL")
:SetHeight(20)
:SetMargin(0, 0, 0, 10)
:AddChild(UIElements.New("Text", "desc")
:SetWidth("AUTO")
:SetFont("BODY_BODY2")
:SetText(L["Total Price"]..":")
)
:AddChild(UIElements.New("Text", "money")
:SetJustifyH("RIGHT")
:SetFont("TABLE_TABLE1")
:SetText(Money.ToString(displayTotalBuyout, nil, "OPT_83_NO_COPPER"))
)
)
:AddChild(UIElements.New("Text", "warning")
:SetHeight(20)
:SetMargin(0, 0, 0, 10)
:SetFont("BODY_BODY3")
:SetJustifyH("CENTER")
:SetTextColor(Theme.GetFeedbackColor("YELLOW"))
:SetText("")
)
:AddChild(UIElements.NewNamed("ActionButton", "confirmBtn", "TSMBidBuyConfirmBtn")
:SetHeight(24)
:SetText(isCommodity and L["Buy Commodity"] or (isBuy and L["Buy Auction"] or L["Bid Auction"]))
:SetContext(not isCommodity and quantity or nil)
:SetDisabled(private.future and true or false)
:SetScript("OnClick", private.ConfirmBtnOnClick)
)
)
:AddChildIf(isCommodity, UIElements.New("Frame", "item")
:SetLayout("HORIZONTAL")
:SetWidth(266)
:SetMargin(12, 0, 0, 0)
:SetPadding(4, 4, 8, 8)
:SetBackgroundColor("PRIMARY_BG_ALT", true)
:AddChild(UIElements.New("CommodityList", "items")
:SetBackgroundColor("PRIMARY_BG_ALT")
:SetData(subRow:GetResultRow())
:SetMarketValueFunction(marketValueFunc)
:SetAlertThreshold(TSM.db.global.shoppingOptions.buyoutConfirm and (CustomPrice.GetValue(TSM.db.global.shoppingOptions.buyoutAlertSource, itemString) or 0) or nil)
:SelectQuantity(defaultQuantity)
:SetScript("OnRowClick", private.CommodityOnRowClick)
)
)
)
:SetScript("OnHide", private.DialogOnHide)
baseFrame:ShowDialogFrame(private.dialogFrame)
private.prepareQuantity = nil
private.Prepare(defaultQuantity)
return true
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.DialogOnHide()
if private.future then
private.future:Cancel()
private.future = nil
end
private.baseFrame = nil
private.dialogFrame = nil
private.isBuy = nil
private.auctionScan = nil
private.subRow = nil
private.index = nil
private.noSeller = nil
end
function private.BuyoutConfirmCloseBtnOnClick(button)
button:GetBaseElement():HideDialog()
end
function private.EnableFutureOnDone()
local result = private.future:GetValue()
private.future = nil
if not result or result ~= (private.subRow:IsCommodity() and private.totalBuyout or select(2, private.subRow:GetBuyouts())) then
-- the unit price changed
Log.PrintUser(L["Failed to buy auction."])
private.baseFrame:HideDialog()
return
end
local input = private.dialogFrame:GetElement("content.left.quantity.input")
private.prepareSuccess = tonumber(input:GetValue()) == private.prepareQuantity
private.UpdateConfirmButton()
end
function private.GetUnitPriceMoneyStr(itemBuyout)
local priceStr = Money.ToString(itemBuyout, nil, "OPT_83_NO_COPPER")
local marketValueStr = nil
local marketValue = private.marketValueFunc(private.subRow)
local pct = marketValue and marketValue > 0 and itemBuyout > 0 and Math.Round(100 * itemBuyout / marketValue) or nil
if pct then
local pctColor = Theme.GetAuctionPercentColor(pct)
if pct > 999 then
marketValueStr = pctColor:ColorText(">999%")
else
marketValueStr = pctColor:ColorText(pct.."%")
end
else
marketValueStr = "---"
end
return format("%s (%s)", priceStr, marketValueStr)
end
function private.CommodityResultsByQuantity(itemString, quantity)
local remainingQuantity = quantity
local totalPrice, maxPrice = 0, 0
for _, query in private.auctionScan:QueryIterator() do
for _, subRow in query:ItemSubRowIterator(itemString) do
if remainingQuantity > 0 then
local _, itemBuyout = subRow:GetBuyouts()
local _, numOwnerItems = subRow:GetOwnerInfo()
local quantityAvailable = subRow:GetQuantities() - numOwnerItems
local quantityToBuy = min(quantityAvailable, remainingQuantity)
totalPrice = totalPrice + (itemBuyout * quantityToBuy)
remainingQuantity = remainingQuantity - quantityToBuy
maxPrice = max(maxPrice, itemBuyout)
end
end
end
return totalPrice, maxPrice
end
function private.InputQtyOnValueChanged(input, noListUpdate)
local quantity = tonumber(input:GetValue())
input:SetValue(quantity)
local totalBuyout = private.subRow:IsCommodity() and private.CommodityResultsByQuantity(private.subRow:GetItemString(), quantity) or input:GetElement("__parent.__parent.price.money"):GetContext() * quantity
local totalQuantity = quantity
local itemBuyout = totalQuantity > 0 and Math.Ceil(totalBuyout / totalQuantity, COPPER_PER_SILVER) or 0
input:GetElement("__parent.__parent.price.money")
:SetContext(itemBuyout)
:SetText(private.GetUnitPriceMoneyStr(itemBuyout))
:Draw()
input:GetElement("__parent.__parent.total.money")
:SetText(Money.ToString(totalBuyout, nil, "OPT_83_NO_COPPER"))
:Draw()
if not noListUpdate then
input:GetElement("__parent.__parent.__parent.item.items")
:SelectQuantity(quantity)
end
if quantity ~= private.prepareQuantity then
private.prepareSuccess = false
private.prepareQuantity = nil
end
private.UpdateConfirmButton()
end
function private.CommodityOnRowClick(list, index)
local input = list:GetElement("__parent.__parent.left.quantity.input")
input:SetValue(list:GetTotalQuantity(index))
:Draw()
private.Prepare(tonumber(input:GetValue()))
private.InputQtyOnValueChanged(input, true)
end
function private.ConfirmBtnOnClick(button)
local inputQuantity = nil
if not TSM.IsWowClassic() and private.subRow:IsCommodity() then
local input = private.dialogFrame:GetElement("content.left.quantity.input")
inputQuantity = tonumber(input:GetValue())
if not private.prepareSuccess and not TSM.IsWowClassic() then
-- this is a prepare click
private.Prepare(inputQuantity)
return
end
assert(private.prepareQuantity == inputQuantity)
end
local isBuy = private.isBuy
local callbackQuantity = button:GetContext()
if callbackQuantity == nil then
assert(inputQuantity)
callbackQuantity = inputQuantity
end
local callback = button:GetElement("__parent.__parent.__parent"):GetContext()
button:GetBaseElement():HideDialog()
callback(isBuy, callbackQuantity)
end
function private.UpdateConfirmButton()
local confirmBtn = private.dialogFrame:GetElement("content.left.confirmBtn")
local text, disabled, requireManualClick = nil, false, false
if not TSM.IsWowClassic() and private.subRow:IsCommodity() then
local input = confirmBtn:GetElement("__parent.quantity.input")
local inputQuantity = tonumber(input:GetValue())
local minQuantity = 1
local maxQuantity = confirmBtn:GetElement("__parent.quantity.input"):GetContext()
local itemString = private.subRow:GetItemString()
local totalCost, maxCost = private.CommodityResultsByQuantity(itemString, inputQuantity)
local alertThreshold = TSM.db.global.shoppingOptions.buyoutConfirm and (CustomPrice.GetValue(TSM.db.global.shoppingOptions.buyoutAlertSource, itemString) or 0) or math.huge
if maxCost >= alertThreshold then
requireManualClick = true
confirmBtn:GetElement("__parent.warning")
:SetText(L["Contains auctions above your alert threshold!"])
:Draw()
else
confirmBtn:GetElement("__parent.warning")
:SetText("")
:Draw()
end
if GetMoney() < totalCost then
text = L["Not Enough Money"]
disabled = true
elseif totalCost <= 0 or inputQuantity < minQuantity or inputQuantity > maxQuantity then
text = L["Invalid Quantity"]
disabled = true
elseif private.prepareSuccess or TSM.IsWowClassic() then
text = L["Buy Commodity"]
disabled = false
elseif private.prepareQuantity then
text = L["Preparing..."]
disabled = true
else
text = private.isBuy and L["Prepare Buy"] or L["Prepare Bid"]
disabled = false
end
else
if GetMoney() < confirmBtn:GetElement("__parent.price.money"):GetContext() then
text = L["Not Enough Money"]
disabled = true
else
text = private.isBuy and L["Buy Auction"] or L["Bid Auction"]
disabled = false
end
end
confirmBtn:SetText(text)
:SetDisabled(disabled)
:SetRequireManualClick(requireManualClick)
:Draw()
end
function private.Prepare(quantity)
if quantity == private.prepareQuantity then
return
end
if private.future then
private.future:Cancel()
private.future = nil
end
private.prepareQuantity = quantity
private.prepareSuccess = false
local totalBuyout = not TSM.IsWowClassic() and private.subRow:IsCommodity() and private.CommodityResultsByQuantity(private.subRow:GetItemString(), quantity) or (select(2, private.subRow:GetBuyouts()))
local totalQuantity = quantity
private.totalBuyout = totalBuyout
local itemBuyout = totalQuantity > 0 and Math.Ceil(totalBuyout / totalQuantity, COPPER_PER_SILVER)
local result, future = private.auctionScan:PrepareForBidOrBuyout(private.index, private.subRow, private.noSeller, quantity, itemBuyout)
if not result then
private.prepareQuantity = nil
return
elseif future then
private.future = future
future:SetScript("OnDone", private.EnableFutureOnDone)
end
private.UpdateConfirmButton()
end

271
Core/UI/AuctionUI/Core.lua Normal file
View File

@@ -0,0 +1,271 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
local AuctionUI = TSM.UI:NewPackage("AuctionUI")
local L = TSM.Include("Locale").GetTable()
local Delay = TSM.Include("Util.Delay")
local Event = TSM.Include("Util.Event")
local Log = TSM.Include("Util.Log")
local ScriptWrapper = TSM.Include("Util.ScriptWrapper")
local Settings = TSM.Include("Service.Settings")
local ItemLinked = TSM.Include("Service.ItemLinked")
local UIElements = TSM.Include("UI.UIElements")
local private = {
settings = nil,
topLevelPages = {},
frame = nil,
hasShown = false,
isSwitching = false,
scanningPage = nil,
updateCallbacks = {},
defaultFrame = nil,
}
local MIN_FRAME_SIZE = { width = 750, height = 450 }
-- ============================================================================
-- Module Functions
-- ============================================================================
function AuctionUI.OnInitialize()
private.settings = Settings.NewView()
:AddKey("global", "auctionUIContext", "showDefault")
:AddKey("global", "auctionUIContext", "frame")
UIParent:UnregisterEvent("AUCTION_HOUSE_SHOW")
Event.Register("AUCTION_HOUSE_SHOW", private.AuctionFrameInit)
Event.Register("AUCTION_HOUSE_CLOSED", private.HideAuctionFrame)
if TSM.IsWowClassic() then
Delay.AfterTime(1, function() LoadAddOn("Blizzard_AuctionUI") end)
else
Delay.AfterTime(1, function() LoadAddOn("Blizzard_AuctionHouseUI") end)
end
ItemLinked.RegisterCallback(private.ItemLinkedCallback)
end
function AuctionUI.OnDisable()
if private.frame then
-- hide the frame
private.frame:Hide()
assert(not private.frame)
end
end
function AuctionUI.RegisterTopLevelPage(name, callback, itemLinkedHandler)
tinsert(private.topLevelPages, { name = name, callback = callback, itemLinkedHandler = itemLinkedHandler })
end
function AuctionUI.StartingScan(pageName)
if private.scanningPage and private.scanningPage ~= pageName then
Log.PrintfUser(L["A scan is already in progress. Please stop that scan before starting another one."])
return false
end
private.scanningPage = pageName
Log.Info("Starting scan %s", pageName)
if private.frame then
private.frame:SetPulsingNavButton(private.scanningPage)
end
for _, callback in ipairs(private.updateCallbacks) do
callback()
end
return true
end
function AuctionUI.EndedScan(pageName)
if private.scanningPage == pageName then
Log.Info("Ended scan %s", pageName)
private.scanningPage = nil
if private.frame then
private.frame:SetPulsingNavButton()
end
for _, callback in ipairs(private.updateCallbacks) do
callback()
end
end
end
function AuctionUI.SetOpenPage(name)
private.frame:SetSelectedNavButton(name, true)
end
function AuctionUI.IsPageOpen(name)
if not private.frame then
return false
end
return private.frame:GetSelectedNavButton() == name
end
function AuctionUI.IsScanning()
return private.scanningPage and true or false
end
function AuctionUI.RegisterUpdateCallback(callback)
tinsert(private.updateCallbacks, callback)
end
function AuctionUI.IsVisible()
return private.frame and true or false
end
-- ============================================================================
-- Main Frame
-- ============================================================================
function private.AuctionFrameInit()
local tabTemplateName = nil
if TSM.IsWowClassic() then
private.defaultFrame = AuctionFrame
tabTemplateName = "AuctionTabTemplate"
else
private.defaultFrame = AuctionHouseFrame
tabTemplateName = "AuctionHouseFrameTabTemplate"
end
if not private.hasShown then
private.hasShown = true
local tabId = private.defaultFrame.numTabs + 1
local tab = CreateFrame("Button", "AuctionFrameTab"..tabId, private.defaultFrame, tabTemplateName)
tab:Hide()
tab:SetID(tabId)
tab:SetText(Log.ColorUserAccentText("TSM4"))
tab:SetNormalFontObject(GameFontHighlightSmall)
if TSM.IsWowClassic() then
tab:SetPoint("LEFT", _G["AuctionFrameTab"..tabId - 1], "RIGHT", -8, 0)
else
tab:SetPoint("LEFT", AuctionHouseFrame.Tabs[tabId - 1], "RIGHT", -15, 0)
tinsert(AuctionHouseFrame.Tabs, tab)
end
tab:Show()
PanelTemplates_SetNumTabs(private.defaultFrame, tabId)
PanelTemplates_EnableTab(private.defaultFrame, tabId)
ScriptWrapper.Set(tab, "OnClick", private.TSMTabOnClick)
end
if private.settings.showDefault then
UIParent_OnEvent(UIParent, "AUCTION_HOUSE_SHOW")
else
PlaySound(SOUNDKIT.AUCTION_WINDOW_OPEN)
private.ShowAuctionFrame()
end
end
function private.ShowAuctionFrame()
if private.frame then
return
end
private.frame = private.CreateMainFrame()
private.frame:Show()
private.frame:Draw()
for _, callback in ipairs(private.updateCallbacks) do
callback()
end
end
function private.HideAuctionFrame()
if not private.frame then
return
end
private.frame:Hide()
assert(not private.frame)
for _, callback in ipairs(private.updateCallbacks) do
callback()
end
end
function private.CreateMainFrame()
TSM.UI.AnalyticsRecordPathChange("auction")
local frame = UIElements.New("LargeApplicationFrame", "base")
:SetParent(UIParent)
:SetSettingsContext(private.settings, "frame")
:SetMinResize(MIN_FRAME_SIZE.width, MIN_FRAME_SIZE.height)
:SetStrata("HIGH")
:SetProtected(TSM.db.global.coreOptions.protectAuctionHouse)
:AddPlayerGold()
:AddAppStatusIcon()
:AddSwitchButton(private.SwitchBtnOnClick)
:SetScript("OnHide", private.BaseFrameOnHide)
for _, info in ipairs(private.topLevelPages) do
frame:AddNavButton(info.name, info.callback)
end
local whatsNewDialog = TSM.UI.WhatsNew.GetDialog()
if whatsNewDialog then
frame:ShowDialogFrame(whatsNewDialog)
end
return frame
end
-- ============================================================================
-- Local Script Handlers
-- ============================================================================
function private.BaseFrameOnHide(frame)
assert(frame == private.frame)
frame:Release()
private.frame = nil
if not private.isSwitching then
PlaySound(SOUNDKIT.AUCTION_WINDOW_CLOSE)
if TSM.IsWowClassic() then
CloseAuctionHouse()
else
C_AuctionHouse.CloseAuctionHouse()
end
end
TSM.UI.AnalyticsRecordClose("auction")
end
function private.SwitchBtnOnClick(button)
private.isSwitching = true
private.settings.showDefault = true
private.HideAuctionFrame()
UIParent_OnEvent(UIParent, "AUCTION_HOUSE_SHOW")
private.isSwitching = false
end
local function NoOp()
-- do nothing - what did you expect?
end
function private.TSMTabOnClick()
private.settings.showDefault = false
if TSM.IsWowClassic() then
ClearCursor()
ClickAuctionSellItemButton(AuctionsItemButton, "LeftButton")
end
ClearCursor()
-- Replace CloseAuctionHouse() with a no-op while hiding the AH frame so we don't stop interacting with the AH NPC
if TSM.IsWowClassic() then
local origCloseAuctionHouse = CloseAuctionHouse
CloseAuctionHouse = NoOp
AuctionFrame_Hide()
CloseAuctionHouse = origCloseAuctionHouse
else
local origCloseAuctionHouse = C_AuctionHouse.CloseAuctionHouse
C_AuctionHouse.CloseAuctionHouse = NoOp
HideUIPanel(private.defaultFrame)
C_AuctionHouse.CloseAuctionHouse = origCloseAuctionHouse
end
private.ShowAuctionFrame()
end
function private.ItemLinkedCallback(name, itemLink)
if not private.frame then
return
end
local path = private.frame:GetSelectedNavButton()
for _, info in ipairs(private.topLevelPages) do
if info.name == path then
if info.itemLinkedHandler(name, itemLink) then
return true
else
return
end
end
end
error("Invalid frame path")
end

View File

@@ -0,0 +1,715 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
local MyAuctions = TSM.UI.AuctionUI:NewPackage("MyAuctions")
local L = TSM.Include("Locale").GetTable()
local FSM = TSM.Include("Util.FSM")
local Money = TSM.Include("Util.Money")
local String = TSM.Include("Util.String")
local Math = TSM.Include("Util.Math")
local TempTable = TSM.Include("Util.TempTable")
local ItemString = TSM.Include("Util.ItemString")
local Theme = TSM.Include("Util.Theme")
local ItemInfo = TSM.Include("Service.ItemInfo")
local Settings = TSM.Include("Service.Settings")
local UIElements = TSM.Include("UI.UIElements")
local private = {
settings = nil,
fsm = nil,
frame = nil,
query = nil,
}
local DURATION_LIST = {
L["None"],
AUCTION_TIME_LEFT1_DETAIL,
AUCTION_TIME_LEFT2_DETAIL,
AUCTION_TIME_LEFT3_DETAIL,
AUCTION_TIME_LEFT4_DETAIL,
}
local SECONDS_PER_MIN = 60
local SECONDS_PER_HOUR = 60 * SECONDS_PER_MIN
-- ============================================================================
-- Module Functions
-- ============================================================================
function MyAuctions.OnInitialize()
private.settings = Settings.NewView()
:AddKey("global", "auctionUIContext", "myAuctionsScrollingTable")
private.FSMCreate()
TSM.UI.AuctionUI.RegisterTopLevelPage(L["My Auctions"], private.GetMyAuctionsFrame, private.OnItemLinked)
end
-- ============================================================================
-- MyAuctions UI
-- ============================================================================
function private.GetMyAuctionsFrame()
TSM.UI.AnalyticsRecordPathChange("auction", "my_auctions")
private.query = private.query or TSM.MyAuctions.CreateQuery()
local frame = UIElements.New("Frame", "myAuctions")
:SetLayout("VERTICAL")
:SetBackgroundColor("PRIMARY_BG_ALT")
:AddChild(UIElements.New("Frame", "header")
:SetLayout("VERTICAL")
:SetHeight(72)
:SetPadding(8)
:AddChild(UIElements.New("Frame", "filters")
:SetLayout("HORIZONTAL")
:SetHeight(24)
:SetMargin(0, 0, 0, 8)
:AddChild(UIElements.New("SelectionDropdown", "duration")
:SetWidth(232)
:SetItems(DURATION_LIST)
:SetHintText(L["Filter by duration"])
:SetScript("OnSelectionChanged", private.DurationFilterChanged)
)
:AddChild(UIElements.New("GroupSelector", "group")
:SetWidth(232)
:SetMargin(8, 8, 0, 0)
:SetHintText(L["Filter by groups"])
:SetScript("OnSelectionChanged", private.FilterChanged)
)
:AddChild(UIElements.New("Input", "keyword")
:SetIconTexture("iconPack.18x18/Search")
:AllowItemInsert(false)
:SetClearButtonEnabled(true)
:SetHintText(L["Filter by keyword"])
:SetScript("OnValueChanged", private.FilterChanged)
)
)
:AddChild(UIElements.New("Frame", "buttons")
:SetLayout("HORIZONTAL")
:SetHeight(24)
:AddChild(UIElements.New("Checkbox", "ignoreBid")
:SetWidth("AUTO")
:SetCheckboxPosition("LEFT")
:SetText(L["Hide auctions with bids"])
:SetScript("OnValueChanged", private.ToggleFilterChanged)
)
:AddChild(UIElements.New("Checkbox", "onlyBid")
:SetMargin(16, 0, 0, 0)
:SetWidth("AUTO")
:SetCheckboxPosition("LEFT")
:SetText(L["Show only auctions with bids"])
:SetScript("OnValueChanged", private.ToggleFilterChanged)
)
:AddChild(UIElements.New("Checkbox", "onlySold")
:SetMargin(16, 0, 0, 0)
:SetWidth("AUTO")
:SetCheckboxPosition("LEFT")
:SetText(L["Only show sold auctions"])
:SetScript("OnValueChanged", private.ToggleFilterChanged)
)
:AddChild(UIElements.New("Spacer"))
:AddChild(UIElements.New("ActionButton", "clearfilter")
:SetSize(142, 24)
:SetText(L["Clear Filters"])
:SetScript("OnClick", private.ClearFilterOnClick)
)
)
)
if TSM.IsWowClassic() then
frame:AddChild(UIElements.New("MyAuctionsScrollingTable", "auctions")
:SetSettingsContext(private.settings, "myAuctionsScrollingTable")
:GetScrollingTableInfo()
:NewColumn("item")
:SetTitle(L["Item Name"])
:SetIconSize(12)
:SetFont("ITEM_BODY3")
:SetJustifyH("LEFT")
:SetTextInfo(nil, private.AuctionsGetItemText)
:SetIconInfo("itemString", ItemInfo.GetTexture)
:SetTooltipInfo("itemString", private.AuctionsGetItemTooltip)
:SetActionIconInfo(1, 12, private.AuctionsGetSoldIcon)
:DisableHiding()
:Commit()
:NewColumn("stackSize")
:SetTitle(L["Qty"])
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetTextInfo(nil, private.AuctionsGetStackSizeText)
:Commit()
:NewColumn("timeLeft")
:SetTitleIcon("iconPack.14x14/Clock")
:SetFont("BODY_BODY3")
:SetJustifyH("CENTER")
:SetTextInfo(nil, private.AuctionsGetTimeLeftText)
:Commit()
:NewColumn("highbidder")
:SetTitle(L["High Bidder"])
:SetFont("BODY_BODY3")
:SetJustifyH("LEFT")
:SetTextInfo(nil, private.AuctionsGetHighBidderText)
:Commit()
:NewColumn("group")
:SetTitle(GROUP)
:SetFont("BODY_BODY3")
:SetJustifyH("LEFT")
:SetTextInfo(nil, private.AuctionsGetGroupNameText)
:Commit()
:NewColumn("currentBid")
:SetTitle(BID)
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetTextInfo(nil, private.AuctionsGetCurrentBidText)
:Commit()
:NewColumn("buyout")
:SetTitle(BUYOUT)
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetTextInfo(nil, private.AuctionsGetCurrentBuyoutText)
:Commit()
:Commit()
:SetQuery(private.query)
:SetSelectionValidator(private.AuctionsValidateSelection)
:SetScript("OnSelectionChanged", private.AuctionsOnSelectionChanged)
:SetScript("OnDataUpdated", private.AuctionsOnDataUpdated)
)
else
frame:AddChild(UIElements.New("MyAuctionsScrollingTable", "auctions")
:SetSettingsContext(private.settings, "myAuctionsScrollingTable")
:GetScrollingTableInfo()
:NewColumn("item")
:SetTitle(L["Item Name"])
:SetIconSize(12)
:SetFont("ITEM_BODY3")
:SetJustifyH("LEFT")
:SetTextInfo(nil, private.AuctionsGetItemText)
:SetIconInfo("itemString", ItemInfo.GetTexture)
:SetTooltipInfo("itemString", private.AuctionsGetItemTooltip)
:SetActionIconInfo(1, 12, private.AuctionsGetSoldIcon)
:SetSortInfo("name")
:DisableHiding()
:Commit()
:NewColumn("stackSize")
:SetTitle(L["Qty"])
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetTextInfo(nil, private.AuctionsGetStackSizeText)
:SetSortInfo("stackSize")
:Commit()
:NewColumn("timeLeft")
:SetTitleIcon("iconPack.14x14/Clock")
:SetFont("BODY_BODY3")
:SetJustifyH("CENTER")
:SetTextInfo(nil, private.AuctionsGetTimeLeftText)
:SetSortInfo("duration")
:Commit()
:NewColumn("group")
:SetTitle(GROUP)
:SetFont("BODY_BODY3")
:SetJustifyH("LEFT")
:SetTextInfo(nil, private.AuctionsGetGroupNameText)
:SetSortInfo("group")
:Commit()
:NewColumn("currentBid")
:SetTitle(BID)
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetTextInfo(nil, private.AuctionsGetCurrentBidText)
:SetSortInfo("currentBid")
:Commit()
:NewColumn("buyout")
:SetTitle(BUYOUT)
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetTextInfo(nil, private.AuctionsGetCurrentBuyoutText)
:SetSortInfo("buyout")
:Commit()
:Commit()
:SetQuery(private.query)
:SetSelectionValidator(private.AuctionsValidateSelection)
:SetScript("OnSelectionChanged", private.AuctionsOnSelectionChanged)
:SetScript("OnDataUpdated", private.AuctionsOnDataUpdated)
)
end
frame:AddChild(UIElements.New("Texture", "line")
:SetHeight(2)
:SetTexture("ACTIVE_BG")
)
:AddChild(UIElements.New("Frame", "bottom")
:SetLayout("VERTICAL")
:SetHeight(68)
:SetPadding(8)
:SetBackgroundColor("PRIMARY_BG_ALT")
:AddChild(UIElements.New("Frame", "row1")
:SetLayout("HORIZONTAL")
:SetHeight(20)
:SetMargin(0, 0, 0, 8)
:AddChild(UIElements.New("Text", "sold")
:SetWidth("AUTO")
:SetFont("BODY_BODY3_MEDIUM")
)
:AddChild(UIElements.New("Text", "soldValue")
:SetWidth("AUTO")
:SetFont("TABLE_TABLE1")
)
:AddChild(UIElements.New("Texture", "vline")
:SetWidth(1)
:SetMargin(8, 8, 0, 0)
:SetTexture("ACTIVE_BG_ALT")
)
:AddChild(UIElements.New("Text", "posted")
:SetWidth("AUTO")
:SetFont("BODY_BODY3_MEDIUM")
)
:AddChild(UIElements.New("Text", "postedValue")
:SetWidth("AUTO")
:SetFont("TABLE_TABLE1")
)
:AddChild(UIElements.New("Spacer", "spacer"))
)
:AddChild(UIElements.New("Frame", "row2")
:SetLayout("HORIZONTAL")
:SetHeight(24)
:AddChild(UIElements.New("ProgressBar", "progressBar")
:SetMargin(0, 8, 0, 0)
:SetProgress(0)
)
:AddChild(UIElements.NewNamed("ActionButton", "cancelBtn", "TSMCancelAuctionBtn")
:SetSize(110, 24)
:SetMargin(0, 8, 0, 0)
:SetText(CANCEL)
:SetDisabled(true)
:DisableClickCooldown(true)
:SetScript("OnClick", private.CancelButtonOnClick)
)
:AddChild(UIElements.New("ActionButton", "skipBtn")
:SetSize(110, 24)
:SetText(L["Skip"])
:SetDisabled(true)
:DisableClickCooldown(true)
:SetScript("OnClick", private.SkipButtonOnClick)
)
)
)
:SetScript("OnUpdate", private.FrameOnUpdate)
:SetScript("OnHide", private.FrameOnHide)
private.frame = frame
return frame
end
-- ============================================================================
-- Local Script Handlers
-- ============================================================================
function private.OnItemLinked(name)
private.frame:GetElement("header.filters.keyword")
:SetValue(name)
:Draw()
private.FilterChanged()
return true
end
function private.FrameOnUpdate(frame)
frame:SetScript("OnUpdate", nil)
private.fsm:ProcessEvent("EV_FRAME_SHOWN", frame)
end
function private.FrameOnHide(frame)
assert(frame == private.frame)
private.frame = nil
private.fsm:ProcessEvent("EV_FRAME_HIDDEN")
end
function private.ToggleFilterChanged(toggle)
local ignoreBidToggle = toggle:GetElement("__parent.ignoreBid")
local onlyBidToggle = toggle:GetElement("__parent.onlyBid")
local onlySoldToggle = toggle:GetElement("__parent.onlySold")
if ignoreBidToggle:IsChecked() and onlyBidToggle:IsChecked() then
-- uncheck the other toggle in the pair of "bid" toggles
if toggle == ignoreBidToggle then
onlyBidToggle:SetChecked(false, true)
:Draw()
else
ignoreBidToggle:SetChecked(false, true)
:Draw()
end
end
if onlyBidToggle:IsChecked() and onlySoldToggle:IsChecked() then
-- uncheck the other toggle in the pair of "only" toggles
if toggle == onlyBidToggle then
onlySoldToggle:SetChecked(false, true)
:Draw()
else
onlyBidToggle:SetChecked(false, true)
:Draw()
end
end
private.FilterChanged()
end
function private.DurationFilterChanged(dropdown)
if dropdown:GetSelectedItemKey() == 1 then
-- none
dropdown:SetSelectedItem(nil, true)
end
private.FilterChanged()
end
function private.FilterChanged()
private.fsm:ProcessEvent("EV_FILTER_UPDATED")
end
function private.ClearFilterOnClick(button)
button:GetElement("__parent.__parent.filters.duration")
:SetOpen(false)
:SetSelectedItem()
button:GetElement("__parent.__parent.filters.group")
:ClearSelectedGroups()
button:GetElement("__parent.__parent.filters.keyword")
:SetValue("")
:SetFocused(false)
button:GetElement("__parent.ignoreBid")
:SetChecked(false, true)
button:GetElement("__parent.onlyBid")
:SetChecked(false, true)
button:GetElement("__parent.onlySold")
:SetChecked(false, true)
button:GetParentElement():GetParentElement():Draw()
private.fsm:ProcessEvent("EV_FILTER_UPDATED")
end
function private.AuctionsValidateSelection(_, row)
return row:GetField("saleStatus") == 0
end
function private.AuctionsOnSelectionChanged()
private.fsm:ProcessEvent("EV_SELECTION_CHANGED")
end
function private.AuctionsOnDataUpdated()
if not private.frame then
return
end
private.fsm:SetLoggingEnabled(false)
private.fsm:ProcessEvent("EV_DATA_UPDATED")
private.fsm:SetLoggingEnabled(true)
end
function private.CancelButtonOnClick(button)
private.fsm:ProcessEvent("EV_CANCEL_CLICKED")
end
function private.SkipButtonOnClick(button)
private.fsm:ProcessEvent("EV_SKIP_CLICKED")
end
-- ============================================================================
-- FSM
-- ============================================================================
function private.FSMCreate()
local fsmContext = {
frame = nil,
durationFilter = nil,
keywordFilter = nil,
groupFilter = {},
bidFilter = nil,
soldFilter = false,
filterChanged = false,
}
local function UpdateFrame(context)
if not context.frame then
return
end
local auctions = context.frame:GetElement("auctions")
if context.filterChanged then
context.filterChanged = false
private.query:ResetFilters()
if context.durationFilter then
if TSM.IsWowClassic() then
private.query:Equal("duration", context.durationFilter)
else
if context.durationFilter == 1 then
private.query:LessThan("duration", time() + (30 * SECONDS_PER_MIN))
elseif context.durationFilter == 2 then
private.query:LessThan("duration", time() + (2 * SECONDS_PER_HOUR))
elseif context.durationFilter == 3 then
private.query:LessThanOrEqual("duration", time() + (12 * SECONDS_PER_HOUR))
else
private.query:GreaterThan("duration", time() + (12 * SECONDS_PER_HOUR))
end
end
end
if context.keywordFilter then
private.query:Matches("itemName", context.keywordFilter)
end
if next(context.groupFilter) then
private.query:InTable("group", context.groupFilter)
end
if context.bidFilter ~= nil then
if context.bidFilter then
private.query:NotEqual("highBidder", "")
else
private.query:Equal("highBidder", "")
end
end
if context.soldFilter then
private.query:Equal("saleStatus", 1)
end
auctions:SetQuery(private.query, true)
end
local selectedRow = auctions:GetSelection()
local hasFilter = context.durationFilter or context.keywordFilter or next(context.groupFilter) or context.bidFilter ~= nil or context.soldFilter
context.frame:GetElement("header.buttons.clearfilter")
:SetDisabled(not hasFilter)
:Draw()
local numPending = TSM.MyAuctions.GetNumPending()
local progressText = nil
if numPending > 0 then
progressText = format(L["Canceling %d Auctions..."], numPending)
elseif selectedRow then
progressText = L["Ready to Cancel"]
else
progressText = L["Select Auction to Cancel"]
end
local row2 = context.frame:GetElement("bottom.row2")
row2:GetElement("cancelBtn")
:SetDisabled(not selectedRow or (not TSM.IsWowClassic() and numPending > 0) or (not TSM.IsWowClassic() and C_AuctionHouse.GetCancelCost(selectedRow:GetField("auctionId")) > GetMoney()))
row2:GetElement("skipBtn")
:SetDisabled(not selectedRow)
row2:GetElement("progressBar")
:SetProgressIconHidden(numPending == 0)
:SetText(progressText)
row2:Draw()
local numPosted, numSold, postedGold, soldGold = 0, 0, 0, 0
for _, row in private.query:Iterator() do
local itemString, saleStatus, buyout, currentBid, stackSize = row:GetFields("itemString", "saleStatus", "buyout", "currentBid", "stackSize")
if saleStatus == 1 then
numSold = numSold + 1
-- if somebody did a buyout, then bid will be equal to buyout, otherwise it'll be the winning bid
soldGold = soldGold + currentBid
else
numPosted = numPosted + 1
if ItemInfo.IsCommodity(itemString) then
postedGold = postedGold + (buyout * stackSize)
else
postedGold = postedGold + buyout
end
end
end
local row1 = context.frame:GetElement("bottom.row1")
row1:GetElement("sold")
:SetFormattedText((hasFilter and L["%s Sold Auctions (Filtered)"] or L["%s Sold Auctions"])..":", Theme.GetColor("INDICATOR"):ColorText(numSold))
row1:GetElement("soldValue")
:SetText(Money.ToString(soldGold))
row1:GetElement("posted")
:SetFormattedText((hasFilter and L["%s Posted Auctions (Filtered)"] or L["%s Posted Auctions"])..":", Theme.GetColor("INDICATOR_ALT"):ColorText(numPosted))
row1:GetElement("postedValue")
:SetText(Money.ToString(postedGold))
row1:Draw()
end
private.fsm = FSM.New("MY_AUCTIONS")
:AddState(FSM.NewState("ST_HIDDEN")
:SetOnEnter(function(context)
context.frame = nil
context.durationFilter = nil
context.keywordFilter = nil
wipe(context.groupFilter)
context.bidFilter = nil
context.soldFilter = false
end)
:AddTransition("ST_HIDDEN")
:AddTransition("ST_SHOWNING")
:AddEventTransition("EV_FRAME_SHOWN", "ST_SHOWNING")
)
:AddState(FSM.NewState("ST_SHOWNING")
:SetOnEnter(function(context, frame)
context.frame = frame
context.filterChanged = true
return "ST_SHOWN"
end)
:AddTransition("ST_SHOWN")
)
:AddState(FSM.NewState("ST_SHOWN")
:SetOnEnter(function(context)
UpdateFrame(context)
end)
:AddTransition("ST_HIDDEN")
:AddTransition("ST_SHOWN")
:AddTransition("ST_CANCELING")
:AddEventTransition("EV_CANCEL_CLICKED", "ST_CANCELING")
:AddEventTransition("EV_SELECTION_CHANGED", "ST_SHOWN")
:AddEventTransition("EV_DATA_UPDATED", "ST_SHOWN")
:AddEvent("EV_SKIP_CLICKED", function(context)
context.frame:GetElement("auctions"):SelectNextRow()
return "ST_SHOWN"
end)
:AddEvent("EV_FILTER_UPDATED", function(context)
local didChange = false
local durationFilter = context.frame:GetElement("header.filters.duration"):GetSelectedItemKey()
durationFilter = durationFilter and (durationFilter - 1) or nil
if durationFilter ~= context.durationFilter then
context.durationFilter = durationFilter
didChange = true
end
local keywordFilter = context.frame:GetElement("header.filters.keyword"):GetValue()
keywordFilter = keywordFilter ~= "" and String.Escape(keywordFilter) or nil
if keywordFilter ~= context.keywordFilter then
context.keywordFilter = keywordFilter
didChange = true
end
local newGroupFilter = TempTable.Acquire()
for groupPath in context.frame:GetElement("header.filters.group"):SelectedGroupIterator() do
newGroupFilter[groupPath] = true
end
if Math.CalculateHash(newGroupFilter) ~= Math.CalculateHash(context.groupFilter) then
didChange = true
wipe(context.groupFilter)
for groupPath in pairs(newGroupFilter) do
context.groupFilter[groupPath] = true
end
end
TempTable.Release(newGroupFilter)
local bidFilter = nil
if context.frame:GetElement("header.buttons.ignoreBid"):IsChecked() then
bidFilter = false
elseif context.frame:GetElement("header.buttons.onlyBid"):IsChecked() then
bidFilter = true
end
if bidFilter ~= context.bidFilter then
context.bidFilter = bidFilter
didChange = true
end
local soldFilter = context.frame:GetElement("header.buttons.onlySold"):IsChecked()
if soldFilter ~= context.soldFilter then
context.soldFilter = soldFilter
didChange = true
end
if didChange then
context.filterChanged = true
return "ST_SHOWN"
end
end)
)
:AddState(FSM.NewState("ST_CANCELING")
:SetOnEnter(function(context)
local buttonsFrame = context.frame:GetElement("bottom")
buttonsFrame:GetElement("row2.cancelBtn"):SetDisabled(true)
buttonsFrame:GetElement("row2.skipBtn"):SetDisabled(true)
buttonsFrame:Draw()
local auctionId = context.frame:GetElement("auctions"):GetSelection():GetField("auctionId")
if TSM.MyAuctions.CanCancel(auctionId) then
TSM.MyAuctions.CancelAuction(auctionId)
end
return "ST_SHOWN"
end)
:AddTransition("ST_HIDDEN")
:AddTransition("ST_SHOWN")
)
:AddDefaultEventTransition("EV_FRAME_HIDDEN", "ST_HIDDEN")
:Init("ST_HIDDEN", fsmContext)
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.AuctionsGetItemText(row)
if row:GetField("saleStatus") == 1 then
return Theme.GetColor("INDICATOR"):ColorText(row:GetField("itemName"))
else
return TSM.UI.GetQualityColoredText(row:GetField("itemName"), row:GetField("itemQuality"))
end
end
function private.AuctionsGetItemTooltip(itemString)
return itemString ~= ItemString.GetPetCage() and itemString or nil
end
function private.AuctionsGetSoldIcon(self, data, iconIndex)
assert(iconIndex == 1)
local row = private.query:GetResultRowByUUID(data)
if row:GetField("saleStatus") ~= 1 then
return
end
return true, "iconPack.12x12/Bid", true
end
function private.AuctionsGetStackSizeText(row)
if row:GetField("saleStatus") == 1 then
return Theme.GetColor("INDICATOR"):ColorText(row:GetField("stackSize"))
else
return row:GetField("stackSize")
end
end
function private.AuctionsGetTimeLeftText(row)
local saleStatus, duration, isPending = row:GetFields("saleStatus", "duration", "isPending")
if saleStatus == 0 and isPending then
return "..."
elseif saleStatus == 1 or not TSM.IsWowClassic() then
local timeLeft = duration - time()
local color = nil
if saleStatus == 0 then
if timeLeft <= 2 * SECONDS_PER_HOUR then
color = Theme.GetFeedbackColor("RED")
elseif timeLeft <= (TSM.IsWowClassic() and 8 or 24) * SECONDS_PER_HOUR then
color = Theme.GetFeedbackColor("YELLOW")
else
color = Theme.GetFeedbackColor("GREEN")
end
else
color = Theme.GetColor("INDICATOR")
end
local str = nil
if timeLeft < SECONDS_PER_MIN then
str = timeLeft.."s"
elseif timeLeft < SECONDS_PER_HOUR then
str = floor(timeLeft / SECONDS_PER_MIN).."m"
else
str = floor(timeLeft / SECONDS_PER_HOUR).."h"
end
return color and color:ColorText(str) or str
else
return TSM.UI.GetTimeLeftString(duration)
end
end
function private.AuctionsGetHighBidderText(row)
if row:GetField("saleStatus") == 1 then
return Theme.GetColor("INDICATOR"):ColorText(row:GetField("highBidder"))
else
return row:GetField("highBidder")
end
end
function private.AuctionsGetGroupNameText(row)
local groupPath = row:GetField("group")
local groupName = TSM.Groups.Path.GetName(groupPath)
local level = select('#', strsplit(TSM.CONST.GROUP_SEP, groupPath))
return Theme.GetGroupColor(level):ColorText(groupName)
end
function private.AuctionsGetCurrentBidText(row)
if row:GetField("saleStatus") == 1 then
return Theme.GetColor("INDICATOR"):ColorText(L["Sold for:"])
elseif not TSM.IsWowClassic() and row:GetField("highBidder") ~= "" then
return Money.ToString(row:GetField("currentBid"), Theme.GetColor("INDICATOR"):GetTextColorPrefix())
else
return Money.ToString(row:GetField("currentBid"))
end
end
function private.AuctionsGetCurrentBuyoutText(row)
return Money.ToString(row:GetField(row:GetField("saleStatus") == 1 and "currentBid" or "buyout"))
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,818 @@
-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
local Sniper = TSM.UI.AuctionUI:NewPackage("Sniper")
local L = TSM.Include("Locale").GetTable()
local Delay = TSM.Include("Util.Delay")
local Event = TSM.Include("Util.Event")
local FSM = TSM.Include("Util.FSM")
local Sound = TSM.Include("Util.Sound")
local Money = TSM.Include("Util.Money")
local Log = TSM.Include("Util.Log")
local ItemString = TSM.Include("Util.ItemString")
local ItemInfo = TSM.Include("Service.ItemInfo")
local AuctionScan = TSM.Include("Service.AuctionScan")
local MailTracking = TSM.Include("Service.MailTracking")
local Settings = TSM.Include("Service.Settings")
local AuctionHouseWrapper = TSM.Include("Service.AuctionHouseWrapper")
local PlayerInfo = TSM.Include("Service.PlayerInfo")
local UIElements = TSM.Include("UI.UIElements")
local private = {
settings = nil,
fsm = nil,
selectionFrame = nil,
hasLastScan = nil,
contentPath = "selection",
}
local PHASED_TIME = 60
local RETAIL_RESCAN_DELAY = 30
-- ============================================================================
-- Module Functions
-- ============================================================================
function Sniper.OnInitialize()
private.settings = Settings.NewView()
:AddKey("global", "auctionUIContext", "sniperScrollingTable")
:AddKey("global", "sniperOptions", "sniperSound")
TSM.UI.AuctionUI.RegisterTopLevelPage(L["Sniper"], private.GetSniperFrame, private.OnItemLinked)
private.FSMCreate()
end
-- ============================================================================
-- Sniper UI
-- ============================================================================
function private.GetSniperFrame()
TSM.UI.AnalyticsRecordPathChange("auction", "sniper")
if not private.hasLastScan then
private.contentPath = "selection"
end
return UIElements.New("ViewContainer", "sniper")
:SetNavCallback(private.GetSniperContentFrame)
:AddPath("selection")
:AddPath("scan")
:SetPath(private.contentPath)
end
function private.GetSniperContentFrame(viewContainer, path)
private.contentPath = path
if path == "selection" then
return private.GetSelectionFrame()
elseif path == "scan" then
return private.GetScanFrame()
else
error("Unexpected path: "..tostring(path))
end
end
function private.GetSelectionFrame()
TSM.UI.AnalyticsRecordPathChange("auction", "sniper", "selection")
local frame = UIElements.New("Frame", "selection")
:SetLayout("VERTICAL")
:SetBackgroundColor("PRIMARY_BG_ALT")
:AddChildIf(TSM.IsWowClassic(), UIElements.New("Text", "text")
:SetHeight(20)
:SetMargin(8, 8, 12, 0)
:SetFont("BODY_BODY2_MEDIUM")
:SetJustifyH("CENTER")
:SetText(L["Start either a 'Buyout' or 'Bid' sniper using the buttons above."])
)
:AddChild(UIElements.New("Frame", "buttons")
:SetLayout("HORIZONTAL")
:SetHeight(24)
:SetMargin(8, 8, 12, 12)
:AddChild(UIElements.New("ActionButton", "buyoutScanBtn")
:SetMargin(0, TSM.IsWowClassic() and 8 or 0, 0, 0)
:SetText(L["Run Buyout Sniper"])
:SetScript("OnClick", private.BuyoutScanButtonOnClick)
)
:AddChildIf(TSM.IsWowClassic(), UIElements.New("ActionButton", "bidScanBtn")
:SetText(L["Run Bid Sniper"])
:SetScript("OnClick", private.BidScanButtonOnClick)
)
)
:AddChild(UIElements.New("SniperScrollingTable", "auctions")
:SetSettingsContext(private.settings, "sniperScrollingTable")
)
:SetScript("OnHide", private.SelectionFrameOnHide)
private.selectionFrame = frame
return frame
end
function private.GetScanFrame()
TSM.UI.AnalyticsRecordPathChange("auction", "sniper", "scan")
return UIElements.New("Frame", "scan")
:SetLayout("VERTICAL")
:SetBackgroundColor("PRIMARY_BG_ALT")
:AddChild(UIElements.New("Frame", "header")
:SetLayout("HORIZONTAL")
:SetHeight(48)
:SetPadding(8, 8, 14, 14)
:AddChild(UIElements.New("ActionButton", "backBtn")
:SetSize(64, 24)
:SetMargin(0, 16, 0, 0)
:SetIcon("iconPack.14x14/Chevron/Right@180")
:SetText(BACK)
:SetScript("OnClick", private.BackButtonOnClick)
)
:AddChild(UIElements.New("Text", "title")
:SetFont("BODY_BODY2_MEDIUM")
:SetJustifyH("CENTER")
)
:AddChild(UIElements.New("ActionButton", "restartBtn")
:SetSize(80, 24)
:SetText(L["Restart"])
:SetScript("OnClick", private.RestartButtonOnClick)
)
)
:AddChild(UIElements.New("SniperScrollingTable", "auctions")
:SetSettingsContext(private.settings, "sniperScrollingTable")
:SetScript("OnSelectionChanged", private.AuctionsOnSelectionChanged)
:SetScript("OnRowRemoved", private.AuctionsOnRowRemoved)
)
:AddChild(UIElements.New("Texture", "line")
:SetHeight(2)
:SetTexture("ACTIVE_BG")
)
:AddChild(UIElements.New("Frame", "bottom")
:SetLayout("HORIZONTAL")
:SetHeight(40)
:SetPadding(8)
:SetBackgroundColor("PRIMARY_BG_ALT")
:AddChild(UIElements.New("ActionButton", "pauseResumeBtn")
:SetSize(24, 24)
:SetMargin(0, 8, 0, 0)
:SetIcon("iconPack.18x18/PlayPause")
:SetScript("OnClick", private.PauseResumeBtnOnClick)
)
:AddChild(UIElements.New("ProgressBar", "progressBar")
:SetHeight(24)
:SetMargin(0, 8, 0, 0)
:SetProgress(0)
:SetText(L["Starting Scan..."])
)
:AddChild(UIElements.NewNamed("ActionButton", "actionBtn", "TSMSniperBtn")
:SetSize(165, 24)
:SetText(BID)
:SetDisabled(true)
:DisableClickCooldown(true)
:SetScript("OnClick", private.ActionButtonOnClick)
)
)
:SetScript("OnUpdate", private.ScanFrameOnUpdate)
:SetScript("OnHide", private.ScanFrameOnHide)
end
-- ============================================================================
-- Local Script Handlers
-- ============================================================================
function private.OnItemLinked(name, itemLink)
if private.selectionFrame then
return false
end
private.fsm:ProcessEvent("EV_STOP_CLICKED")
TSM.UI.AuctionUI.SetOpenPage(L["Browse"])
TSM.UI.AuctionUI.Shopping.StartItemSearch(itemLink)
return true
end
function private.SelectionFrameOnHide(frame)
assert(frame == private.selectionFrame)
private.selectionFrame = nil
end
function private.StartScanHelper(viewContainer, searchContext)
if not TSM.UI.AuctionUI.StartingScan(L["Sniper"]) then
return
end
viewContainer:SetPath("scan", true)
private.fsm:ProcessEvent("EV_START_SCAN", searchContext)
end
function private.BuyoutScanButtonOnClick(button)
local viewContainer = button:GetParentElement():GetParentElement():GetParentElement()
local searchContext = TSM.Sniper.BuyoutSearch.GetSearchContext()
private.StartScanHelper(viewContainer, searchContext)
end
function private.BidScanButtonOnClick(button)
local viewContainer = button:GetParentElement():GetParentElement():GetParentElement()
local searchContext = TSM.Sniper.BidSearch.GetSearchContext()
private.StartScanHelper(viewContainer, searchContext)
end
function private.AuctionsOnSelectionChanged()
private.fsm:ProcessEvent("EV_AUCTION_SELECTION_CHANGED")
end
function private.AuctionsOnRowRemoved(_, row)
private.fsm:ProcessEvent("EV_AUCTION_ROW_REMOVED", row)
end
function private.BackButtonOnClick()
private.fsm:ProcessEvent("EV_STOP_CLICKED")
end
function private.PauseResumeBtnOnClick(button)
private.fsm:ProcessEvent("EV_PAUSE_RESUME_CLICKED")
end
function private.ActionButtonOnClick(button)
private.fsm:ProcessEvent("EV_ACTION_CLICKED")
end
function private.RestartButtonOnClick(button)
if not TSM.UI.AuctionUI.StartingScan(L["Sniper"]) then
return
end
local lastScanType = private.hasLastScan
local sniperFrame = button:GetParentElement():GetParentElement():GetParentElement()
private.fsm:ProcessEvent("EV_STOP_CLICKED")
if lastScanType == "bid" then
sniperFrame:GetElement("selection.buttons.bidScanBtn"):Click()
elseif lastScanType == "buyout" then
sniperFrame:GetElement("selection.buttons.buyoutScanBtn"):Click()
else
error("Invalid last scan type: "..tostring(lastScanType))
end
end
function private.ScanFrameOnUpdate(frame)
frame:SetScript("OnUpdate", nil)
private.fsm:ProcessEvent("EV_SCAN_FRAME_SHOWN", frame)
end
function private.ScanFrameOnHide(frame)
private.fsm:ProcessEvent("EV_SCAN_FRAME_HIDDEN")
end
-- ============================================================================
-- FSM
-- ============================================================================
function private.FSMCreate()
local fsmContext = {
scanFrame = nil,
auctionScan = nil,
query = nil,
progress = 0,
progressText = L["Running Sniper Scan"],
buttonsDisabled = true,
findHash = nil,
findAuction = nil,
findResult = nil,
numFound = 0,
maxQuantity = 0,
numActioned = 0,
lastBuyQuantity = 0,
numConfirmed = 0,
searchContext = nil,
scanDone = false,
}
Event.Register("CHAT_MSG_SYSTEM", private.FSMMessageEventHandler)
Event.Register("UI_ERROR_MESSAGE", private.FSMMessageEventHandler)
if not TSM.IsWowClassic() then
Event.Register("COMMODITY_PURCHASE_SUCCEEDED", private.FSMBuyoutSuccess)
end
Event.Register("AUCTION_HOUSE_CLOSED", function()
private.fsm:ProcessEvent("EV_AUCTION_HOUSE_CLOSED")
end)
AuctionHouseWrapper.RegisterAuctionIdUpdateCallback(function(...)
private.fsm:ProcessEvent("EV_AUCTION_ID_UPDATE", ...)
end)
local function UpdateScanFrame(context)
if not context.scanFrame or not context.searchContext then
return
end
local actionText = nil
if context.searchContext:IsBuyoutScan() then
actionText = BUYOUT
elseif context.searchContext:IsBidScan() then
actionText = BID
else
error("Invalid scan type")
end
local bottom = context.scanFrame:GetElement("bottom")
bottom:GetElement("actionBtn")
:SetText(actionText)
:SetDisabled(context.buttonsDisabled)
bottom:GetElement("progressBar")
:SetProgress(context.progress)
:SetText(context.progressText or "")
local auctionList = context.scanFrame:GetElement("auctions")
:SetContext(context.auctionScan)
:SetAuctionScan(context.auctionScan)
:SetMarketValueFunction(context.searchContext:GetMarketValueFunc())
if context.findAuction and not auctionList:GetSelection() then
auctionList:SetSelection(context.findAuction)
end
local title = context.scanFrame:GetElement("header.title")
if context.scanPausing or auctionList:GetSelection() then
if context.searchContext:IsBuyoutScan() then
title:SetText(L["Buyout Sniper Paused"])
elseif context.searchContext:IsBidScan() then
title:SetText(L["Bid Sniper Paused"])
else
error("Invalid scan type")
end
else
if context.searchContext:IsBuyoutScan() then
title:SetText(L["Buyout Sniper Running"])
elseif context.searchContext:IsBidScan() then
title:SetText(L["Bid Sniper Running"])
else
error("Invalid scan type")
end
end
context.scanFrame:Draw()
end
local function ScanOnFilterDone(self, filter, numNewResults)
if numNewResults > 0 then
Sound.PlaySound(private.settings.sniperSound)
end
end
private.fsm = FSM.New("SNIPER")
:AddState(FSM.NewState("ST_INIT")
:SetOnEnter(function(context, searchContext)
private.hasLastScan = nil
if context.searchContext then
context.searchContext:KillThread()
context.searchContext = nil
end
context.progress = 0
context.progressText = L["Running Sniper Scan"]
context.buttonsDisabled = true
context.findHash = nil
context.findAuction = nil
context.findResult = nil
context.numFound = 0
context.numActioned = 0
context.lastBuyQuantity = 0
context.numConfirmed = 0
if context.auctionScan then
context.auctionScan:Release()
context.auctionScan = nil
end
if searchContext then
context.searchContext = searchContext
return "ST_RUNNING_SCAN"
elseif context.scanFrame then
context.scanFrame:GetParentElement():SetPath("selection", true)
context.scanFrame = nil
end
TSM.UI.AuctionUI.EndedScan(L["Sniper"])
end)
:AddTransition("ST_INIT")
:AddTransition("ST_RUNNING_SCAN")
:AddEventTransition("EV_START_SCAN", "ST_INIT")
)
:AddState(FSM.NewState("ST_RUNNING_SCAN")
:SetOnEnter(function(context)
context.scanDone = false
if not context.searchContext then
private.hasLastScan = nil
elseif context.searchContext:IsBuyoutScan() then
private.hasLastScan = "buyout"
elseif context.searchContext:IsBidScan() then
private.hasLastScan = "bid"
else
error("Invalid scan type")
end
if not context.auctionScan then
context.auctionScan = AuctionScan.GetManager()
:SetResolveSellers(false)
:SetScript("OnQueryDone", ScanOnFilterDone)
end
if context.scanFrame then
context.scanFrame:GetElement("bottom.progressBar"):SetProgressIconHidden(false)
end
UpdateScanFrame(context)
context.searchContext:StartThread(private.FSMScanCallback, context.auctionScan)
if TSM.IsWowClassic() then
Delay.AfterTime("sniperPhaseDetect", PHASED_TIME, private.FSMPhasedCallback)
end
end)
:SetOnExit(function(context)
Delay.Cancel("sniperPhaseDetect")
end)
:AddTransition("ST_RESULTS")
:AddTransition("ST_WAITING_FOR_PAUSE")
:AddTransition("ST_FINDING_AUCTION")
:AddTransition("ST_INIT")
:AddEvent("EV_PAUSE_RESUME_CLICKED", function(context)
return "ST_WAITING_FOR_PAUSE"
end)
:AddEvent("EV_SCAN_COMPLETE", function(context)
local selection = context.scanFrame and context.scanFrame:GetElement("auctions"):GetSelection()
if selection and selection:IsSubRow() then
return "ST_FINDING_AUCTION"
else
if TSM.IsWowClassic() then
return "ST_RESULTS"
else
-- wait 30 seconds before rescanning to avoid spamming the server with API calls
context.scanDone = true
Delay.AfterTime("SNIPER_RESCAN_DELAY", RETAIL_RESCAN_DELAY, private.FSMRescanDelayed)
end
end
end)
:AddEventTransition("EV_RESCAN_DELAYED", "ST_RESULTS")
:AddEventTransition("EV_SCAN_FAILED", "ST_INIT")
:AddEvent("EV_PHASED", function()
Log.PrintUser(L["You've been phased which has caused the AH to stop working due to a bug on Blizzard's end. Please close and reopen the AH and restart Sniper."])
return "ST_INIT"
end)
:AddEvent("EV_AUCTION_SELECTION_CHANGED", function(context)
assert(context.scanFrame)
if context.scanFrame:GetElement("auctions"):GetSelection() then
if context.scanDone then
return "ST_RESULTS"
else
-- the user selected something, so cancel the current scan
context.auctionScan:Cancel()
end
end
end)
)
:AddState(FSM.NewState("ST_WAITING_FOR_PAUSE")
:SetOnEnter(function(context)
context.scanPausing = true
context.progressText = L["Scan Paused"]
if context.scanFrame then
context.scanFrame:GetElement("bottom.progressBar"):SetProgressIconHidden(true)
end
UpdateScanFrame(context)
end)
:SetOnExit(function(context)
context.scanPausing = false
context.progressText = L["Running Sniper Scan"]
if context.scanFrame then
context.scanFrame:GetElement("bottom.progressBar"):SetProgressIconHidden(false)
end
UpdateScanFrame(context)
end)
:AddEvent("EV_PAUSE_RESUME_CLICKED", function(context)
return "ST_RESULTS"
end)
:AddTransition("ST_RESULTS")
:AddTransition("ST_INIT")
)
:AddState(FSM.NewState("ST_RESULTS")
:SetOnEnter(function(context)
context.searchContext:KillThread()
context.findAuction = nil
context.findResult = nil
context.numFound = 0
context.numActioned = 0
context.lastBuyQuantity = 0
context.numConfirmed = 0
context.progress = 0
context.progressText = L["Running Sniper Scan"]
context.buttonsDisabled = true
UpdateScanFrame(context)
local selection = context.scanFrame and context.scanFrame:GetElement("auctions"):GetSelection()
if selection and selection:IsSubRow() then
return "ST_FINDING_AUCTION"
else
return "ST_RUNNING_SCAN"
end
end)
:AddTransition("ST_RUNNING_SCAN")
:AddTransition("ST_AUCTION_FOUND")
:AddTransition("ST_FINDING_AUCTION")
:AddTransition("ST_INIT")
)
:AddState(FSM.NewState("ST_FINDING_AUCTION")
:SetOnEnter(function(context)
assert(context.scanFrame)
context.findAuction = context.scanFrame:GetElement("auctions"):GetSelection()
assert(context.findAuction:IsSubRow())
context.findHash = context.findAuction:GetHashes()
context.progress = 0
context.progressText = L["Finding Selected Auction"]
context.buttonsDisabled = true
if context.scanFrame then
context.scanFrame:GetElement("bottom.progressBar"):SetProgressIconHidden(false)
end
UpdateScanFrame(context)
TSM.Shopping.SearchCommon.StartFindAuction(context.auctionScan, context.findAuction, private.FSMFindAuctionCallback, true)
end)
:SetOnExit(function(context)
TSM.Shopping.SearchCommon.StopFindAuction()
end)
:AddTransition("ST_RESULTS")
:AddTransition("ST_FINDING_AUCTION")
:AddTransition("ST_AUCTION_FOUND")
:AddTransition("ST_AUCTION_NOT_FOUND")
:AddTransition("ST_INIT")
:AddEventTransition("EV_AUCTION_FOUND", "ST_AUCTION_FOUND")
:AddEventTransition("EV_AUCTION_NOT_FOUND", "ST_AUCTION_NOT_FOUND")
:AddEvent("EV_AUCTION_SELECTION_CHANGED", function(context)
assert(context.scanFrame)
local selection = context.scanFrame and context.scanFrame:GetElement("auctions"):GetSelection()
if selection and selection:IsSubRow() then
return "ST_FINDING_AUCTION"
else
return "ST_RESULTS"
end
end)
:AddEvent("EV_AUCTION_ROW_REMOVED", function(context, row)
if not row:IsSubRow() then
return
end
local removingFindAuction = context.findAuction == row
row:GetResultRow():RemoveSubRow(row)
context.scanFrame:GetElement("auctions"):UpdateData(true)
if removingFindAuction then
return "ST_RESULTS"
end
end)
:AddEvent("EV_SCAN_FRAME_HIDDEN", function(context)
context.scanFrame = nil
context.findAuction = nil
return "ST_RESULTS"
end)
)
:AddState(FSM.NewState("ST_AUCTION_FOUND")
:SetOnEnter(function(context, result)
local selection = context.scanFrame:GetElement("auctions"):GetSelection()
-- update the selection in case the result rows changed
if context.findHash == selection:GetHashes() then
context.findAuction = selection
end
if TSM.IsWowClassic() then
context.findResult = result
context.numFound = #result
else
local maxCommodity = context.findAuction:IsCommodity() and context.findAuction:GetResultRow():GetMaxQuantities()
local numCanBuy = maxCommodity or result
context.findResult = numCanBuy > 0
context.numFound = numCanBuy
context.maxQuantity = maxCommodity or 1
end
assert(context.numActioned == 0 and context.numConfirmed == 0)
return "ST_BIDDING_BUYING"
end)
:AddTransition("ST_BIDDING_BUYING")
)
:AddState(FSM.NewState("ST_AUCTION_NOT_FOUND")
:SetOnEnter(function(context)
local _, rawLink = context.findAuction:GetLinks()
context.findAuction:GetResultRow():RemoveSubRow(context.findAuction)
Log.PrintfUser(L["Failed to find auction for %s, so removing it from the results."], rawLink)
return "ST_RESULTS"
end)
:AddTransition("ST_RESULTS")
)
:AddState(FSM.NewState("ST_BIDDING_BUYING")
:SetOnEnter(function(context, numToRemove)
if numToRemove then
-- remove the one we just bought
context.findAuction:DecrementQuantity(numToRemove)
context.scanFrame:GetElement("auctions"):UpdateData()
context.findAuction = context.scanFrame and context.scanFrame:GetElement("auctions"):GetSelection()
if context.findAuction and not context.findAuction:IsSubRow() then
context.findAuction = nil
end
end
local selection = context.scanFrame and context.scanFrame:GetElement("auctions"):GetSelection()
if selection and not selection:IsSubRow() then
selection = nil
end
local isPlayer, isHighBidder = false, false
if selection then
assert(selection:IsSubRow())
local ownerStr = selection and selection:GetOwnerInfo() or nil
isPlayer = PlayerInfo.IsPlayer(ownerStr, true, true, true)
isHighBidder = select(4, selection:GetBidInfo())
end
local auctionSelected = selection and context.findHash == selection:GetHashes()
local numCanAction = not auctionSelected and 0 or (context.numFound - context.numActioned)
local numConfirming = context.numActioned - context.numConfirmed
local progressText = nil
local actionFormatStr = nil
if context.searchContext:IsBuyoutScan() then
actionFormatStr = L["Buy %d / %d"]
elseif context.searchContext:IsBidScan() then
actionFormatStr = L["Bid %d / %d"]
else
error("Invalid scan type")
end
if numConfirming == 0 and numCanAction == 0 then
-- we're done bidding/buying and confirming this batch
return "ST_RESULTS"
elseif numConfirming == 0 then
-- we can still bid/buy more
progressText = format(actionFormatStr, context.numActioned + 1, context.numFound)
elseif numCanAction == 0 then
-- we're just confirming
progressText = format(L["Confirming %d / %d"], context.numConfirmed + 1, context.numFound)
else
-- we can bid/buy more while confirming
progressText = format(actionFormatStr.." ("..L["Confirming %d / %d"]..")", context.numActioned + 1, context.numFound, context.numConfirmed + 1, context.numFound)
end
context.progress = context.numConfirmed / context.numFound
context.progressText = L["Scan Paused"].." - "..progressText
if numCanAction == 0 or isPlayer or (not TSM.IsWowClassic() and numConfirming > 0) then
context.buttonsDisabled = true
else
if context.searchContext:IsBuyoutScan() then
context.buttonsDisabled = not AuctionScan.CanBuyout(selection, context.auctionScan)
elseif context.searchContext:IsBidScan() then
context.buttonsDisabled = not AuctionScan.CanBid(selection)
else
error("Invalid scan type")
end
end
if context.scanFrame then
context.scanFrame:GetElement("bottom.progressBar")
:SetProgressIconHidden(context.numConfirmed == context.numActioned)
context.scanFrame:GetElement("bottom.actionBtn")
:SetDisabled(isPlayer or (isHighBidder and context.searchContext:IsBidScan()))
:Draw()
end
UpdateScanFrame(context)
end)
:AddTransition("ST_BID_BUY_CONFIRMATION")
:AddTransition("ST_BIDDING_BUYING")
:AddTransition("ST_PLACING_BID_BUY")
:AddTransition("ST_CONFIRMING_BID_BUY")
:AddTransition("ST_RESULTS")
:AddTransition("ST_INIT")
:AddEvent("EV_PAUSE_RESUME_CLICKED", function(context)
context.scanFrame:GetElement("auctions"):SetSelection(nil)
return "ST_RESULTS"
end)
:AddEventTransition("EV_AUCTION_SELECTION_CHANGED", "ST_RESULTS")
:AddEventTransition("EV_ACTION_CLICKED", "ST_BID_BUY_CONFIRMATION")
:AddEvent("EV_CONFIRMED", function(context, isBuy, quantity)
assert(isBuy == context.searchContext:IsBuyoutScan())
return "ST_PLACING_BID_BUY", quantity
end)
:AddEvent("EV_MSG", function(context, msg)
local _, rawLink = context.findAuction:GetLinks()
if msg == LE_GAME_ERR_AUCTION_HIGHER_BID or msg == LE_GAME_ERR_ITEM_NOT_FOUND or msg == LE_GAME_ERR_AUCTION_BID_OWN or msg == LE_GAME_ERR_NOT_ENOUGH_MONEY or msg == LE_GAME_ERR_ITEM_MAX_COUNT then
-- failed to bid/buy an auction
return "ST_CONFIRMING_BID_BUY", false
elseif context.searchContext:IsBidScan() and msg == ERR_AUCTION_BID_PLACED then
-- bid on an auction
return "ST_CONFIRMING_BID_BUY", true
elseif context.searchContext:IsBuyoutScan() and msg == format(ERR_AUCTION_WON_S, ItemInfo.GetName(rawLink)) then
-- bought an auction
return "ST_CONFIRMING_BID_BUY", true
end
end)
:AddEvent("EV_BUYOUT_SUCCESS", function(context)
return "ST_CONFIRMING_BID_BUY", true
end)
:AddEvent("EV_AUCTION_ID_UPDATE", function(context, oldAuctionId, newAuctionId, newResultInfo)
if not context.findAuction or select(2, context.findAuction:GetListingInfo()) ~= oldAuctionId then
return
end
context.findAuction:UpdateResultInfo(newAuctionId, newResultInfo)
context.findHash = context.findAuction:GetHashes()
end)
)
:AddState(FSM.NewState("ST_BID_BUY_CONFIRMATION")
:SetOnEnter(function(context)
local selection = context.scanFrame:GetElement("auctions"):GetSelection()
local index = TSM.IsWowClassic() and context.findResult[#context.findResult] or nil
if TSM.UI.AuctionUI.BuyUtil.ShowConfirmation(context.scanFrame, selection, context.searchContext:IsBuyoutScan(), context.numConfirmed + 1, context.numFound, context.maxQuantity, private.FSMConfirmationCallback, context.auctionScan, index, true, context.searchContext:GetMarketValueFunc()) then
return "ST_BIDDING_BUYING"
else
local quantity = selection:GetQuantities()
return "ST_PLACING_BID_BUY", quantity
end
end)
:AddTransition("ST_PLACING_BID_BUY")
:AddTransition("ST_BIDDING_BUYING")
)
:AddState(FSM.NewState("ST_PLACING_BID_BUY")
:SetOnEnter(function(context, quantity)
local index = TSM.IsWowClassic() and tremove(context.findResult, #context.findResult) or nil
assert(not TSM.IsWowClassic() or index)
local bidBuyout = nil
if context.searchContext:IsBuyoutScan() then
bidBuyout = context.findAuction:GetBuyouts()
elseif context.searchContext:IsBidScan() then
bidBuyout = context.findAuction:GetRequiredBid()
else
error("Invalid scan type")
end
local result = context.auctionScan:PlaceBidOrBuyout(index, bidBuyout, context.findAuction, quantity)
if result then
MailTracking.RecordAuctionBuyout(ItemString.GetBaseFast(context.findAuction:GetItemString()), quantity)
context.numActioned = context.numActioned + (TSM.IsWowClassic() and 1 or quantity)
context.lastBuyQuantity = quantity
else
local _, rawLink = context.findAuction:GetLinks()
if context.searchContext:IsBuyoutScan() then
Log.PrintfUser(L["Failed to buy auction of %s (x%s) for %s."], rawLink, quantity, Money.ToString(bidBuyout, nil, "OPT_83_NO_COPPER"))
elseif context.searchContext:IsBidScan() then
Log.PrintfUser(L["Failed to bid on auction of %s (x%s) for %s."], rawLink, quantity, Money.ToString(bidBuyout, nil, "OPT_83_NO_COPPER"))
else
error("Invalid scan type")
end
end
return "ST_BIDDING_BUYING"
end)
:AddTransition("ST_BIDDING_BUYING")
)
:AddState(FSM.NewState("ST_CONFIRMING_BID_BUY")
:SetOnEnter(function(context, success)
if not success then
local _, rawLink = context.findAuction:GetLinks()
local quantity = context.findAuction:GetQuantities()
local bidBuyout = nil
if context.searchContext:IsBuyoutScan() then
bidBuyout = context.findAuction:GetBuyouts()
elseif context.searchContext:IsBidScan() then
bidBuyout = context.findAuction:GetRequiredBid()
else
error("Invalid scan type")
end
if context.searchContext:IsBuyoutScan() then
Log.PrintfUser(L["Failed to buy auction of %s (x%s) for %s."], rawLink, quantity, Money.ToString(bidBuyout, nil, "OPT_83_NO_COPPER"))
elseif context.searchContext:IsBidScan() then
Log.PrintfUser(L["Failed to bid on auction of %s (x%s) for %s."], rawLink, quantity, Money.ToString(bidBuyout, nil, "OPT_83_NO_COPPER"))
else
error("Invalid scan type")
end
end
context.numConfirmed = context.numConfirmed + (TSM.IsWowClassic() and 1 or context.lastBuyQuantity)
context.findAuction = context.scanFrame and context.scanFrame:GetElement("auctions"):GetSelection()
return "ST_BIDDING_BUYING", context.lastBuyQuantity
end)
:AddTransition("ST_BIDDING_BUYING")
)
:AddDefaultEvent("EV_SCAN_FRAME_SHOWN", function(context, scanFrame)
context.scanFrame = scanFrame
UpdateScanFrame(context)
end)
:AddDefaultEvent("EV_SCAN_FRAME_HIDDEN", function(context)
context.scanFrame = nil
context.findAuction = nil
end)
:AddDefaultEventTransition("EV_AUCTION_HOUSE_CLOSED", "ST_INIT")
:AddDefaultEventTransition("EV_STOP_CLICKED", "ST_INIT")
:AddDefaultEvent("EV_AUCTION_ROW_REMOVED", function(context, row)
if not row:IsSubRow() then
return
end
row:GetResultRow():RemoveSubRow(row)
context.scanFrame:GetElement("auctions"):UpdateData(true)
end)
:Init("ST_INIT", fsmContext)
end
function private.FSMMessageEventHandler(_, msg)
private.fsm:SetLoggingEnabled(false)
private.fsm:ProcessEvent("EV_MSG", msg)
private.fsm:SetLoggingEnabled(true)
end
function private.FSMBuyoutSuccess()
private.fsm:ProcessEvent("EV_BUYOUT_SUCCESS")
end
function private.FSMRescanDelayed()
private.fsm:ProcessEvent("EV_RESCAN_DELAYED")
end
function private.FSMScanCallback(success)
if success then
private.fsm:ProcessEvent("EV_SCAN_COMPLETE")
else
private.fsm:ProcessEvent("EV_SCAN_FAILED")
end
end
function private.FSMPhasedCallback()
private.fsm:ProcessEvent("EV_PHASED")
end
function private.FSMFindAuctionCallback(result)
if result then
private.fsm:ProcessEvent("EV_AUCTION_FOUND", result)
else
private.fsm:ProcessEvent("EV_AUCTION_NOT_FOUND")
end
end
function private.FSMConfirmationCallback(isBuy, quantity)
private.fsm:ProcessEvent("EV_CONFIRMED", isBuy, quantity)
end