view Mover.lua @ 80:c0bf2ddb5288

Added initial item refilling from the bank/guild. Not yet fully functional.
author Zerotorescue
date Wed, 05 Jan 2011 13:05:15 +0100
parents
children 58617c7827fa
line wrap: on
line source
local addon = select(2, ...);
local mod = addon:NewModule("Mover", "AceEvent-3.0", "AceTimer-3.0");

local Scanner;
local queuedMoves = {}; -- table storing all queued moves before BeginMove is called
local combinedMoves = {}; -- table storing all combined moves (with source and target) that is to be processed by the actual mover in the order of the index (1 to #)

function mod:AddMove(itemId, amount)
	table.insert(queuedMoves, {
		id = itemId,
		num = amount,
	});
end

function mod:BeginMove(location, onFinish)
	
	-- Find the outgoing moves
	-- We need the source container and slot, find all the requires sources and put them in a list which we go through later to find matching targets
	
	-- Get a list of items in the source container
	local sourceContents = Scanner:CacheLocation(location, false);
	
	local outgoingMoves = {};

	for singleMove in pairs(queuedMoves) do
		local sourceItem = sourceContents[singleMove.id];
		if not sourceItem then
			print("Can't move " .. IdToItemLink(singleMove.id) .. ", non-existant in source");
		else
			-- We want to move the smallest stacks first to keep stuff pretty
			table.sort(sourceItem.locations, function(a, b)
				return a.count > b.count;
			end);
			
			for itemLocation in pairs(sourceItem.locations) do
				-- if this location has more items than we need, only move what we need, otherwise move everything in this stack
				local movingNum = ((itemLocation.count > singleMove.num and singleMove.num) or itemLocation.count);
				
				table.insert(outgoingMoves, {
					itemId = singleMove.id,
					container = itemLocation.container,
					slot = itemLocation.slot,
					count = movingNum,
				});
				
				singleMove.num = (singleMove.num - movingNum);
				
				if singleMove.num == 0 then
					-- If we have prepared everything we wanted, go to the next queued move
					break;
				end
			end
		end
	end
	
	-- No longer needed
	table.wipe(queuedMoves);
	
	-- Process every single outgoing move and find fitting targets
	
	-- Get a list of items already in the target container
	local targetContents = Scanner:CacheLocation(addon.Locations.Bag, false);
	
	-- Find all empty slots
	
	local emptySlots = {};
	
	local start = 0;
	local stop = NUM_BAG_SLOTS;
	
	-- Go through all our bags, including the backpack
	for bagId = start, stop do
		-- Go through all our slots
		for slotId = 1, GetContainerNumSlots(bagId) do
			local itemId = GetContainerItemID(bagId, slotId);
			
			if not itemId then
				table.insert(emptySlots, {
					container: bagId,
					slot: slotId,
				});
			end
		end
	end
	
	while #outgoingMoves ~= 0 do
		-- A not equal-comparison should be quicker than a larger/smaller than-comparison
		
		for outgoingMove in pairs(outgoingMoves) do
			-- itemId  will be set to nil when this outgoing move was processed - sanity check
			if outgoingMove.itemId then
				local targetItem = targetContents[outgoingMove.itemId];
				
				if not targetItem then
					-- grab an empty slot
					-- make new instance of ItemMove
					-- populate targetContents with it so future moves of this item can be put on top of it if this isn't a full stack
					
					local firstAvailableSlot = emptySlots[1];
					
					if not firstAvailableSlot then
						print("Bags are full. Skipping " .. IdToItemLink(outgoingMove.itemId) .. ".");
						
						outgoingMove.itemId = nil;
					else
						table.insert(combinedMoves, {
							sourceContainer = outgoingMove.container,
							sourceSlot = outgoingMove.slot,
							targetContainer = firstAvailableSlot.container,
							targetSlot = firstAvailableSlot.slot,
							itemId = outgoingMove.itemId,
							num = outgoingMove.count,
						});
						
						-- We filled an empty slot so the target contents now has one more item,
						-- make a new instance of the ItemMove class so any additional items with this id can be stacked on top of it
						local itemMove = addon.ItemMove:New();
						itemMove.AddLocation(firstAvailableSlot.container, firstAvailableSlot.slot, outgoingMove.count);
						targetContents[outgoingMove.itemId] = itemMove;
						
						firstAvailableSlot = nil; -- no longer empty
						
						outgoingMove.count = 0; -- nothing remaining - sanity check
						outgoingMove.itemId = nil; -- remove this record from the outgoingMoves-table
					end
				else
					-- Find the maximum stack size for this item
					local itemStackCount = select(8, GetItemInfo(outgoingMove.itemId));
					
					-- We want to move to the largest stacks first to keep stuff pretty
					table.sort(targetItem.locations, function(a, b)
						return a.count < b.count;
					end);
					
					for itemLocation in pairs(targetItem.locations) do
						if itemLocation.count < itemStackCount and outgoingMove.count > 0 then
							-- Check if this stack isn't already full (and we still need to move this item)
							
							local remainingSpace = (itemStackCount - itemLocation.count);
							if remainingSpace > outgoingMove.count then
								-- Enough room to move this entire stack
								-- Deposit this item and then forget this outgoing move as everything in it was processed
								
								table.insert(combinedMoves, {
									sourceContainer = outgoingMove.container,
									sourceSlot = outgoingMove.slot,
									targetContainer = itemLocation.container,
									targetSlot = itemLocation.slot,
									itemId = outgoingMove.itemId,
									num = outgoingMove.count,
								});
								
								itemLocation.count = (itemLocation.count + outgoingMove.count);
								outgoingMove.count = 0; -- nothing remaining
								outgoingMove.itemId = nil; -- remove this record from the outgoingMoves-table
								break; -- stop the locations-loop
							else
								-- Deposit this item but don't remove the outgoing move as there are some items left to move
								
								table.insert(combinedMoves, {
									sourceContainer = outgoingMove.container,
									sourceSlot = outgoingMove.slot,
									targetContainer = itemLocation.container,
									targetSlot = itemLocation.slot,
									itemId = outgoingMove.itemId,
									num = outgoingMove.count,
								});
								
								-- The target will be full when we complete, but the source will still have remaining items left to be moved
								itemLocation.count = itemStackCount;
								outgoingMove.count = (outgoingMove.count - remainingSpace);
							end
						end
					end
					
					if outgoingMove.count > 0 then
						-- We went through all matching items and checked their stack sizes if we could move this there, no room available
						-- So forget about the target item (even though it may just have full locations, these are useless anyway) and the next loop move it onto an empty slot
						targetItem = nil;
					end
				end
			end
		end
		
		-- Loop through the array to find items that should be removed, start with the last element or the loop would break
		local numOutgoingMoves = #outgoingMoves; -- since LUA-tables start at an index of 1, this is actually an existing index (outgoingMoves[#outgoingMoves] would return a value)
		while numOutgoingMoves ~= 0 do
			-- A not equal-comparison should be quicker than a larger/smaller than-comparison
			
			-- Check if the item id is nil, this is set to nil when this outgoing move has been processed
			if not outgoingMoves[numOutgoingMoves].itemId then
				-- Remove this element from the array
				table.remove(outgoingMoves, numOutgoingMoves);
			end
			
			-- Proceed with the next element (or previous considering we're going from last to first)
			numOutgoingMoves = (numOutgoingMoves - 1);
		end
	end
	
	-- No longer needed
	table.wipe(emptySlots);
	
	DoMoveNow();
	
	--ProcessMove();
	
	--mod:RegisterEvent("BAG_UPDATE", BAG_UPDATE);
	
	--onFinish();
end

function DoMoveNow()
	-- combinedMoves now has all moves in it (source -> target)
	-- go through list, move everything inside it
	-- add source and target to one single list
	-- if either is already in this list, skip this move
	-- repeat every 5 seconds until we're completely done
	
	local sourceLocationsLocked = {};
	local targetLocationsLocked = {};
	
	local numCurrentMove = #combinedMoves;
	while numCurrentMove ~= 0 do
		local move = combinedMoves[numCurrentMove];
		
		-- sourceContainer, sourceSlot, targetContainer, targetSlot, itemId, num
		if (not sourceLocationsLocked[move.sourceContainer] or not sourceLocationsLocked[move.sourceContainer][move.sourceSlot]) and 
			(not targetLocationsLocked[move.targetContainer] or not targetLocationsLocked[move.targetContainer][move.targetSlot]) then
			
			print("Moving " .. IdToItemLink(move.itemId));
			
			-- Pickup stack
			SplitGuildBankItem(move.sourceContainer, move.sourceSlot, move.num);
			
			-- Remember we picked this item up and thus it is now locked
			if not sourceLocationsLocked[move.sourceContainer] then
				sourceLocationsLocked[move.sourceContainer] = {};
			end
			sourceLocationsLocked[move.sourceContainer][move.sourceSlot] = true;
			
			if CursorHasItem() then
				-- And drop it
				PickupContainerItem(move.targetContainer, move.targetSlot);
				
				-- Remember we dropped an item here and thus this is now locked
				if not sourceLocationsLocked[move.targetContainer] then
					sourceLocationsLocked[move.targetContainer] = {};
				end
				sourceLocationsLocked[move.targetContainer][move.targetSlot] = true;
				
				-- This move was processed
				table.remove(combinedMoves, numCurrentMove);
			end
		end
		
		-- Proceed with the next element (or previous considering we're going from last to first)
		numCurrentMove = (numCurrentMove - 1);
	end
end

local tmrProcessNext;
function BAG_UPDATE()
	mod:CancelTimer(tmrProcessNext, true); -- silent
	tmrProcessNext = mod:ScheduleTimer(function()
		ProcessMove();
	end, 2);
end

function ProcessMove()
	local currentMove = queuedMoves[1];
	
	if currentMove then
		addon:Debug("Moving " .. currentMove.num .. " of " .. IdToItemLink(currentMove.id));
	
		local requestedMoves = currentMove.num;
		
		if currentMove.src == addon.Locations.Bank then
			MoveBankItem(currentMove);
		elseif currentMove.src == addon.Locations.Guild then
			MoveGuildItem(currentMove);
		end
		
		if requestedMoves == currentMove.num then
			print("Skipping " .. IdToItemLink(move.id));
			move.num = 0;
		elseif currentMove.num > 0 then
			-- bags are full
			print("bags are full");
		end
		
		if currentMove.num == 0 then
			table.remove(queuedMoves, 1);
		end
	end
end

function MoveGuildItem(move)
	local tabId = GetCurrentGuildBankTab();
	local slotId = (MAX_GUILDBANK_SLOTS_PER_TAB or 98); -- start by scanning the last slot
	
	if tabId == nil or tabId < 1 then return; end
	
	while slotId ~= 0 do
		-- A not equal-comparison should be quicker than a larger than-comparison
		
		local itemLink = GetGuildBankItemLink(tabId, slotId);
		if itemLink then
			-- If there is actually an item in this slot
			
			local itemId = GetItemId(itemLink);
			
			if itemId and move.id == itemId then
				-- This is one of the items we're looking for
				
				local itemCount = select(2, GetGuildBankItemInfo(tabId, slotId));
				
				if itemCount and itemCount > 0 then
					-- if the amount we still have to move is more than this stack, move the entire stack, otherwise move the still needed part of the stack
					local moveable = (move.num > itemCount and itemCount) or move.num;
					
					-- Pickup stack
					SplitGuildBankItem(tabId, slotId, moveable);
					
					-- Find an target slot and put it there
					local reallyMoved = DropItem(itemId, moveable);
					if reallyMoved then
						-- Keep track of how many we have moved
						moved = (moved + reallyMoved);
						
						-- Update the required amount of items so it has the remaining num
						move.num = (move.num - reallyMoved);
						
						--if reallyMoved ~= moveable then
						--	-- Scan this slot again because if we moved a 18 stack onto a 16 stack, we'd actually have moved only 4 items and still need to move the remaining 14 (we're capping stacks before using empty slots)
						--	slotId = (slotId + 1);
						--end
						
						--if move.num == 0 then
							-- if no required items are left to move, then stop and tell the caller function how many we moved
							return moved;
						--end
					end
				end
			end
		end
		
		-- Continue scanning a different slot
		slotId = (slotId - 1);
	end
end

function MoveBankItem(move)
	local start = 0;
	local stop = NUM_BAG_SLOTS;
	
	-- If we requested the bank then we don't want the bag info
	start = ( NUM_BAG_SLOTS + 1 );
	stop = ( NUM_BAG_SLOTS + NUM_BANKBAGSLOTS );
	
	-- Scan the default 100 slots
	move.num = (move.num - MoveFromContainter(BANK_CONTAINER, move));
	
	-- Go through all our bags, including the backpack
	for bagID = start, stop do
		move.num = (move.num - MoveFromContainter(bagID, move));
	end
end

-- Go through all slots of this bag and if a match was found, move the items
function MoveFromContainter(bagID, move)
	-- Keep track of how many we have moved
	local moved = 0;
	
	-- Go through all slots of this bag
	for slot = 1, GetContainerNumSlots(bagID) do
		local itemId = GetContainerItemID(bagID, slot);
		
		if itemId and move.id == itemId then
			-- This is one of the items we're looking for
			
			local itemCount = select(2, GetContainerItemInfo(bagID, slot));
			
			if itemCount and itemCount > 0 then
				-- if the amount we still have to move is more than this stack, move the entire stack, otherwise move the still needed part of the stack
				local moveable = (move.num > itemCount and itemCount) or move.num;
				
				-- Pickup stack
				SplitContainerItem(bagID, slot, moveable);
				
				addon:Debug("Picked " .. IdToItemLink(itemId) .. " up");
				
				-- Find an target slot and put it there
				if CursorHasItem() then
					local reallyMoved = DropItem(itemId, moveable);
					
					if reallyMoved then
						addon:Debug("Dropped " .. reallyMoved .. " of " .. IdToItemLink(itemId));
						
						-- Keep track of how many we have moved
						moved = (moved + reallyMoved);
						
						-- Update the required amount of items so it has the remaining num
						move.num = (move.num - reallyMoved);
						
						--if reallyMoved ~= moveable then
							-- Scan this slot again because if we moved a 18 stack onto a 16 stack, we'd actually have moved only 4 items and still need to move the remaining 14 (we're capping stacks before using empty slots)
						--	slot = (slot - 1);
						--end
						
						--if move.num == 0 then
							-- if no required items are left to move, then stop and tell the caller function how many we moved
							return moved;
						--end
					end
				end
			end
		end
	end
	
	return moved;
end


-- This currently only uses empty slots, it will have to fill stacks in the future
function DropItem(itemId, amount)
	local start = 0;
	local stop = NUM_BAG_SLOTS;
	
	-- Go through all our bags, including the backpack
	for bagID = start, stop do
		-- Go through all our slots
		for slot = 1, GetContainerNumSlots(bagID) do
			local itemId = GetContainerItemID(bagID, slot);
			
			if not itemId then
				-- If this slot is empty, put the item here
				PickupContainerItem(bagID, slot);
				
				return amount;
			end
		end
	end
	
	return;
end

function IdToItemLink(itemId)
	return select(2, GetItemInfo(itemId));
end

function mod:OnEnable()
	Scanner = addon:GetModule("Scanner");
end

function mod:OnDisable()
	Scanner = nil;
end