annotate Modules/Events.lua @ 144:03e108d12ef1

Ticket 45 - Added the ability suppress COD warnings when mailing to characters on other accounts in Altoholic/DataStore. Instead of sending COD, ItemAuditor will send tracking info in the message. Thanks to Zerotorescue for giving me the solution to detect whether or not Postal or MailOpener is processing. It was the last step holding this back from being released.
author Asa Ayers <Asa.Ayers@Gmail.com>
date Sat, 09 Oct 2010 00:21:06 -0700
parents 828cd9bf919e
children 47885ba83d38
rev   line source
Asa@63 1 local ItemAuditor = select(2, ...)
Asa@63 2 local Events = ItemAuditor:NewModule("Events", "AceEvent-3.0")
Asa@3 3
Asa@92 4 local Utils = ItemAuditor:GetModule("Utils")
Asa@92 5
Asa@63 6 function ItemAuditor:OnEnable()
Asa@3 7 self:RegisterEvent("MAIL_SHOW")
Asa@4 8 self:RegisterEvent("UNIT_SPELLCAST_START")
Asa@81 9
Asa@81 10 self:RegisterEvent("BANKFRAME_OPENED", 'BankFrameChanged')
Asa@81 11 self:RegisterEvent("BANKFRAME_CLOSED", 'BankFrameChanged')
Asa@81 12
Asa@63 13 ItemAuditor:UpdateCurrentInventory()
Asa@3 14 self:WatchBags()
Asa@9 15
Asa@63 16 self:SetEnabled(nil, self.db.profile.ItemAuditor_enabled)
Asa@38 17 end
Asa@38 18
Asa@63 19 function ItemAuditor:OnDisable()
Asa@38 20 self:UnwatchBags()
Asa@38 21 self:UnregisterAllEvents()
Asa@63 22 ItemAuditor:HideAllFrames()
Asa@3 23 end
Asa@3 24
Asa@63 25 function ItemAuditor:MAIL_SHOW()
Asa@3 26 self:Debug("MAIL_SHOW")
Asa@113 27 self.mailOpen = true
Asa@63 28 ItemAuditor:UpdateCurrentInventory()
Asa@3 29 self.lastMailScan = self:ScanMail()
Asa@7 30
Asa@3 31 self:UnregisterEvent("MAIL_SHOW")
Asa@3 32 self:RegisterEvent("MAIL_CLOSED")
Asa@3 33 self:RegisterEvent("MAIL_INBOX_UPDATE")
Asa@39 34
Asa@39 35 self:GenerateBlankOutbox()
Asa@39 36
Asa@39 37 self:RegisterEvent("MAIL_SUCCESS")
Asa@39 38 end
Asa@39 39
Asa@63 40 function ItemAuditor:GenerateBlankOutbox()
Asa@39 41 self.mailOutbox = {
Asa@39 42 from = UnitName("player"),
Asa@39 43 to = "",
Asa@39 44 subject = "",
Asa@39 45 link = '',
Asa@39 46 count = 0,
Asa@39 47 COD = 0,
Asa@39 48 key = random(10000),
Asa@39 49 sent = 0,
Asa@39 50 }
Asa@39 51
Asa@39 52 if self.db.factionrealm.outbound_cod[self.mailOutbox.key] ~= nil then
Asa@39 53 return self:GenerateBlankOutbox()
Asa@39 54 end
Asa@39 55 end
Asa@39 56
Asa@40 57 local attachedItems = {}
Asa@39 58 local Orig_SendMail = SendMail
Asa@42 59 local skipCODTracking = false
Asa@92 60 local skipCODCheck = false
Asa@42 61
Asa@42 62 StaticPopupDialogs["ItemAuditor_Send_COD_without_tracking_number"] = {
Asa@42 63 text = "ItemAuditor cannot track COD mail with multiple item types attached. Do you want to send this mail without tracking?",
Asa@42 64 button1 = "Yes",
Asa@42 65 button2 = "No",
Asa@42 66 OnAccept = function()
Asa@42 67 skipCODTracking = true
Asa@42 68 end,
Asa@42 69 timeout = 0,
Asa@42 70 whileDead = true,
Asa@42 71 hideOnEscape = true,
Asa@42 72 }
Asa@39 73
Asa@92 74 StaticPopupDialogs["ItemAuditor_Insufficient_COD"] = {
Asa@92 75 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 76 button1 = "Yes",
Asa@92 77 button2 = "No",
Asa@92 78 OnAccept = function()
Asa@92 79 skipCODCheck = true
Asa@92 80 end,
Asa@92 81 timeout = 0,
Asa@92 82 whileDead = true,
Asa@92 83 hideOnEscape = true,
Asa@92 84 }
Asa@92 85
Asa@39 86 function SendMail(recipient, subject, body, ...)
Asa@39 87 local self = ItemAuditor
Asa@39 88 self:GenerateBlankOutbox()
Asa@39 89
Asa@39 90 self:Debug(format("[To: %s] [Subject: %s]", recipient, subject))
Asa@39 91
Asa@39 92 self.mailOutbox.COD = GetSendMailCOD()
Asa@39 93
Asa@92 94 wipe(attachedItems)
Asa@40 95 local totalStacks = 0
Asa@39 96 local link
Asa@40 97 for index = 1, ATTACHMENTS_MAX_SEND do
Asa@39 98 local itemName, _, itemCount = GetSendMailItem(index)
Asa@39 99 local newLink = GetSendMailItemLink(index)
Asa@39 100
Asa@40 101 if newLink ~= nil then
Asa@40 102 newLink = self:GetSafeLink(newLink)
Asa@40 103 totalStacks = totalStacks + 1
Asa@40 104 attachedItems[newLink] = (attachedItems[newLink] or {stacks = 0, count = 0})
Asa@40 105 attachedItems[newLink].stacks = attachedItems[newLink].stacks + 1
Asa@40 106 attachedItems[newLink].count = attachedItems[newLink].count + itemCount
Asa@40 107 attachedItems[newLink].price = 0 -- This is a placeholder for below.
Asa@144 108 attachedItems[newLink].costEach = select(2, ItemAuditor:GetItemCost(newLink))
Asa@39 109 end
Asa@40 110 end
Asa@92 111 local attachedValue = 0
Asa@40 112 for link, data in pairs(attachedItems) do
Asa@53 113 data.price = 30 * data.stacks
Asa@144 114 attachedValue = attachedValue + data.price + (data.costEach * data.count)
Asa@40 115 end
Asa@144 116
Asa@144 117 local destinationType = 'unknown'
Asa@144 118 local realm = GetRealmName()
Asa@144 119 for account in pairs(DataStore:GetAccounts()) do
Asa@144 120 for character in pairs(DataStore:GetCharacters(realm, account)) do
Asa@144 121 if strlower(recipient) == strlower(character) then
Asa@144 122 destinationType = (account == 'Default') and 'same_account' or 'owned_account'
Asa@144 123 destinationType = 'owned_account'
Asa@144 124 break
Asa@144 125 end
Asa@92 126 end
Asa@92 127 end
Asa@144 128 self.mailOutbox.destinationType = destinationType
Asa@144 129 if destinationType == 'unknown' and attachedValue > self.mailOutbox.COD and not skipCODCheck and ItemAuditor.db.char.cod_warnings then
Asa@92 130 self:GenerateBlankOutbox()
Asa@92 131 skipCODCheck = false;
Asa@92 132 local vararg = ...
Asa@92 133 StaticPopupDialogs["ItemAuditor_Insufficient_COD"].OnAccept = function()
Asa@92 134 skipCODCheck = true
Asa@92 135 SendMail(recipient, subject, body, vararg)
Asa@92 136 skipCODCheck = false
Asa@92 137 end
Asa@92 138 StaticPopup_Show ("ItemAuditor_Insufficient_COD", Utils.FormatMoney(attachedValue));
Asa@92 139 return
Asa@144 140 elseif destinationType == 'owned_account' then
Asa@144 141 -- If we are mailing to an alt on a different account, a uniqueue tracking number
Asa@144 142 -- is generated and all of the needed data is attached to the message.
Asa@144 143 -- The tracking number is only used to make sure the other character doesn't count the
Asa@144 144 -- mail more than once.
Asa@144 145 local key = time()..":"..random(10000)
Asa@144 146 self.mailOutbox.attachedItems = attachedItems
Asa@144 147 body = body .. ItemAuditor.TRACKING_DATA_DIVIDER .. ItemAuditor:Serialize(key, attachedItems)
Asa@92 148 elseif self.mailOutbox.COD > 0 and skipCODTracking then
Asa@42 149
Asa@42 150 elseif self.mailOutbox.COD > 0 then
Asa@40 151 if self:tcount(attachedItems) > 1 then
Asa@39 152 self:GenerateBlankOutbox()
Asa@42 153 local vararg = ...
Asa@42 154 StaticPopupDialogs["ItemAuditor_Send_COD_without_tracking_number"].OnAccept = function()
Asa@42 155 skipCODTracking = true
Asa@42 156 SendMail(recipient, subject, body, vararg)
Asa@42 157 skipCODTracking = false
Asa@42 158 end
Asa@42 159 StaticPopup_Show ("ItemAuditor_Send_COD_without_tracking_number");
Asa@39 160 return
Asa@39 161 end
Asa@40 162 self:Debug("COD mail")
Asa@39 163
Asa@40 164 subject = format("[IA: %s] %s", self.mailOutbox.key, subject)
Asa@40 165 self.mailOutbox.subject = subject
Asa@40 166 self.mailOutbox.to = recipient
Asa@40 167
Asa@40 168 -- At this point we know there is only one item
Asa@40 169 for link, data in pairs(attachedItems) do
Asa@40 170 self.mailOutbox.link = link
Asa@40 171 self.mailOutbox.count = data.count
Asa@40 172 end
Asa@40 173 else
Asa@40 174 self:Debug("Non-COD mail")
Asa@39 175 end
Asa@40 176
Asa@39 177 return Orig_SendMail(recipient, subject, body, ...)
Asa@39 178 end
Asa@39 179
Asa@63 180 function ItemAuditor:MAIL_SUCCESS(event)
Asa@42 181 skipCODTracking = false
Asa@92 182 skipCODCheck = false
Asa@92 183
Asa@40 184 for link, data in pairs(attachedItems) do
Asa@144 185 -- When mailing to an alt on a different account, we still
Asa@144 186 -- should add the price of postage, but need to subtract the
Asa@144 187 -- cost of the items. This will simulate CODing the mail and
Asa@144 188 -- getting the money back, except that postage is paid by the
Asa@144 189 -- sender
Asa@144 190 if self.mailOutbox.destinationType == 'owned_account' then
Asa@144 191 data.price = data.price - (data.costEach * data.count)
Asa@144 192 end
Asa@40 193 self:SaveValue(link, data.price, data.count)
Asa@40 194 end
Asa@39 195 if self.mailOutbox.COD > 0 then
Asa@39 196 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 197
Asa@39 198 self.mailOutbox.sent = time()
Asa@39 199 self.db.factionrealm.outbound_cod[self.mailOutbox.key] = self.mailOutbox
Asa@39 200 end
Asa@92 201
Asa@92 202 wipe(attachedItems)
Asa@40 203 self:GenerateBlankOutbox()
Asa@3 204 end
Asa@3 205
Asa@63 206 function ItemAuditor:MAIL_CLOSED()
Asa@23 207 self:Debug("MAIL_CLOSED")
Asa@63 208 ItemAuditor:UnregisterEvent("MAIL_CLOSED")
Asa@7 209 self:MAIL_INBOX_UPDATE()
Asa@3 210 self:UnregisterEvent("MAIL_INBOX_UPDATE")
Asa@3 211 self:RegisterEvent("MAIL_SHOW")
Asa@113 212 self.mailOpen = nil
Asa@3 213 end
Asa@3 214
Asa@144 215 local function CanMailBeDeleted(mailIndex)
Asa@144 216 local msgMoney, _, _, msgItem = select(5, GetInboxHeaderInfo(mailIndex))
Asa@144 217 local body = GetInboxText(mailIndex)
Asa@144 218 if msgMoney == 0 and msgItem == nil and body and body:find(ItemAuditor.TRACKING_DATA_DIVIDER) then
Asa@144 219 local serialized = body:gsub('.*'..ItemAuditor.TRACKING_DATA_DIVIDER, '')
Asa@144 220 local body = body:gsub(ItemAuditor.TRACKING_DATA_DIVIDER..'.*', '')
Asa@144 221 local success, trackingID, data = ItemAuditor:Deserialize(serialized)
Asa@144 222 if success and body == '' then
Asa@144 223 return true
Asa@144 224 end
Asa@144 225 end
Asa@144 226 return false
Asa@144 227 end
Asa@144 228
Asa@144 229 local Postal_L
Asa@144 230 local function blockMailOperations()
Asa@144 231 if MailAddonBusy == 'ItemAuditor' then
Asa@144 232 return false
Asa@144 233 end
Asa@144 234 if Postal_L == nil then
Asa@144 235 local locale = LibStub("AceLocale-3.0", true)
Asa@144 236 Postal_L = locale and locale:GetLocale("Postal", true)
Asa@144 237 end
Asa@144 238 return MailAddonBusy or PostalOpenAllButton and Postal_L and PostalOpenAllButton:GetText() == Postal_L["In Progress"]
Asa@144 239 end
Asa@144 240
Asa@26 241 local storedCountDiff
Asa@63 242 function ItemAuditor:MAIL_INBOX_UPDATE()
Asa@23 243 self:Debug("MAIL_INBOX_UPDATE")
Asa@144 244 self.deleteQueue = nil
Asa@144 245 local newScan = self:ScanMail()
Asa@3 246 local diff
Asa@39 247
Asa@6 248 for mailType, collection in pairs(self.lastMailScan) do
Asa@7 249 newScan[mailType] = (newScan[mailType] or {})
Asa@26 250 for itemName, data in pairs(collection) do
Asa@26 251 newScan[mailType][itemName] = (newScan[mailType][itemName] or {total=0,count=0})
Asa@26 252 local totalDiff = data.total - newScan[mailType][itemName].total
Asa@26 253 local countDiff = data.count - newScan[mailType][itemName].count
Asa@26 254 --[[
Asa@26 255 In one update the item will be taken and in the following update the invoice
Asa@26 256 will be gone. I need to store the item difference in order ot pass it into
Asa@26 257 SaveValue.
Asa@26 258 ]]
Asa@26 259 if countDiff ~= 0 then
Asa@26 260 storedCountDiff = countDiff
Asa@26 261 end
Asa@26 262
Asa@26 263 if totalDiff ~= 0 then
Asa@39 264 if mailType == "CODPayment" then
Asa@39 265 local trackID
Asa@39 266 trackID, itemName= strsplit("|", itemName, 2)
Asa@39 267 self.db.factionrealm.outbound_cod[tonumber(trackID)] = nil
Asa@39 268 self:Debug("Removing COD Tracker: " .. trackID)
Asa@39 269 end
Asa@26 270 self:SaveValue(itemName, totalDiff, storedCountDiff)
Asa@26 271 storedCountDiff = 0
Asa@6 272 end
Asa@6 273
Asa@3 274 end
Asa@3 275 end
Asa@3 276
Asa@3 277 self.lastMailScan = newScan
Asa@144 278
Asa@144 279 if self.deleteQueue and not self.deleteScheduled then
Asa@144 280 -- For some reason DeleteInboxItem will not trigger a MAIL_INBOX_UPDATE
Asa@144 281 -- if it is called from here, so I have to use a timer to get it
Asa@144 282 -- to run outside of this function.
Asa@144 283
Asa@144 284 -- If the mailbox is full of items to be deleted, this will speed up because
Asa@144 285 -- postal shouldn't be running at this point. Keeping at 0.1 breaks postal.
Asa@144 286 local delay = (GetInboxNumItems() > #(self.deleteQueue)) and 1 or 0.1
Asa@144 287 self:ScheduleTimer("ProcessDeleteQueue", delay)
Asa@144 288 self.deleteScheduled = true
Asa@144 289 elseif MailAddonBusy == 'ItemAuditor' then
Asa@144 290 MailAddonBusy = nil
Asa@144 291 end
Asa@144 292 end
Asa@144 293
Asa@144 294 function ItemAuditor:ProcessDeleteQueue()
Asa@144 295 if blockMailOperations() then
Asa@144 296 self:ScheduleTimer("ProcessDeleteQueue", 1)
Asa@144 297 return
Asa@144 298 end
Asa@144 299 self.deleteScheduled = false
Asa@144 300 if self.deleteQueue then
Asa@144 301 MailAddonBusy = 'ItemAuditor'
Asa@144 302 while #(self.deleteQueue) > 0 do
Asa@144 303 local mailIndex = table.remove(self.deleteQueue)
Asa@144 304 if CanMailBeDeleted(mailIndex) then
Asa@144 305 DeleteInboxItem(mailIndex)
Asa@144 306 -- This returns after the first item because you can't delete
Asa@144 307 -- all mail at once and this is in a loop so that if for some
Asa@144 308 -- reason CanMailBeDeleted returns false, we can delete the next
Asa@144 309 -- mail in the queue instead.
Asa@144 310 return
Asa@144 311 end
Asa@144 312 end
Asa@144 313 else
Asa@144 314 MailAddonBusy = nil
Asa@144 315 end
Asa@3 316 end
Asa@3 317
Asa@63 318 function ItemAuditor:UNIT_SPELLCAST_START(event, target, spell)
Asa@5 319 if target == "player" and spell == "Milling" or spell == "Prospecting" or spell == "Disenchanting" then
Asa@23 320 self:Debug(event .. " " .. spell)
Asa@4 321 self:UnwatchBags()
Asa@4 322 self:UpdateCurrentInventory()
Asa@4 323 self:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
Asa@4 324 self:RegisterEvent("LOOT_CLOSED")
Asa@3 325 end
Asa@3 326 end
Asa@3 327
Asa@4 328 --[[
Asa@4 329 The item should be destroyed before this point, so the last inventory check
Asa@4 330 needs to be kept so it can be combined with the up coming loot.
Asa@4 331 ]]
Asa@63 332 function ItemAuditor:LOOT_CLOSED()
Asa@23 333 self:Debug("LOOT_CLOSED")
Asa@4 334 self:UnregisterEvent("LOOT_CLOSED")
Asa@4 335 self:UnregisterEvent("UNIT_SPELLCAST_INTERRUPTED")
Asa@4 336 local inventory = self.lastInventory
Asa@4 337 self:WatchBags()
Asa@4 338 self.lastInventory = inventory
Asa@4 339 end
Asa@3 340
Asa@63 341 function ItemAuditor:UNIT_SPELLCAST_INTERRUPTED(event, target, spell)
Asa@5 342 if target == "player" and spell == "Milling" or spell == "Prospecting" or spell == "Disenchanting" then
Asa@23 343 self:Debug(event .. " " .. spell)
Asa@4 344 self:UnregisterEvent("UNIT_SPELLCAST_INTERRUPTED")
Asa@4 345 self:UnregisterEvent("LOOT_CLOSED")
Asa@4 346 self:WatchBags()
Asa@4 347 end
Asa@4 348 end
Asa@4 349
Asa@63 350 function ItemAuditor:UpdateCurrentInventory()
Asa@4 351 self.lastInventory = self:GetCurrentInventory()
Asa@3 352 end
Asa@3 353
Asa@49 354 local function distributeValue(self, totalValue, targetItems)
Asa@46 355
Asa@46 356 local weights = {}
Asa@46 357 local totalWeight = 0
Asa@137 358 for itemID, change in pairs(targetItems) do
Asa@46 359 --[[
Asa@46 360 If something has never been seen on the AH, it must not be very valuable.
Asa@46 361 I'm using 1c so it doesn't have much weight and I can't get a devided by zero error.
Asa@46 362 The only time I know that this is a problem is when crafting a BOP item, and it
Asa@46 363 is always crafted 1 at a time, so a weight of 1 will work.
Asa@46 364 ]]
Asa@137 365 local ap = (ItemAuditor:GetAuctionPrice(itemID) or 1) * change
Asa@46 366 totalWeight = totalWeight + ap
Asa@137 367 weights[itemID] = ap
Asa@46 368 end
Asa@46 369
Asa@137 370 for itemID, change in pairs(targetItems) do
Asa@137 371 local value = totalValue * (weights[itemID]/totalWeight)
Asa@137 372 self:SaveValue(itemID, value, change)
Asa@46 373 end
Asa@46 374 end
Asa@46 375
Asa@63 376 function ItemAuditor:UpdateAudit()
Asa@23 377 -- self:Debug("UpdateAudit " .. event)
Asa@3 378 local currentInventory = self:GetCurrentInventory()
Asa@63 379 local diff = ItemAuditor:GetInventoryDiff(self.lastInventory, currentInventory)
Asa@3 380
Asa@5 381 local positive, negative = {}, {}
Asa@5 382 local positiveCount, negativeCount = 0, 0
Asa@137 383 for itemID, count in pairs(diff.items) do
Asa@5 384 if count > 0 then
Asa@137 385 positive[itemID] = count
Asa@5 386 positiveCount = positiveCount + count
Asa@5 387 elseif count < 0 then
Asa@137 388 negative[itemID] = count
Asa@5 389 negativeCount = negativeCount + abs(count)
Asa@5 390 end
Asa@5 391 end
Asa@5 392
Asa@23 393 if positiveCount + negativeCount == 0 then
Asa@33 394 --[[
Asa@33 395 Nothing needs to be done, but this will prevent mistakenly attributing
Asa@33 396 the cost of flights to the first item you pick up.
Asa@33 397 ]]
Asa@33 398 elseif diff.money > 0 and self:tcount(positive) > 0 and self:tcount(negative) == 0 then
Asa@15 399 self:Debug("loot")
Asa@113 400 elseif abs(diff.money) > 0 and self:tcount(diff.items) == 1 and not self.mailOpen then
Asa@15 401 self:Debug("purchase or sale")
Asa@3 402
Asa@137 403 for itemID, count in pairs(diff.items) do
Asa@140 404 self:SaveValue(itemID, 0 - diff.money, itemID)
Asa@3 405 end
Asa@23 406 elseif self:tcount(diff.items) > 1 and self:tcount(positive) > 0 and self:tcount(negative) > 0 then
Asa@23 407 -- we must have created/converted something
Asa@23 408 self:Debug("conversion")
Asa@3 409
Asa@23 410 local totalChange = 0
Asa@137 411 for itemID, change in pairs(negative) do
Asa@137 412 local _, itemCost, count = self:GetItemCost(itemID, change)
Asa@137 413 self:SaveValue(itemID, itemCost * change, change)
Asa@10 414
Asa@23 415 totalChange = totalChange + (itemCost * abs(change))
Asa@3 416 end
Asa@91 417 totalChange = totalChange - diff.money
Asa@23 418
Asa@46 419 distributeValue(self, totalChange, positive)
Asa@23 420 else
Asa@23 421 self:Debug("No match in UpdateAudit.")
Asa@3 422 end
Asa@3 423
Asa@3 424 self.lastInventory = currentInventory
Asa@63 425 ItemAuditor:WatchBags()
Asa@3 426 end