annotate 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
rev   line source
Zerotorescue@80 1 local addon = select(2, ...);
Zerotorescue@80 2 local mod = addon:NewModule("Mover", "AceEvent-3.0", "AceTimer-3.0");
Zerotorescue@80 3
Zerotorescue@80 4 local Scanner;
Zerotorescue@80 5 local queuedMoves = {}; -- table storing all queued moves before BeginMove is called
Zerotorescue@80 6 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@81 7 local movesSource;
Zerotorescue@80 8
Zerotorescue@80 9 function mod:AddMove(itemId, amount)
Zerotorescue@80 10 table.insert(queuedMoves, {
Zerotorescue@80 11 id = itemId,
Zerotorescue@80 12 num = amount,
Zerotorescue@80 13 });
Zerotorescue@80 14 end
Zerotorescue@80 15
Zerotorescue@81 16 function mod:HasMoves()
Zerotorescue@81 17 return (#queuedMoves ~= 0);
Zerotorescue@81 18 end
Zerotorescue@81 19
Zerotorescue@80 20 function mod:BeginMove(location, onFinish)
Zerotorescue@81 21 addon:Debug("BeginMove");
Zerotorescue@80 22
Zerotorescue@80 23 -- Find the outgoing moves
Zerotorescue@80 24 -- 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 25
Zerotorescue@80 26 -- Get a list of items in the source container
Zerotorescue@80 27 local sourceContents = Scanner:CacheLocation(location, false);
Zerotorescue@80 28
Zerotorescue@80 29 local outgoingMoves = {};
Zerotorescue@80 30
Zerotorescue@81 31 for _, singleMove in pairs(queuedMoves) do
Zerotorescue@80 32 local sourceItem = sourceContents[singleMove.id];
Zerotorescue@80 33 if not sourceItem then
Zerotorescue@80 34 print("Can't move " .. IdToItemLink(singleMove.id) .. ", non-existant in source");
Zerotorescue@80 35 else
Zerotorescue@80 36 -- We want to move the smallest stacks first to keep stuff pretty
Zerotorescue@80 37 table.sort(sourceItem.locations, function(a, b)
Zerotorescue@81 38 return a.count < b.count;
Zerotorescue@80 39 end);
Zerotorescue@80 40
Zerotorescue@81 41 for _, itemLocation in pairs(sourceItem.locations) do
Zerotorescue@80 42 -- if this location has more items than we need, only move what we need, otherwise move everything in this stack
Zerotorescue@80 43 local movingNum = ((itemLocation.count > singleMove.num and singleMove.num) or itemLocation.count);
Zerotorescue@80 44
Zerotorescue@80 45 table.insert(outgoingMoves, {
Zerotorescue@80 46 itemId = singleMove.id,
Zerotorescue@80 47 container = itemLocation.container,
Zerotorescue@80 48 slot = itemLocation.slot,
Zerotorescue@80 49 count = movingNum,
Zerotorescue@80 50 });
Zerotorescue@80 51
Zerotorescue@80 52 singleMove.num = (singleMove.num - movingNum);
Zerotorescue@80 53
Zerotorescue@80 54 if singleMove.num == 0 then
Zerotorescue@80 55 -- If we have prepared everything we wanted, go to the next queued move
Zerotorescue@81 56 break; -- stop the locations-loop
Zerotorescue@80 57 end
Zerotorescue@80 58 end
Zerotorescue@80 59 end
Zerotorescue@80 60 end
Zerotorescue@80 61
Zerotorescue@80 62 -- No longer needed
Zerotorescue@80 63 table.wipe(queuedMoves);
Zerotorescue@80 64
Zerotorescue@80 65 -- Process every single outgoing move and find fitting targets
Zerotorescue@80 66
Zerotorescue@80 67 -- Get a list of items already in the target container
Zerotorescue@80 68 local targetContents = Scanner:CacheLocation(addon.Locations.Bag, false);
Zerotorescue@80 69
Zerotorescue@80 70 -- Find all empty slots
Zerotorescue@80 71
Zerotorescue@80 72 local emptySlots = {};
Zerotorescue@80 73
Zerotorescue@80 74 local start = 0;
Zerotorescue@80 75 local stop = NUM_BAG_SLOTS;
Zerotorescue@80 76
Zerotorescue@80 77 -- Go through all our bags, including the backpack
Zerotorescue@80 78 for bagId = start, stop do
Zerotorescue@80 79 -- Go through all our slots
Zerotorescue@80 80 for slotId = 1, GetContainerNumSlots(bagId) do
Zerotorescue@80 81 local itemId = GetContainerItemID(bagId, slotId);
Zerotorescue@80 82
Zerotorescue@80 83 if not itemId then
Zerotorescue@80 84 table.insert(emptySlots, {
Zerotorescue@81 85 container = bagId,
Zerotorescue@81 86 slot = slotId,
Zerotorescue@80 87 });
Zerotorescue@80 88 end
Zerotorescue@80 89 end
Zerotorescue@80 90 end
Zerotorescue@80 91
Zerotorescue@81 92 -- Remember where we're moving from
Zerotorescue@81 93 movesSource = location;
Zerotorescue@81 94
Zerotorescue@80 95 while #outgoingMoves ~= 0 do
Zerotorescue@80 96 -- A not equal-comparison should be quicker than a larger/smaller than-comparison
Zerotorescue@80 97
Zerotorescue@81 98 for _, outgoingMove in pairs(outgoingMoves) do
Zerotorescue@80 99 -- itemId will be set to nil when this outgoing move was processed - sanity check
Zerotorescue@80 100 if outgoingMove.itemId then
Zerotorescue@80 101 local targetItem = targetContents[outgoingMove.itemId];
Zerotorescue@80 102
Zerotorescue@80 103 if not targetItem then
Zerotorescue@80 104 -- grab an empty slot
Zerotorescue@80 105 -- make new instance of ItemMove
Zerotorescue@80 106 -- 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 107
Zerotorescue@80 108 local firstAvailableSlot = emptySlots[1];
Zerotorescue@80 109
Zerotorescue@80 110 if not firstAvailableSlot then
Zerotorescue@80 111 print("Bags are full. Skipping " .. IdToItemLink(outgoingMove.itemId) .. ".");
Zerotorescue@80 112
Zerotorescue@80 113 outgoingMove.itemId = nil;
Zerotorescue@80 114 else
Zerotorescue@80 115 table.insert(combinedMoves, {
Zerotorescue@80 116 sourceContainer = outgoingMove.container,
Zerotorescue@80 117 sourceSlot = outgoingMove.slot,
Zerotorescue@80 118 targetContainer = firstAvailableSlot.container,
Zerotorescue@80 119 targetSlot = firstAvailableSlot.slot,
Zerotorescue@80 120 itemId = outgoingMove.itemId,
Zerotorescue@80 121 num = outgoingMove.count,
Zerotorescue@80 122 });
Zerotorescue@80 123
Zerotorescue@80 124 -- We filled an empty slot so the target contents now has one more item,
Zerotorescue@80 125 -- make a new instance of the ItemMove class so any additional items with this id can be stacked on top of it
Zerotorescue@81 126 local itemMove = addon.ContainerItem:New();
Zerotorescue@81 127 itemMove:AddLocation(firstAvailableSlot.container, firstAvailableSlot.slot, outgoingMove.count);
Zerotorescue@80 128 targetContents[outgoingMove.itemId] = itemMove;
Zerotorescue@80 129
Zerotorescue@81 130 table.remove(emptySlots, 1); -- no longer empty
Zerotorescue@80 131
Zerotorescue@80 132 outgoingMove.count = 0; -- nothing remaining - sanity check
Zerotorescue@80 133 outgoingMove.itemId = nil; -- remove this record from the outgoingMoves-table
Zerotorescue@80 134 end
Zerotorescue@80 135 else
Zerotorescue@80 136 -- Find the maximum stack size for this item
Zerotorescue@80 137 local itemStackCount = select(8, GetItemInfo(outgoingMove.itemId));
Zerotorescue@80 138
Zerotorescue@80 139 -- We want to move to the largest stacks first to keep stuff pretty
Zerotorescue@80 140 table.sort(targetItem.locations, function(a, b)
Zerotorescue@81 141 return a.count > b.count;
Zerotorescue@80 142 end);
Zerotorescue@80 143
Zerotorescue@81 144 for _, itemLocation in pairs(targetItem.locations) do
Zerotorescue@80 145 if itemLocation.count < itemStackCount and outgoingMove.count > 0 then
Zerotorescue@80 146 -- Check if this stack isn't already full (and we still need to move this item)
Zerotorescue@80 147
Zerotorescue@80 148 local remainingSpace = (itemStackCount - itemLocation.count);
Zerotorescue@80 149 if remainingSpace > outgoingMove.count then
Zerotorescue@80 150 -- Enough room to move this entire stack
Zerotorescue@80 151 -- Deposit this item and then forget this outgoing move as everything in it was processed
Zerotorescue@80 152
Zerotorescue@80 153 table.insert(combinedMoves, {
Zerotorescue@80 154 sourceContainer = outgoingMove.container,
Zerotorescue@80 155 sourceSlot = outgoingMove.slot,
Zerotorescue@80 156 targetContainer = itemLocation.container,
Zerotorescue@80 157 targetSlot = itemLocation.slot,
Zerotorescue@80 158 itemId = outgoingMove.itemId,
Zerotorescue@80 159 num = outgoingMove.count,
Zerotorescue@80 160 });
Zerotorescue@80 161
Zerotorescue@80 162 itemLocation.count = (itemLocation.count + outgoingMove.count);
Zerotorescue@80 163 outgoingMove.count = 0; -- nothing remaining
Zerotorescue@80 164 outgoingMove.itemId = nil; -- remove this record from the outgoingMoves-table
Zerotorescue@80 165 break; -- stop the locations-loop
Zerotorescue@80 166 else
Zerotorescue@80 167 -- Deposit this item but don't remove the outgoing move as there are some items left to move
Zerotorescue@80 168
Zerotorescue@80 169 table.insert(combinedMoves, {
Zerotorescue@80 170 sourceContainer = outgoingMove.container,
Zerotorescue@80 171 sourceSlot = outgoingMove.slot,
Zerotorescue@80 172 targetContainer = itemLocation.container,
Zerotorescue@80 173 targetSlot = itemLocation.slot,
Zerotorescue@80 174 itemId = outgoingMove.itemId,
Zerotorescue@80 175 num = outgoingMove.count,
Zerotorescue@80 176 });
Zerotorescue@80 177
Zerotorescue@80 178 -- The target will be full when we complete, but the source will still have remaining items left to be moved
Zerotorescue@80 179 itemLocation.count = itemStackCount;
Zerotorescue@80 180 outgoingMove.count = (outgoingMove.count - remainingSpace);
Zerotorescue@80 181 end
Zerotorescue@80 182 end
Zerotorescue@80 183 end
Zerotorescue@80 184
Zerotorescue@80 185 if outgoingMove.count > 0 then
Zerotorescue@80 186 -- We went through all matching items and checked their stack sizes if we could move this there, no room available
Zerotorescue@80 187 -- 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 188 targetItem = nil;
Zerotorescue@80 189 end
Zerotorescue@80 190 end
Zerotorescue@80 191 end
Zerotorescue@80 192 end
Zerotorescue@80 193
Zerotorescue@80 194 -- Loop through the array to find items that should be removed, start with the last element or the loop would break
Zerotorescue@80 195 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 196 while numOutgoingMoves ~= 0 do
Zerotorescue@80 197 -- A not equal-comparison should be quicker than a larger/smaller than-comparison
Zerotorescue@80 198
Zerotorescue@80 199 -- Check if the item id is nil, this is set to nil when this outgoing move has been processed
Zerotorescue@80 200 if not outgoingMoves[numOutgoingMoves].itemId then
Zerotorescue@80 201 -- Remove this element from the array
Zerotorescue@80 202 table.remove(outgoingMoves, numOutgoingMoves);
Zerotorescue@80 203 end
Zerotorescue@80 204
Zerotorescue@80 205 -- Proceed with the next element (or previous considering we're going from last to first)
Zerotorescue@80 206 numOutgoingMoves = (numOutgoingMoves - 1);
Zerotorescue@80 207 end
Zerotorescue@80 208 end
Zerotorescue@80 209
Zerotorescue@80 210 -- No longer needed
Zerotorescue@80 211 table.wipe(emptySlots);
Zerotorescue@80 212
Zerotorescue@81 213 self:ProcessMove();
Zerotorescue@80 214
Zerotorescue@81 215 self:RegisterEvent("BAG_UPDATE");
Zerotorescue@80 216
Zerotorescue@81 217 onFinish();
Zerotorescue@80 218 end
Zerotorescue@80 219
Zerotorescue@81 220 if not table.reverse then
Zerotorescue@81 221 -- table.reverse = function(orig)
Zerotorescue@81 222 -- local temp = CopyTable(orig);
Zerotorescue@81 223 -- local origLength = #temp;
Zerotorescue@81 224 -- for i = 1, origLength do
Zerotorescue@81 225 -- orig[(origLength - i + 1)] = temp[i];
Zerotorescue@81 226 -- end
Zerotorescue@81 227 -- end
Zerotorescue@81 228 table.reverse = function(orig)
Zerotorescue@81 229 local temp = {};
Zerotorescue@81 230 local origLength = #orig;
Zerotorescue@81 231 for i = 1, origLength do
Zerotorescue@81 232 temp[(origLength - i + 1)] = orig[i];
Zerotorescue@81 233 end
Zerotorescue@81 234
Zerotorescue@81 235 -- -- Update the original table (can't do orig = temp as that would change the reference-link instead of the original table)
Zerotorescue@81 236 -- for i, v in pairs(temp) do
Zerotorescue@81 237 -- orig[i] = v;
Zerotorescue@81 238 -- end
Zerotorescue@81 239 return temp; -- for speed we choose to do a return instead
Zerotorescue@81 240 end
Zerotorescue@81 241 end
Zerotorescue@81 242
Zerotorescue@81 243 function mod:ProcessMove()
Zerotorescue@81 244 addon:Debug("ProcessMove");
Zerotorescue@81 245
Zerotorescue@81 246 if #combinedMoves == 0 then
Zerotorescue@81 247 print("Nothing to move.");
Zerotorescue@81 248
Zerotorescue@81 249 self:Abort();
Zerotorescue@81 250
Zerotorescue@81 251 return;
Zerotorescue@81 252 end
Zerotorescue@81 253
Zerotorescue@81 254 self:RegisterEvent("UI_ERROR_MESSAGE");
Zerotorescue@81 255
Zerotorescue@80 256 -- combinedMoves now has all moves in it (source -> target)
Zerotorescue@80 257 -- go through list, move everything inside it
Zerotorescue@81 258 -- add source and target to lists, if either is already in this list, skip the move
Zerotorescue@81 259 -- repeat every few seconds until we're completely done
Zerotorescue@80 260
Zerotorescue@80 261 local sourceLocationsLocked = {};
Zerotorescue@80 262 local targetLocationsLocked = {};
Zerotorescue@80 263
Zerotorescue@81 264 -- 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
Zerotorescue@81 265 combinedMoves = table.reverse(combinedMoves);
Zerotorescue@81 266
Zerotorescue@80 267 local numCurrentMove = #combinedMoves;
Zerotorescue@80 268 while numCurrentMove ~= 0 do
Zerotorescue@80 269 local move = combinedMoves[numCurrentMove];
Zerotorescue@80 270
Zerotorescue@80 271 -- sourceContainer, sourceSlot, targetContainer, targetSlot, itemId, num
Zerotorescue@81 272 if move and (not sourceLocationsLocked[move.sourceContainer] or not sourceLocationsLocked[move.sourceContainer][move.sourceSlot]) and
Zerotorescue@80 273 (not targetLocationsLocked[move.targetContainer] or not targetLocationsLocked[move.targetContainer][move.targetSlot]) then
Zerotorescue@80 274
Zerotorescue@80 275 print("Moving " .. IdToItemLink(move.itemId));
Zerotorescue@80 276
Zerotorescue@81 277 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));
Zerotorescue@81 278
Zerotorescue@81 279 if GetContainerItemID(move.sourceContainer, move.sourceSlot) ~= move.itemId then
Zerotorescue@81 280 self:Abort("source changed", "Source (" .. move.sourceContainer .. "," .. move.sourceSlot .. ") is not " .. IdToItemLink(move.itemId));
Zerotorescue@81 281
Zerotorescue@81 282 return;
Zerotorescue@81 283 end
Zerotorescue@81 284
Zerotorescue@80 285 -- Pickup stack
Zerotorescue@81 286 if movesSource == addon.Locations.Bank then
Zerotorescue@81 287 SplitContainerItem(move.sourceContainer, move.sourceSlot, move.num);
Zerotorescue@81 288 elseif movesSource == addon.Locations.Guild then
Zerotorescue@81 289 SplitGuildBankItem(move.sourceContainer, move.sourceSlot, move.num);
Zerotorescue@81 290 end
Zerotorescue@80 291
Zerotorescue@80 292 -- Remember we picked this item up and thus it is now locked
Zerotorescue@80 293 if not sourceLocationsLocked[move.sourceContainer] then
Zerotorescue@80 294 sourceLocationsLocked[move.sourceContainer] = {};
Zerotorescue@80 295 end
Zerotorescue@80 296 sourceLocationsLocked[move.sourceContainer][move.sourceSlot] = true;
Zerotorescue@80 297
Zerotorescue@81 298 if movesSource == addon.Locations.Guild or CursorHasItem() then -- CursorHasItem is always false if source is a guild tab
Zerotorescue@81 299 if GetContainerItemID(move.targetContainer, move.targetSlot) and GetContainerItemID(move.targetContainer, move.targetSlot) ~= move.itemId then
Zerotorescue@81 300 self:Abort("target changed", "Target (" .. move.targetContainer .. "," .. move.targetSlot .. ") is not " .. IdToItemLink(move.itemId) .. " nor empty");
Zerotorescue@81 301 return;
Zerotorescue@81 302 end
Zerotorescue@81 303
Zerotorescue@80 304 -- And drop it
Zerotorescue@80 305 PickupContainerItem(move.targetContainer, move.targetSlot);
Zerotorescue@80 306
Zerotorescue@80 307 -- Remember we dropped an item here and thus this is now locked
Zerotorescue@81 308 if not targetLocationsLocked[move.targetContainer] then
Zerotorescue@81 309 targetLocationsLocked[move.targetContainer] = {};
Zerotorescue@80 310 end
Zerotorescue@81 311 targetLocationsLocked[move.targetContainer][move.targetSlot] = true;
Zerotorescue@80 312
Zerotorescue@80 313 -- This move was processed
Zerotorescue@80 314 table.remove(combinedMoves, numCurrentMove);
Zerotorescue@81 315 else
Zerotorescue@81 316 self:Abort("item disappeared from mouse", "Couldn't move " .. IdToItemLink(move.itemId) .. ", CursorHasItem() is false");
Zerotorescue@81 317 return;
Zerotorescue@80 318 end
Zerotorescue@80 319 end
Zerotorescue@80 320
Zerotorescue@80 321 -- Proceed with the next element (or previous considering we're going from last to first)
Zerotorescue@80 322 numCurrentMove = (numCurrentMove - 1);
Zerotorescue@80 323 end
Zerotorescue@81 324
Zerotorescue@81 325 if #combinedMoves == 0 then
Zerotorescue@81 326 print("Finished.");
Zerotorescue@81 327
Zerotorescue@81 328 self:Abort();
Zerotorescue@81 329
Zerotorescue@81 330 return;
Zerotorescue@81 331 end
Zerotorescue@80 332 end
Zerotorescue@80 333
Zerotorescue@80 334 local tmrProcessNext;
Zerotorescue@81 335 function mod:BAG_UPDATE()
Zerotorescue@81 336 self:CancelTimer(tmrProcessNext, true); -- silent
Zerotorescue@81 337 tmrProcessNext = self:ScheduleTimer("ProcessMove", 1);
Zerotorescue@80 338 end
Zerotorescue@80 339
Zerotorescue@81 340 function IdToItemLink(itemId)
Zerotorescue@81 341 local itemLink = select(2, GetItemInfo(itemId));
Zerotorescue@81 342 itemLink = itemLink or "Unknown (" .. itemId .. ")";
Zerotorescue@81 343 return itemLink;
Zerotorescue@81 344 end
Zerotorescue@81 345
Zerotorescue@81 346 function mod:UI_ERROR_MESSAGE(e, errorMessage)
Zerotorescue@81 347 if errorMessage == ERR_SPLIT_FAILED then
Zerotorescue@81 348 self:Abort("splitting failed", "Splitting failed.");
Zerotorescue@80 349 end
Zerotorescue@80 350 end
Zerotorescue@80 351
Zerotorescue@81 352 function mod:Abort(simple, debugMsg)
Zerotorescue@81 353 if debugMsg then
Zerotorescue@81 354 addon:Debug("Aborting:" .. debugMsg);
Zerotorescue@81 355 end
Zerotorescue@81 356 if simple then
Zerotorescue@81 357 print("|cffff0000Aborting: " .. simple .. ".|r");
Zerotorescue@81 358 end
Zerotorescue@81 359 table.wipe(combinedMoves);
Zerotorescue@81 360 movesSource = nil;
Zerotorescue@80 361
Zerotorescue@81 362 -- Stop timer
Zerotorescue@81 363 self:UnregisterEvent("BAG_UPDATE");
Zerotorescue@81 364 self:CancelTimer(tmrProcessNext, true); -- silent
Zerotorescue@80 365
Zerotorescue@81 366 self:UnregisterEvent("UI_ERROR_MESSAGE");
Zerotorescue@80 367 end
Zerotorescue@80 368
Zerotorescue@80 369 function mod:OnEnable()
Zerotorescue@80 370 Scanner = addon:GetModule("Scanner");
Zerotorescue@80 371 end
Zerotorescue@80 372
Zerotorescue@80 373 function mod:OnDisable()
Zerotorescue@80 374 Scanner = nil;
Zerotorescue@81 375
Zerotorescue@81 376 self:Abort();
Zerotorescue@80 377 end