Mercurial > wow > inventory
changeset 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 | b89b6981783f |
children | 58617c7827fa |
files | Inventorium.toc ItemMove.class.lua Mover.lua Scanner.lua Todo.txt |
diffstat | 5 files changed, 736 insertions(+), 2 deletions(-) [+] |
line wrap: on
line diff
--- a/Inventorium.toc Sun Dec 26 03:50:06 2010 +0100 +++ b/Inventorium.toc Wed Jan 05 13:05:15 2011 +0100 @@ -14,12 +14,15 @@ # Modules Config.lua +Mover.lua +Scanner.lua Summary.lua Queue.lua # Stuff Widgets.lua ItemData.class.lua +ItemMove.class.lua # Data Data\PremadeGroups.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ItemMove.class.lua Wed Jan 05 13:05:15 2011 +0100 @@ -0,0 +1,37 @@ +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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Mover.lua Wed Jan 05 13:05:15 2011 +0100 @@ -0,0 +1,458 @@ +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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Scanner.lua Wed Jan 05 13:05:15 2011 +0100 @@ -0,0 +1,236 @@ +local addon = select(2, ...); +local mod = addon:NewModule("Scanner", "AceEvent-3.0"); + +addon.Locations = { + Bag = 0, + Bank = 1, + Guild = 2, +}; + +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 + +function mod:ClearCache() + table.wipe(itemCache); +end + +function mod:CacheLocation(location, remember) + if location == addon.Locations.Bag or location == addon.Locations.Bank then + -- Reset cache just in case it was filled + self:ClearCache(); + + local start, stop; + if location == addon.Locations.Bag then + start = 0; + stop = NUM_BAG_SLOTS; + else + -- If we requested the bank then we don't want the bag info + start = ( NUM_BAG_SLOTS + 1 ); + stop = ( NUM_BAG_SLOTS + NUM_BANKBAGSLOTS ); + 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 + -- 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); + + while slotId ~= 0 do + -- A not equal-comparison should be quicker than a larger than-comparison + + local itemId = GetContainerItemID(bagId, slotId); + local itemCount = itemId and select(2, GetContainerItemInfo(bagId, slotId)); + + if itemId and itemCount and itemCount > 0 then + 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(); + else + -- If we had this item in another slot too + itemMove = itemCache[itemId]; + end + + itemMove.AddLocation(bagId, slotId, itemCount); + end + + -- Continue scanning a different slot + slotId = (slotId - 1); + end + end + elseif location == addon.Locations.Guild then + -- Reset cache before we scan + self:ClearCache(); + + 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 + + -- If there is actually an item in this slot + + local itemLink = GetGuildBankItemLink(tabId, slotId); + local itemId = itemLink and GetItemId(itemLink); + local itemCount = itemLink and select(2, GetGuildBankItemInfo(tabId, slotId)); + + if itemLink and itemId and itemCount and itemCount > 0 then + 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(); + else + -- If we had this item in another slot too + itemMove = itemCache[itemId]; + end + + itemMove.AddLocation(tabId, slotId, itemCount); + end + + -- Continue scanning a different slot + slotId = (slotId - 1); + end + else + error("Invalid location provided for the local _GetItemCount. Must be Bank or Guild."); + end + + if not remember then + -- Copy the table as clearing the cache wipes it empty (and tables are passed by reference) + local cacheCopy = CopyTable(itemCache); + + self:ClearCache(); + + return cacheCopy; + end +end + +function mod:Scan(location) + -- We might pause the scanning when we invoke moves ourself + if paused then + return; + end + + local playerName = UnitName("player"); + + self:CacheLocation(location, true); + + -- Go through all groups + for groupName, values in pairs(addon.db.profile.groups) do + local trackAt = addon:GetOptionByKey(groupName, "trackAtCharacters"); + + if values.items and trackAt[playerName] then + -- Is this character interested in this data? + + local minLocalStock = addon:GetOptionByKey(groupName, "minLocalStock"); + + -- 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)); + + if missingItems > 0 then + -- Check how many are available + local availableItems = _GetItemCount(itemId, location); + + 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))); + end + end + end + end + end + + self:ClearCache(); + + self:Pause(); + Mover:BeginMove(location, self.Unpause); +end + +local function BANKFRAME_OPENED() + -- 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() + mod:UnregisterEvent("GUILDBANKFRAME_CLOSED"); + mod:UnregisterEvent("GUILDBANKBAGSLOTS_CHANGED"); + + table.wipe(guildBankTabsScanned); + + addon:Debug("Scanner:GUILDBANKFRAME_CLOSED"); +end + +local function GUILDBANKBAGSLOTS_CHANGED() + if not guildBankTabsScanned[GetCurrentGuildBankTab()] then + 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() + table.wipe(guildBankTabsScanned); + + mod:RegisterEvent("GUILDBANKFRAME_CLOSED", GUILDBANKFRAME_CLOSED); + mod:RegisterEvent("GUILDBANKBAGSLOTS_CHANGED", GUILDBANKBAGSLOTS_CHANGED); + + addon:Debug("Scanner:GUILDBANKFRAME_OPENED"); +end + +function mod:OnEnable() + -- Scan once when the bankframe is opened + mod:RegisterEvent("BANKFRAME_OPENED", BANKFRAME_OPENED); + mod:RegisterEvent("GUILDBANKFRAME_OPENED", GUILDBANKFRAME_OPENED); + + Mover = addon:GetModule("Mover"); +end + +function mod:OnDisable() + Mover = nil; + + -- Bank + mod:UnregisterEvent("BANKFRAME_OPENED"); + + -- Guild + GUILDBANKFRAME_CLOSED(); + mod:UnregisterEvent("GUILDBANKFRAME_OPENED"); +end + +function mod:Pause() + paused = true; +end + +function mod:Unpause() + paused = nil; +end
--- a/Todo.txt Sun Dec 26 03:50:06 2010 +0100 +++ b/Todo.txt Wed Jan 05 13:05:15 2011 +0100 @@ -3,9 +3,9 @@ Might also need an undo button - Advanced implementation: record queueing with exact time and date and show a nice linechart for each item. Probably would have to write a new lib for the symbols. (low) * Restock from vendor (similar to ATSW reagent buying) (medium) - * Window containing a list of items that were unqueueable when a group is queued (low-medium) + * Automatic refill from the bank + * Window containing a list of items that were unqueueable when a group is queued (low) * Enchanting -> scroll self-learning (low) * Local item count display (bags or AH), thresholds and alerts (high) - Simple implementation: add a column displaying this, settings to specify how much should be local and an alert box - * Verifiy Cauldron support * Verify AuctionLite support \ No newline at end of file