You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
535 lines
19 KiB
535 lines
19 KiB
-- ------------------------------------------------------------------------------ -- |
|
-- 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 |
|
|
|
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 |
|
TSMAPI:ModuleAPI("AuctionDB", "processScanData", TSM.Scan.auctionData, scanItems, time()) |
|
end |
|
return totalToPost |
|
end
|
|
|