annotate Modules/Mover.lua @ 157:e136c99fe5bb

You can now select which guilds to include in the item count when selecting an item count supporting this. Renamed DataStore (with guilds) to ?all accounts?. Removed DataStore (without guilds) option.
author Zerotorescue
date Sat, 22 Jan 2011 19:24:48 +0100
parents 6724bc8eface
children 84e38318f569
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@122 4 local _G = _G;
Zerotorescue@122 5 local select, pairs = _G.select, _G.pairs;
Zerotorescue@122 6 local tinsert, twipe, treverse, tsort, tremove = _G.table.insert, _G.table.wipe, _G.table.reverse, _G.table.sort, _G.table.remove;
Zerotorescue@122 7
Zerotorescue@80 8 local Scanner;
Zerotorescue@80 9 local queuedMoves = {}; -- table storing all queued moves before BeginMove is called
Zerotorescue@80 10 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 11 local movesSource;
Zerotorescue@80 12
Zerotorescue@110 13 local ContainerFunctions = {
Zerotorescue@110 14 [addon.Locations.Bag] = {
Zerotorescue@110 15 GetItemId = GetContainerItemID,
Zerotorescue@110 16 PickupItem = SplitContainerItem,
Zerotorescue@110 17 IsLocked = function(sourceContainer, sourceSlot)
Zerotorescue@110 18 return select(3, GetContainerItemInfo(sourceContainer, sourceSlot));
Zerotorescue@110 19 end,
Zerotorescue@110 20 Event = "ITEM_LOCK_CHANGED",
Zerotorescue@110 21 },
Zerotorescue@110 22 [addon.Locations.Bank] = {
Zerotorescue@110 23 GetItemId = GetContainerItemID,
Zerotorescue@110 24 PickupItem = SplitContainerItem,
Zerotorescue@110 25 IsLocked = function(sourceContainer, sourceSlot)
Zerotorescue@110 26 return select(3, GetContainerItemInfo(sourceContainer, sourceSlot));
Zerotorescue@110 27 end,
Zerotorescue@110 28 Event = "ITEM_LOCK_CHANGED",
Zerotorescue@110 29 },
Zerotorescue@110 30 [addon.Locations.Guild] = {
Zerotorescue@110 31 GetItemId = function(tabId, slotId)
Zerotorescue@110 32 return addon:GetItemId(GetGuildBankItemLink(tabId, slotId));
Zerotorescue@110 33 end,
Zerotorescue@110 34 PickupItem = SplitGuildBankItem,
Zerotorescue@110 35 IsLocked = function(sourceContainer, sourceSlot)
Zerotorescue@110 36 return select(3, GetGuildBankItemInfo(sourceContainer, sourceSlot));
Zerotorescue@110 37 end,
Zerotorescue@110 38 Event = "ITEM_LOCK_CHANGED",
Zerotorescue@110 39 },
Zerotorescue@110 40 [addon.Locations.Mailbox] = {
Zerotorescue@110 41 GetItemId = function(mailIndex, attachmentId)
Zerotorescue@110 42 return addon:GetItemId(GetInboxItemLink(mailIndex, attachmentId));
Zerotorescue@110 43 end,
Zerotorescue@110 44 PickupItem = TakeInboxItem,
Zerotorescue@110 45 IsLocked = function() return false; end,
Zerotorescue@110 46 DoNotDrop = true, -- TakeInboxItem does not support picking up
Zerotorescue@110 47 Synchronous = true, -- wait after every single move
Zerotorescue@110 48 Event = "BAG_UPDATE",
Zerotorescue@110 49 },
Zerotorescue@119 50 [addon.Locations.Merchant] = {
Zerotorescue@119 51 GetItemId = function(_, merchantIndex)
Zerotorescue@119 52 return addon:GetItemId(GetMerchantItemLink(merchantIndex));
Zerotorescue@119 53 end,
Zerotorescue@119 54 PickupItem = function(_, merchantIndex, num)
Zerotorescue@119 55 return BuyMerchantItem(merchantIndex, num);
Zerotorescue@119 56 end,
Zerotorescue@119 57 IsLocked = function() return false; end,
Zerotorescue@119 58 DoNotDrop = true, -- BuyMerchantItem does not support picking up
Zerotorescue@119 59 Burst = true, -- spam buy items, the source can take it
Zerotorescue@119 60 Event = "BAG_UPDATE",
Zerotorescue@119 61 },
Zerotorescue@110 62 };
Zerotorescue@110 63
Zerotorescue@119 64 function mod:AddMove(itemId, amount, numMissing, numAvailable, cost)
Zerotorescue@122 65 tinsert(queuedMoves, {
Zerotorescue@110 66 ["itemId"] = itemId,
Zerotorescue@110 67 ["num"] = amount, -- can not be unlimited
Zerotorescue@110 68 ["missing"] = numMissing,
Zerotorescue@110 69 ["available"] = numAvailable,
Zerotorescue@119 70 ["cost"] = cost,
Zerotorescue@80 71 });
Zerotorescue@80 72 end
Zerotorescue@80 73
Zerotorescue@81 74 function mod:HasMoves()
Zerotorescue@81 75 return (#queuedMoves ~= 0);
Zerotorescue@81 76 end
Zerotorescue@81 77
Zerotorescue@101 78 function mod:GetMoves()
Zerotorescue@101 79 return queuedMoves;
Zerotorescue@101 80 end
Zerotorescue@101 81
Zerotorescue@101 82 function mod:ResetQueue()
Zerotorescue@122 83 twipe(queuedMoves);
Zerotorescue@101 84 end
Zerotorescue@101 85
Zerotorescue@122 86 if not treverse then
Zerotorescue@122 87 treverse = function(orig)
Zerotorescue@84 88 local temp = {};
Zerotorescue@84 89 local origLength = #orig;
Zerotorescue@84 90 for i = 1, origLength do
Zerotorescue@84 91 temp[(origLength - i + 1)] = orig[i];
Zerotorescue@84 92 end
Zerotorescue@84 93
Zerotorescue@84 94 -- -- Update the original table (can't do orig = temp as that would change the reference-link instead of the original table)
Zerotorescue@84 95 -- for i, v in pairs(temp) do
Zerotorescue@84 96 -- orig[i] = v;
Zerotorescue@84 97 -- end
Zerotorescue@84 98 return temp; -- for speed we choose to do a return instead
Zerotorescue@84 99 end
Zerotorescue@84 100 end
Zerotorescue@84 101
Zerotorescue@110 102 local function GetEmptySlots()
Zerotorescue@110 103 local emptySlots = {};
Zerotorescue@110 104
Zerotorescue@110 105 -- Go through all our bags, including the backpack
Zerotorescue@110 106 for bagId = 0, NUM_BAG_SLOTS do
Zerotorescue@110 107 -- Go through all our slots (0 = backpack)
Zerotorescue@110 108 for slotId = 1, GetContainerNumSlots(bagId) do
Zerotorescue@110 109 local itemId = GetContainerItemID(bagId, slotId); -- we're scanning our local bags here, so no need to get messy with guild bank support
Zerotorescue@110 110 local bagFamily = select(2, GetContainerNumFreeSlots(bagId));
Zerotorescue@110 111
Zerotorescue@110 112 if not itemId then
Zerotorescue@122 113 tinsert(emptySlots, {
Zerotorescue@110 114 ["container"] = bagId,
Zerotorescue@110 115 ["slot"] = slotId,
Zerotorescue@110 116 ["family"] = bagFamily,
Zerotorescue@110 117 });
Zerotorescue@110 118 end
Zerotorescue@110 119 end
Zerotorescue@110 120 end
Zerotorescue@110 121
Zerotorescue@110 122 return emptySlots;
Zerotorescue@110 123 end
Zerotorescue@110 124
Zerotorescue@110 125 local function GetFirstEmptySlot(emptySlots, prefFamily)
Zerotorescue@110 126 while prefFamily do
Zerotorescue@110 127 for _, slot in pairs(emptySlots) do
Zerotorescue@110 128 if slot.family == prefFamily then
Zerotorescue@110 129 return slot;
Zerotorescue@110 130 end
Zerotorescue@110 131 end
Zerotorescue@110 132
Zerotorescue@110 133 -- If this was a special family, no special bag available, now check normal bags
Zerotorescue@110 134 if prefFamily > 0 then
Zerotorescue@110 135 prefFamily = 0;
Zerotorescue@110 136 else
Zerotorescue@110 137 prefFamily = nil;
Zerotorescue@110 138 end
Zerotorescue@110 139 end
Zerotorescue@110 140 end
Zerotorescue@110 141
Zerotorescue@80 142 function mod:BeginMove(location, onFinish)
Zerotorescue@81 143 addon:Debug("BeginMove");
Zerotorescue@80 144
Zerotorescue@80 145 -- Find the outgoing moves
Zerotorescue@80 146 -- 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 147
Zerotorescue@80 148 -- Get a list of items in the source container
Zerotorescue@80 149 local sourceContents = Scanner:CacheLocation(location, false);
Zerotorescue@80 150
Zerotorescue@80 151 local outgoingMoves = {};
Zerotorescue@80 152
Zerotorescue@89 153 addon:Debug("%d moves were queued.", #queuedMoves);
Zerotorescue@82 154
Zerotorescue@81 155 for _, singleMove in pairs(queuedMoves) do
Zerotorescue@110 156 local sourceItem = sourceContents[singleMove.itemId];
Zerotorescue@80 157 if not sourceItem then
Zerotorescue@110 158 addon:Print(("Can't move %s, this doesn't exist in the source."):format(IdToItemLink(singleMove.itemId)), addon.Colors.Red);
Zerotorescue@80 159 else
Zerotorescue@82 160 -- 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)
Zerotorescue@122 161 tsort(sourceItem.locations, function(a, b)
Zerotorescue@119 162 -- -1 indicates unlimited, this is always more than an actual amount
Zerotorescue@119 163 if a.count == -1 then
Zerotorescue@119 164 return false;
Zerotorescue@119 165 elseif b.count == -1 then
Zerotorescue@119 166 return true;
Zerotorescue@119 167 end
Zerotorescue@119 168
Zerotorescue@81 169 return a.count < b.count;
Zerotorescue@80 170 end);
Zerotorescue@80 171
Zerotorescue@81 172 for _, itemLocation in pairs(sourceItem.locations) do
Zerotorescue@80 173 -- if this location has more items than we need, only move what we need, otherwise move everything in this stack
Zerotorescue@119 174 -- -1 items indicates unlimited amount, in that case we must cap at missing items
Zerotorescue@119 175 local movingNum = (((itemLocation.count == -1 or itemLocation.count > singleMove.num) and singleMove.num) or itemLocation.count);
Zerotorescue@119 176
Zerotorescue@119 177 if itemLocation.count == -1 then
Zerotorescue@119 178 -- If the source has an unlimited quantity, makes moves based on the max stacksize
Zerotorescue@119 179
Zerotorescue@119 180 local stackSize = select(8, GetItemInfo(singleMove.itemId)); -- 8 = stacksize
Zerotorescue@119 181
Zerotorescue@119 182 if stackSize then
Zerotorescue@119 183 while movingNum > stackSize do
Zerotorescue@119 184 -- Move a single stack size while the amount remaining to be moved is above the stack size num
Zerotorescue@119 185
Zerotorescue@122 186 tinsert(outgoingMoves, {
Zerotorescue@119 187 ["itemId"] = singleMove.itemId,
Zerotorescue@119 188 ["num"] = stackSize,
Zerotorescue@119 189 ["container"] = itemLocation.container,
Zerotorescue@119 190 ["slot"] = itemLocation.slot,
Zerotorescue@119 191 });
Zerotorescue@119 192
Zerotorescue@119 193 movingNum = (movingNum - stackSize);
Zerotorescue@119 194 singleMove.num = (singleMove.num - stackSize);
Zerotorescue@119 195 end
Zerotorescue@119 196 end
Zerotorescue@119 197 end
Zerotorescue@80 198
Zerotorescue@122 199 tinsert(outgoingMoves, {
Zerotorescue@110 200 ["itemId"] = singleMove.itemId,
Zerotorescue@110 201 ["num"] = movingNum,
Zerotorescue@110 202 ["container"] = itemLocation.container,
Zerotorescue@110 203 ["slot"] = itemLocation.slot,
Zerotorescue@80 204 });
Zerotorescue@80 205
Zerotorescue@80 206 singleMove.num = (singleMove.num - movingNum);
Zerotorescue@80 207
Zerotorescue@80 208 if singleMove.num == 0 then
Zerotorescue@80 209 -- If we have prepared everything we wanted, go to the next queued move
Zerotorescue@81 210 break; -- stop the locations-loop
Zerotorescue@80 211 end
Zerotorescue@80 212 end
Zerotorescue@80 213 end
Zerotorescue@80 214 end
Zerotorescue@82 215
Zerotorescue@89 216 addon:Debug("%d outgoing moves are possible.", #outgoingMoves);
Zerotorescue@80 217
Zerotorescue@80 218 -- No longer needed
Zerotorescue@122 219 twipe(queuedMoves);
Zerotorescue@80 220
Zerotorescue@110 221
Zerotorescue@110 222
Zerotorescue@80 223 -- Process every single outgoing move and find fitting targets
Zerotorescue@80 224
Zerotorescue@80 225 -- Get a list of items already in the target container
Zerotorescue@80 226 local targetContents = Scanner:CacheLocation(addon.Locations.Bag, false);
Zerotorescue@80 227
Zerotorescue@80 228 -- Find all empty slots
Zerotorescue@110 229 local emptySlots = GetEmptySlots();
Zerotorescue@82 230
Zerotorescue@89 231 addon:Debug("%d empty slots are available.", #emptySlots);
Zerotorescue@80 232
Zerotorescue@81 233 -- Remember where we're moving from
Zerotorescue@81 234 movesSource = location;
Zerotorescue@81 235
Zerotorescue@110 236 local backup = 0; -- the below loop should never break, but if it does for any reason, this is here to stop it from freezing the game
Zerotorescue@84 237
Zerotorescue@80 238 while #outgoingMoves ~= 0 do
Zerotorescue@110 239 -- Repeat the below loop until nothing is remaining
Zerotorescue@80 240
Zerotorescue@81 241 for _, outgoingMove in pairs(outgoingMoves) do
Zerotorescue@110 242 if outgoingMove.itemId then -- itemId will be set to nil when this outgoing move was processed - sanity check
Zerotorescue@80 243 local targetItem = targetContents[outgoingMove.itemId];
Zerotorescue@80 244
Zerotorescue@110 245 if not targetItem or ContainerFunctions[location].DoNotDrop then
Zerotorescue@110 246 -- There is no partial stack which can be filled or this source container doesn't allow manual allocation of items (in which case we always assume it takes a full empty slot)
Zerotorescue@80 247
Zerotorescue@110 248 local family = GetItemFamily(outgoingMove.itemId);
Zerotorescue@110 249 if family and family ~= 0 and select(9, GetItemInfo(outgoingMove.itemId)) == "INVTYPE_BAG" then
Zerotorescue@110 250 -- Containers can only fit in general slots but GetItemFamily will return what they can contain themselves
Zerotorescue@110 251 family = 0;
Zerotorescue@110 252 end
Zerotorescue@110 253 local firstAvailableSlot = GetFirstEmptySlot(emptySlots, family);
Zerotorescue@80 254
Zerotorescue@80 255 if not firstAvailableSlot then
Zerotorescue@110 256 -- No empty slot available - bags are full
Zerotorescue@110 257
Zerotorescue@98 258 addon:Print(("Bags are full. Skipping %s."):format(IdToItemLink(outgoingMove.itemId)), addon.Colors.Orange);
Zerotorescue@80 259
Zerotorescue@82 260 outgoingMove.itemId = nil; -- remove this record from the outgoingMoves-table
Zerotorescue@109 261
Zerotorescue@109 262 -- Not a single item with this item id can be moved, since we only want the bags are full announcement once, we remove all other moves with this item id
Zerotorescue@109 263 for _, otherMove in pairs(outgoingMoves) do
Zerotorescue@109 264 if otherMove.itemId and otherMove.itemId == outgoingMove.itemId then
Zerotorescue@109 265 otherMove.itemId = nil;
Zerotorescue@109 266 end
Zerotorescue@109 267 end
Zerotorescue@80 268 else
Zerotorescue@110 269 -- Consume empty slot
Zerotorescue@110 270
Zerotorescue@122 271 tinsert(combinedMoves, {
Zerotorescue@110 272 ["itemId"] = outgoingMove.itemId,
Zerotorescue@110 273 ["num"] = outgoingMove.num,
Zerotorescue@110 274 ["sourceContainer"] = outgoingMove.container,
Zerotorescue@110 275 ["sourceSlot"] = outgoingMove.slot,
Zerotorescue@110 276 ["targetContainer"] = firstAvailableSlot.container,
Zerotorescue@110 277 ["targetSlot"] = firstAvailableSlot.slot,
Zerotorescue@80 278 });
Zerotorescue@80 279
Zerotorescue@80 280 -- We filled an empty slot so the target contents now has one more item,
Zerotorescue@80 281 -- make a new instance of the ItemMove class so any additional items with this id can be stacked on top of it
Zerotorescue@81 282 local itemMove = addon.ContainerItem:New();
Zerotorescue@82 283 itemMove:AddLocation(firstAvailableSlot.container, firstAvailableSlot.slot, outgoingMove.num);
Zerotorescue@80 284 targetContents[outgoingMove.itemId] = itemMove;
Zerotorescue@80 285
Zerotorescue@122 286 tremove(emptySlots, 1); -- no longer empty
Zerotorescue@80 287
Zerotorescue@82 288 outgoingMove.num = 0; -- nothing remaining - sanity check
Zerotorescue@80 289 outgoingMove.itemId = nil; -- remove this record from the outgoingMoves-table
Zerotorescue@80 290 end
Zerotorescue@80 291 else
Zerotorescue@80 292 -- Find the maximum stack size for this item
Zerotorescue@80 293 local itemStackCount = select(8, GetItemInfo(outgoingMove.itemId));
Zerotorescue@80 294
Zerotorescue@80 295 -- We want to move to the largest stacks first to keep stuff pretty
Zerotorescue@122 296 tsort(targetItem.locations, function(a, b)
Zerotorescue@81 297 return a.count > b.count;
Zerotorescue@80 298 end);
Zerotorescue@80 299
Zerotorescue@81 300 for _, itemLocation in pairs(targetItem.locations) do
Zerotorescue@82 301 if itemLocation.count < itemStackCount and outgoingMove.num > 0 then
Zerotorescue@80 302 -- Check if this stack isn't already full (and we still need to move this item)
Zerotorescue@80 303
Zerotorescue@80 304 local remainingSpace = (itemStackCount - itemLocation.count);
Zerotorescue@84 305 if remainingSpace >= outgoingMove.num then
Zerotorescue@80 306 -- Enough room to move this entire stack
Zerotorescue@80 307 -- Deposit this item and then forget this outgoing move as everything in it was processed
Zerotorescue@80 308
Zerotorescue@122 309 tinsert(combinedMoves, {
Zerotorescue@110 310 ["itemId"] = outgoingMove.itemId,
Zerotorescue@110 311 ["num"] = outgoingMove.num,
Zerotorescue@110 312 ["sourceContainer"] = outgoingMove.container,
Zerotorescue@110 313 ["sourceSlot"] = outgoingMove.slot,
Zerotorescue@110 314 ["targetContainer"] = itemLocation.container,
Zerotorescue@110 315 ["targetSlot"] = itemLocation.slot,
Zerotorescue@80 316 });
Zerotorescue@80 317
Zerotorescue@82 318 itemLocation.count = (itemLocation.count + outgoingMove.num);
Zerotorescue@84 319 outgoingMove.num = 0; -- nothing remaining
Zerotorescue@80 320 outgoingMove.itemId = nil; -- remove this record from the outgoingMoves-table
Zerotorescue@80 321 break; -- stop the locations-loop
Zerotorescue@80 322 else
Zerotorescue@80 323 -- Deposit this item but don't remove the outgoing move as there are some items left to move
Zerotorescue@80 324
Zerotorescue@122 325 tinsert(combinedMoves, {
Zerotorescue@110 326 ["itemId"] = outgoingMove.itemId,
Zerotorescue@110 327 ["num"] = outgoingMove.num,
Zerotorescue@110 328 ["sourceContainer"] = outgoingMove.container,
Zerotorescue@110 329 ["sourceSlot"] = outgoingMove.slot,
Zerotorescue@110 330 ["targetContainer"] = itemLocation.container,
Zerotorescue@110 331 ["targetSlot"] = itemLocation.slot,
Zerotorescue@80 332 });
Zerotorescue@80 333
Zerotorescue@80 334 -- The target will be full when we complete, but the source will still have remaining items left to be moved
Zerotorescue@80 335 itemLocation.count = itemStackCount;
Zerotorescue@82 336 outgoingMove.num = (outgoingMove.num - remainingSpace);
Zerotorescue@80 337 end
Zerotorescue@80 338 end
Zerotorescue@80 339 end
Zerotorescue@80 340
Zerotorescue@82 341 if outgoingMove.num > 0 then
Zerotorescue@80 342 -- We went through all matching items and checked their stack sizes if we could move this there, no room available
Zerotorescue@80 343 -- 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@84 344 targetContents[outgoingMove.itemId] = nil;
Zerotorescue@80 345 end
Zerotorescue@80 346 end
Zerotorescue@80 347 end
Zerotorescue@80 348 end
Zerotorescue@80 349
Zerotorescue@80 350 -- Loop through the array to find items that should be removed, start with the last element or the loop would break
Zerotorescue@80 351 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 352 while numOutgoingMoves ~= 0 do
Zerotorescue@80 353 -- A not equal-comparison should be quicker than a larger/smaller than-comparison
Zerotorescue@80 354
Zerotorescue@80 355 -- Check if the item id is nil, this is set to nil when this outgoing move has been processed
Zerotorescue@84 356 if not outgoingMoves[numOutgoingMoves].itemId or outgoingMoves[numOutgoingMoves].num == 0 then
Zerotorescue@80 357 -- Remove this element from the array
Zerotorescue@122 358 tremove(outgoingMoves, numOutgoingMoves);
Zerotorescue@80 359 end
Zerotorescue@80 360
Zerotorescue@80 361 -- Proceed with the next element (or previous considering we're going from last to first)
Zerotorescue@80 362 numOutgoingMoves = (numOutgoingMoves - 1);
Zerotorescue@80 363 end
Zerotorescue@84 364
Zerotorescue@89 365 addon:Debug("%d moves remaining.", #outgoingMoves);
Zerotorescue@84 366
Zerotorescue@84 367 backup = (backup + 1);
Zerotorescue@84 368 if backup > 1000 then
Zerotorescue@122 369 twipe(outgoingMoves);
Zerotorescue@84 370 self:Abort("mover crashed", "Error preparing moves, hit an endless loop");
Zerotorescue@84 371 onFinish();
Zerotorescue@84 372 return;
Zerotorescue@84 373 end
Zerotorescue@80 374 end
Zerotorescue@84 375
Zerotorescue@84 376 -- 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@122 377 combinedMoves = treverse(combinedMoves);
Zerotorescue@82 378
Zerotorescue@89 379 addon:Debug("%d moves should be possible.", #combinedMoves);
Zerotorescue@80 380
Zerotorescue@80 381 -- No longer needed
Zerotorescue@122 382 twipe(emptySlots);
Zerotorescue@80 383
Zerotorescue@81 384 self:ProcessMove();
Zerotorescue@80 385
Zerotorescue@82 386 -- Even though we aren't completely done yet, allow requeueing
Zerotorescue@81 387 onFinish();
Zerotorescue@80 388 end
Zerotorescue@80 389
Zerotorescue@110 390 local tmrRetry;
Zerotorescue@81 391 function mod:ProcessMove()
Zerotorescue@81 392 addon:Debug("ProcessMove");
Zerotorescue@81 393
Zerotorescue@81 394 if #combinedMoves == 0 then
Zerotorescue@98 395 addon:Print("Nothing to move.");
Zerotorescue@81 396
Zerotorescue@81 397 self:Abort();
Zerotorescue@81 398
Zerotorescue@81 399 return;
Zerotorescue@110 400 elseif movesSource == addon.Locations.Mailbox and MailAddonBusy and MailAddonBusy ~= addon:GetName() then
Zerotorescue@110 401 addon:Debug("Anoter addon (%s) is busy with the mailbox.", MailAddonBusy);
Zerotorescue@110 402
Zerotorescue@110 403 self:CancelTimer(tmrRetry, true); -- silent
Zerotorescue@110 404 tmrRetry = self:ScheduleTimer("ProcessMove", .5);
Zerotorescue@110 405
Zerotorescue@110 406 return;
Zerotorescue@81 407 end
Zerotorescue@81 408
Zerotorescue@98 409 -- Make sure nothing is at the mouse
Zerotorescue@98 410 ClearCursor();
Zerotorescue@98 411
Zerotorescue@110 412 if movesSource == addon.Locations.Mailbox then
Zerotorescue@110 413 MailAddonBusy = addon:GetName();
Zerotorescue@117 414
Zerotorescue@117 415 -- Since mailbox indexes change as mail is emptied (emptied mail is automatically deleted, thus number 50 would become 49 when 1 disappears), we must start with the last mail first
Zerotorescue@122 416 tsort(combinedMoves, function(a, b)
Zerotorescue@117 417 return a.sourceContainer < b.sourceContainer;
Zerotorescue@117 418 end);
Zerotorescue@110 419 end
Zerotorescue@110 420
Zerotorescue@110 421 self:RegisterEvent(ContainerFunctions[movesSource].Event, "SourceUpdated");
Zerotorescue@81 422 self:RegisterEvent("UI_ERROR_MESSAGE");
Zerotorescue@81 423
Zerotorescue@80 424 -- combinedMoves now has all moves in it (source -> target)
Zerotorescue@80 425 -- go through list, move everything inside it
Zerotorescue@81 426 -- add source and target to lists, if either is already in this list, skip the move
Zerotorescue@81 427 -- repeat every few seconds until we're completely done
Zerotorescue@80 428
Zerotorescue@80 429 local sourceLocationsLocked = {};
Zerotorescue@80 430 local targetLocationsLocked = {};
Zerotorescue@80 431
Zerotorescue@110 432 local hasMoved;
Zerotorescue@110 433
Zerotorescue@82 434 local combinedMovesOriginalLength = #combinedMoves;
Zerotorescue@82 435 local numCurrentMove = combinedMovesOriginalLength;
Zerotorescue@80 436 while numCurrentMove ~= 0 do
Zerotorescue@80 437 local move = combinedMoves[numCurrentMove];
Zerotorescue@80 438
Zerotorescue@110 439 -- Only check if the source is locked when we're not bursting (some functions allow mass calling simultaneously and don't trigger item locks)
Zerotorescue@110 440 local isSourceLocked = ((not ContainerFunctions[movesSource].Burst and sourceLocationsLocked[move.sourceContainer] and sourceLocationsLocked[move.sourceContainer][move.sourceSlot]) or ContainerFunctions[movesSource].IsLocked(move.sourceContainer, move.sourceSlot));
Zerotorescue@109 441 -- Target are always the local bags
Zerotorescue@109 442 local isTargetLocked = ((targetLocationsLocked[move.targetContainer] and targetLocationsLocked[move.targetContainer][move.targetSlot]) or ContainerFunctions[addon.Locations.Bag].IsLocked(move.targetContainer, move.targetSlot));
Zerotorescue@88 443
Zerotorescue@110 444 if move and not isSourceLocked and not isTargetLocked and (not ContainerFunctions[movesSource].Synchronous or not hasMoved) then
Zerotorescue@98 445 addon:Print(("Moving %dx%s."):format(move.num, IdToItemLink(move.itemId)));
Zerotorescue@80 446
Zerotorescue@89 447 addon:Debug("Moving %dx%s from (%d,%d) to (%d,%d)", move.num, IdToItemLink(move.itemId), move.sourceContainer, move.sourceSlot, move.targetContainer, move.targetSlot);
Zerotorescue@81 448
Zerotorescue@110 449 -- Check if the source has been changed since out scan
Zerotorescue@109 450 if ContainerFunctions[movesSource].GetItemId(move.sourceContainer, move.sourceSlot) ~= move.itemId then
Zerotorescue@81 451 self:Abort("source changed", "Source (" .. move.sourceContainer .. "," .. move.sourceSlot .. ") is not " .. IdToItemLink(move.itemId));
Zerotorescue@81 452 return;
Zerotorescue@81 453 end
Zerotorescue@81 454
Zerotorescue@80 455 -- Pickup stack
Zerotorescue@109 456 ContainerFunctions[movesSource].PickupItem(move.sourceContainer, move.sourceSlot, move.num);
Zerotorescue@80 457
Zerotorescue@110 458 hasMoved = true;
Zerotorescue@110 459
Zerotorescue@80 460 -- Remember we picked this item up and thus it is now locked
Zerotorescue@80 461 if not sourceLocationsLocked[move.sourceContainer] then
Zerotorescue@80 462 sourceLocationsLocked[move.sourceContainer] = {};
Zerotorescue@80 463 end
Zerotorescue@80 464 sourceLocationsLocked[move.sourceContainer][move.sourceSlot] = true;
Zerotorescue@80 465
Zerotorescue@110 466 if not ContainerFunctions[movesSource].DoNotDrop then
Zerotorescue@110 467 -- Some sources don't actually pick items up but just move them, this makes the code below unnessary
Zerotorescue@110 468
Zerotorescue@110 469 if movesSource ~= addon.Locations.Bank or CursorHasItem() then -- CursorHasItem only works when moving from the bank
Zerotorescue@110 470 -- We are moving into our local bags, so the below must check normal bags
Zerotorescue@110 471
Zerotorescue@110 472 -- Check if the target has been changed since out scan
Zerotorescue@110 473 local targetItemId = ContainerFunctions[addon.Locations.Bag].GetItemId(move.targetContainer, move.targetSlot);
Zerotorescue@110 474 if targetItemId and targetItemId ~= move.itemId then
Zerotorescue@110 475 self:Abort("target changed", "Target (" .. move.targetContainer .. "," .. move.targetSlot .. ") is not " .. IdToItemLink(move.itemId) .. " nor empty");
Zerotorescue@110 476 return;
Zerotorescue@110 477 end
Zerotorescue@110 478
Zerotorescue@110 479 -- And drop it (this is always a local bag so no need to do any guild-checks)
Zerotorescue@110 480 PickupContainerItem(move.targetContainer, move.targetSlot);
Zerotorescue@110 481
Zerotorescue@110 482 -- Remember we dropped an item here and thus this is now locked
Zerotorescue@110 483 if not targetLocationsLocked[move.targetContainer] then
Zerotorescue@110 484 targetLocationsLocked[move.targetContainer] = {};
Zerotorescue@110 485 end
Zerotorescue@110 486 targetLocationsLocked[move.targetContainer][move.targetSlot] = true;
Zerotorescue@110 487
Zerotorescue@110 488 -- This move was processed
Zerotorescue@122 489 tremove(combinedMoves, numCurrentMove);
Zerotorescue@110 490 else
Zerotorescue@110 491 self:Abort("item disappeared from mouse", "Couldn't move " .. IdToItemLink(move.itemId) .. ", CursorHasItem() is false");
Zerotorescue@81 492 return;
Zerotorescue@81 493 end
Zerotorescue@110 494 else
Zerotorescue@110 495 -- When items are deposit automatically we still need to remember when a move has been processed
Zerotorescue@80 496
Zerotorescue@80 497 -- This move was processed
Zerotorescue@122 498 tremove(combinedMoves, numCurrentMove);
Zerotorescue@80 499 end
Zerotorescue@80 500 end
Zerotorescue@80 501
Zerotorescue@80 502 -- Proceed with the next element (or previous considering we're going from last to first)
Zerotorescue@80 503 numCurrentMove = (numCurrentMove - 1);
Zerotorescue@80 504 end
Zerotorescue@81 505
Zerotorescue@89 506 addon:Debug("%d moves processed. %d moves remaining.", (combinedMovesOriginalLength - #combinedMoves), #combinedMoves);
Zerotorescue@82 507
Zerotorescue@81 508 if #combinedMoves == 0 then
Zerotorescue@98 509 addon:Print("Finished.", addon.Colors.Green);
Zerotorescue@81 510
Zerotorescue@81 511 self:Abort();
Zerotorescue@81 512
Zerotorescue@81 513 return;
Zerotorescue@81 514 end
Zerotorescue@80 515 end
Zerotorescue@80 516
Zerotorescue@80 517 local tmrProcessNext;
Zerotorescue@110 518 function mod:SourceUpdated()
Zerotorescue@81 519 self:CancelTimer(tmrProcessNext, true); -- silent
Zerotorescue@89 520 tmrProcessNext = self:ScheduleTimer("ProcessMove", .5);
Zerotorescue@80 521 end
Zerotorescue@80 522
Zerotorescue@81 523 function IdToItemLink(itemId)
Zerotorescue@81 524 local itemLink = select(2, GetItemInfo(itemId));
Zerotorescue@81 525 itemLink = itemLink or "Unknown (" .. itemId .. ")";
Zerotorescue@81 526 return itemLink;
Zerotorescue@81 527 end
Zerotorescue@81 528
Zerotorescue@81 529 function mod:UI_ERROR_MESSAGE(e, errorMessage)
Zerotorescue@81 530 if errorMessage == ERR_SPLIT_FAILED then
Zerotorescue@81 531 self:Abort("splitting failed", "Splitting failed.");
Zerotorescue@110 532 elseif errorMessage == ERR_GUILD_WITHDRAW_LIMIT then
Zerotorescue@110 533 self:Abort("at guild withdrawal limit", "At guild withdrawal limit");
Zerotorescue@80 534 end
Zerotorescue@80 535 end
Zerotorescue@80 536
Zerotorescue@81 537 function mod:Abort(simple, debugMsg)
Zerotorescue@110 538 -- Announce
Zerotorescue@81 539 if debugMsg then
Zerotorescue@89 540 addon:Debug("Aborting:%s", debugMsg);
Zerotorescue@81 541 end
Zerotorescue@81 542 if simple then
Zerotorescue@98 543 addon:Print(("Aborting: %s."):format(simple), addon.Colors.Red);
Zerotorescue@81 544 end
Zerotorescue@80 545
Zerotorescue@98 546 -- Make sure nothing is at the mouse
Zerotorescue@98 547 ClearCursor();
Zerotorescue@98 548
Zerotorescue@81 549 -- Stop timer
Zerotorescue@110 550 self:UnregisterEvent(ContainerFunctions[movesSource].Event);
Zerotorescue@81 551 self:CancelTimer(tmrProcessNext, true); -- silent
Zerotorescue@80 552
Zerotorescue@81 553 self:UnregisterEvent("UI_ERROR_MESSAGE");
Zerotorescue@110 554
Zerotorescue@110 555 -- Reset vars
Zerotorescue@122 556 twipe(combinedMoves);
Zerotorescue@110 557 movesSource = nil;
Zerotorescue@110 558 if MailAddonBusy == addon:GetName() then
Zerotorescue@110 559 MailAddonBusy = nil;
Zerotorescue@110 560 end
Zerotorescue@80 561 end
Zerotorescue@80 562
Zerotorescue@80 563 function mod:OnEnable()
Zerotorescue@80 564 Scanner = addon:GetModule("Scanner");
Zerotorescue@80 565 end
Zerotorescue@80 566
Zerotorescue@80 567 function mod:OnDisable()
Zerotorescue@80 568 Scanner = nil;
Zerotorescue@81 569
Zerotorescue@81 570 self:Abort();
Zerotorescue@80 571 end