Mercurial > wow > inventory
view Mover.lua @ 81:58617c7827fa
Item refilling should now be working. Probably very slow if your bags, bank or guild bank is filled with over 200 unique items or so (needs some real testing, but know that it is a known (possible) issue).
author | Zerotorescue |
---|---|
date | Thu, 06 Jan 2011 01:01:25 +0100 |
parents | c0bf2ddb5288 |
children | f885805da5d6 |
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 #) local movesSource; function mod:AddMove(itemId, amount) table.insert(queuedMoves, { id = itemId, num = amount, }); end function mod:HasMoves() return (#queuedMoves ~= 0); end function mod:BeginMove(location, onFinish) addon:Debug("BeginMove"); -- 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; -- stop the locations-loop 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 -- Remember where we're moving from movesSource = location; 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.ContainerItem:New(); itemMove:AddLocation(firstAvailableSlot.container, firstAvailableSlot.slot, outgoingMove.count); targetContents[outgoingMove.itemId] = itemMove; table.remove(emptySlots, 1); -- 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); self:ProcessMove(); self:RegisterEvent("BAG_UPDATE"); onFinish(); end if not table.reverse then -- table.reverse = function(orig) -- local temp = CopyTable(orig); -- local origLength = #temp; -- for i = 1, origLength do -- orig[(origLength - i + 1)] = temp[i]; -- end -- end table.reverse = function(orig) local temp = {}; local origLength = #orig; for i = 1, origLength do temp[(origLength - i + 1)] = orig[i]; end -- -- Update the original table (can't do orig = temp as that would change the reference-link instead of the original table) -- for i, v in pairs(temp) do -- orig[i] = v; -- end return temp; -- for speed we choose to do a return instead end end function mod:ProcessMove() addon:Debug("ProcessMove"); if #combinedMoves == 0 then print("Nothing to move."); self:Abort(); return; end self:RegisterEvent("UI_ERROR_MESSAGE"); -- combinedMoves now has all moves in it (source -> target) -- go through list, move everything inside it -- add source and target to lists, if either is already in this list, skip the move -- repeat every few seconds until we're completely done local sourceLocationsLocked = {}; local targetLocationsLocked = {}; -- Reverse table, we need to go through it from last to first because we'll be removing elements, but we don't want the actions to be executed in a different order combinedMoves = table.reverse(combinedMoves); local numCurrentMove = #combinedMoves; while numCurrentMove ~= 0 do local move = combinedMoves[numCurrentMove]; -- sourceContainer, sourceSlot, targetContainer, targetSlot, itemId, num if move and (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)); addon:Debug(("Moving %dx%s from (%d,%d) to (%d,%d)"):format(move.num, IdToItemLink(move.itemId), move.sourceContainer, move.sourceSlot, move.targetContainer, move.targetSlot)); if GetContainerItemID(move.sourceContainer, move.sourceSlot) ~= move.itemId then self:Abort("source changed", "Source (" .. move.sourceContainer .. "," .. move.sourceSlot .. ") is not " .. IdToItemLink(move.itemId)); return; end -- Pickup stack if movesSource == addon.Locations.Bank then SplitContainerItem(move.sourceContainer, move.sourceSlot, move.num); elseif movesSource == addon.Locations.Guild then SplitGuildBankItem(move.sourceContainer, move.sourceSlot, move.num); end -- 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 movesSource == addon.Locations.Guild or CursorHasItem() then -- CursorHasItem is always false if source is a guild tab if GetContainerItemID(move.targetContainer, move.targetSlot) and GetContainerItemID(move.targetContainer, move.targetSlot) ~= move.itemId then self:Abort("target changed", "Target (" .. move.targetContainer .. "," .. move.targetSlot .. ") is not " .. IdToItemLink(move.itemId) .. " nor empty"); return; end -- And drop it PickupContainerItem(move.targetContainer, move.targetSlot); -- Remember we dropped an item here and thus this is now locked if not targetLocationsLocked[move.targetContainer] then targetLocationsLocked[move.targetContainer] = {}; end targetLocationsLocked[move.targetContainer][move.targetSlot] = true; -- This move was processed table.remove(combinedMoves, numCurrentMove); else self:Abort("item disappeared from mouse", "Couldn't move " .. IdToItemLink(move.itemId) .. ", CursorHasItem() is false"); return; end end -- Proceed with the next element (or previous considering we're going from last to first) numCurrentMove = (numCurrentMove - 1); end if #combinedMoves == 0 then print("Finished."); self:Abort(); return; end end local tmrProcessNext; function mod:BAG_UPDATE() self:CancelTimer(tmrProcessNext, true); -- silent tmrProcessNext = self:ScheduleTimer("ProcessMove", 1); end function IdToItemLink(itemId) local itemLink = select(2, GetItemInfo(itemId)); itemLink = itemLink or "Unknown (" .. itemId .. ")"; return itemLink; end function mod:UI_ERROR_MESSAGE(e, errorMessage) if errorMessage == ERR_SPLIT_FAILED then self:Abort("splitting failed", "Splitting failed."); end end function mod:Abort(simple, debugMsg) if debugMsg then addon:Debug("Aborting:" .. debugMsg); end if simple then print("|cffff0000Aborting: " .. simple .. ".|r"); end table.wipe(combinedMoves); movesSource = nil; -- Stop timer self:UnregisterEvent("BAG_UPDATE"); self:CancelTimer(tmrProcessNext, true); -- silent self:UnregisterEvent("UI_ERROR_MESSAGE"); end function mod:OnEnable() Scanner = addon:GetModule("Scanner"); end function mod:OnDisable() Scanner = nil; self:Abort(); end