-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster_Auctioning --
-- http://www.curse.com/addons/wow/tradeskillmaster_auctioning --
-- --
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) --
-- All Rights Reserved* - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local TSM = select ( 2 , ... )
local Post = TSM : NewModule ( " Post " , " AceEvent-3.0 " )
local L = LibStub ( " AceLocale-3.0 " ) : GetLocale ( " TradeSkillMaster_Auctioning " ) -- loads the localization table
local bagInfo , bagState = { } , { }
local bagInfoUpdate = 0
local postQueue , currentItem , itemLocations = { } , { } , { }
local scanItems
local totalToPost , totalPosted , count = 0 , 0 , 0
local isScanning , GUI
local lastProcessedAuctionDataHash
local lastProcessedAuctionDataCount
function Post : ValidateOperation ( itemString , operation )
local itemLink , salePrice = TSMAPI : Select ( { 2 , 11 } , TSMAPI : GetSafeItemInfo ( itemString ) )
local prices = TSM.Util : GetItemPrices ( operation , itemString )
-- don't post this item if their settings are invalid
if operation.postCap == 0 then
return -- posting is disabled
elseif not prices.minPrice then
if not TSM.db . global.disableInvalidMsg then
TSM : Printf ( L [ " Did not post %s because your minimum price (%s) is invalid. Check your settings. " ] , itemLink or itemString , operation.minPrice )
end
TSM.Log : AddLogRecord ( itemString , " post " , " Skip " , " invalid " , operation )
elseif not prices.maxPrice then
if not TSM.db . global.disableInvalidMsg then
TSM : Printf ( L [ " Did not post %s because your maximum price (%s) is invalid. Check your settings. " ] , itemLink or itemString , operation.maxPrice )
end
TSM.Log : AddLogRecord ( itemString , " post " , " Skip " , " invalid " , operation )
elseif not prices.normalPrice then
if not TSM.db . global.disableInvalidMsg then
TSM : Printf ( L [ " Did not post %s because your normal price (%s) is invalid. Check your settings. " ] , itemLink or itemString , operation.normalPrice )
end
TSM.Log : AddLogRecord ( itemString , " post " , " Skip " , " invalid " , operation )
elseif not prices.undercut then
if not TSM.db . global.disableInvalidMsg then
TSM : Printf ( L [ " Did not post %s because your undercut (%s) is invalid. Check your settings. " ] , itemLink or itemString , operation.undercut )
end
TSM.Log : AddLogRecord ( itemString , " post " , " Skip " , " invalid " )
elseif prices.normalPrice < prices.minPrice then
if not TSM.db . global.disableInvalidMsg then
TSM : Printf ( L [ " Did not post %s because your normal price (%s) is lower than your minimum price (%s). Check your settings. " ] , itemLink or itemString , operation.normalPrice , operation.minPrice )
end
TSM.Log : AddLogRecord ( itemString , " post " , " Skip " , " invalid " , operation )
elseif prices.maxPrice < prices.minPrice then
if not TSM.db . global.disableInvalidMsg then
TSM : Printf ( L [ " Did not post %s because your maximum price (%s) is lower than your minimum price (%s). Check your settings. " ] , itemLink or itemString , operation.maxPrice , operation.minPrice )
end
TSM.Log : AddLogRecord ( itemString , " post " , " Skip " , " invalid " , operation )
elseif salePrice > 0 and prices.minPrice <= salePrice * 1.05 then
TSM : Printf ( L [ " WARNING: You minimum price for %s is below its vendorsell price (with AH cut taken into account). Consider raising your minimum price, or vendoring the item. " ] , itemLink or itemString )
return true -- just a warning, doesn't make this invalid
else
return true
end
end
function Post : UpdateBagState ( )
if time ( ) == bagInfoUpdate then return end
wipe ( bagInfo )
wipe ( bagState )
for bag , slot , itemString , quantity in TSMAPI : GetBagIterator ( true ) do
tinsert ( bagInfo , { bag , slot , itemString , quantity } )
bagState [ itemString ] = ( bagState [ itemString ] or 0 ) + quantity
end
bagInfoUpdate = time ( )
end
function Post : GetScanListAndSetup ( GUIRef , options )
-- setup stuff
GUI = GUIRef
isScanning = true
wipe ( postQueue )
wipe ( currentItem )
wipe ( itemLocations )
wipe ( TSM.operationLookup )
totalToPost , totalPosted , count = 0 , 0 , 0
local tempList , scanList = { } , { }
Post : UpdateBagState ( )
local function HasEnoughToPost ( operation , itemString )
local maxStackSize = select ( 8 , TSMAPI : GetSafeItemInfo ( itemString ) ) or 1
local perAuction = min ( maxStackSize , operation.stackSize )
local perAuctionIsCap = operation.stackSizeIsCap
local num = ( bagState [ itemString ] or 0 ) - operation.keepQuantity
return num >= perAuction or ( perAuctionIsCap and num > 0 )
end
for itemString in pairs ( bagState ) do
local operations = options.itemOperations [ itemString ]
if not tempList [ itemString ] and operations then
for _ , operation in ipairs ( operations ) do
if operation.postCap > 0 and HasEnoughToPost ( operation , itemString ) then
tempList [ itemString ] = tempList [ itemString ] or { }
tinsert ( tempList [ itemString ] , operation )
end
end
end
end
for itemString , operations in pairs ( tempList ) do
TSM.operationLookup [ itemString ] = operations
local isValid = true
for _ , operation in ipairs ( operations ) do
if not Post : ValidateOperation ( itemString , operation ) then
isValid = nil
break
end
end
if # options.itemOperations [ itemString ] ~= # operations then
local j = 1
for i = 1 , # options.itemOperations [ itemString ] do
if options.itemOperations [ itemString ] [ i ] ~= operations [ j ] then
TSM.Log : AddLogRecord ( itemString , " post " , " Skip " , " notEnough " , options.itemOperations [ itemString ] [ i ] )
else
j = j + 1
end
end
end
if isValid then
tinsert ( scanList , itemString )
end
end
scanItems = { }
for _ , itemString in ipairs ( scanList ) do
tinsert ( scanItems , itemString )
end
TSMAPI : FireEvent ( " AUCTIONING:POST:START " , { numItems =# scanList , isGroup = true } )
return scanList
end
function Post : ProcessItem ( itemString )
local operations = TSM.operationLookup [ itemString ]
if not operations then return end
local numInBags = Post : GetNumInBags ( itemString )
for _ , operation in ipairs ( operations ) do
local toPost , reason , buyout
numInBags = numInBags - operation.keepQuantity
toPost , reason , numInBags = Post : ShouldPost ( itemString , operation , numInBags )
numInBags = numInBags + operation.keepQuantity -- restore for the next operation if there are multiple
local data = { }
if toPost then
local bid
bid , buyout , reason = Post : GetPostPrice ( itemString , operation )
local postTime = ( operation.duration == 48 and 3 ) or ( operation.duration == 24 and 2 ) or 1
for i = 1 , # toPost do
local stackSize , numStacks = unpack ( toPost [ i ] )
-- Increase the bid/buyout based on how many items we're posting
local stackBid , stackBuyout = floor ( bid * stackSize ) , floor ( buyout * stackSize )
Post : QueueItemToPost ( itemString , numStacks , stackSize , stackBid , stackBuyout , postTime , operation )
tinsert ( data , { numStacks = numStacks , stackSize = stackSize , buyout = buyout , postTime = postTime } )
end
end
TSM.Log : AddLogRecord ( itemString , " post " , ( toPost and L [ " Post " ] or L [ " Skip " ] ) , reason , operation , buyout )
if # postQueue > 0 and not currentItem.bag then
Post : SetupForAction ( )
end
if numInBags < 0 then error ( " less than 0 " ) end
if numInBags == 0 then break end
end
end
function Post : ShouldPost ( itemString , operation , numInBags )
local maxStackSize = select ( 8 , TSMAPI : GetSafeItemInfo ( itemString ) )
local perAuction = min ( maxStackSize , operation.stackSize )
local maxCanPost = floor ( numInBags / perAuction )
local perAuctionIsCap = operation.stackSizeIsCap
if maxCanPost == 0 then
if perAuctionIsCap then
perAuction = numInBags
maxCanPost = 1
else
return nil , " notEnough " , numInBags -- not enough for single post
end
end
local activeAuctions = 0
local extraStack
local buyout , bid , _ , isWhitelist , isPlayer , isInvalidSeller = TSM.Scan : GetLowestAuction ( itemString , operation )
if isInvalidSeller then
TSM : Printf ( L [ " Seller name of lowest auction for item %s was not returned from server. Skipping this item. " ] , select ( 2 , TSMAPI : GetSafeItemInfo ( itemString ) ) )
return nil , " invalidSeller " , numInBags
end
local prices = TSM.Util : GetItemPrices ( operation , itemString )
if buyout and buyout <= prices.minPrice then
-- lowest is below min price
if not prices.resetPrice then
-- lowest is below the min price and there's no reset price
return nil , " belowMinPrice " , numInBags
else
-- lowest is below the min price, but there is a reset price
local priceResetBuyout = prices.resetPrice
local priceResetBid = priceResetBuyout * operation.bidPercent
activeAuctions = TSM.Scan : GetPlayerAuctionCount ( itemString , priceResetBuyout , priceResetBid , perAuction , operation )
end
elseif isWhitelist and not isPlayer and not TSM.db . global.matchWhitelist then
-- lowest is somebody on the whitelist and we aren't price matching
return nil , " notPostingWhitelist " , numInBags
elseif isPlayer or isWhitelist then
-- Either the player or a whitelist person is the lowest teir so use this tiers quantity of items
activeAuctions = TSM.Scan : GetPlayerAuctionCount ( itemString , buyout or 0 , bid or 0 , perAuction , operation )
end
-- If we have a post cap of 20, and 10 active auctions, but we can only have 5 of the item then this will only let us create 5 auctions
-- however, if we have 20 of the item it will let us post another 10
local auctionsCreated = min ( operation.postCap - activeAuctions , maxCanPost )
if auctionsCreated <= 0 then
return nil , " tooManyPosted " , numInBags
end
if ( auctionsCreated + activeAuctions ) < operation.postCap then
-- can post at least one more
local extra = numInBags % perAuction
if perAuctionIsCap and extra > 0 then
extraStack = extra
end
end
if Post : FindItemSlot ( itemString ) then
local posts = { { perAuction , auctionsCreated } }
numInBags = numInBags - perAuction * auctionsCreated
if extraStack then
numInBags = numInBags - extraStack
tinsert ( posts , { extraStack , 1 } )
end
-- third return value specifies whether there's extra left over in the player's bags after this operation
return posts , nil , numInBags
end
end
function Post : GetPostPrice ( itemString , operation )
local lowestBuyout , lowestBid , lowestOwner , isWhitelist , isPlayer = TSM.Scan : GetLowestAuction ( itemString , operation )
local bid , buyout , info
local prices = TSM.Util : GetItemPrices ( operation , itemString )
if not lowestOwner then
-- No other auctions up, default to normalPrice
info = " postingNormal "
buyout = prices.normalPrice
elseif prices.resetPrice and lowestBuyout <= prices.minPrice then
-- item is below min price and a priceReset is set
if operation.priceReset == " minPrice " then
info = " postingResetMin "
elseif operation.priceReset == " maxPrice " then
info = " postingResetMax "
elseif operation.priceReset == " normalPrice " then
info = " postingResetNormal "
else
-- should never happen, but better to throw an error here than cause issues later on
error ( " Unknown 'below minimum' price setting. " )
end
buyout = prices.resetPrice
elseif isPlayer or ( isWhitelist and lowestBuyout - prices.undercut <= prices.maxPrice ) then
-- Either we already have one up or someone on the whitelist does
bid , buyout = min ( lowestBid , lowestBuyout ) , lowestBuyout
info = isPlayer and " postingPlayer " or " postingWhitelist "
else
-- we've been undercut and we are going to undercut back
buyout = lowestBuyout - prices.undercut
-- if the cheapest is above our max price, follow the aboveMax setting
if buyout > prices.maxPrice then
if operation.aboveMax == " minPrice " then
info = " aboveMaxMin "
elseif operation.aboveMax == " maxPrice " then
info = " aboveMaxMax "
elseif operation.aboveMax == " normalPrice " then
info = " aboveMaxNormal "
else
-- should never happen, but better to throw an error here than cause issues later on
error ( " Unknown 'above maximum' price setting. " )
end
buyout = prices.aboveMax
end
-- make sure the buyout and bid aren't below the minPrice
buyout = max ( buyout , prices.minPrice )
-- Check if the bid is too low
bid = max ( buyout * operation.bidPercent , prices.minPrice )
info = info or " postingUndercut "
end
-- set the bid if it hasn't been set
bid = bid or ( buyout * operation.bidPercent )
return bid , buyout , info
end
function Post : QueueItemToPost ( itemString , numStacks , stackSize , bid , buyout , postTime , operation )
itemLocations [ itemString ] = itemLocations [ itemString ] or Post : FindItemSlot ( itemString , true )
for i = 1 , numStacks do
local oBag , oSlot
for j = 1 , # itemLocations [ itemString ] do
if itemLocations [ itemString ] [ j ] . quantity >= stackSize then
oBag , oSlot = itemLocations [ itemString ] [ j ] . bag , itemLocations [ itemString ] [ j ] . slot
itemLocations [ itemString ] [ j ] . quantity = itemLocations [ itemString ] [ j ] . quantity - stackSize
break
end
end
if not oBag or not oSlot then
oBag , oSlot = Post : FindItemSlot ( itemString )
if not ( oBag and oSlot ) then break end
end
tinsert ( postQueue , { bag = oBag , slot = oSlot , bid = bid , buyout = buyout , postTime = postTime , stackSize = stackSize , numStacks = ( numStacks - i + 1 ) , itemString = itemString , operation = operation } )
totalToPost = totalToPost + 1
end
TSM.Manage : UpdateStatus ( " manage " , totalPosted , totalToPost )
end
function Post : FindItemSlot ( findItemString , allLocations )
local locations = { }
Post : UpdateBagState ( )
for _ , data in ipairs ( bagInfo ) do
local bag , slot , itemString , quantity = unpack ( data )
if findItemString == itemString then
if not allLocations then
return bag , slot
end
tinsert ( locations , { bag = bag , slot = slot , quantity = quantity } )
end
end
return allLocations and locations
end
function Post : GetNumInBags ( itemString )
local num = 0
Post : UpdateBagState ( )
return bagState [ itemString ] or 0
end
local timeout = CreateFrame ( " Frame " )
timeout : Hide ( )
timeout : SetScript ( " OnUpdate " , function ( self , elapsed )
self.timeLeft = self.timeLeft - elapsed
if self.timeLeft <= 0 or ( postQueue [ 1 ] and postQueue [ 1 ] . bag and postQueue [ 1 ] . slot and not select ( 3 , GetContainerItemInfo ( postQueue [ 1 ] . bag , postQueue [ 1 ] . slot ) ) and not AuctionsCreateAuctionButton : IsEnabled ( ) ) then
tremove ( postQueue , 1 )
Post : UpdateItem ( )
end
end )
function Post : SetupForAction ( )
Post : RegisterEvent ( " CHAT_MSG_SYSTEM " )
timeout : Hide ( )
ClearCursor ( )
TSM.Manage : UpdateStatus ( " manage " , totalPosted , totalToPost )
wipe ( currentItem )
currentItem = postQueue [ 1 ]
TSM.Manage : SetCurrentItem ( currentItem )
GUI.buttons : Enable ( )
end
-- Check if an auction was posted and move on if so
function Post : CHAT_MSG_SYSTEM ( _ , msg )
if msg == ERR_AUCTION_STARTED then
count = count + 1
TSM.Manage : UpdateStatus ( " confirm " , count , totalToPost )
end
end
local countFrame = CreateFrame ( " Frame " )
countFrame : Hide ( )
countFrame.count = - 1
countFrame.timeLeft = 10
countFrame : SetScript ( " OnUpdate " , function ( self , elapsed )
self.timeLeft = self.timeLeft - elapsed
if count >= totalToPost or self.timeLeft <= 0 then
self : Hide ( )
Post : Stop ( )
elseif count ~= self.count then
self.count = count
self.timeLeft = ( totalToPost - count ) * 2
end
end )
local function DelayFrame ( )
if # postQueue > 0 then
Post : UpdateItem ( )
TSMAPI : CancelFrame ( " postDelayFrame " )
elseif not isScanning then
TSM.Manage : UpdateStatus ( " manage " , totalPosted , totalToPost )
Post : Stop ( )
TSMAPI : CancelFrame ( " postDelayFrame " )
end
end
function Post : UpdateItem ( )
ClearCursor ( )
timeout : Hide ( )
if # postQueue == 0 then
GUI.buttons : Disable ( )
if isScanning then
TSMAPI : CreateFunctionRepeat ( " postDelayFrame " , DelayFrame )
else
TSM.Manage : UpdateStatus ( " manage " , totalPosted + 1 , totalToPost )
countFrame : Show ( )
end
return
end
totalPosted = totalPosted + 1
TSM.Manage : UpdateStatus ( " manage " , totalPosted , totalToPost )
wipe ( currentItem )
currentItem = postQueue [ 1 ]
TSM.Manage : SetCurrentItem ( currentItem )
GUI.buttons : Enable ( )
end
function Post : DoAction ( )
timeout.timeLeft = 0.1
timeout : Show ( )
if not AuctionFrameAuctions.duration then
-- Fix in case Blizzard_AuctionUI hasn't set this value yet (which could cause an error)
AuctionFrameAuctions.duration = 2
end
if not currentItem.itemString then
timeout : Hide ( )
Post : SkipItem ( )
return
end
if type ( currentItem.bag ) ~= " number " or type ( currentItem.slot ) ~= " number " then
local bag , slot = Post : FindItemSlot ( currentItem.itemString )
if not bag or not slot then
local link = select ( 2 , TSMAPI : GetSafeItemInfo ( currentItem.itemString ) ) or currentItem.itemString
TSM : Printf ( L [ " Auctioning could not find %s in your bags so has skipped posting it. Running the scan again should resolve this issue. " ] , link )
timeout : Hide ( )
Post : SkipItem ( )
return
end
currentItem.bag = bag
currentItem.slot = slot
end
local itemString = TSMAPI : GetBaseItemString ( GetContainerItemLink ( currentItem.bag , currentItem.slot ) , true )
if itemString ~= currentItem.itemString then
TSM : Print ( L [ " Please don't move items around in your bags while a post scan is running! The item was skipped to avoid an incorrect item being posted. " ] )
timeout : Hide ( )
Post : SkipItem ( )
return
end
PickupContainerItem ( currentItem.bag , currentItem.slot )
ClickAuctionSellItemButton ( AuctionsItemButton , " LeftButton " )
StartAuction ( currentItem.bid , currentItem.buyout , currentItem.postTime , currentItem.stackSize , 1 )
GUI.buttons : Disable ( )
end
function Post : SkipItem ( )
local toSkip = { }
local skipped = tremove ( postQueue , 1 )
count = count + 1
for i , info in ipairs ( postQueue ) do
if info.itemString == skipped.itemString and info.bid == skipped.bid and info.buyout == skipped.buyout then
tinsert ( toSkip , i )
end
end
sort ( toSkip , function ( a , b ) return a > b end )
for _ , index in ipairs ( toSkip ) do
tremove ( postQueue , index )
count = count + 1
totalPosted = totalPosted + 1
end
TSM.Manage : UpdateStatus ( " manage " , totalPosted , totalToPost )
TSM.Manage : UpdateStatus ( " confirm " , count , totalToPost )
Post : UpdateItem ( )
end
function Post : Stop ( )
GUI : Stopped ( )
TSMAPI : CancelFrame ( " postDelayFrame " )
Post : UnregisterAllEvents ( )
TSMAPI : FireEvent ( " AUCTIONING:POST:STOPPED " )
wipe ( currentItem )
totalToPost , totalPosted = 0 , 0
isScanning = false
end
function Post : GetAHGoldTotal ( )
local total = 0
local incomingTotal = 0
for i = 1 , GetNumAuctionItems ( " owner " ) do
local count , _ , _ , _ , _ , _ , buyoutAmount = select ( 3 , GetAuctionItemInfo ( " owner " , i ) )
total = total + buyoutAmount
if count == 0 then
incomingTotal = incomingTotal + buyoutAmount
end
end
return TSMAPI : FormatTextMoneyIcon ( total ) , TSMAPI : FormatTextMoneyIcon ( incomingTotal )
end
function Post : GetCurrentItem ( )
return currentItem
end
function Post : EditPostPrice ( itemString , buyout , operation )
local bid = buyout * operation.bidPercent
if currentItem.itemString == itemString then
currentItem.buyout = buyout
currentItem.bid = bid
end
for _ , data in ipairs ( postQueue ) do
if data.itemString == itemString then
data.buyout = buyout
data.bid = bid
end
end
TSM.Manage : SetCurrentItem ( currentItem )
end
function Post : DoneScanning ( )
isScanning = false
if scanItems and next ( TSM.Scan . auctionData ) then
local currentHash = TSM.Scan . auctionDataHash
local currentCount = TSM.Scan . auctionDataCount
if currentHash and currentCount and ( currentHash ~= lastProcessedAuctionDataHash or currentCount ~= lastProcessedAuctionDataCount ) then
lastProcessedAuctionDataHash = currentHash
lastProcessedAuctionDataCount = currentCount
TSMAPI : ModuleAPI ( " AuctionDB " , " processScanData " , TSM.Scan . auctionData , scanItems , time ( ) )
end
end
return totalToPost
end