view Modules/Scanner.lua @ 117:239e25a058c7

Implemented mail refilling support. Respecting the MailAddonBusy global when opening so addons like MailOpener shouldn?t interfere.
author Zerotorescue
date Sat, 15 Jan 2011 13:15:16 +0100
parents 41f0689dfda1
children dc6f405c1a5d
line wrap: on
line source
local addon = select(2, ...);
local mod = addon:NewModule("Scanner", "AceEvent-3.0", "AceTimer-3.0");

local Mover, paused, currentLocation;
local itemCache = {};

local function OnMoveAccept()
	mod:Pause();
	Mover:BeginMove(currentLocation, mod.Unpause);
	
	InventoriumItemMover:Hide();
end

local function OnMoveCancel()
	Mover:ResetQueue();
	currentLocation = nil;
	
	InventoriumItemMover:Hide();
end

local function UseStorageRefillST()
	local frame = InventoriumItemMover; -- both for speed as code-consistency
	
	-- Scrolling table with a list of items to be moved
	local scrollTableWidth = ( frame.frmMeasureDummy:GetWidth() - 30 ); -- adjust width by the scrollbar size
	local headers = {
		{
			["name"] = "Item",
			["width"] = (scrollTableWidth * .60),
			["defaultsort"] = "asc",
			["comparesort"] = function(this, aRow, bRow, column)
				local aName, _, aRarity = GetItemInfo(this:GetRow(aRow).rowData.itemId);
				local bName, _, bRarity = GetItemInfo(this:GetRow(bRow).rowData.itemId);
				local template = "%d%s";
				aName = template:format((10 - (aRarity or 10)), (aName or ""):lower());
				bName = template:format((10 - (bRarity or 10)), (bName or ""):lower());
				
				if this.cols[column].sort == "dsc" then
					return aName > bName;
				else
					return aName < bName;
				end
			end,
			["sort"] = "asc", -- when the data is set, use this column so sort the default data
			["tooltipTitle"] = (not addon.db.profile.defaults.hideHelp and "Item"),
			["tooltip"] = (not addon.db.profile.defaults.hideHelp and "Click to sort the list by item quality then item name."),
		},
		{
			["name"] = "Moving",
			["width"] = (scrollTableWidth * .15),
			["align"] = "RIGHT",
			["defaultsort"] = "dsc",
			["sortnext"] = 1,
			["tooltipTitle"] = (not addon.db.profile.defaults.hideHelp and "Moving"),
			["tooltip"] = (not addon.db.profile.defaults.hideHelp and "Click to sort the list by the amount of movable items."),
		},
		{
			["name"] = "Available",
			["width"] = (scrollTableWidth * .25),
			["align"] = "RIGHT",
			["defaultsort"] = "dsc",
			["sortnext"] = 1,
			["comparesort"] = function(this, aRow, bRow, column)
				local aAvailablePercent = (this:GetRow(aRow).rowData.available / this:GetRow(aRow).rowData.missing);
				local bAvailablePercent = (this:GetRow(bRow).rowData.available / this:GetRow(bRow).rowData.missing);
				
				if this.cols[column].sort == "dsc" then
					return aAvailablePercent > bAvailablePercent;
				else
					return aAvailablePercent < bAvailablePercent;
				end
			end,
			["tooltipTitle"] = (not addon.db.profile.defaults.hideHelp and "Item"),
			["tooltip"] = (not addon.db.profile.defaults.hideHelp and "Click to sort the list by the availibility percentage."),
		},
	};
	
	local proceedButton = {
		text = "Move Items",
		tooltipTitle = (not addon.db.profile.defaults.hideHelp and "Move Items"),
		tooltip = (not addon.db.profile.defaults.hideHelp and "Start moving these items from the bank."),
		onClick = OnMoveAccept,
	};
	local cancelButton = {
		text = "Cancel",
		tooltipTitle = (not addon.db.profile.defaults.hideHelp and "Cancel"),
		tooltip = (not addon.db.profile.defaults.hideHelp and "Do not move anything and close the window."),
		onClick = OnMoveCancel,
	};
	
	addon:SetFrameSettings("Inventorium Bank Refill", "The items listed below can be refilled from this location, do you wish to move them to your bags?", proceedButton, cancelButton, headers);
end

function mod:ClearCache()
	table.wipe(itemCache);
end

function mod:CacheLocation(location, remember)
	-- Reset cache just in case it was filled
	self:ClearCache();
	
	if location == addon.Locations.Bag or location == addon.Locations.Bank then
		local start, stop;
		if location == addon.Locations.Bag then
			start = 0;
			stop = NUM_BAG_SLOTS;
		else
			-- If we requested the bank then we don't want the bag info
			start = ( NUM_BAG_SLOTS + 1 );
			stop = ( NUM_BAG_SLOTS + NUM_BANKBAGSLOTS );
		end
		
		-- Go through all our bags, including the backpack
		for i = start, ((location == addon.Locations.Bag and stop) or (location == addon.Locations.Bank and (stop + 1))) do -- if scanning bags stop at normal bag slot, if scanning bank, stop one later to allow BANK_CONTAINER to be scanned too
			-- Scan the default 100 slots whenever we're at a non-existing index
			local bagId = (i == (stop + 1) and BANK_CONTAINER) or i;
			local slotId = GetContainerNumSlots(bagId);
			
			while slotId ~= 0 do
				-- A not equal-comparison should be quicker than a larger than-comparison
				
				local itemId = GetContainerItemID(bagId, slotId);
				local itemCount = itemId and select(2, GetContainerItemInfo(bagId, slotId));
				
				if itemId and itemCount and itemCount > 0 then
					local itemMove;
					if not itemCache[itemId] then
						-- If this is the first time we see this item, make a new object
						itemMove = addon.ContainerItem:New();
						itemCache[itemId] = itemMove;
					else
						-- If we had this item in another slot too
						itemMove = itemCache[itemId];
					end
					
					itemMove:AddLocation(bagId, slotId, itemCount);
				end
			
				-- Continue scanning a different slot
				slotId = (slotId - 1);
			end
		end
	elseif location == addon.Locations.Guild then
		for tabId = 1, GetNumGuildBankTabs() do
			local _, _, isViewable, _, _, remainingWithdrawals = GetGuildBankTabInfo(tabId);
			
			if isViewable and (remainingWithdrawals > 0 or remainingWithdrawals == -1) then
				local slotId = (MAX_GUILDBANK_SLOTS_PER_TAB or 98); -- start by scanning the last slot
				
				while slotId ~= 0 do
					-- A not equal-comparison should be quicker than a larger than-comparison
					
					local itemLink = GetGuildBankItemLink(tabId, slotId);
					local itemId = itemLink and addon:GetItemId(itemLink);
					local itemCount = itemLink and select(2, GetGuildBankItemInfo(tabId, slotId));
						
					if itemLink and itemId and itemCount and itemCount > 0 then
						-- If there is actually an item in this slot
						local itemMove;
						if not itemCache[itemId] then
							-- If this is the first time we see this item, make a new object
							itemMove = addon.ContainerItem:New();
							itemCache[itemId] = itemMove;
						else
							-- If we had this item in another slot too
							itemMove = itemCache[itemId];
						end
						
						itemMove:AddLocation(tabId, slotId, itemCount);
					end
					
					-- Continue scanning a different slot
					slotId = (slotId - 1);
				end
			end
		end
	elseif location == addon.Locations.Mailbox then
		for mailIndex = 1, GetInboxNumItems() do
			-- All mail items
			
			for attachIndex = 1, ATTACHMENTS_MAX_RECEIVE do
				-- All attachments
				
				local itemLink = GetInboxItemLink(mailIndex, attachIndex);
				local itemId = itemLink and addon:GetItemId(itemLink);
				local itemCount = itemLink and select(3, GetInboxItem(mailIndex, attachIndex));
				
				if itemLink and itemId and itemCount and itemCount > 0 then
					local itemMove;
					if not itemCache[itemId] then
						-- If this is the first time we see this item, make a new object
						itemMove = addon.ContainerItem:New();
						itemCache[itemId] = itemMove;
					else
						-- If we had this item in another slot too
						itemMove = itemCache[itemId];
					end
					
					itemMove:AddLocation(mailIndex, attachIndex, itemCount);
				end
			end
		end
	else
		error("Invalid location provided for CacheLocation. Must be Bank or Guild.");
	end
	
	if not remember then
		-- Copy the table as clearing the cache wipes it empty (and tables are passed by reference)
		local cacheCopy = CopyTable(itemCache);
		
		self:ClearCache();
		
		return cacheCopy;
	end
end

function mod:Scan(location)
	-- We might pause the scanning when we invoke moves ourself
	if paused then
		addon:Debug("Not scanning; paused...");
		return;
	end
	
	local playerName = UnitName("player");
	
	currentLocation = location;
	self:CacheLocation(location, true);
	
	-- Ensure previous queue isn't remaining
	Mover:ResetQueue();
	
	-- Go through all groups
	for groupName, values in pairs(addon.db.profile.groups) do
		local trackAt = addon:GetOptionByKey(groupName, "trackAtCharacters");
		local localItemData = addon:GetOptionByKey(groupName, "localItemData");
		
		if values.items and trackAt[playerName] and addon:GetOptionByKey(groupName, "autoRefill") and (location ~= addon.Locations.Bank or not localItemData or not localItemData["Bank"]) then
			-- Is this character interested in this data?
			
			local minLocalStock = addon:GetOptionByKey(groupName, "minLocalStock");
			
			-- Go through all items
			for itemId, _ in pairs(values.items) do
				
				-- Check if we have enough items local (but only do so if this location also has enough available)
				local missingItems = itemCache[itemId] and (minLocalStock - addon:GetLocalItemCount(itemId, groupName));
				
				if itemCache[itemId] and missingItems > 0 then
					-- Check how many are available
					local availableItems = ((itemCache[itemId] and itemCache[itemId].totalCount) or 0);
					-- Calculate how many we'll be moving (less missing than available? use missing, otherwise use available)
					local moving = ((missingItems <= availableItems and missingItems) or availableItems);
					
					if availableItems > 0 then
						addon:Debug("Insufficient %s but this location has %d (moving %d)", IdToItemLink(itemId), availableItems, moving);
						
						Mover:AddMove(itemId, moving, missingItems, availableItems);
					end
				end
			end
		end
	end
	
	self:ClearCache();
	
	if Mover:HasMoves() then
		if addon.db.profile.defaults.autoRefillSkipConfirm then
			OnMoveAccept();
		else
			UseStorageRefillST();
			
			-- This table is never copied, just referenced. It is the same for every row.
			local columns = {
				{
					["value"] = function(data, cols, realrow, column, table)
						return IdToItemLink(data[realrow].rowData.itemId);
					end,
				}, -- item
				{
					["value"] = function(data, cols, realrow, column, table)
						return data[realrow].rowData.num;
					end,
				}, -- moving
				{
					["value"] = function(data, cols, realrow, column, table)
						return addon:DisplayItemCount(data[realrow].rowData.available, data[realrow].rowData.missing); -- available / missing
					end,
					["color"] = function(data, cols, realrow, column, table)
						return ((data[realrow].rowData.available < data[realrow].rowData.missing) and { r = 1, g = 0, b = 0, a = 1 }) or { r = 1, g = 1, b = 1, a = 1 };
					end,
				}, -- missing / available
			};
			
			-- Store the list with rows in this
			local data = {};
			
			for i, move in pairs(Mover:GetMoves()) do
				table.insert(data, {
					["rowData"] = move, -- this is not a key usually found in a row item and ignored by the library
					["cols"] = columns,
				});
			end
			
			addon:SetMoverFrameData(data);
		end
	end
end



-- Events

-- Player bank

function mod:BANKFRAME_OPENED()
	addon:Debug("Scanner:BANKFRAME_OPENED");
	
	mod:RegisterEvent("BANKFRAME_CLOSED");
	
	-- Scan once when the bank is opened, but no need to scan after
	mod:Scan(addon.Locations.Bank);
end

function mod:BANKFRAME_CLOSED()
	addon:Debug("Scanner:BANKFRAME_CLOSED");
	
	self:ClearCache();
	
	mod:UnregisterEvent("BANKFRAME_CLOSED");
	
	InventoriumItemMover:Hide();
	Mover:ResetQueue();
end

-- Guild bank

local tmrScanGuild, scanned;
function mod:GUILDBANKBAGSLOTS_CHANGED()
	-- This event is spammed the first time the guild bank is opened
	if not scanned then
		self:CancelTimer(tmrScanGuild, true); -- silent
		tmrScanGuild = self:ScheduleTimer("DoScanGuild", 1);
	end
end

function mod:DoScanGuild()
	if not scanned then
		addon:Debug("Scanner:DoScanGuild");
		
		scanned = true;
		
		self:Scan(addon.Locations.Guild);
	end
end

function mod:GUILDBANKFRAME_CLOSED()
	addon:Debug("Scanner:GUILDBANKFRAME_CLOSED");
	
	scanned = nil;
	self:ClearCache();
	
	self:UnregisterEvent("GUILDBANKFRAME_CLOSED");
	self:UnregisterEvent("GUILDBANKBAGSLOTS_CHANGED");
	
	self:CancelTimer(tmrScanGuild, true); -- silent
	
	InventoriumItemMover:Hide();
	Mover:ResetQueue();
end

function mod:GUILDBANKFRAME_OPENED()
	addon:Debug("Scanner:GUILDBANKFRAME_OPENED");
	
	scanned = nil;
	
	-- Get the contents for every tab into our cache
	for tabId = 1, GetNumGuildBankTabs() do
		local _, _, isViewable, _, _, remainingWithdrawals = GetGuildBankTabInfo(tabId);
		
		if isViewable and (remainingWithdrawals > 0 or remainingWithdrawals == -1) then
			QueryGuildBankTab(tabId);
		end
	end
	
	self:RegisterEvent("GUILDBANKFRAME_CLOSED");
	self:RegisterEvent("GUILDBANKBAGSLOTS_CHANGED");
end

local previousMailCount;
function mod:MAIL_SHOW()
	addon:Debug("Scanner:MAIL_SHOW");
	
	self:RegisterEvent("MAIL_INBOX_UPDATE");
	self:RegisterEvent("MAIL_CLOSED");
	
	scanned = nil;
	previousMailCount = nil;
	
	self:Scan(addon.Locations.Mailbox);
end

function mod:MAIL_INBOX_UPDATE()
	if not scanned then
		addon:Debug("Scanner:MAIL_INBOX_UPDATE");
		
		local current, total = GetInboxNumItems();
		
		if not previousMailCount or current > previousMailCount then
			-- New mail received
			
			scanned = true;
			
			self:Scan(addon.Locations.Mailbox);
		end
		
		-- Also remember the new mailcount when losing items, otherwise deleting item 50 and getting to 50 again wouldn't trigger a re-scan
		previousMailCount = current;
	else
		addon:Debug("Scanner:MAIL_INBOX_UPDATE skipped, already scanned");
	end
end

function mod:MAIL_CLOSED()
	addon:Debug("Scanner:MAIL_CLOSED");
	
	previousMailCount = nil;
	scanned = nil;
	self:ClearCache();
	
	self:UnregisterEvent("MAIL_INBOX_UPDATE");
	self:UnregisterEvent("MAIL_CLOSED");
	
	InventoriumItemMover:Hide();
	Mover:ResetQueue();
end

function mod:OnEnable()
	-- Scan once when the bankframe is opened
	self:RegisterEvent("BANKFRAME_OPENED");
	self:RegisterEvent("GUILDBANKFRAME_OPENED");
	self:RegisterEvent("MAIL_SHOW");
	
	Mover = addon:GetModule("Mover");
	
	if not InventoriumItemMover then
		addon:CreateMoverFrame(OnMoveAccept, OnMoveCancel);
	end
end

function mod:OnDisable()
	Mover = nil;
	currentLocation = nil;
	paused = nil;
	
	-- Bank
	self:BANKFRAME_CLOSED();
	self:UnregisterEvent("BANKFRAME_OPENED");
	
	-- Guild
	self:GUILDBANKFRAME_CLOSED();
	self:UnregisterEvent("GUILDBANKFRAME_OPENED");
	
	-- Mailbox
	self:MAIL_CLOSED();
	self:UnregisterEvent("MAIL_SHOW");
end

function mod:Pause()
	paused = true;
end

function mod:Unpause()
	paused = nil;
end