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.
854 lines
30 KiB
854 lines
30 KiB
-- ------------------------------------------------------------------------------ -- |
|
-- TradeSkillMaster_Mailing -- |
|
-- http://www.curse.com/addons/wow/tradeskillmaster_mailing -- |
|
-- -- |
|
-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- |
|
-- All Rights Reserved* - Detailed license information included with addon. -- |
|
-- ------------------------------------------------------------------------------ -- |
|
|
|
local TSM = select(2, ...) |
|
local Inbox = TSM:NewModule("Inbox", "AceEvent-3.0", "AceHook-3.0") |
|
local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster_Mailing") -- loads the localization table |
|
|
|
local private = { recheckTime = 1, allowTimerStart = true, lootIndex = 1, freeSlots = true } |
|
|
|
|
|
function Inbox:OnEnable() |
|
Inbox:RegisterEvent("MAIL_SHOW") |
|
TSMAPI:CreateEventBucket("MAIL_INBOX_UPDATE", private.InboxUpdate, 0.3) |
|
Inbox:RegisterEvent("MAIL_CLOSED") |
|
end |
|
|
|
function Inbox:CreateTab(parent) |
|
local frame = CreateFrame("Frame", nil, parent) |
|
frame:Hide() |
|
frame:SetAllPoints() |
|
frame:SetScript("OnHide", function() private:StopAutoLooting() end) |
|
frame:SetScript("OnShow", private.InboxUpdate) |
|
|
|
local label = TSMAPI.GUI:CreateLabel(frame, "small") |
|
label:SetPoint("TOPLEFT", 5, -5) |
|
label:SetPoint("TOPRIGHT", -5, -5) |
|
label:SetHeight(15) |
|
label:SetJustifyH("CENTER") |
|
label:SetJustifyV("CENTER") |
|
frame.topLabel = label |
|
|
|
TSMAPI.GUI:CreateHorizontalLine(frame, -25) |
|
|
|
local stContainer = CreateFrame("Frame", nil, frame) |
|
stContainer:SetPoint("TOPLEFT", 5, -35) |
|
stContainer:SetPoint("BOTTOMRIGHT", -5, 55) |
|
TSMAPI.Design:SetFrameColor(stContainer) |
|
|
|
local handlers = { |
|
OnClick = function(_, data) |
|
if IsShiftKeyDown() and select(6, GetInboxHeaderInfo(data.index)) <= 0 then |
|
if private:CanLootMailIndex(data.index) then |
|
private:LootMailItem(data.index) |
|
else |
|
TSM:Print(L["Could not loot item from mail because your bags are full."]) |
|
end |
|
end |
|
|
|
if InboxFrame.openMailID ~= data.index then |
|
InboxFrame.openMailID = data.index |
|
OpenMailFrame.updateButtonPositions = true |
|
OpenMail_Update() |
|
ShowUIPanel(OpenMailFrame) |
|
OpenMailFrame:SetPoint("TOPLEFT", InboxFrame, "TOPRIGHT", 32, 0) |
|
PlaySound("igSpellBookOpen") |
|
else |
|
InboxFrame.openMailID = 0 |
|
HideUIPanel(OpenMailFrame) |
|
end |
|
InboxFrame_Update() |
|
end, |
|
OnEnter = function(_, data, self) |
|
end, |
|
OnLeave = function() |
|
end, |
|
} |
|
|
|
local st = TSMAPI:CreateScrollingTable(stContainer, nil, handlers) |
|
st:SetData({}) |
|
frame.st = st |
|
|
|
local btn = TSMAPI.GUI:CreateButton(frame, 18, "TSMMailingOpenAllButton") |
|
btn:SetPoint("BOTTOMLEFT", 5, 30) |
|
btn:SetPoint("BOTTOMRIGHT", -5, 30) |
|
btn:SetHeight(20) |
|
btn:SetText(L["Open All Mail"]) |
|
btn:SetScript("OnClick", function() private:StartAutoLooting("all") end) |
|
frame.allBtn = btn |
|
|
|
local label = TSMAPI.GUI:CreateLabel(frame, "normal") |
|
label:SetPoint("BOTTOMLEFT", 5, 5) |
|
label:SetHeight(20) |
|
label:SetJustifyH("LEFT") |
|
label:SetJustifyV("CENTER") |
|
label:SetText(L["AH Mail:"]) |
|
|
|
local btnWidth = (frame:GetWidth() - label:GetWidth() - 25) / 5 |
|
|
|
local btn = TSMAPI.GUI:CreateButton(frame, 18, "TSMMailingSalesButton") |
|
btn:SetPoint("BOTTOMLEFT", label, "BOTTOMRIGHT", 5, 0) |
|
btn:SetWidth(btnWidth) |
|
btn:SetHeight(20) |
|
btn:SetText(L["Sales"]) |
|
btn:SetScript("OnClick", function() private:StartAutoLooting("sales") end) |
|
frame.salesBtn = btn |
|
|
|
local btn = TSMAPI.GUI:CreateButton(frame, 18, "TSMMailingBuysButton") |
|
btn:SetPoint("BOTTOMLEFT", frame.salesBtn, "BOTTOMRIGHT", 5, 0) |
|
btn:SetWidth(btnWidth) |
|
btn:SetHeight(20) |
|
btn:SetText(L["Buys"]) |
|
btn:SetScript("OnClick", function() private:StartAutoLooting("buys") end) |
|
frame.buysBtn = btn |
|
|
|
local btn = TSMAPI.GUI:CreateButton(frame, 18, "TSMMailingCancelsButton") |
|
btn:SetPoint("BOTTOMLEFT", frame.buysBtn, "BOTTOMRIGHT", 5, 0) |
|
btn:SetWidth(btnWidth) |
|
btn:SetHeight(20) |
|
btn:SetText(L["Cancels"]) |
|
btn:SetScript("OnClick", function() private:StartAutoLooting("cancels") end) |
|
frame.cancelsBtn = btn |
|
|
|
local btn = TSMAPI.GUI:CreateButton(frame, 18, "TSMMailingExpiresButton") |
|
btn:SetPoint("BOTTOMLEFT", frame.cancelsBtn, "BOTTOMRIGHT", 5, 0) |
|
btn:SetWidth(btnWidth) |
|
btn:SetHeight(20) |
|
btn:SetText(L["Expires"]) |
|
btn:SetScript("OnClick", function() private:StartAutoLooting("expires") end) |
|
frame.expiresBtn = btn |
|
|
|
local btn = TSMAPI.GUI:CreateButton(frame, 18) |
|
btn:SetPoint("BOTTOMLEFT", frame.expiresBtn, "BOTTOMRIGHT", 5, 0) |
|
btn:SetPoint("BOTTOMRIGHT", -5, 5) |
|
btn:SetHeight(20) |
|
btn:SetText("Pending") |
|
btn:SetScript("OnClick", function() private:StartAutoLooting("pendings") end) |
|
frame.expiresBtn = btn |
|
|
|
local btn = TSMAPI.GUI:CreateButton(frame, 16) |
|
btn:SetFrameStrata("HIGH") |
|
btn:SetPoint("CENTER") |
|
btn:SetHeight(30) |
|
btn:SetWidth(150) |
|
btn:SetText(L["Reload UI"]) |
|
btn:SetScript("OnClick", ReloadUI) |
|
btn:Hide() |
|
frame.reloadBtn = btn |
|
|
|
frame.EnableButtons = function(self) |
|
self.allBtn:Enable() |
|
self.salesBtn:Enable() |
|
self.buysBtn:Enable() |
|
self.cancelsBtn:Enable() |
|
self.expiresBtn:Enable() |
|
self.buttonsEnabled = true |
|
end |
|
frame.DisableButtons = function(self) |
|
self.allBtn:Disable() |
|
self.salesBtn:Disable() |
|
self.buysBtn:Disable() |
|
self.cancelsBtn:Disable() |
|
self.expiresBtn:Disable() |
|
self.buttonsEnabled = nil |
|
end |
|
frame.buttonsEnabled = true |
|
|
|
private.frame = frame |
|
return frame |
|
end |
|
|
|
|
|
local function CacheFrameOnUpdate(self, elapsed) |
|
if not private.waitingForData then |
|
local seconds = self.endTime - GetTime() |
|
if seconds <= 0 then |
|
-- Look for new mail |
|
-- Sometimes it fails and isn't available at exactly 60-61 seconds, and more like 62-64, will keep rechecking every 1 second |
|
-- until data becomes available |
|
if TSM.db.global.autoCheck then |
|
private.waitingForData = true |
|
self.timeLeft = private.recheckTime |
|
private.lootIndex = 1 |
|
private.resetIndex = nil |
|
CheckInbox() |
|
private.frame.reloadBtn:Hide() |
|
else |
|
self:Hide() |
|
end |
|
return |
|
end |
|
|
|
private:UpdateTopLabel() |
|
else |
|
self.timeLeft = self.timeLeft - elapsed |
|
if self.timeLeft <= 0 then |
|
self.timeLeft = private.recheckTime |
|
private.lootIndex = 1 |
|
private.resetIndex = nil |
|
CheckInbox() |
|
private.frame.reloadBtn:Hide() |
|
end |
|
end |
|
end |
|
|
|
function Inbox:MAIL_SHOW() |
|
if not private.cacheFrame then |
|
-- Timer for mailbox cache updates |
|
private.cacheFrame = CreateFrame("Frame", nil, MailFrame) |
|
private.cacheFrame:Hide() |
|
private.cacheFrame:SetScript("OnUpdate", CacheFrameOnUpdate) |
|
end |
|
end |
|
|
|
local function FormatDaysLeft(daysLeft, index) |
|
-- code taken from Blizzard MailFrame.lua code |
|
if daysLeft >= 1 then |
|
if InboxItemCanDelete(index) then |
|
daysLeft = YELLOW_FONT_COLOR_CODE .. format(DAYS_ABBR, floor(daysLeft)) .. " " .. FONT_COLOR_CODE_CLOSE; |
|
else |
|
daysLeft = GREEN_FONT_COLOR_CODE .. format(DAYS_ABBR, floor(daysLeft)) .. " " .. FONT_COLOR_CODE_CLOSE; |
|
end |
|
else |
|
daysLeft = RED_FONT_COLOR_CODE .. SecondsToTime(floor(daysLeft * 24 * 60 * 60)) .. FONT_COLOR_CODE_CLOSE; |
|
end |
|
return daysLeft |
|
end |
|
|
|
function private:UpdateTopLabel() |
|
local parts = {} |
|
|
|
local numMail, totalMail = GetInboxNumItems() |
|
if totalMail == numMail then |
|
tinsert(parts, format(L["Showing all %d mail."], numMail)) |
|
else |
|
tinsert(parts, format(L["Showing %d of %d mail."], numMail, totalMail)) |
|
end |
|
|
|
local collectGold = private.collectGold or 0 |
|
if collectGold > 0 then |
|
tinsert(parts, format(L["%s to collect."], TSMAPI:FormatTextMoney(collectGold))) |
|
end |
|
|
|
local nextRefresh = private.cacheFrame:IsVisible() and private.cacheFrame.endTime |
|
if nextRefresh then |
|
if numMail == 0 and TSM.db.global.showReloadBtn then |
|
private.frame.reloadBtn:Show() |
|
end |
|
tinsert(parts, format(L["Next inbox update in %d seconds."], max(ceil(nextRefresh - GetTime()), 0))) |
|
end |
|
|
|
private.frame.topLabel:SetText(table.concat(parts, " ")) |
|
end |
|
|
|
function private:InboxUpdate() |
|
if not private.frame or (not private.frame:IsVisible() and private.frame.buttonsEnabled) then return end |
|
TSMAPI:CancelFrame("inboxLootTextDelay") |
|
|
|
local numMail, totalMail = GetInboxNumItems() |
|
|
|
local greenColor, redColor, yellowColor = "|cff00ff00", "|cffff0000", "|cffeeff00" |
|
local mailInfo = {} |
|
local collectGold = 0 |
|
for i = 1, numMail do |
|
mailInfo[i] = "" |
|
local isInvoice = select(4, GetInboxText(i)) |
|
--local _, _, sender, subject, money, cod, _, hasItem = GetInboxHeaderInfo(index) |
|
local _, _, sender, subject, money, cod, daysLeft, hasItem, _, _, _, _, _, itemQuantity = GetInboxHeaderInfo(i) |
|
if isInvoice then |
|
local invoiceType, itemName, playerName, bid, buyout, deposit, ahcut, _, _, _, quantity = GetInboxInvoiceInfo(i) |
|
|
|
sender = sender or "?" |
|
if sender == "Blackwater Auction House" then sender = "AH" end |
|
if not quantity then |
|
quantity = select(3, GetInboxItem(i)) |
|
end |
|
-- fix MoP difference |
|
if invoiceType == "buyer" then |
|
local itemLink = GetInboxItemLink(i, 1) or itemName |
|
mailInfo[i] = format(L["Buy: %s (%d) | %s | %s"], itemLink, quantity or 1, TSMAPI:FormatTextMoney(bid, redColor), FormatDaysLeft(daysLeft, i)) |
|
elseif invoiceType == "seller" then |
|
collectGold = collectGold + bid - ahcut |
|
mailInfo[i] = format(L["Sale: %s (%d) | %s | %s"], itemName, quantity or 1, TSMAPI:FormatTextMoney(bid - ahcut, greenColor), FormatDaysLeft(daysLeft, i)) |
|
-- mailInfo[i] = format("Sale: %s | %s | %s", itemName, TSMAPI:FormatTextMoney(bid - ahcut, greenColor), FormatDaysLeft(daysLeft, i)) |
|
elseif invoiceType == "seller_temp_invoice" then |
|
mailInfo[i] = format("Pending Sale: %s (%d) | %s | %s", itemName, quantity or 1, TSMAPI:FormatTextMoney(bid - ahcut, yellowColor), FormatDaysLeft(daysLeft, i)) |
|
-- mailInfo[i] = format("Pending Sale: %s | %s | %s", itemName, TSMAPI:FormatTextMoney(bid - ahcut, yellowColor), FormatDaysLeft(daysLeft, i)) |
|
end |
|
elseif hasItem then |
|
local itemLink |
|
local quantity = 0 |
|
for j = 1, hasItem do |
|
local link = GetInboxItemLink(i, j) |
|
itemLink = itemLink or link |
|
quantity = quantity + select(3, GetInboxItem(i, j)) |
|
if TSMAPI:GetItemString(itemLink) ~= TSMAPI:GetItemString(link) then |
|
itemLink = L["Multiple Items"] |
|
quantity = -1 |
|
break |
|
end |
|
end |
|
local itemDesc = (quantity > 0 and format("%s (%d)", itemLink, quantity)) or (quantity == -1 and L["Multiple Items"]) or "---" |
|
|
|
if hasItem == 1 and itemLink and strfind(subject, "^" .. TSMAPI:StrEscape(format(AUCTION_EXPIRED_MAIL_SUBJECT, TSMAPI:GetSafeItemInfo(itemLink)))) then |
|
mailInfo[i] = format(L["Expired: %s | %s"], itemDesc, FormatDaysLeft(daysLeft, i)) |
|
elseif cod > 0 then |
|
mailInfo[i] = format(L["COD: %s | %s | %s | %s"], itemDesc, TSMAPI:FormatTextMoney(cod, redColor), sender or "---", FormatDaysLeft(daysLeft, i)) |
|
elseif money > 0 then |
|
collectGold = collectGold + money |
|
mailInfo[i] = format("%s + %s | %s | %s", itemDesc, TSMAPI:FormatTextMoney(money, greenColor), sender or "---", FormatDaysLeft(daysLeft, i)) |
|
else |
|
mailInfo[i] = format("%s | %s | %s", itemDesc, sender or "---", FormatDaysLeft(daysLeft, i)) |
|
end |
|
elseif money > 0 then |
|
mailInfo[i] = format("%s | %s | %s | %s", subject, TSMAPI:FormatTextMoney(money, greenColor), sender or "---", FormatDaysLeft(daysLeft, i)) |
|
else |
|
mailInfo[i] = format("%s | %s | %s", subject, sender or "---", FormatDaysLeft(daysLeft, i)) |
|
end |
|
end |
|
private.collectGold = collectGold |
|
|
|
local stData = {} |
|
for i, info in ipairs(mailInfo) do |
|
tinsert(stData, { cols = { { value = info } }, index = i }) |
|
end |
|
private.frame.st:SetData(stData) |
|
|
|
private:UpdateTopLabel() |
|
|
|
-- -- Yay nothing else to loot, so nothing else to update the cache for! |
|
-- if private.cacheFrame.endTime and numMail == totalMail and private.lastTotal ~= totalMail then |
|
-- private.cacheFrame.endTime = nil |
|
-- private.cacheFrame:Hide() |
|
-- -- Start a timer since we're over the limit of 50 items before waiting for it to recache |
|
-- elseif (private.cacheFrame.endTime and numMail >= 50 and private.lastTotal ~= totalMail) or (numMail >= 50 and private.allowTimerStart) then |
|
-- private.resetIndex = nil |
|
-- private.allowTimerStart = nil |
|
-- private.waitingForData = nil |
|
-- private.lastTotal = totalMail |
|
-- private.cacheFrame.endTime = GetTime() + 60 |
|
-- private.cacheFrame:Show() |
|
-- end |
|
|
|
-- The last item we setup to auto loot is finished, time for the next one |
|
if not private.frame.buttonsEnabled then |
|
-- if private.autoLootTotal ~= numMail then |
|
-- private.autoLootTotal = GetInboxNumItems() |
|
|
|
-- If we're auto checking mail when new data is available, will wait and continue auto looting, otherwise we just stop now |
|
if totalMail == 0 then |
|
private:StopAutoLooting() |
|
else |
|
private:AutoLoot() |
|
end |
|
else |
|
TSMAPI:CreateTimeDelay("mailSkipDelay", 1, function() |
|
local money, _, _, hasItem, _, _, _, canReply = select(5, GetInboxHeaderInfo(private.lootIndex)) |
|
if not hasItem and money == 0 then |
|
private.lootIndex = private.lootIndex + 1 |
|
return private:AutoLoot() |
|
end |
|
end) |
|
-- end |
|
end |
|
end |
|
|
|
function private:ShouldOpenMail(index) |
|
local shouldOpen |
|
if private.mode == "all" then |
|
return true |
|
elseif private.mode == "sales" then |
|
local money = select(5, GetInboxHeaderInfo(index)) |
|
if money > 0 and GetInboxInvoiceInfo(index) == "seller" then |
|
return true |
|
end |
|
elseif private.mode == "buys" then |
|
local hasItem = select(8, GetInboxHeaderInfo(index)) |
|
if hasItem and GetInboxInvoiceInfo(index) == "buyer" then |
|
return true |
|
end |
|
elseif private.mode == "cancels" then |
|
local isInvoice = select(4, GetInboxText(index)) |
|
local subject, _, _, _, hasItem = select(4, GetInboxHeaderInfo(index)) |
|
if not isInvoice and hasItem == 1 then |
|
local itemLink = GetInboxItemLink(index, 1) |
|
if itemLink then |
|
local itemName = TSMAPI:GetSafeItemInfo(itemLink) |
|
local quantity = select(3, GetInboxItem(index, 1)) |
|
if quantity and quantity > 0 and (subject == format(AUCTION_REMOVED_MAIL_SUBJECT.." (%d)", itemName, quantity) or subject == format(AUCTION_REMOVED_MAIL_SUBJECT, itemName)) then |
|
return true |
|
end |
|
end |
|
end |
|
elseif private.mode == "pendings" then |
|
local isInvoice = select(4, GetInboxText(index)) |
|
if isInvoice then |
|
invoiceType = GetInboxInvoiceInfo(index) |
|
if invoiceType == "seller_temp_invoice" then |
|
return true |
|
end |
|
end |
|
elseif private.mode == "expires" then |
|
local isInvoice = select(4, GetInboxText(index)) |
|
local subject, _, _, _, hasItem = select(4, GetInboxHeaderInfo(index)) |
|
if not isInvoice and hasItem == 1 then |
|
local itemLink = GetInboxItemLink(index, 1) |
|
if itemLink and strfind(subject, "^" .. TSMAPI:StrEscape(format(AUCTION_EXPIRED_MAIL_SUBJECT, TSMAPI:GetSafeItemInfo(itemLink)))) then |
|
return true |
|
end |
|
end |
|
end |
|
end |
|
|
|
|
|
|
|
-- Deals with auto looting of mail! |
|
function private:StartAutoLooting(mode) |
|
if not MailFrame or not MailFrame:IsShown() then return end |
|
private.mode = mode |
|
local canCollectMail |
|
if private.mode == "all" then |
|
local total |
|
private.autoLootTotal, total = GetInboxNumItems() |
|
canCollectMail = not (private.autoLootTotal == 0 and total == 0) |
|
else |
|
for i = 1, GetInboxNumItems() do |
|
if private:ShouldOpenMail(i) then |
|
canCollectMail = true |
|
break |
|
end |
|
end |
|
end |
|
if not canCollectMail then |
|
private.mode = nil |
|
return |
|
end |
|
|
|
Inbox:RegisterEvent("UI_ERROR_MESSAGE") |
|
private.frame:DisableButtons() |
|
private.moneyCollected = 0 |
|
private.mode = mode |
|
private.lootIndex = 1 |
|
private:AutoLoot() |
|
end |
|
|
|
local function CanLootItem(itemString, quantity) |
|
if not itemString or not quantity then return false end |
|
local maxStack = select(8, TSMAPI:GetSafeItemInfo(itemString)) |
|
local hasPartial = false |
|
local hasEmpty = false |
|
local totalFreeSlots = 0 |
|
|
|
for bag = 0, NUM_BAG_SLOTS do |
|
if TSMAPI:ItemWillGoInBag(itemString, bag) then |
|
totalFreeSlots = totalFreeSlots + (GetContainerNumFreeSlots(bag) or 0) |
|
for slot = 1, GetContainerNumSlots(bag) do |
|
local iLink = GetContainerItemLink(bag, slot) |
|
if iLink then |
|
if TSMAPI:GetItemString(iLink) == itemString and maxStack then |
|
local stackSize = select(2, GetContainerItemInfo(bag, slot)) |
|
if stackSize and (maxStack - stackSize) >= quantity then |
|
hasPartial = true |
|
end |
|
end |
|
else |
|
hasEmpty = true |
|
end |
|
end |
|
end |
|
end |
|
|
|
if hasPartial then |
|
return true |
|
end |
|
if not hasEmpty then |
|
return false |
|
end |
|
if TSM.db.global.keepMailSpace and TSM.db.global.keepMailSpace > 0 then |
|
return (totalFreeSlots - 1) >= TSM.db.global.keepMailSpace |
|
end |
|
return true |
|
end |
|
|
|
local function HasFittableStack(itemString, needed) |
|
if not itemString or not needed or needed <= 0 then return false end |
|
for i = 1, GetInboxNumItems() do |
|
local _, _, _, _, _, cod, _, hasItem = GetInboxHeaderInfo(i) |
|
if hasItem and hasItem > 0 and (not cod or cod == 0) then |
|
for j = 1, hasItem do |
|
local itemLink = GetInboxItemLink(i, j) |
|
if itemLink and TSMAPI:GetItemString(itemLink) == itemString then |
|
local quantity = select(3, GetInboxItem(i, j)) or 0 |
|
if quantity > 0 and quantity <= needed then |
|
return true |
|
end |
|
end |
|
end |
|
end |
|
end |
|
return false |
|
end |
|
|
|
function Inbox:CollectItems(items, callback) |
|
if not items or not next(items) then return end |
|
if not MailFrame or not MailFrame:IsShown() then return end |
|
|
|
local remaining = CopyTable(items) |
|
local collected = false |
|
local candidates = {} |
|
for i = GetInboxNumItems(), 1, -1 do |
|
local _, _, _, _, _, cod, _, hasItem = GetInboxHeaderInfo(i) |
|
if hasItem and hasItem > 0 and (not cod or cod == 0) then |
|
for j = hasItem, 1, -1 do |
|
local itemLink = GetInboxItemLink(i, j) |
|
local itemString = itemLink and TSMAPI:GetItemString(itemLink) |
|
local need = itemString and remaining[itemString] |
|
if need and need > 0 then |
|
local quantity = select(3, GetInboxItem(i, j)) or 0 |
|
if quantity > 0 then |
|
if not candidates[itemString] then |
|
candidates[itemString] = {} |
|
end |
|
tinsert(candidates[itemString], { index = i, itemIndex = j, quantity = quantity }) |
|
end |
|
end |
|
end |
|
end |
|
end |
|
|
|
for itemString, entries in pairs(candidates) do |
|
table.sort(entries, function(a, b) |
|
if a.quantity ~= b.quantity then |
|
return a.quantity < b.quantity |
|
end |
|
return a.index > b.index |
|
end) |
|
for _, entry in ipairs(entries) do |
|
local need = remaining[itemString] |
|
if not need or need <= 0 then |
|
break |
|
end |
|
if entry.quantity > 0 and (entry.quantity <= need or not HasFittableStack(itemString, need)) then |
|
if CanLootItem(itemString, entry.quantity) then |
|
TakeInboxItem(entry.index, entry.itemIndex) |
|
remaining[itemString] = max(need - entry.quantity, 0) |
|
collected = true |
|
elseif callback then |
|
callback(L["Cannot finish auto looting, inventory is full or too many unique items."]) |
|
return |
|
end |
|
end |
|
end |
|
end |
|
|
|
-- Keep looting as new mail indexes shift, while preserving 1 free slot. |
|
if collected then |
|
TSMAPI:CreateTimeDelay("craftingGatherMailLoop", 0.4, function() |
|
Inbox:CollectItems(remaining, callback) |
|
end) |
|
end |
|
|
|
end |
|
|
|
function private:AutoLoot() |
|
TSMAPI:CancelFrame("mailSkipDelay") |
|
if not MailFrame or not MailFrame:IsShown() then |
|
private:StopAutoLooting() |
|
return |
|
end |
|
|
|
-- Already looted everything after the invalid indexes we had, so fail it |
|
if private.lootIndex > 1 and private.lootIndex > GetInboxNumItems() then |
|
if private.resetIndex then |
|
private:StopAutoLooting() |
|
else |
|
private.resetIndex = true |
|
private.lootIndex = 1 |
|
private:AutoLoot() |
|
end |
|
return |
|
end |
|
|
|
local _, _, sender, subject, money, cod, _, items, _, _, _, _, isGM = GetInboxHeaderInfo(private.lootIndex) |
|
if (sender == "The Postmaster" or sender == "Blackwater Auction House" or sender == "Customer Support") or not isGM and (not cod or cod <= 0) and ((money and money > 0) or (items and items > 0)) or GetInboxInvoiceInfo(private.lootIndex) == "seller_temp_invoice" then |
|
TSMAPI:CancelFrame("mailWaitDelay") |
|
if private.mode == "all" then |
|
if money > 0 then |
|
if TSM.db.global.openAllLeaveGold then |
|
private.lootIndex = private.lootIndex + 1 |
|
return private:AutoLoot() |
|
end |
|
end |
|
if private:CanLootMailIndex(private.lootIndex) then |
|
if money > 0 then |
|
private.moneyCollected = private.moneyCollected + money |
|
end |
|
private:LootMailItem(private.lootIndex) |
|
else |
|
private.lootIndex = private.lootIndex + 1 |
|
return private:AutoLoot() |
|
end |
|
else |
|
if private:CanLootMailIndex(private.lootIndex) and private:ShouldOpenMail(private.lootIndex) then |
|
if money > 0 then |
|
private.moneyCollected = private.moneyCollected + money |
|
end |
|
private:LootMailItem(private.lootIndex) |
|
else |
|
private.lootIndex = private.lootIndex + 1 |
|
return private:AutoLoot() |
|
end |
|
end |
|
-- Can't grab the first mail, so increase it and try again |
|
elseif GetInboxNumItems() >= private.lootIndex then |
|
private.lootIndex = private.lootIndex + 1 |
|
private:AutoLoot() |
|
end |
|
end |
|
|
|
function private:LootMailItem(index) |
|
if not MailFrame or not MailFrame:IsShown() then |
|
private:StopAutoLooting() |
|
return |
|
end |
|
if TSM.db.global.inboxMessages then |
|
--local _, _, sender, subject, money, cod, _, hasItem = GetInboxHeaderInfo(index) |
|
local _, _, sender, subject, money, cod, daysLeft, hasItem, _, _, _, _, _, itemQuantity = GetInboxHeaderInfo(index) |
|
sender = sender or "?" |
|
isTakeable = select(4, GetInboxText(index)) |
|
if isTakeable then |
|
-- it's an invoice |
|
local invoiceType, itemName, playerName, bid, _, _, ahcut, _, _, _, quantity = GetInboxInvoiceInfo(index) |
|
|
|
-- fix MoP difference |
|
if not quantity then |
|
quantity = select(3, GetInboxItem(index)) |
|
end |
|
|
|
local redColor = "|cffFF0000" |
|
local greenColor = "|cff00FF00" |
|
local yellowColor = "|cffFFFF00" |
|
|
|
if invoiceType == "buyer" then |
|
local itemLink = GetInboxItemLink(index, 1) or itemName |
|
TSM:Printf(L["Collected purchase of %s (%d) for %s."], itemLink, quantity or 1, TSMAPI:FormatTextMoney(bid, redColor)) |
|
elseif invoiceType == "seller" then |
|
TSM:Printf(L["Collected sale of %s (%d) for %s."], itemName, quantity or 1, TSMAPI:FormatTextMoney(bid - ahcut, greenColor)) |
|
-- TSM:Printf("Collected sale of %s for %s.", itemName, TSMAPI:FormatTextMoney(bid - ahcut, greenColor)) |
|
elseif invoiceType == "seller_temp_invoice" then |
|
-- TSM:Printf("Removing pending sale: %s (%s)", itemName, TSMAPI:FormatTextMoney(bid - ahcut, yellowColor)) |
|
TSM:Printf("Removing pending sale of %s (%d) for %s.", itemName, quantity or 1, TSMAPI:FormatTextMoney(bid - ahcut, yellowColor)) |
|
DeleteInboxItem(index) |
|
return |
|
end |
|
elseif hasItem then |
|
local itemLink |
|
local quantity = 0 |
|
for i = 1, hasItem do |
|
local link = GetInboxItemLink(index, i) |
|
itemLink = itemLink or link |
|
quantity = quantity + select(3, GetInboxItem(index, i)) |
|
if TSMAPI:GetItemString(itemLink) ~= TSMAPI:GetItemString(link) then |
|
itemLink = L["Multiple Items"] |
|
quantity = -1 |
|
break |
|
end |
|
end |
|
local itemDesc = (quantity > 0 and format("%s (%d)", itemLink, quantity)) or (quantity == -1 and "Multiple Items") or "?" |
|
if hasItem == 1 and itemLink and strfind(subject, "^" .. TSMAPI:StrEscape(format(AUCTION_EXPIRED_MAIL_SUBJECT, TSMAPI:GetSafeItemInfo(itemLink)))) then |
|
TSM:Printf(L["Collected expired auction of %s"], itemDesc) |
|
elseif cod > 0 then |
|
TSM:Printf(L["Collected COD of %s from %s for %s."], itemDesc, sender, TSMAPI:FormatTextMoney(cod, redColor)) |
|
elseif money > 0 then |
|
TSM:Printf(L["Collected %s and %s from %s."], itemDesc, TSMAPI:FormatTextMoney(money, greenColor), sender) |
|
else |
|
TSM:Printf(L["Collected %s from %s."], itemDesc, sender) |
|
end |
|
elseif money > 0 then |
|
TSM:Printf(L["Collected %s from %s."], TSMAPI:FormatTextMoney(money, greenColor), sender) |
|
elseif (sender == "The Postmaster" or sender == "Blackwater Auction House" or sender == "Customer Support" and subject == "Your Ascension Order" and daysLeft < 270) and money == 0 and hasItem == nil then |
|
TSM:Printf("Removing empty mail from %s.", sender) |
|
DeleteInboxItem(index) |
|
else |
|
TSM:Printf(L["Collected mail from %s with a subject of '%s'."], sender, subject) |
|
end |
|
end |
|
AutoLootMailItem(index) |
|
end |
|
|
|
function private:CanLootMailIndex(index) |
|
local hasItem = select(8, GetInboxHeaderInfo(index)) |
|
if not hasItem or hasItem == 0 then return true end |
|
|
|
if not TSM.db.global.keepMailSpace or TSM.db.global.keepMailSpace == 0 then |
|
for j = 1, ATTACHMENTS_MAX_RECEIVE do |
|
local itemString = TSMAPI:GetItemString(GetInboxItemLink(index, j)) |
|
local quantity = select(3, GetInboxItem(index, j)) |
|
local space = 0 |
|
if itemString then |
|
for bag = 0, NUM_BAG_SLOTS do |
|
if TSMAPI:ItemWillGoInBag(itemString, bag) then |
|
for slot = 1, GetContainerNumSlots(bag) do |
|
local iString = TSMAPI:GetItemString(GetContainerItemLink(bag, slot)) |
|
if iString == itemString then |
|
local stackSize = select(2, GetContainerItemInfo(bag, slot)) |
|
local maxStackSize = select(8, TSMAPI:GetSafeItemInfo(itemString)) |
|
if (maxStackSize - stackSize) >= quantity then |
|
return true |
|
end |
|
elseif not iString then |
|
return true |
|
end |
|
end |
|
end |
|
end |
|
end |
|
end |
|
else |
|
-- get number of free slots per generic / special bags and partial slots by bag |
|
local genericSpace, uniqueSpace, partSlots = private:GetBagSlots() |
|
local usedSlots = {} |
|
for j = 1, ATTACHMENTS_MAX_RECEIVE do |
|
local itemString = TSMAPI:GetItemString(GetInboxItemLink(index, j)) |
|
local quantity = select(3, GetInboxItem(index, j)) |
|
local isDone = false |
|
if itemString then |
|
for bag = 0, NUM_BAG_SLOTS do |
|
if TSMAPI:ItemWillGoInBag(itemString, bag) then |
|
for slot = 1, GetContainerNumSlots(bag) do |
|
local iString = TSMAPI:GetItemString(GetContainerItemLink(bag, slot)) |
|
if iString == itemString and (partSlots[bag] and partSlots[bag][slot]) then |
|
local stackSize = select(2, GetContainerItemInfo(bag, slot)) |
|
local maxStackSize = select(8, TSMAPI:GetSafeItemInfo(itemString)) |
|
if (maxStackSize - stackSize - (usedSlots[bag] and usedSlots[bag][slot] or 0)) >= quantity then |
|
if stackSize + quantity == maxStackSize then |
|
if partSlots[bag] and partSlots[bag][slot] then |
|
partSlots[bag][slot] = nil -- this partial slot would be filled so remove it from available part slots |
|
end |
|
else |
|
if not usedSlots[bag] then |
|
usedSlots[bag] = {} |
|
end |
|
usedSlots[bag][slot] = (usedSlots[bag][slot] or 0) + stackSize -- store the stacksize for this slot after adding this item |
|
end |
|
isDone = true |
|
break |
|
end |
|
else |
|
local itemFamily = GetItemFamily(itemString) |
|
local bagFamily = GetItemFamily(GetBagName(bag)) or 0 |
|
if itemFamily and bagFamily and bagFamily > 0 and bit.band(itemFamily, bagFamily) > 0 and (uniqueSpace[bag] and uniqueSpace[bag] > 0) then |
|
uniqueSpace[bag] = uniqueSpace[bag] - 1 -- remove one empty slot from the bag |
|
isDone = true |
|
break |
|
else |
|
if genericSpace[bag] and genericSpace[bag] > 0 then |
|
genericSpace[bag] = genericSpace[bag] - 1 -- remove one empty slot from the bag |
|
if genericSpace[bag] <= 0 then |
|
genericSpace[bag] = nil |
|
end |
|
isDone = true |
|
break |
|
end |
|
end |
|
end |
|
end |
|
if isDone then break end |
|
end |
|
end |
|
end |
|
end |
|
|
|
--calculate the total remaining empty slots in generic bags after looting this mail |
|
local remainingSpace = 0 |
|
for bag, space in pairs(genericSpace) do |
|
remainingSpace = remainingSpace + space |
|
end |
|
if remainingSpace >= TSM.db.global.keepMailSpace then |
|
return true -- either not using keepMailSpace option or we can loot all of this mail |
|
else |
|
private.keepFreeSlots = true -- can't loot the whole of this mail and leave enough free slots so set the flag that displays the chat message |
|
end |
|
end |
|
end |
|
|
|
function private:StopAutoLooting(failed) |
|
if failed and (not private.frame or not private.frame:IsVisible()) then |
|
TSM:Print(L["Cannot finish auto looting, inventory is full or too many unique items."]) |
|
end |
|
|
|
if private.keepFreeSlots then |
|
TSM:Printf(L["Cannot finish auto looting, keeping %s slots free."], TSM.db.global.keepMailSpace) |
|
private.keepFreeSlots = false |
|
end |
|
|
|
private.mode = nil |
|
private.resetIndex = nil |
|
private.autoLootTotal = nil |
|
if not private.frame then return end |
|
private.frame:EnableButtons() |
|
TSMAPI:CancelFrame("mailWaitDelay") |
|
|
|
--Tell user how much money has been collected if they don't have it turned off in TradeSkillMaster_Mailing options |
|
if private.moneyCollected and private.moneyCollected > 0 and TSM.db.global.displayMoneyCollected then |
|
TSM:Printf(L["%s total gold collected!"], TSMAPI:FormatTextMoney(private.moneyCollected)) |
|
private.moneyCollected = 0 |
|
end |
|
end |
|
|
|
function private:GetBagSlots() |
|
local genericSpace, uniqueSpace, partSlots = {}, {}, {} |
|
for bag = 0, NUM_BAG_SLOTS do |
|
if (GetItemFamily(GetBagName(bag)) or 0) > 0 then |
|
uniqueSpace[bag] = GetContainerNumFreeSlots(bag) or 0 |
|
else |
|
genericSpace[bag] = GetContainerNumFreeSlots(bag) or 0 |
|
end |
|
for slot = 1, GetContainerNumSlots(bag) do |
|
local iLink = GetContainerItemLink(bag, slot) |
|
if iLink then |
|
if not partSlots[bag] then |
|
partSlots[bag] = {} |
|
end |
|
table.insert(partSlots[bag], slot) |
|
end |
|
end |
|
end |
|
return genericSpace, uniqueSpace, partSlots |
|
end |
|
|
|
function Inbox:UI_ERROR_MESSAGE(event, msg) |
|
if msg == ERR_MAIL_DATABASE_ERROR then |
|
-- recover from internal mail error |
|
TSMAPI:CreateTimeDelay("mailWaitDelay", 1, private.AutoLoot) |
|
elseif msg == ERR_INV_FULL or msg == ERR_ITEM_MAX_COUNT then |
|
-- Try the next index in case we can still loot more such as in the case of glyphs |
|
private.lootIndex = private.lootIndex + 1 |
|
|
|
-- If we've exhausted all slots, but we still have <50 and more mail pending, wait until new data comes and keep looting it |
|
local current, total = GetInboxNumItems() |
|
if private.lootIndex > current then |
|
if private.lootIndex > total and total <= 50 then |
|
private:StopAutoLooting(true) |
|
end |
|
return |
|
end |
|
|
|
TSMAPI:CreateTimeDelay("mailWaitDelay", math.max(5/GetFramerate(), 0.3), private.AutoLoot) |
|
end |
|
end |
|
|
|
function Inbox:MAIL_CLOSED() |
|
private.resetIndex = nil |
|
private.allowTimerStart = true |
|
private.waitingForData = nil |
|
private:StopAutoLooting() |
|
TSMAPI:CancelFrame("inboxLootTextDelay") |
|
TSMAPI:CancelFrame("mailSkipDelay") |
|
Inbox:UnregisterEvent("UI_ERROR_MESSAGE") --sometimes shows error messages even when closed, so unregister that event. |
|
end
|
|
|