annotate Modules/Mover.lua @ 111:41f0689dfda1

This implementation of vendor buying did not work well. Too many customizations were needed that made the code hard to read and understand and eventually it was found that vendor buying should be based on refill target, not local stock. The mover/refiller is not meant for this, we should just do this somewhere else.
author Zerotorescue
date Fri, 14 Jan 2011 23:31:12 +0100
parents 67bd5057ecb7
children 239e25a058c7
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@110 374 end
Zerotorescue@110 375
Zerotorescue@110 376 self:RegisterEvent(ContainerFunctions[movesSource].Event, "SourceUpdated");
Zerotorescue@81 377 self:RegisterEvent("UI_ERROR_MESSAGE");
Zerotorescue@81 378
Zerotorescue@80 379 -- combinedMoves now has all moves in it (source -> target)
Zerotorescue@80 380 -- go through list, move everything inside it
Zerotorescue@81 381 -- add source and target to lists, if either is already in this list, skip the move
Zerotorescue@81 382 -- repeat every few seconds until we're completely done
Zerotorescue@80 383
Zerotorescue@80 384 local sourceLocationsLocked = {};
Zerotorescue@80 385 local targetLocationsLocked = {};
Zerotorescue@80 386
Zerotorescue@110 387 local hasMoved;
Zerotorescue@110 388
Zerotorescue@82 389 local combinedMovesOriginalLength = #combinedMoves;
Zerotorescue@82 390 local numCurrentMove = combinedMovesOriginalLength;
Zerotorescue@80 391 while numCurrentMove ~= 0 do
Zerotorescue@80 392 local move = combinedMoves[numCurrentMove];
Zerotorescue@80 393
Zerotorescue@110 394 -- 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 395 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 396 -- Target are always the local bags
Zerotorescue@109 397 local isTargetLocked = ((targetLocationsLocked[move.targetContainer] and targetLocationsLocked[move.targetContainer][move.targetSlot]) or ContainerFunctions[addon.Locations.Bag].IsLocked(move.targetContainer, move.targetSlot));
Zerotorescue@88 398
Zerotorescue@110 399 if move and not isSourceLocked and not isTargetLocked and (not ContainerFunctions[movesSource].Synchronous or not hasMoved) then
Zerotorescue@98 400 addon:Print(("Moving %dx%s."):format(move.num, IdToItemLink(move.itemId)));
Zerotorescue@80 401
Zerotorescue@89 402 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 403
Zerotorescue@110 404 -- Check if the source has been changed since out scan
Zerotorescue@109 405 if ContainerFunctions[movesSource].GetItemId(move.sourceContainer, move.sourceSlot) ~= move.itemId then
Zerotorescue@81 406 self:Abort("source changed", "Source (" .. move.sourceContainer .. "," .. move.sourceSlot .. ") is not " .. IdToItemLink(move.itemId));
Zerotorescue@81 407 return;
Zerotorescue@81 408 end
Zerotorescue@81 409
Zerotorescue@80 410 -- Pickup stack
Zerotorescue@109 411 ContainerFunctions[movesSource].PickupItem(move.sourceContainer, move.sourceSlot, move.num);
Zerotorescue@80 412
Zerotorescue@110 413 hasMoved = true;
Zerotorescue@110 414
Zerotorescue@80 415 -- Remember we picked this item up and thus it is now locked
Zerotorescue@80 416 if not sourceLocationsLocked[move.sourceContainer] then
Zerotorescue@80 417 sourceLocationsLocked[move.sourceContainer] = {};
Zerotorescue@80 418 end
Zerotorescue@80 419 sourceLocationsLocked[move.sourceContainer][move.sourceSlot] = true;
Zerotorescue@80 420
Zerotorescue@110 421 if not ContainerFunctions[movesSource].DoNotDrop then
Zerotorescue@110 422 -- Some sources don't actually pick items up but just move them, this makes the code below unnessary
Zerotorescue@110 423
Zerotorescue@110 424 if movesSource ~= addon.Locations.Bank or CursorHasItem() then -- CursorHasItem only works when moving from the bank
Zerotorescue@110 425 -- We are moving into our local bags, so the below must check normal bags
Zerotorescue@110 426
Zerotorescue@110 427 -- Check if the target has been changed since out scan
Zerotorescue@110 428 local targetItemId = ContainerFunctions[addon.Locations.Bag].GetItemId(move.targetContainer, move.targetSlot);
Zerotorescue@110 429 if targetItemId and targetItemId ~= move.itemId then
Zerotorescue@110 430 self:Abort("target changed", "Target (" .. move.targetContainer .. "," .. move.targetSlot .. ") is not " .. IdToItemLink(move.itemId) .. " nor empty");
Zerotorescue@110 431 return;
Zerotorescue@110 432 end
Zerotorescue@110 433
Zerotorescue@110 434 -- And drop it (this is always a local bag so no need to do any guild-checks)
Zerotorescue@110 435 PickupContainerItem(move.targetContainer, move.targetSlot);
Zerotorescue@110 436
Zerotorescue@110 437 -- Remember we dropped an item here and thus this is now locked
Zerotorescue@110 438 if not targetLocationsLocked[move.targetContainer] then
Zerotorescue@110 439 targetLocationsLocked[move.targetContainer] = {};
Zerotorescue@110 440 end
Zerotorescue@110 441 targetLocationsLocked[move.targetContainer][move.targetSlot] = true;
Zerotorescue@110 442
Zerotorescue@110 443 -- This move was processed
Zerotorescue@110 444 table.remove(combinedMoves, numCurrentMove);
Zerotorescue@110 445 else
Zerotorescue@110 446 self:Abort("item disappeared from mouse", "Couldn't move " .. IdToItemLink(move.itemId) .. ", CursorHasItem() is false");
Zerotorescue@81 447 return;
Zerotorescue@81 448 end
Zerotorescue@110 449 else
Zerotorescue@110 450 -- When items are deposit automatically we still need to remember when a move has been processed
Zerotorescue@80 451
Zerotorescue@80 452 -- This move was processed
Zerotorescue@80 453 table.remove(combinedMoves, numCurrentMove);
Zerotorescue@80 454 end
Zerotorescue@80 455 end
Zerotorescue@80 456
Zerotorescue@80 457 -- Proceed with the next element (or previous considering we're going from last to first)
Zerotorescue@80 458 numCurrentMove = (numCurrentMove - 1);
Zerotorescue@80 459 end
Zerotorescue@81 460
Zerotorescue@89 461 addon:Debug("%d moves processed. %d moves remaining.", (combinedMovesOriginalLength - #combinedMoves), #combinedMoves);
Zerotorescue@82 462
Zerotorescue@81 463 if #combinedMoves == 0 then
Zerotorescue@98 464 addon:Print("Finished.", addon.Colors.Green);
Zerotorescue@81 465
Zerotorescue@81 466 self:Abort();
Zerotorescue@81 467
Zerotorescue@81 468 return;
Zerotorescue@81 469 end
Zerotorescue@80 470 end
Zerotorescue@80 471
Zerotorescue@80 472 local tmrProcessNext;
Zerotorescue@110 473 function mod:SourceUpdated()
Zerotorescue@81 474 self:CancelTimer(tmrProcessNext, true); -- silent
Zerotorescue@89 475 tmrProcessNext = self:ScheduleTimer("ProcessMove", .5);
Zerotorescue@80 476 end
Zerotorescue@80 477
Zerotorescue@81 478 function IdToItemLink(itemId)
Zerotorescue@81 479 local itemLink = select(2, GetItemInfo(itemId));
Zerotorescue@81 480 itemLink = itemLink or "Unknown (" .. itemId .. ")";
Zerotorescue@81 481 return itemLink;
Zerotorescue@81 482 end
Zerotorescue@81 483
Zerotorescue@81 484 function mod:UI_ERROR_MESSAGE(e, errorMessage)
Zerotorescue@81 485 if errorMessage == ERR_SPLIT_FAILED then
Zerotorescue@81 486 self:Abort("splitting failed", "Splitting failed.");
Zerotorescue@110 487 elseif errorMessage == ERR_GUILD_WITHDRAW_LIMIT then
Zerotorescue@110 488 self:Abort("at guild withdrawal limit", "At guild withdrawal limit");
Zerotorescue@80 489 end
Zerotorescue@80 490 end
Zerotorescue@80 491
Zerotorescue@81 492 function mod:Abort(simple, debugMsg)
Zerotorescue@110 493 -- Announce
Zerotorescue@81 494 if debugMsg then
Zerotorescue@89 495 addon:Debug("Aborting:%s", debugMsg);
Zerotorescue@81 496 end
Zerotorescue@81 497 if simple then
Zerotorescue@98 498 addon:Print(("Aborting: %s."):format(simple), addon.Colors.Red);
Zerotorescue@81 499 end
Zerotorescue@80 500
Zerotorescue@98 501 -- Make sure nothing is at the mouse
Zerotorescue@98 502 ClearCursor();
Zerotorescue@98 503
Zerotorescue@81 504 -- Stop timer
Zerotorescue@110 505 self:UnregisterEvent(ContainerFunctions[movesSource].Event);
Zerotorescue@81 506 self:CancelTimer(tmrProcessNext, true); -- silent
Zerotorescue@80 507
Zerotorescue@81 508 self:UnregisterEvent("UI_ERROR_MESSAGE");
Zerotorescue@110 509
Zerotorescue@110 510 -- Reset vars
Zerotorescue@110 511 table.wipe(combinedMoves);
Zerotorescue@110 512 movesSource = nil;
Zerotorescue@110 513 if MailAddonBusy == addon:GetName() then
Zerotorescue@110 514 MailAddonBusy = nil;
Zerotorescue@110 515 end
Zerotorescue@80 516 end
Zerotorescue@80 517
Zerotorescue@80 518 function mod:OnEnable()
Zerotorescue@80 519 Scanner = addon:GetModule("Scanner");
Zerotorescue@80 520 end
Zerotorescue@80 521
Zerotorescue@80 522 function mod:OnDisable()
Zerotorescue@80 523 Scanner = nil;
Zerotorescue@81 524
Zerotorescue@81 525 self:Abort();
Zerotorescue@80 526 end