# HG changeset patch # User Zerotorescue # Date 1294272085 -3600 # Node ID 58617c7827fae238170c8981121ecb9c768e4557 # Parent c0bf2ddb5288ef805aed9b5200d48c0a7ee7bf13 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). diff -r c0bf2ddb5288 -r 58617c7827fa ContainerItem.class.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ContainerItem.class.lua Thu Jan 06 01:01:25 2011 +0100 @@ -0,0 +1,32 @@ +local addon = select(2, ...); + +-- Define the class + +addon.ContainerItem = {}; +addon.ContainerItem.__index = addon.ContainerItem; + +-- Construct +function addon.ContainerItem:New(id) + local self = {}; + + setmetatable(self, addon.ContainerItem); + + -- Standard info everything needs + self.id = id; + self.totalCount = 0; + self.locations = {}; + + return self; +end + +function addon.ContainerItem:AddLocation(container, slot, count) + table.insert(self.locations, { + container = container, + slot = slot, + count = count, + }); + + self.totalCount = (self.totalCount + count); + + return true; +end diff -r c0bf2ddb5288 -r 58617c7827fa Inventorium.toc --- a/Inventorium.toc Wed Jan 05 13:05:15 2011 +0100 +++ b/Inventorium.toc Thu Jan 06 01:01:25 2011 +0100 @@ -22,7 +22,7 @@ # Stuff Widgets.lua ItemData.class.lua -ItemMove.class.lua +ContainerItem.class.lua # Data Data\PremadeGroups.lua diff -r c0bf2ddb5288 -r 58617c7827fa ItemMove.class.lua --- a/ItemMove.class.lua Wed Jan 05 13:05:15 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ -local addon = select(2, ...); - --- Define the class - -addon.ItemMove = {}; -addon.ItemMove.__index = addon.ItemMove; - --- Construct -function addon.ItemMove:New(id) - local self = {}; - - setmetatable(self, addon.ItemMove); - - -- Standard info everything needs - self.id = id; - self.totalCount = 0; - self.locations = {}; - - return self; -end - -function addon.ItemMove:AddLocation(container, slot, count) - table.insert(self.locations, { - container = container, - slot = slot, - count = count, - }); - - self.totalCount = (self.totalCount + count); - - return true; -end - -function addon.ItemMove:Move(location, targetBag, targetSlot) - -- move location (container, slot, count) to targetBag, targetSlot - return true; -end diff -r c0bf2ddb5288 -r 58617c7827fa Mover.lua --- a/Mover.lua Wed Jan 05 13:05:15 2011 +0100 +++ b/Mover.lua Thu Jan 06 01:01:25 2011 +0100 @@ -4,6 +4,7 @@ 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, { @@ -12,7 +13,12 @@ }); 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 @@ -22,17 +28,17 @@ local outgoingMoves = {}; - for singleMove in pairs(queuedMoves) do + 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; + return a.count < b.count; end); - for itemLocation in pairs(sourceItem.locations) do + 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); @@ -47,7 +53,7 @@ if singleMove.num == 0 then -- If we have prepared everything we wanted, go to the next queued move - break; + break; -- stop the locations-loop end end end @@ -76,17 +82,20 @@ if not itemId then table.insert(emptySlots, { - container: bagId, - slot: slotId, + 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 + 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]; @@ -114,11 +123,11 @@ -- 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); + local itemMove = addon.ContainerItem:New(); + itemMove:AddLocation(firstAvailableSlot.container, firstAvailableSlot.slot, outgoingMove.count); targetContents[outgoingMove.itemId] = itemMove; - firstAvailableSlot = nil; -- no longer empty + table.remove(emptySlots, 1); -- no longer empty outgoingMove.count = 0; -- nothing remaining - sanity check outgoingMove.itemId = nil; -- remove this record from the outgoingMoves-table @@ -129,10 +138,10 @@ -- 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; + return a.count > b.count; end); - for itemLocation in pairs(targetItem.locations) do + 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) @@ -201,37 +210,84 @@ -- No longer needed table.wipe(emptySlots); - DoMoveNow(); + self:ProcessMove(); - --ProcessMove(); + self:RegisterEvent("BAG_UPDATE"); - --mod:RegisterEvent("BAG_UPDATE", BAG_UPDATE); - - --onFinish(); + onFinish(); end -function DoMoveNow() +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 one single list - -- if either is already in this list, skip this move - -- repeat every 5 seconds until we're completely done + -- 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 (not sourceLocationsLocked[move.sourceContainer] or not sourceLocationsLocked[move.sourceContainer][move.sourceSlot]) and + 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 - SplitGuildBankItem(move.sourceContainer, move.sourceSlot, move.num); + 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 @@ -239,214 +295,75 @@ end sourceLocationsLocked[move.sourceContainer][move.sourceSlot] = true; - if CursorHasItem() then + 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 sourceLocationsLocked[move.targetContainer] then - sourceLocationsLocked[move.targetContainer] = {}; + if not targetLocationsLocked[move.targetContainer] then + targetLocationsLocked[move.targetContainer] = {}; end - sourceLocationsLocked[move.targetContainer][move.targetSlot] = true; + 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 BAG_UPDATE() - mod:CancelTimer(tmrProcessNext, true); -- silent - tmrProcessNext = mod:ScheduleTimer(function() - ProcessMove(); - end, 2); +function mod:BAG_UPDATE() + self:CancelTimer(tmrProcessNext, true); -- silent + tmrProcessNext = self:ScheduleTimer("ProcessMove", 1); 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 +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 MoveGuildItem(move) - local tabId = GetCurrentGuildBankTab(); - local slotId = (MAX_GUILDBANK_SLOTS_PER_TAB or 98); -- start by scanning the last slot +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; - if tabId == nil or tabId < 1 then return; end + -- Stop timer + self:UnregisterEvent("BAG_UPDATE"); + self:CancelTimer(tmrProcessNext, true); -- silent - 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)); + self:UnregisterEvent("UI_ERROR_MESSAGE"); end function mod:OnEnable() @@ -455,4 +372,6 @@ function mod:OnDisable() Scanner = nil; + + self:Abort(); end diff -r c0bf2ddb5288 -r 58617c7827fa Scanner.lua --- a/Scanner.lua Wed Jan 05 13:05:15 2011 +0100 +++ b/Scanner.lua Thu Jan 06 01:01:25 2011 +0100 @@ -10,18 +10,6 @@ local Mover, paused; local itemCache = {}; -local function _GetItemCount(itemId, location) - if location == addon.Locations.Bank then - -- No longer using GetItemCount as this includes equiped items and containers (e.g. bank bags) - --return (GetItemCount(itemId, true) - GetItemCount(itemId, false)); -- GetItemCount(X, true) provides count for bag+bank, GetItemCount(X, false) provides just bag, so (GetItemCount(X, true) - GetItemCount(X, false)) = just bank - return ((itemCache[itemId] and itemCache[itemId].totalCount) or 0); - elseif location == addon.Locations.Guild then - return ((itemCache[itemId] and itemCache[itemId].totalCount) or 0); - else - error("Invalid location provided for the local _GetItemCount. Must be Bag or Bank."); - end -end - local function GetItemID(link) return tonumber(link:match("|Hitem:([-0-9]+):")); end @@ -46,7 +34,7 @@ end -- Go through all our bags, including the backpack - for i = start, ((addon.Locations.Bag and stop) or (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 + 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); @@ -61,13 +49,14 @@ local itemMove; if not itemCache[itemId] then -- If this is the first time we see this item, make a new object - itemMove = addon.ItemMove:New(); + 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); + itemMove:AddLocation(bagId, slotId, itemCount); end -- Continue scanning a different slot @@ -96,13 +85,14 @@ local itemMove; if not itemCache[itemId] then -- If this is the first time we see this item, make a new object - itemMove = addon.ItemMove:New(); + 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); + itemMove:AddLocation(tabId, slotId, itemCount); end -- Continue scanning a different slot @@ -144,19 +134,19 @@ -- Go through all items for itemId, _ in pairs(values.items) do - -- Check if we have enough items local - local missingItems = (minLocalStock - addon:GetLocalItemCount(itemId, groupName)); + -- 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 missingItems > 0 then + if itemCache[itemId] and missingItems > 0 then -- Check how many are available - local availableItems = _GetItemCount(itemId, location); + local availableItems = ((itemCache[itemId] and itemCache[itemId].totalCount) or 0); if availableItems > 0 then print("Insufficient " .. select(2, GetItemInfo(itemId)) .. " but this location has " .. availableItems .. " (moving " .. missingItems .. ")"); Mover:AddMove(itemId, missingItems); else - print("Insufficient " .. select(2, GetItemInfo(itemId))); + print("Insufficient " .. IdToItemLink(itemId)); end end end @@ -165,47 +155,72 @@ self:ClearCache(); - self:Pause(); - Mover:BeginMove(location, self.Unpause); + if Mover:HasMoves() then + StaticPopupDialogs["InventoriumRefill"] = { + text = "There are items that can be refilled from this location, do you wish to proceed?", + button1 = YES, + button2 = NO, + OnAccept = function() + mod:Pause(); + Mover:BeginMove(location, self.Unpause); + end, + timeout = 0, + whileDead = 1, + hideOnEscape = 1, + }; + StaticPopup_Show("InventoriumRefill"); + end +end + +local function BANKFRAME_CLOSED() + addon:Debug("Scanner:BANKFRAME_CLOSED"); + + mod:UnregisterEvent("BANKFRAME_CLOSED"); + + StaticPopup_Hide("InventoriumRefill"); end local function BANKFRAME_OPENED() + addon:Debug("Scanner:BANKFRAME_OPENED"); + + mod:RegisterEvent("BANKFRAME_CLOSED", BANKFRAME_CLOSED); + -- Scan once when the bank is opened, but no need to scan after mod:Scan(addon.Locations.Bank); - - addon:Debug("Scanner:BANKFRAME_OPENED"); end -- Remember which tabs were scanned and don't scan them again local guildBankTabsScanned = {}; local function GUILDBANKFRAME_CLOSED() + addon:Debug("Scanner:GUILDBANKFRAME_CLOSED"); + + table.wipe(guildBankTabsScanned); + mod:UnregisterEvent("GUILDBANKFRAME_CLOSED"); mod:UnregisterEvent("GUILDBANKBAGSLOTS_CHANGED"); - table.wipe(guildBankTabsScanned); - - addon:Debug("Scanner:GUILDBANKFRAME_CLOSED"); + StaticPopup_Hide("InventoriumRefill"); end local function GUILDBANKBAGSLOTS_CHANGED() if not guildBankTabsScanned[GetCurrentGuildBankTab()] then + addon:Debug("Scanner:GUILDBANKBAGSLOTS_CHANGED (" .. GetCurrentGuildBankTab() .. ") - scanning"); + mod:Scan(addon.Locations.Guild); guildBankTabsScanned[GetCurrentGuildBankTab()] = true; - - addon:Debug("Scanner:GUILDBANKBAGSLOTS_CHANGED (" .. GetCurrentGuildBankTab() .. ") - scanning"); else addon:Debug("Scanner:GUILDBANKBAGSLOTS_CHANGED (" .. GetCurrentGuildBankTab() .. ") - not scanning"); end end local function GUILDBANKFRAME_OPENED() + addon:Debug("Scanner:GUILDBANKFRAME_OPENED"); + table.wipe(guildBankTabsScanned); mod:RegisterEvent("GUILDBANKFRAME_CLOSED", GUILDBANKFRAME_CLOSED); mod:RegisterEvent("GUILDBANKBAGSLOTS_CHANGED", GUILDBANKBAGSLOTS_CHANGED); - - addon:Debug("Scanner:GUILDBANKFRAME_OPENED"); end function mod:OnEnable()