annotate Modules/Mover.lua @ 120:00cf4fc1697f

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