-- ------------------------------------------------------------------------------ -- -- 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