Zerotorescue@80: local addon = select(2, ...); Zerotorescue@80: local mod = addon:NewModule("Mover", "AceEvent-3.0", "AceTimer-3.0"); Zerotorescue@80: Zerotorescue@80: local Scanner; Zerotorescue@80: local queuedMoves = {}; -- table storing all queued moves before BeginMove is called Zerotorescue@80: 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 #) Zerotorescue@80: Zerotorescue@80: function mod:AddMove(itemId, amount) Zerotorescue@80: table.insert(queuedMoves, { Zerotorescue@80: id = itemId, Zerotorescue@80: num = amount, Zerotorescue@80: }); Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: function mod:BeginMove(location, onFinish) Zerotorescue@80: Zerotorescue@80: -- Find the outgoing moves Zerotorescue@80: -- 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 Zerotorescue@80: Zerotorescue@80: -- Get a list of items in the source container Zerotorescue@80: local sourceContents = Scanner:CacheLocation(location, false); Zerotorescue@80: Zerotorescue@80: local outgoingMoves = {}; Zerotorescue@80: Zerotorescue@80: for singleMove in pairs(queuedMoves) do Zerotorescue@80: local sourceItem = sourceContents[singleMove.id]; Zerotorescue@80: if not sourceItem then Zerotorescue@80: print("Can't move " .. IdToItemLink(singleMove.id) .. ", non-existant in source"); Zerotorescue@80: else Zerotorescue@80: -- We want to move the smallest stacks first to keep stuff pretty Zerotorescue@80: table.sort(sourceItem.locations, function(a, b) Zerotorescue@80: return a.count > b.count; Zerotorescue@80: end); Zerotorescue@80: Zerotorescue@80: for itemLocation in pairs(sourceItem.locations) do Zerotorescue@80: -- if this location has more items than we need, only move what we need, otherwise move everything in this stack Zerotorescue@80: local movingNum = ((itemLocation.count > singleMove.num and singleMove.num) or itemLocation.count); Zerotorescue@80: Zerotorescue@80: table.insert(outgoingMoves, { Zerotorescue@80: itemId = singleMove.id, Zerotorescue@80: container = itemLocation.container, Zerotorescue@80: slot = itemLocation.slot, Zerotorescue@80: count = movingNum, Zerotorescue@80: }); Zerotorescue@80: Zerotorescue@80: singleMove.num = (singleMove.num - movingNum); Zerotorescue@80: Zerotorescue@80: if singleMove.num == 0 then Zerotorescue@80: -- If we have prepared everything we wanted, go to the next queued move Zerotorescue@80: break; Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: -- No longer needed Zerotorescue@80: table.wipe(queuedMoves); Zerotorescue@80: Zerotorescue@80: -- Process every single outgoing move and find fitting targets Zerotorescue@80: Zerotorescue@80: -- Get a list of items already in the target container Zerotorescue@80: local targetContents = Scanner:CacheLocation(addon.Locations.Bag, false); Zerotorescue@80: Zerotorescue@80: -- Find all empty slots Zerotorescue@80: Zerotorescue@80: local emptySlots = {}; Zerotorescue@80: Zerotorescue@80: local start = 0; Zerotorescue@80: local stop = NUM_BAG_SLOTS; Zerotorescue@80: Zerotorescue@80: -- Go through all our bags, including the backpack Zerotorescue@80: for bagId = start, stop do Zerotorescue@80: -- Go through all our slots Zerotorescue@80: for slotId = 1, GetContainerNumSlots(bagId) do Zerotorescue@80: local itemId = GetContainerItemID(bagId, slotId); Zerotorescue@80: Zerotorescue@80: if not itemId then Zerotorescue@80: table.insert(emptySlots, { Zerotorescue@80: container: bagId, Zerotorescue@80: slot: slotId, Zerotorescue@80: }); Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: while #outgoingMoves ~= 0 do Zerotorescue@80: -- A not equal-comparison should be quicker than a larger/smaller than-comparison Zerotorescue@80: Zerotorescue@80: for outgoingMove in pairs(outgoingMoves) do Zerotorescue@80: -- itemId will be set to nil when this outgoing move was processed - sanity check Zerotorescue@80: if outgoingMove.itemId then Zerotorescue@80: local targetItem = targetContents[outgoingMove.itemId]; Zerotorescue@80: Zerotorescue@80: if not targetItem then Zerotorescue@80: -- grab an empty slot Zerotorescue@80: -- make new instance of ItemMove Zerotorescue@80: -- populate targetContents with it so future moves of this item can be put on top of it if this isn't a full stack Zerotorescue@80: Zerotorescue@80: local firstAvailableSlot = emptySlots[1]; Zerotorescue@80: Zerotorescue@80: if not firstAvailableSlot then Zerotorescue@80: print("Bags are full. Skipping " .. IdToItemLink(outgoingMove.itemId) .. "."); Zerotorescue@80: Zerotorescue@80: outgoingMove.itemId = nil; Zerotorescue@80: else Zerotorescue@80: table.insert(combinedMoves, { Zerotorescue@80: sourceContainer = outgoingMove.container, Zerotorescue@80: sourceSlot = outgoingMove.slot, Zerotorescue@80: targetContainer = firstAvailableSlot.container, Zerotorescue@80: targetSlot = firstAvailableSlot.slot, Zerotorescue@80: itemId = outgoingMove.itemId, Zerotorescue@80: num = outgoingMove.count, Zerotorescue@80: }); Zerotorescue@80: Zerotorescue@80: -- We filled an empty slot so the target contents now has one more item, Zerotorescue@80: -- make a new instance of the ItemMove class so any additional items with this id can be stacked on top of it Zerotorescue@80: local itemMove = addon.ItemMove:New(); Zerotorescue@80: itemMove.AddLocation(firstAvailableSlot.container, firstAvailableSlot.slot, outgoingMove.count); Zerotorescue@80: targetContents[outgoingMove.itemId] = itemMove; Zerotorescue@80: Zerotorescue@80: firstAvailableSlot = nil; -- no longer empty Zerotorescue@80: Zerotorescue@80: outgoingMove.count = 0; -- nothing remaining - sanity check Zerotorescue@80: outgoingMove.itemId = nil; -- remove this record from the outgoingMoves-table Zerotorescue@80: end Zerotorescue@80: else Zerotorescue@80: -- Find the maximum stack size for this item Zerotorescue@80: local itemStackCount = select(8, GetItemInfo(outgoingMove.itemId)); Zerotorescue@80: Zerotorescue@80: -- We want to move to the largest stacks first to keep stuff pretty Zerotorescue@80: table.sort(targetItem.locations, function(a, b) Zerotorescue@80: return a.count < b.count; Zerotorescue@80: end); Zerotorescue@80: Zerotorescue@80: for itemLocation in pairs(targetItem.locations) do Zerotorescue@80: if itemLocation.count < itemStackCount and outgoingMove.count > 0 then Zerotorescue@80: -- Check if this stack isn't already full (and we still need to move this item) Zerotorescue@80: Zerotorescue@80: local remainingSpace = (itemStackCount - itemLocation.count); Zerotorescue@80: if remainingSpace > outgoingMove.count then Zerotorescue@80: -- Enough room to move this entire stack Zerotorescue@80: -- Deposit this item and then forget this outgoing move as everything in it was processed Zerotorescue@80: Zerotorescue@80: table.insert(combinedMoves, { Zerotorescue@80: sourceContainer = outgoingMove.container, Zerotorescue@80: sourceSlot = outgoingMove.slot, Zerotorescue@80: targetContainer = itemLocation.container, Zerotorescue@80: targetSlot = itemLocation.slot, Zerotorescue@80: itemId = outgoingMove.itemId, Zerotorescue@80: num = outgoingMove.count, Zerotorescue@80: }); Zerotorescue@80: Zerotorescue@80: itemLocation.count = (itemLocation.count + outgoingMove.count); Zerotorescue@80: outgoingMove.count = 0; -- nothing remaining Zerotorescue@80: outgoingMove.itemId = nil; -- remove this record from the outgoingMoves-table Zerotorescue@80: break; -- stop the locations-loop Zerotorescue@80: else Zerotorescue@80: -- Deposit this item but don't remove the outgoing move as there are some items left to move Zerotorescue@80: Zerotorescue@80: table.insert(combinedMoves, { Zerotorescue@80: sourceContainer = outgoingMove.container, Zerotorescue@80: sourceSlot = outgoingMove.slot, Zerotorescue@80: targetContainer = itemLocation.container, Zerotorescue@80: targetSlot = itemLocation.slot, Zerotorescue@80: itemId = outgoingMove.itemId, Zerotorescue@80: num = outgoingMove.count, Zerotorescue@80: }); Zerotorescue@80: Zerotorescue@80: -- The target will be full when we complete, but the source will still have remaining items left to be moved Zerotorescue@80: itemLocation.count = itemStackCount; Zerotorescue@80: outgoingMove.count = (outgoingMove.count - remainingSpace); Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: if outgoingMove.count > 0 then Zerotorescue@80: -- We went through all matching items and checked their stack sizes if we could move this there, no room available Zerotorescue@80: -- 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 Zerotorescue@80: targetItem = nil; Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: -- Loop through the array to find items that should be removed, start with the last element or the loop would break Zerotorescue@80: local numOutgoingMoves = #outgoingMoves; -- since LUA-tables start at an index of 1, this is actually an existing index (outgoingMoves[#outgoingMoves] would return a value) Zerotorescue@80: while numOutgoingMoves ~= 0 do Zerotorescue@80: -- A not equal-comparison should be quicker than a larger/smaller than-comparison Zerotorescue@80: Zerotorescue@80: -- Check if the item id is nil, this is set to nil when this outgoing move has been processed Zerotorescue@80: if not outgoingMoves[numOutgoingMoves].itemId then Zerotorescue@80: -- Remove this element from the array Zerotorescue@80: table.remove(outgoingMoves, numOutgoingMoves); Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: -- Proceed with the next element (or previous considering we're going from last to first) Zerotorescue@80: numOutgoingMoves = (numOutgoingMoves - 1); Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: -- No longer needed Zerotorescue@80: table.wipe(emptySlots); Zerotorescue@80: Zerotorescue@80: DoMoveNow(); Zerotorescue@80: Zerotorescue@80: --ProcessMove(); Zerotorescue@80: Zerotorescue@80: --mod:RegisterEvent("BAG_UPDATE", BAG_UPDATE); Zerotorescue@80: Zerotorescue@80: --onFinish(); Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: function DoMoveNow() Zerotorescue@80: -- combinedMoves now has all moves in it (source -> target) Zerotorescue@80: -- go through list, move everything inside it Zerotorescue@80: -- add source and target to one single list Zerotorescue@80: -- if either is already in this list, skip this move Zerotorescue@80: -- repeat every 5 seconds until we're completely done Zerotorescue@80: Zerotorescue@80: local sourceLocationsLocked = {}; Zerotorescue@80: local targetLocationsLocked = {}; Zerotorescue@80: Zerotorescue@80: local numCurrentMove = #combinedMoves; Zerotorescue@80: while numCurrentMove ~= 0 do Zerotorescue@80: local move = combinedMoves[numCurrentMove]; Zerotorescue@80: Zerotorescue@80: -- sourceContainer, sourceSlot, targetContainer, targetSlot, itemId, num Zerotorescue@80: if (not sourceLocationsLocked[move.sourceContainer] or not sourceLocationsLocked[move.sourceContainer][move.sourceSlot]) and Zerotorescue@80: (not targetLocationsLocked[move.targetContainer] or not targetLocationsLocked[move.targetContainer][move.targetSlot]) then Zerotorescue@80: Zerotorescue@80: print("Moving " .. IdToItemLink(move.itemId)); Zerotorescue@80: Zerotorescue@80: -- Pickup stack Zerotorescue@80: SplitGuildBankItem(move.sourceContainer, move.sourceSlot, move.num); Zerotorescue@80: Zerotorescue@80: -- Remember we picked this item up and thus it is now locked Zerotorescue@80: if not sourceLocationsLocked[move.sourceContainer] then Zerotorescue@80: sourceLocationsLocked[move.sourceContainer] = {}; Zerotorescue@80: end Zerotorescue@80: sourceLocationsLocked[move.sourceContainer][move.sourceSlot] = true; Zerotorescue@80: Zerotorescue@80: if CursorHasItem() then Zerotorescue@80: -- And drop it Zerotorescue@80: PickupContainerItem(move.targetContainer, move.targetSlot); Zerotorescue@80: Zerotorescue@80: -- Remember we dropped an item here and thus this is now locked Zerotorescue@80: if not sourceLocationsLocked[move.targetContainer] then Zerotorescue@80: sourceLocationsLocked[move.targetContainer] = {}; Zerotorescue@80: end Zerotorescue@80: sourceLocationsLocked[move.targetContainer][move.targetSlot] = true; Zerotorescue@80: Zerotorescue@80: -- This move was processed Zerotorescue@80: table.remove(combinedMoves, numCurrentMove); Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: -- Proceed with the next element (or previous considering we're going from last to first) Zerotorescue@80: numCurrentMove = (numCurrentMove - 1); Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: local tmrProcessNext; Zerotorescue@80: function BAG_UPDATE() Zerotorescue@80: mod:CancelTimer(tmrProcessNext, true); -- silent Zerotorescue@80: tmrProcessNext = mod:ScheduleTimer(function() Zerotorescue@80: ProcessMove(); Zerotorescue@80: end, 2); Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: function ProcessMove() Zerotorescue@80: local currentMove = queuedMoves[1]; Zerotorescue@80: Zerotorescue@80: if currentMove then Zerotorescue@80: addon:Debug("Moving " .. currentMove.num .. " of " .. IdToItemLink(currentMove.id)); Zerotorescue@80: Zerotorescue@80: local requestedMoves = currentMove.num; Zerotorescue@80: Zerotorescue@80: if currentMove.src == addon.Locations.Bank then Zerotorescue@80: MoveBankItem(currentMove); Zerotorescue@80: elseif currentMove.src == addon.Locations.Guild then Zerotorescue@80: MoveGuildItem(currentMove); Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: if requestedMoves == currentMove.num then Zerotorescue@80: print("Skipping " .. IdToItemLink(move.id)); Zerotorescue@80: move.num = 0; Zerotorescue@80: elseif currentMove.num > 0 then Zerotorescue@80: -- bags are full Zerotorescue@80: print("bags are full"); Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: if currentMove.num == 0 then Zerotorescue@80: table.remove(queuedMoves, 1); Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: function MoveGuildItem(move) Zerotorescue@80: local tabId = GetCurrentGuildBankTab(); Zerotorescue@80: local slotId = (MAX_GUILDBANK_SLOTS_PER_TAB or 98); -- start by scanning the last slot Zerotorescue@80: Zerotorescue@80: if tabId == nil or tabId < 1 then return; end Zerotorescue@80: Zerotorescue@80: while slotId ~= 0 do Zerotorescue@80: -- A not equal-comparison should be quicker than a larger than-comparison Zerotorescue@80: Zerotorescue@80: local itemLink = GetGuildBankItemLink(tabId, slotId); Zerotorescue@80: if itemLink then Zerotorescue@80: -- If there is actually an item in this slot Zerotorescue@80: Zerotorescue@80: local itemId = GetItemId(itemLink); Zerotorescue@80: Zerotorescue@80: if itemId and move.id == itemId then Zerotorescue@80: -- This is one of the items we're looking for Zerotorescue@80: Zerotorescue@80: local itemCount = select(2, GetGuildBankItemInfo(tabId, slotId)); Zerotorescue@80: Zerotorescue@80: if itemCount and itemCount > 0 then Zerotorescue@80: -- 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 Zerotorescue@80: local moveable = (move.num > itemCount and itemCount) or move.num; Zerotorescue@80: Zerotorescue@80: -- Pickup stack Zerotorescue@80: SplitGuildBankItem(tabId, slotId, moveable); Zerotorescue@80: Zerotorescue@80: -- Find an target slot and put it there Zerotorescue@80: local reallyMoved = DropItem(itemId, moveable); Zerotorescue@80: if reallyMoved then Zerotorescue@80: -- Keep track of how many we have moved Zerotorescue@80: moved = (moved + reallyMoved); Zerotorescue@80: Zerotorescue@80: -- Update the required amount of items so it has the remaining num Zerotorescue@80: move.num = (move.num - reallyMoved); Zerotorescue@80: Zerotorescue@80: --if reallyMoved ~= moveable then Zerotorescue@80: -- -- 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) Zerotorescue@80: -- slotId = (slotId + 1); Zerotorescue@80: --end Zerotorescue@80: Zerotorescue@80: --if move.num == 0 then Zerotorescue@80: -- if no required items are left to move, then stop and tell the caller function how many we moved Zerotorescue@80: return moved; Zerotorescue@80: --end Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: -- Continue scanning a different slot Zerotorescue@80: slotId = (slotId - 1); Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: function MoveBankItem(move) Zerotorescue@80: local start = 0; Zerotorescue@80: local stop = NUM_BAG_SLOTS; Zerotorescue@80: Zerotorescue@80: -- If we requested the bank then we don't want the bag info Zerotorescue@80: start = ( NUM_BAG_SLOTS + 1 ); Zerotorescue@80: stop = ( NUM_BAG_SLOTS + NUM_BANKBAGSLOTS ); Zerotorescue@80: Zerotorescue@80: -- Scan the default 100 slots Zerotorescue@80: move.num = (move.num - MoveFromContainter(BANK_CONTAINER, move)); Zerotorescue@80: Zerotorescue@80: -- Go through all our bags, including the backpack Zerotorescue@80: for bagID = start, stop do Zerotorescue@80: move.num = (move.num - MoveFromContainter(bagID, move)); Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: -- Go through all slots of this bag and if a match was found, move the items Zerotorescue@80: function MoveFromContainter(bagID, move) Zerotorescue@80: -- Keep track of how many we have moved Zerotorescue@80: local moved = 0; Zerotorescue@80: Zerotorescue@80: -- Go through all slots of this bag Zerotorescue@80: for slot = 1, GetContainerNumSlots(bagID) do Zerotorescue@80: local itemId = GetContainerItemID(bagID, slot); Zerotorescue@80: Zerotorescue@80: if itemId and move.id == itemId then Zerotorescue@80: -- This is one of the items we're looking for Zerotorescue@80: Zerotorescue@80: local itemCount = select(2, GetContainerItemInfo(bagID, slot)); Zerotorescue@80: Zerotorescue@80: if itemCount and itemCount > 0 then Zerotorescue@80: -- 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 Zerotorescue@80: local moveable = (move.num > itemCount and itemCount) or move.num; Zerotorescue@80: Zerotorescue@80: -- Pickup stack Zerotorescue@80: SplitContainerItem(bagID, slot, moveable); Zerotorescue@80: Zerotorescue@80: addon:Debug("Picked " .. IdToItemLink(itemId) .. " up"); Zerotorescue@80: Zerotorescue@80: -- Find an target slot and put it there Zerotorescue@80: if CursorHasItem() then Zerotorescue@80: local reallyMoved = DropItem(itemId, moveable); Zerotorescue@80: Zerotorescue@80: if reallyMoved then Zerotorescue@80: addon:Debug("Dropped " .. reallyMoved .. " of " .. IdToItemLink(itemId)); Zerotorescue@80: Zerotorescue@80: -- Keep track of how many we have moved Zerotorescue@80: moved = (moved + reallyMoved); Zerotorescue@80: Zerotorescue@80: -- Update the required amount of items so it has the remaining num Zerotorescue@80: move.num = (move.num - reallyMoved); Zerotorescue@80: Zerotorescue@80: --if reallyMoved ~= moveable then Zerotorescue@80: -- 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) Zerotorescue@80: -- slot = (slot - 1); Zerotorescue@80: --end Zerotorescue@80: Zerotorescue@80: --if move.num == 0 then Zerotorescue@80: -- if no required items are left to move, then stop and tell the caller function how many we moved Zerotorescue@80: return moved; Zerotorescue@80: --end Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: return moved; Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: Zerotorescue@80: -- This currently only uses empty slots, it will have to fill stacks in the future Zerotorescue@80: function DropItem(itemId, amount) Zerotorescue@80: local start = 0; Zerotorescue@80: local stop = NUM_BAG_SLOTS; Zerotorescue@80: Zerotorescue@80: -- Go through all our bags, including the backpack Zerotorescue@80: for bagID = start, stop do Zerotorescue@80: -- Go through all our slots Zerotorescue@80: for slot = 1, GetContainerNumSlots(bagID) do Zerotorescue@80: local itemId = GetContainerItemID(bagID, slot); Zerotorescue@80: Zerotorescue@80: if not itemId then Zerotorescue@80: -- If this slot is empty, put the item here Zerotorescue@80: PickupContainerItem(bagID, slot); Zerotorescue@80: Zerotorescue@80: return amount; Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: return; Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: function IdToItemLink(itemId) Zerotorescue@80: return select(2, GetItemInfo(itemId)); Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: function mod:OnEnable() Zerotorescue@80: Scanner = addon:GetModule("Scanner"); Zerotorescue@80: end Zerotorescue@80: Zerotorescue@80: function mod:OnDisable() Zerotorescue@80: Scanner = nil; Zerotorescue@80: end