Mercurial > wow > inventory
view Mover.lua @ 82:f885805da5d6
Added options to toggle the automatic refilling. This defaults to true.
Normalized property amount names; a move has a ?num? that must be moved and a location has a ?count? indicating the amount of items at that slot.
Target/source item verification should now be working properly for guilds.
When ?bank? is included in the local item count, we will skip trying to auto refill from this.
author | Zerotorescue |
---|---|
date | Thu, 06 Jan 2011 10:48:56 +0100 |
parents | 58617c7827fa |
children |
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 = {}; addon:Debug(#queuedMoves .. " moves were queued."); 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 (and minimize space usage, splitting a stack takes 2 slots, moving something only 1) 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, num = movingNum, container = itemLocation.container, slot = itemLocation.slot, }); 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 addon:Debug(#outgoingMoves .. " outgoing moves are possible."); -- 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); -- we're scanning our local bags here, so no need to get messy with guild bank support if not itemId then table.insert(emptySlots, { container = bagId, slot = slotId, }); end end end addon:Debug(#emptySlots .. " empty slots are available."); -- 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; -- remove this record from the outgoingMoves-table else table.insert(combinedMoves, { itemId = outgoingMove.itemId, num = outgoingMove.num, sourceContainer = outgoingMove.container, sourceSlot = outgoingMove.slot, targetContainer = firstAvailableSlot.container, targetSlot = firstAvailableSlot.slot, }); -- 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.num); targetContents[outgoingMove.itemId] = itemMove; table.remove(emptySlots, 1); -- no longer empty outgoingMove.num = 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.num > 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.num 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, { itemId = outgoingMove.itemId, num = outgoingMove.num, sourceContainer = outgoingMove.container, sourceSlot = outgoingMove.slot, targetContainer = itemLocation.container, targetSlot = itemLocation.slot, }); itemLocation.count = (itemLocation.count + outgoingMove.num); 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, { itemId = outgoingMove.itemId, num = outgoingMove.num, sourceContainer = outgoingMove.container, sourceSlot = outgoingMove.slot, targetContainer = itemLocation.container, targetSlot = itemLocation.slot, }); -- The target will be full when we complete, but the source will still have remaining items left to be moved itemLocation.count = itemStackCount; outgoingMove.num = (outgoingMove.num - remainingSpace); end end end if outgoingMove.num > 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 addon:Debug(#combinedMoves .. " moves should be possible."); -- No longer needed table.wipe(emptySlots); self:ProcessMove(); -- Even though we aren't completely done yet, allow requeueing 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("BAG_UPDATE"); 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 GetContainerItemID; if movesSource == addon.Locations.Guild then GetContainerItemID = GetGuildBankItemLink; else GetContainerItemID = GetContainerItemID; end local combinedMovesOriginalLength = #combinedMoves; local numCurrentMove = combinedMovesOriginalLength; 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 (this is always a local bag so no need to do any guild-checks) 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 addon:Debug((combinedMovesOriginalLength - #combinedMoves) .. " moves processed. " .. #combinedMoves .. " moves remaining."); 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