view 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
line wrap: on
line source
local ItemAuditor = select(2, ...)
local Events = ItemAuditor:NewModule("Events", "AceEvent-3.0")

local Utils = ItemAuditor:GetModule("Utils")

function ItemAuditor:OnEnable()
	self:RegisterEvent("MAIL_SHOW")
	self:RegisterEvent("UNIT_SPELLCAST_START")
	
	self:RegisterEvent("BANKFRAME_OPENED", 'BankFrameChanged')
	self:RegisterEvent("BANKFRAME_CLOSED", 'BankFrameChanged')

	ItemAuditor:UpdateCurrentInventory()
	self:WatchBags()
	
	self:SetEnabled(nil, self.db.profile.ItemAuditor_enabled)
end

function ItemAuditor:OnDisable()
	self:UnwatchBags()
	self:UnregisterAllEvents()
	ItemAuditor:HideAllFrames()
end
 
 function ItemAuditor:MAIL_SHOW()
	self:Debug("MAIL_SHOW")
	self.mailOpen = true
	ItemAuditor:UpdateCurrentInventory()
	self.lastMailScan = self:ScanMail()
	
	self:UnregisterEvent("MAIL_SHOW")
	self:RegisterEvent("MAIL_CLOSED")
	self:RegisterEvent("MAIL_INBOX_UPDATE")
	
	self:GenerateBlankOutbox()
	
	self:RegisterEvent("MAIL_SUCCESS")
end

function ItemAuditor:GenerateBlankOutbox()
	self.mailOutbox = {
		from = UnitName("player"),
		to = "",
		subject = "",
		link = '',
		count = 0,
		COD = 0,
		key = random(10000),
		sent = 0,
	}
	
	if self.db.factionrealm.outbound_cod[self.mailOutbox.key] ~= nil then
		return self:GenerateBlankOutbox()
	end
end

local attachedItems = {}
local Orig_SendMail = SendMail
local skipCODTracking = false
local skipCODCheck = false

StaticPopupDialogs["ItemAuditor_Send_COD_without_tracking_number"] = {
	text = "ItemAuditor cannot track COD mail with multiple item types attached. Do you want to send this mail without tracking?",
	button1 = "Yes",
	button2 = "No",
	OnAccept = function()
		skipCODTracking = true
	end,
	timeout = 0,
	whileDead = true,
	hideOnEscape = true,
}

StaticPopupDialogs["ItemAuditor_Insufficient_COD"] = {
	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",
	button1 = "Yes",
	button2 = "No",
	OnAccept = function()
		skipCODCheck = true
	end,
	timeout = 0,
	whileDead = true,
	hideOnEscape = true,
}

function SendMail(recipient, subject, body, ...)
	local self = ItemAuditor
	self:GenerateBlankOutbox()

	self:Debug(format("[To: %s] [Subject: %s]", recipient, subject))
	
	self.mailOutbox.COD = GetSendMailCOD()
	
	wipe(attachedItems)
	local totalStacks = 0
	local link
	for index = 1, ATTACHMENTS_MAX_SEND do
		local itemName, _, itemCount = GetSendMailItem(index)
		local newLink = GetSendMailItemLink(index)
		
		if newLink ~= nil then
			newLink = self:GetSafeLink(newLink)
			totalStacks = totalStacks + 1
			attachedItems[newLink] = (attachedItems[newLink] or {stacks = 0, count = 0})
			attachedItems[newLink].stacks = attachedItems[newLink].stacks + 1
			attachedItems[newLink].count = attachedItems[newLink].count + itemCount
			attachedItems[newLink].price = 0 -- This is a placeholder for below.
			attachedItems[newLink].costEach = select(2, ItemAuditor:GetItemCost(newLink))
		end
	end
	local attachedValue = 0
	for link, data in pairs(attachedItems) do
		data.price = 30 * data.stacks
		attachedValue = attachedValue + data.price + (data.costEach * data.count)
	end

	local destinationType = 'unknown'
	local realm = GetRealmName()
	for account in pairs(DataStore:GetAccounts()) do
		for character in pairs(DataStore:GetCharacters(realm, account)) do
			if strlower(recipient) == strlower(character) then
				destinationType = (account == 'Default') and 'same_account' or 'owned_account'
				destinationType = 'owned_account'
				break
			end
		end
	end
	self.mailOutbox.destinationType = destinationType
	if destinationType == 'unknown' and attachedValue > self.mailOutbox.COD and not skipCODCheck and ItemAuditor.db.char.cod_warnings then
		self:GenerateBlankOutbox()
		skipCODCheck = false;
		local vararg = ...
		StaticPopupDialogs["ItemAuditor_Insufficient_COD"].OnAccept = function()
			skipCODCheck = true
			SendMail(recipient, subject, body, vararg)
			skipCODCheck = false
		end
		StaticPopup_Show ("ItemAuditor_Insufficient_COD", Utils.FormatMoney(attachedValue));
		return
	elseif destinationType == 'owned_account' then
		-- If we are mailing to an alt on a different account, a uniqueue tracking number
		-- is generated and all of the needed data is attached to the message.
		-- The tracking number is only used to make sure the other character doesn't count the
		-- mail more than once.
		local key = time()..":"..random(10000)
		self.mailOutbox.attachedItems = attachedItems
		body = body .. ItemAuditor.TRACKING_DATA_DIVIDER .. ItemAuditor:Serialize(key, attachedItems)
	elseif self.mailOutbox.COD > 0 and skipCODTracking then
		
	elseif self.mailOutbox.COD > 0 then
		if self:tcount(attachedItems) > 1 then
			self:GenerateBlankOutbox()
			local vararg = ...
			StaticPopupDialogs["ItemAuditor_Send_COD_without_tracking_number"].OnAccept = function()
				skipCODTracking = true
				SendMail(recipient, subject, body, vararg)
				skipCODTracking = false
			end
			StaticPopup_Show ("ItemAuditor_Send_COD_without_tracking_number");
			return
		end
		self:Debug("COD mail")
		
		subject = format("[IA: %s] %s", self.mailOutbox.key, subject)
		self.mailOutbox.subject = subject
		self.mailOutbox.to = recipient
		
		-- At this point we know there is only one item
		for link, data in pairs(attachedItems) do
			self.mailOutbox.link = link 
			self.mailOutbox.count = data.count
		end
	else
		self:Debug("Non-COD mail")
	end

	return Orig_SendMail(recipient, subject, body, ...)
end

function ItemAuditor:MAIL_SUCCESS(event)
	skipCODTracking = false
	skipCODCheck = false

	for link, data in pairs(attachedItems) do
		-- When mailing to an alt on a different account, we still
		-- should add the price of postage, but need to subtract the
		-- cost of the items. This will simulate CODing the mail and
		-- getting the money back, except that postage is paid by the
		-- sender
		if self.mailOutbox.destinationType == 'owned_account' then
			data.price = data.price - (data.costEach * data.count)
		end
		self:SaveValue(link, data.price, data.count)
	end
	if self.mailOutbox.COD > 0 then
		self:Debug(format("MAIL_SUCCESS %d [To: %s] [Subject: %s] [COD: %s]", self.mailOutbox.key, self.mailOutbox.to, self.mailOutbox.subject, self.mailOutbox.COD))
		
		self.mailOutbox.sent = time()
		self.db.factionrealm.outbound_cod[self.mailOutbox.key] = self.mailOutbox
	end

	wipe(attachedItems)
	self:GenerateBlankOutbox()
end

function ItemAuditor:MAIL_CLOSED()
	self:Debug("MAIL_CLOSED")
	ItemAuditor:UnregisterEvent("MAIL_CLOSED")
	self:MAIL_INBOX_UPDATE()
	self:UnregisterEvent("MAIL_INBOX_UPDATE")
	self:RegisterEvent("MAIL_SHOW")
	self.mailOpen = nil
end

local function CanMailBeDeleted(mailIndex)
	local msgMoney, _, _, msgItem = select(5, GetInboxHeaderInfo(mailIndex))
	local body = GetInboxText(mailIndex)
	if msgMoney == 0 and msgItem == nil and body and body:find(ItemAuditor.TRACKING_DATA_DIVIDER) then
		local serialized = body:gsub('.*'..ItemAuditor.TRACKING_DATA_DIVIDER, '')
		local body = body:gsub(ItemAuditor.TRACKING_DATA_DIVIDER..'.*', '')
		local success, trackingID, data = ItemAuditor:Deserialize(serialized)
		if success and body == '' then
			return true
		end
	end
	return false
end

local Postal_L
local function blockMailOperations()
	if MailAddonBusy == 'ItemAuditor' then
		return false
	end
	if Postal_L == nil then
		local locale = LibStub("AceLocale-3.0", true)
		Postal_L = locale and locale:GetLocale("Postal", true)
	end
	return MailAddonBusy or PostalOpenAllButton and Postal_L and PostalOpenAllButton:GetText() == Postal_L["In Progress"]
end

local storedCountDiff
function ItemAuditor:MAIL_INBOX_UPDATE()
	self:Debug("MAIL_INBOX_UPDATE")
	self.deleteQueue = nil
	local newScan = self:ScanMail()
	local diff
	
	for mailType, collection in pairs(self.lastMailScan) do
		newScan[mailType] = (newScan[mailType] or {})
		for itemName, data in pairs(collection) do
			newScan[mailType][itemName] = (newScan[mailType][itemName] or {total=0,count=0})
			local totalDiff = data.total - newScan[mailType][itemName].total
			local countDiff = data.count - newScan[mailType][itemName].count
			--[[
				In one update the item will be taken and in the following update the invoice
				will be gone. I need to store the item difference in order ot pass it into
				SaveValue.
			]]
			if countDiff ~= 0 then
				storedCountDiff = countDiff
			end
			
			if totalDiff ~= 0 then
				if mailType == "CODPayment" then
					local trackID
					trackID, itemName= strsplit("|", itemName, 2)
					self.db.factionrealm.outbound_cod[tonumber(trackID)] = nil
					self:Debug("Removing COD Tracker: " .. trackID)
				end
				self:SaveValue(itemName, totalDiff, storedCountDiff)
				storedCountDiff = 0
			end

		end
	end

	self.lastMailScan = newScan

	if self.deleteQueue and not self.deleteScheduled then
		-- For some reason DeleteInboxItem will not trigger a MAIL_INBOX_UPDATE
		-- if it is called from here, so I have to use a timer to get it
		-- to run outside of this function.

		-- If the mailbox is full of items to be deleted, this will speed up because
		-- postal shouldn't be running at this point. Keeping at 0.1 breaks postal.
		local delay = (GetInboxNumItems() > #(self.deleteQueue)) and 1 or 0.1
		self:ScheduleTimer("ProcessDeleteQueue", delay)
		self.deleteScheduled = true
	elseif MailAddonBusy == 'ItemAuditor' then
		MailAddonBusy = nil
	end
end

function ItemAuditor:ProcessDeleteQueue()
	if blockMailOperations() then
		self:ScheduleTimer("ProcessDeleteQueue", 1)
		return
	end
	self.deleteScheduled = false
	if self.deleteQueue then
		MailAddonBusy = 'ItemAuditor'
		while  #(self.deleteQueue) > 0 do
			local mailIndex = table.remove(self.deleteQueue)
			if CanMailBeDeleted(mailIndex) then
				DeleteInboxItem(mailIndex)
				-- This returns after the first item because you can't delete
				-- all mail at once and this is in a loop so that if for some
				-- reason CanMailBeDeleted returns false, we can delete the next
				-- mail in the queue instead.
				return
			end
		end
	else
		MailAddonBusy = nil
	end
end

function ItemAuditor:UNIT_SPELLCAST_START(event, target, spell)
	if target == "player" and spell == "Milling" or spell == "Prospecting" or spell == "Disenchanting" then
		self:Debug(event .. " " .. spell)
		self:UnwatchBags()
		self:UpdateCurrentInventory()
		self:RegisterEvent("UNIT_SPELLCAST_INTERRUPTED")
		self:RegisterEvent("LOOT_CLOSED")
	end
end

--[[ 
	The item should be destroyed before this point, so the last inventory check
	needs to be kept so it can be combined with the up coming loot.
 ]]
function ItemAuditor:LOOT_CLOSED()
	self:Debug("LOOT_CLOSED")
	self:UnregisterEvent("LOOT_CLOSED")
	self:UnregisterEvent("UNIT_SPELLCAST_INTERRUPTED")
	local inventory = self.lastInventory
	self:WatchBags()
	self.lastInventory = inventory 
end

function ItemAuditor:UNIT_SPELLCAST_INTERRUPTED(event, target, spell)
	if target == "player" and spell == "Milling" or spell == "Prospecting" or spell == "Disenchanting" then
		self:Debug(event .. " " .. spell)
		self:UnregisterEvent("UNIT_SPELLCAST_INTERRUPTED")
		self:UnregisterEvent("LOOT_CLOSED")
		self:WatchBags()
	end
end

function ItemAuditor:UpdateCurrentInventory()
	self.lastInventory = self:GetCurrentInventory()
end

local function distributeValue(self, totalValue, targetItems)
	
	local weights = {}
	local totalWeight = 0
	for itemID, change in pairs(targetItems) do
		--[[ 
			If something has never been seen on the AH, it must not be very valuable.
			I'm using 1c so it doesn't have much weight and I can't get a devided by zero error.
			The only time I know that this is a problem is when crafting a BOP item, and it 
			is always crafted 1 at a time, so a weight of 1 will work.
		]]
		local ap = (ItemAuditor:GetAuctionPrice(itemID) or 1) * change
		totalWeight = totalWeight + ap
		weights[itemID] = ap
	end
	
	for itemID, change in pairs(targetItems) do
		local value = totalValue * (weights[itemID]/totalWeight)
		self:SaveValue(itemID, value, change)
	end
end

function ItemAuditor:UpdateAudit()
	-- self:Debug("UpdateAudit " .. event)
	local currentInventory = self:GetCurrentInventory()
	local diff =  ItemAuditor:GetInventoryDiff(self.lastInventory, currentInventory)
	
	local positive, negative = {}, {}
	local positiveCount, negativeCount = 0, 0
	for itemID, count in pairs(diff.items) do
		if count > 0 then
			positive[itemID] = count
			positiveCount = positiveCount + count
		elseif count < 0 then
			negative[itemID] = count
			negativeCount = negativeCount + abs(count)
		end
	end
	
	if positiveCount + negativeCount == 0 then
		--[[
			Nothing needs to be done, but this will prevent mistakenly attributing
			the cost of flights to the first item you pick up.
		]]
	elseif diff.money > 0 and self:tcount(positive) > 0 and self:tcount(negative) == 0 then
		self:Debug("loot")
	elseif abs(diff.money) > 0 and self:tcount(diff.items) == 1 and not self.mailOpen then
		self:Debug("purchase or sale")
		
		for itemID, count in pairs(diff.items) do
			self:SaveValue(itemID, 0 - diff.money, itemID)
		end
	elseif self:tcount(diff.items) > 1 and self:tcount(positive) > 0 and self:tcount(negative) > 0 then
		-- we must have created/converted something
		self:Debug("conversion")
		
		local totalChange = 0
		for itemID, change in pairs(negative) do
			local _, itemCost, count = self:GetItemCost(itemID, change)
			self:SaveValue(itemID, itemCost * change, change)
			
			totalChange = totalChange + (itemCost * abs(change))
		end
		totalChange = totalChange - diff.money
		
		distributeValue(self, totalChange, positive)
	else
		self:Debug("No match in UpdateAudit.")
	end
	
	self.lastInventory = currentInventory
	ItemAuditor:WatchBags()
end