annotate Modules/Mover.lua @ 117:239e25a058c7

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