Asa@63: local ItemAuditor = select(2, ...) Asa@63: local Events = ItemAuditor:NewModule("Events", "AceEvent-3.0") Asa@3: Asa@92: local Utils = ItemAuditor:GetModule("Utils") Asa@92: Asa@63: function ItemAuditor:OnEnable() Asa@3: self:RegisterEvent("MAIL_SHOW") Asa@4: self:RegisterEvent("UNIT_SPELLCAST_START") Asa@81: Asa@81: self:RegisterEvent("BANKFRAME_OPENED", 'BankFrameChanged') Asa@81: self:RegisterEvent("BANKFRAME_CLOSED", 'BankFrameChanged') Asa@81: Asa@63: ItemAuditor:UpdateCurrentInventory() Asa@3: self:WatchBags() Asa@9: Asa@63: self:SetEnabled(nil, self.db.profile.ItemAuditor_enabled) Asa@38: end Asa@38: Asa@63: function ItemAuditor:OnDisable() Asa@38: self:UnwatchBags() Asa@38: self:UnregisterAllEvents() Asa@63: ItemAuditor:HideAllFrames() Asa@3: end Asa@3: Asa@63: function ItemAuditor:MAIL_SHOW() Asa@3: self:Debug("MAIL_SHOW") Asa@113: self.mailOpen = true Asa@63: ItemAuditor:UpdateCurrentInventory() Asa@3: self.lastMailScan = self:ScanMail() Asa@7: Asa@3: self:UnregisterEvent("MAIL_SHOW") Asa@3: self:RegisterEvent("MAIL_CLOSED") Asa@3: self:RegisterEvent("MAIL_INBOX_UPDATE") Asa@39: Asa@39: self:GenerateBlankOutbox() Asa@39: Asa@39: self:RegisterEvent("MAIL_SUCCESS") Asa@39: end Asa@39: Asa@63: function ItemAuditor:GenerateBlankOutbox() Asa@39: self.mailOutbox = { Asa@39: from = UnitName("player"), Asa@39: to = "", Asa@39: subject = "", Asa@39: link = '', Asa@39: count = 0, Asa@39: COD = 0, Asa@39: key = random(10000), Asa@39: sent = 0, Asa@39: } Asa@39: Asa@39: if self.db.factionrealm.outbound_cod[self.mailOutbox.key] ~= nil then Asa@39: return self:GenerateBlankOutbox() Asa@39: end Asa@39: end Asa@39: Asa@40: local attachedItems = {} Asa@39: local Orig_SendMail = SendMail Asa@42: local skipCODTracking = false Asa@92: local skipCODCheck = false Asa@42: Asa@42: StaticPopupDialogs["ItemAuditor_Send_COD_without_tracking_number"] = { Asa@42: text = "ItemAuditor cannot track COD mail with multiple item types attached. Do you want to send this mail without tracking?", Asa@42: button1 = "Yes", Asa@42: button2 = "No", Asa@42: OnAccept = function() Asa@42: skipCODTracking = true Asa@42: end, Asa@42: timeout = 0, Asa@42: whileDead = true, Asa@42: hideOnEscape = true, Asa@42: } Asa@39: Asa@92: StaticPopupDialogs["ItemAuditor_Insufficient_COD"] = { Asa@92: text = "The COD on this mail is less than the value of items attached. Are you sure you want to send this?|nTotal value (including postage): %s", Asa@92: button1 = "Yes", Asa@92: button2 = "No", Asa@92: OnAccept = function() Asa@92: skipCODCheck = true Asa@92: end, Asa@92: timeout = 0, Asa@92: whileDead = true, Asa@92: hideOnEscape = true, Asa@92: } Asa@92: Asa@39: function SendMail(recipient, subject, body, ...) Asa@39: local self = ItemAuditor Asa@39: self:GenerateBlankOutbox() Asa@39: Asa@39: self:Debug(format("[To: %s] [Subject: %s]", recipient, subject)) Asa@39: Asa@39: self.mailOutbox.COD = GetSendMailCOD() Asa@39: Asa@92: wipe(attachedItems) Asa@40: local totalStacks = 0 Asa@39: local link Asa@40: for index = 1, ATTACHMENTS_MAX_SEND do Asa@39: local itemName, _, itemCount = GetSendMailItem(index) Asa@39: local newLink = GetSendMailItemLink(index) Asa@39: Asa@40: if newLink ~= nil then Asa@40: newLink = self:GetSafeLink(newLink) Asa@40: totalStacks = totalStacks + 1 Asa@40: attachedItems[newLink] = (attachedItems[newLink] or {stacks = 0, count = 0}) Asa@40: attachedItems[newLink].stacks = attachedItems[newLink].stacks + 1 Asa@40: attachedItems[newLink].count = attachedItems[newLink].count + itemCount Asa@40: attachedItems[newLink].price = 0 -- This is a placeholder for below. Asa@144: attachedItems[newLink].costEach = select(2, ItemAuditor:GetItemCost(newLink)) Asa@39: end Asa@40: end Asa@92: local attachedValue = 0 Asa@40: for link, data in pairs(attachedItems) do Asa@53: data.price = 30 * data.stacks Asa@144: attachedValue = attachedValue + data.price + (data.costEach * data.count) Asa@40: end Asa@144: Asa@144: local destinationType = 'unknown' Asa@144: local realm = GetRealmName() Asa@144: for account in pairs(DataStore:GetAccounts()) do Asa@144: for character in pairs(DataStore:GetCharacters(realm, account)) do Asa@144: if strlower(recipient) == strlower(character) then Asa@144: destinationType = (account == 'Default') and 'same_account' or 'owned_account' Asa@144: destinationType = 'owned_account' Asa@144: break Asa@144: end Asa@92: end Asa@92: end Asa@144: self.mailOutbox.destinationType = destinationType Asa@144: if destinationType == 'unknown' and attachedValue > self.mailOutbox.COD and not skipCODCheck and ItemAuditor.db.char.cod_warnings then Asa@92: self:GenerateBlankOutbox() Asa@92: skipCODCheck = false; Asa@92: local vararg = ... Asa@92: StaticPopupDialogs["ItemAuditor_Insufficient_COD"].OnAccept = function() Asa@92: skipCODCheck = true Asa@92: SendMail(recipient, subject, body, vararg) Asa@92: skipCODCheck = false Asa@92: end Asa@92: StaticPopup_Show ("ItemAuditor_Insufficient_COD", Utils.FormatMoney(attachedValue)); Asa@92: return Asa@144: elseif destinationType == 'owned_account' then Asa@144: -- If we are mailing to an alt on a different account, a uniqueue tracking number Asa@144: -- is generated and all of the needed data is attached to the message. Asa@144: -- The tracking number is only used to make sure the other character doesn't count the Asa@144: -- mail more than once. Asa@144: local key = time()..":"..random(10000) Asa@144: self.mailOutbox.attachedItems = attachedItems Asa@144: body = body .. ItemAuditor.TRACKING_DATA_DIVIDER .. ItemAuditor:Serialize(key, attachedItems) Asa@92: elseif self.mailOutbox.COD > 0 and skipCODTracking then Asa@42: Asa@42: elseif self.mailOutbox.COD > 0 then Asa@40: if self:tcount(attachedItems) > 1 then Asa@39: self:GenerateBlankOutbox() Asa@42: local vararg = ... Asa@42: StaticPopupDialogs["ItemAuditor_Send_COD_without_tracking_number"].OnAccept = function() Asa@42: skipCODTracking = true Asa@42: SendMail(recipient, subject, body, vararg) Asa@42: skipCODTracking = false Asa@42: end Asa@42: StaticPopup_Show ("ItemAuditor_Send_COD_without_tracking_number"); Asa@39: return Asa@39: end Asa@40: self:Debug("COD mail") Asa@39: Asa@40: subject = format("[IA: %s] %s", self.mailOutbox.key, subject) Asa@40: self.mailOutbox.subject = subject Asa@40: self.mailOutbox.to = recipient Asa@40: Asa@40: -- At this point we know there is only one item Asa@40: for link, data in pairs(attachedItems) do Asa@40: self.mailOutbox.link = link Asa@40: self.mailOutbox.count = data.count Asa@40: end Asa@40: else Asa@40: self:Debug("Non-COD mail") Asa@39: end Asa@40: Asa@39: return Orig_SendMail(recipient, subject, body, ...) Asa@39: end Asa@39: Asa@63: function ItemAuditor:MAIL_SUCCESS(event) Asa@42: skipCODTracking = false Asa@92: skipCODCheck = false Asa@92: Asa@40: for link, data in pairs(attachedItems) do Asa@144: -- When mailing to an alt on a different account, we still Asa@144: -- should add the price of postage, but need to subtract the Asa@144: -- cost of the items. This will simulate CODing the mail and Asa@144: -- getting the money back, except that postage is paid by the Asa@144: -- sender Asa@144: if self.mailOutbox.destinationType == 'owned_account' then Asa@144: data.price = data.price - (data.costEach * data.count) Asa@144: end Asa@40: self:SaveValue(link, data.price, data.count) Asa@40: end Asa@39: if self.mailOutbox.COD > 0 then Asa@39: self:Debug(format("MAIL_SUCCESS %d [To: %s] [Subject: %s] [COD: %s]", self.mailOutbox.key, self.mailOutbox.to, self.mailOutbox.subject, self.mailOutbox.COD)) Asa@39: Asa@39: self.mailOutbox.sent = time() Asa@39: self.db.factionrealm.outbound_cod[self.mailOutbox.key] = self.mailOutbox Asa@39: end Asa@92: Asa@92: wipe(attachedItems) Asa@40: self:GenerateBlankOutbox() Asa@3: end Asa@3: Asa@63: function ItemAuditor:MAIL_CLOSED() Asa@23: self:Debug("MAIL_CLOSED") Asa@63: ItemAuditor:UnregisterEvent("MAIL_CLOSED") Asa@7: self:MAIL_INBOX_UPDATE() Asa@3: self:UnregisterEvent("MAIL_INBOX_UPDATE") Asa@3: self:RegisterEvent("MAIL_SHOW") Asa@113: self.mailOpen = nil Asa@3: end Asa@3: Asa@144: local function CanMailBeDeleted(mailIndex) Asa@144: local msgMoney, _, _, msgItem = select(5, GetInboxHeaderInfo(mailIndex)) Asa@144: local body = GetInboxText(mailIndex) Asa@144: if msgMoney == 0 and msgItem == nil and body and body:find(ItemAuditor.TRACKING_DATA_DIVIDER) then Asa@144: local serialized = body:gsub('.*'..ItemAuditor.TRACKING_DATA_DIVIDER, '') Asa@144: local body = body:gsub(ItemAuditor.TRACKING_DATA_DIVIDER..'.*', '') Asa@144: local success, trackingID, data = ItemAuditor:Deserialize(serialized) Asa@144: if success and body == '' then Asa@144: return true Asa@144: end Asa@144: end Asa@144: return false Asa@144: end Asa@144: Asa@144: local Postal_L Asa@144: local function blockMailOperations() Asa@144: if MailAddonBusy == 'ItemAuditor' then Asa@144: return false Asa@144: end Asa@144: if Postal_L == nil then Asa@144: local locale = LibStub("AceLocale-3.0", true) Asa@144: Postal_L = locale and locale:GetLocale("Postal", true) Asa@144: end Asa@144: return MailAddonBusy or PostalOpenAllButton and Postal_L and PostalOpenAllButton:GetText() == Postal_L["In Progress"] Asa@144: end Asa@144: Asa@26: local storedCountDiff Asa@63: function ItemAuditor:MAIL_INBOX_UPDATE() Asa@23: self:Debug("MAIL_INBOX_UPDATE") Asa@144: self.deleteQueue = nil Asa@144: local newScan = self:ScanMail() Asa@3: local diff Asa@39: Asa@6: for mailType, collection in pairs(self.lastMailScan) do Asa@7: newScan[mailType] = (newScan[mailType] or {}) Asa@26: for itemName, data in pairs(collection) do Asa@26: newScan[mailType][itemName] = (newScan[mailType][itemName] or {total=0,count=0}) Asa@26: local totalDiff = data.total - newScan[mailType][itemName].total Asa@26: local countDiff = data.count - newScan[mailType][itemName].count Asa@26: --[[ Asa@26: In one update the item will be taken and in the following update the invoice Asa@26: will be gone. I need to store the item difference in order ot pass it into Asa@26: SaveValue. Asa@26: ]] Asa@26: if countDiff ~= 0 then Asa@26: storedCountDiff = countDiff Asa@26: end Asa@26: Asa@26: if totalDiff ~= 0 then Asa@39: if mailType == "CODPayment" then Asa@39: local trackID Asa@39: trackID, itemName= strsplit("|", itemName, 2) Asa@39: self.db.factionrealm.outbound_cod[tonumber(trackID)] = nil Asa@39: self:Debug("Removing COD Tracker: " .. trackID) Asa@39: end Asa@26: self:SaveValue(itemName, totalDiff, storedCountDiff) Asa@26: storedCountDiff = 0 Asa@6: end Asa@6: Asa@3: end Asa@3: end Asa@3: Asa@3: self.lastMailScan = newScan Asa@144: Asa@144: if self.deleteQueue and not self.deleteScheduled then Asa@144: -- For some reason DeleteInboxItem will not trigger a MAIL_INBOX_UPDATE Asa@144: -- if it is called from here, so I have to use a timer to get it Asa@144: -- to run outside of this function. Asa@144: Asa@144: -- If the mailbox is full of items to be deleted, this will speed up because Asa@144: -- postal shouldn't be running at this point. Keeping at 0.1 breaks postal. Asa@144: local delay = (GetInboxNumItems() > #(self.deleteQueue)) and 1 or 0.1 Asa@144: self:ScheduleTimer("ProcessDeleteQueue", delay) Asa@144: self.deleteScheduled = true Asa@144: elseif MailAddonBusy == 'ItemAuditor' then Asa@144: MailAddonBusy = nil Asa@144: end Asa@144: end Asa@144: Asa@144: function ItemAuditor:ProcessDeleteQueue() Asa@144: if blockMailOperations() then Asa@144: self:ScheduleTimer("ProcessDeleteQueue", 1) Asa@144: return Asa@144: end Asa@144: self.deleteScheduled = false Asa@144: if self.deleteQueue then Asa@144: MailAddonBusy = 'ItemAuditor' Asa@144: while #(self.deleteQueue) > 0 do Asa@144: local mailIndex = table.remove(self.deleteQueue) Asa@144: if CanMailBeDeleted(mailIndex) then Asa@144: DeleteInboxItem(mailIndex) Asa@144: -- This returns after the first item because you can't delete Asa@144: -- all mail at once and this is in a loop so that if for some Asa@144: -- reason CanMailBeDeleted returns false, we can delete the next Asa@144: -- mail in the queue instead. Asa@144: return Asa@144: end Asa@144: end Asa@144: else Asa@144: MailAddonBusy = nil Asa@144: end Asa@3: end Asa@3: Asa@63: function ItemAuditor:UNIT_SPELLCAST_START(event, target, spell) Asa@5: if target == "player" and spell == "Milling" or spell == "Prospecting" or spell == "Disenchanting" then Asa@23: self:Debug(event .. " " .. spell) Asa@4: self:UnwatchBags() Asa@4: self:UpdateCurrentInventory() Asa@4: self:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED") Asa@4: self:RegisterEvent("LOOT_CLOSED") Asa@3: end Asa@3: end Asa@3: Asa@4: --[[ Asa@4: The item should be destroyed before this point, so the last inventory check Asa@4: needs to be kept so it can be combined with the up coming loot. Asa@4: ]] Asa@63: function ItemAuditor:LOOT_CLOSED() Asa@23: self:Debug("LOOT_CLOSED") Asa@4: self:UnregisterEvent("LOOT_CLOSED") Asa@4: self:UnregisterEvent("UNIT_SPELLCAST_INTERRUPTED") Asa@4: local inventory = self.lastInventory Asa@4: self:WatchBags() Asa@4: self.lastInventory = inventory Asa@4: end Asa@3: Asa@63: function ItemAuditor:UNIT_SPELLCAST_INTERRUPTED(event, target, spell) Asa@5: if target == "player" and spell == "Milling" or spell == "Prospecting" or spell == "Disenchanting" then Asa@23: self:Debug(event .. " " .. spell) Asa@4: self:UnregisterEvent("UNIT_SPELLCAST_INTERRUPTED") Asa@4: self:UnregisterEvent("LOOT_CLOSED") Asa@4: self:WatchBags() Asa@4: end Asa@4: end Asa@4: Asa@63: function ItemAuditor:UpdateCurrentInventory() Asa@4: self.lastInventory = self:GetCurrentInventory() Asa@3: end Asa@3: Asa@49: local function distributeValue(self, totalValue, targetItems) Asa@46: Asa@46: local weights = {} Asa@46: local totalWeight = 0 Asa@137: for itemID, change in pairs(targetItems) do Asa@46: --[[ Asa@46: If something has never been seen on the AH, it must not be very valuable. Asa@46: I'm using 1c so it doesn't have much weight and I can't get a devided by zero error. Asa@46: The only time I know that this is a problem is when crafting a BOP item, and it Asa@46: is always crafted 1 at a time, so a weight of 1 will work. Asa@46: ]] Asa@137: local ap = (ItemAuditor:GetAuctionPrice(itemID) or 1) * change Asa@46: totalWeight = totalWeight + ap Asa@137: weights[itemID] = ap Asa@46: end Asa@46: Asa@137: for itemID, change in pairs(targetItems) do Asa@137: local value = totalValue * (weights[itemID]/totalWeight) Asa@137: self:SaveValue(itemID, value, change) Asa@46: end Asa@46: end Asa@46: Asa@63: function ItemAuditor:UpdateAudit() Asa@23: -- self:Debug("UpdateAudit " .. event) Asa@3: local currentInventory = self:GetCurrentInventory() Asa@63: local diff = ItemAuditor:GetInventoryDiff(self.lastInventory, currentInventory) Asa@3: Asa@5: local positive, negative = {}, {} Asa@5: local positiveCount, negativeCount = 0, 0 Asa@137: for itemID, count in pairs(diff.items) do Asa@5: if count > 0 then Asa@137: positive[itemID] = count Asa@5: positiveCount = positiveCount + count Asa@5: elseif count < 0 then Asa@137: negative[itemID] = count Asa@5: negativeCount = negativeCount + abs(count) Asa@5: end Asa@5: end Asa@5: Asa@23: if positiveCount + negativeCount == 0 then Asa@33: --[[ Asa@33: Nothing needs to be done, but this will prevent mistakenly attributing Asa@33: the cost of flights to the first item you pick up. Asa@33: ]] Asa@33: elseif diff.money > 0 and self:tcount(positive) > 0 and self:tcount(negative) == 0 then Asa@15: self:Debug("loot") Asa@113: elseif abs(diff.money) > 0 and self:tcount(diff.items) == 1 and not self.mailOpen then Asa@15: self:Debug("purchase or sale") Asa@3: Asa@137: for itemID, count in pairs(diff.items) do Asa@140: self:SaveValue(itemID, 0 - diff.money, itemID) Asa@3: end Asa@23: elseif self:tcount(diff.items) > 1 and self:tcount(positive) > 0 and self:tcount(negative) > 0 then Asa@23: -- we must have created/converted something Asa@23: self:Debug("conversion") Asa@3: Asa@23: local totalChange = 0 Asa@137: for itemID, change in pairs(negative) do Asa@137: local _, itemCost, count = self:GetItemCost(itemID, change) Asa@137: self:SaveValue(itemID, itemCost * change, change) Asa@10: Asa@23: totalChange = totalChange + (itemCost * abs(change)) Asa@3: end Asa@91: totalChange = totalChange - diff.money Asa@23: Asa@46: distributeValue(self, totalChange, positive) Asa@23: else Asa@23: self:Debug("No match in UpdateAudit.") Asa@3: end Asa@3: Asa@3: self.lastInventory = currentInventory Asa@63: ItemAuditor:WatchBags() Asa@3: end