annotate Modules/Mover.lua @ 225:2e4e52a589e5

Added onQueueStart and onQueueEnd events to crafting addon registering. GnomeWorks queue frame should automatically be closed before adding items to the queue and opened afterwards to speed this process up.
author Zerotorescue
date Mon, 07 Feb 2011 15:06:41 +0100
parents 5f405272cdd4
children
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@171 38 Event = "BAG_UPDATE",
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@167 286 -- Find the slot used and remove it from the empty slots table
Zerotorescue@167 287 for index, emptySlot in pairs(emptySlots) do
Zerotorescue@167 288 if emptySlot == firstAvailableSlot then
Zerotorescue@167 289 tremove(emptySlots, index);
Zerotorescue@167 290 break;
Zerotorescue@167 291 end
Zerotorescue@167 292 end
Zerotorescue@80 293
Zerotorescue@82 294 outgoingMove.num = 0; -- nothing remaining - sanity check
Zerotorescue@80 295 outgoingMove.itemId = nil; -- remove this record from the outgoingMoves-table
Zerotorescue@80 296 end
Zerotorescue@80 297 else
Zerotorescue@80 298 -- Find the maximum stack size for this item
Zerotorescue@80 299 local itemStackCount = select(8, GetItemInfo(outgoingMove.itemId));
Zerotorescue@80 300
Zerotorescue@80 301 -- We want to move to the largest stacks first to keep stuff pretty
Zerotorescue@122 302 tsort(targetItem.locations, function(a, b)
Zerotorescue@81 303 return a.count > b.count;
Zerotorescue@80 304 end);
Zerotorescue@80 305
Zerotorescue@81 306 for _, itemLocation in pairs(targetItem.locations) do
Zerotorescue@82 307 if itemLocation.count < itemStackCount and outgoingMove.num > 0 then
Zerotorescue@80 308 -- Check if this stack isn't already full (and we still need to move this item)
Zerotorescue@80 309
Zerotorescue@80 310 local remainingSpace = (itemStackCount - itemLocation.count);
Zerotorescue@84 311 if remainingSpace >= outgoingMove.num then
Zerotorescue@80 312 -- Enough room to move this entire stack
Zerotorescue@80 313 -- Deposit this item and then forget this outgoing move as everything in it was processed
Zerotorescue@80 314
Zerotorescue@122 315 tinsert(combinedMoves, {
Zerotorescue@110 316 ["itemId"] = outgoingMove.itemId,
Zerotorescue@110 317 ["num"] = outgoingMove.num,
Zerotorescue@110 318 ["sourceContainer"] = outgoingMove.container,
Zerotorescue@110 319 ["sourceSlot"] = outgoingMove.slot,
Zerotorescue@110 320 ["targetContainer"] = itemLocation.container,
Zerotorescue@110 321 ["targetSlot"] = itemLocation.slot,
Zerotorescue@80 322 });
Zerotorescue@80 323
Zerotorescue@82 324 itemLocation.count = (itemLocation.count + outgoingMove.num);
Zerotorescue@84 325 outgoingMove.num = 0; -- nothing remaining
Zerotorescue@80 326 outgoingMove.itemId = nil; -- remove this record from the outgoingMoves-table
Zerotorescue@80 327 break; -- stop the locations-loop
Zerotorescue@80 328 else
Zerotorescue@80 329 -- Deposit this item but don't remove the outgoing move as there are some items left to move
Zerotorescue@80 330
Zerotorescue@122 331 tinsert(combinedMoves, {
Zerotorescue@110 332 ["itemId"] = outgoingMove.itemId,
Zerotorescue@110 333 ["num"] = outgoingMove.num,
Zerotorescue@110 334 ["sourceContainer"] = outgoingMove.container,
Zerotorescue@110 335 ["sourceSlot"] = outgoingMove.slot,
Zerotorescue@110 336 ["targetContainer"] = itemLocation.container,
Zerotorescue@110 337 ["targetSlot"] = itemLocation.slot,
Zerotorescue@80 338 });
Zerotorescue@80 339
Zerotorescue@80 340 -- The target will be full when we complete, but the source will still have remaining items left to be moved
Zerotorescue@80 341 itemLocation.count = itemStackCount;
Zerotorescue@82 342 outgoingMove.num = (outgoingMove.num - remainingSpace);
Zerotorescue@80 343 end
Zerotorescue@80 344 end
Zerotorescue@80 345 end
Zerotorescue@80 346
Zerotorescue@82 347 if outgoingMove.num > 0 then
Zerotorescue@80 348 -- We went through all matching items and checked their stack sizes if we could move this there, no room available
Zerotorescue@80 349 -- 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 350 targetContents[outgoingMove.itemId] = nil;
Zerotorescue@80 351 end
Zerotorescue@80 352 end
Zerotorescue@80 353 end
Zerotorescue@80 354 end
Zerotorescue@80 355
Zerotorescue@80 356 -- Loop through the array to find items that should be removed, start with the last element or the loop would break
Zerotorescue@80 357 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 358 while numOutgoingMoves ~= 0 do
Zerotorescue@80 359 -- A not equal-comparison should be quicker than a larger/smaller than-comparison
Zerotorescue@80 360
Zerotorescue@80 361 -- Check if the item id is nil, this is set to nil when this outgoing move has been processed
Zerotorescue@84 362 if not outgoingMoves[numOutgoingMoves].itemId or outgoingMoves[numOutgoingMoves].num == 0 then
Zerotorescue@80 363 -- Remove this element from the array
Zerotorescue@122 364 tremove(outgoingMoves, numOutgoingMoves);
Zerotorescue@80 365 end
Zerotorescue@80 366
Zerotorescue@80 367 -- Proceed with the next element (or previous considering we're going from last to first)
Zerotorescue@80 368 numOutgoingMoves = (numOutgoingMoves - 1);
Zerotorescue@80 369 end
Zerotorescue@84 370
Zerotorescue@89 371 addon:Debug("%d moves remaining.", #outgoingMoves);
Zerotorescue@84 372
Zerotorescue@84 373 backup = (backup + 1);
Zerotorescue@84 374 if backup > 1000 then
Zerotorescue@122 375 twipe(outgoingMoves);
Zerotorescue@84 376 self:Abort("mover crashed", "Error preparing moves, hit an endless loop");
Zerotorescue@84 377 onFinish();
Zerotorescue@84 378 return;
Zerotorescue@84 379 end
Zerotorescue@80 380 end
Zerotorescue@84 381
Zerotorescue@84 382 -- 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 383 combinedMoves = treverse(combinedMoves);
Zerotorescue@82 384
Zerotorescue@89 385 addon:Debug("%d moves should be possible.", #combinedMoves);
Zerotorescue@80 386
Zerotorescue@80 387 -- No longer needed
Zerotorescue@122 388 twipe(emptySlots);
Zerotorescue@80 389
Zerotorescue@81 390 self:ProcessMove();
Zerotorescue@80 391
Zerotorescue@82 392 -- Even though we aren't completely done yet, allow requeueing
Zerotorescue@81 393 onFinish();
Zerotorescue@80 394 end
Zerotorescue@80 395
Zerotorescue@110 396 local tmrRetry;
Zerotorescue@81 397 function mod:ProcessMove()
Zerotorescue@81 398 addon:Debug("ProcessMove");
Zerotorescue@81 399
Zerotorescue@81 400 if #combinedMoves == 0 then
Zerotorescue@98 401 addon:Print("Nothing to move.");
Zerotorescue@81 402
Zerotorescue@81 403 self:Abort();
Zerotorescue@81 404
Zerotorescue@81 405 return;
Zerotorescue@110 406 elseif movesSource == addon.Locations.Mailbox and MailAddonBusy and MailAddonBusy ~= addon:GetName() then
Zerotorescue@110 407 addon:Debug("Anoter addon (%s) is busy with the mailbox.", MailAddonBusy);
Zerotorescue@110 408
Zerotorescue@110 409 self:CancelTimer(tmrRetry, true); -- silent
Zerotorescue@110 410 tmrRetry = self:ScheduleTimer("ProcessMove", .5);
Zerotorescue@110 411
Zerotorescue@110 412 return;
Zerotorescue@81 413 end
Zerotorescue@81 414
Zerotorescue@98 415 -- Make sure nothing is at the mouse
Zerotorescue@98 416 ClearCursor();
Zerotorescue@98 417
Zerotorescue@110 418 if movesSource == addon.Locations.Mailbox then
Zerotorescue@110 419 MailAddonBusy = addon:GetName();
Zerotorescue@117 420
Zerotorescue@117 421 -- 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 422 tsort(combinedMoves, function(a, b)
Zerotorescue@117 423 return a.sourceContainer < b.sourceContainer;
Zerotorescue@117 424 end);
Zerotorescue@110 425 end
Zerotorescue@110 426
Zerotorescue@110 427 self:RegisterEvent(ContainerFunctions[movesSource].Event, "SourceUpdated");
Zerotorescue@81 428 self:RegisterEvent("UI_ERROR_MESSAGE");
Zerotorescue@81 429
Zerotorescue@80 430 -- combinedMoves now has all moves in it (source -> target)
Zerotorescue@80 431 -- go through list, move everything inside it
Zerotorescue@81 432 -- add source and target to lists, if either is already in this list, skip the move
Zerotorescue@81 433 -- repeat every few seconds until we're completely done
Zerotorescue@80 434
Zerotorescue@80 435 local sourceLocationsLocked = {};
Zerotorescue@80 436 local targetLocationsLocked = {};
Zerotorescue@80 437
Zerotorescue@110 438 local hasMoved;
Zerotorescue@110 439
Zerotorescue@82 440 local combinedMovesOriginalLength = #combinedMoves;
Zerotorescue@82 441 local numCurrentMove = combinedMovesOriginalLength;
Zerotorescue@80 442 while numCurrentMove ~= 0 do
Zerotorescue@80 443 local move = combinedMoves[numCurrentMove];
Zerotorescue@80 444
Zerotorescue@110 445 -- 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 446 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 447 -- Target are always the local bags
Zerotorescue@109 448 local isTargetLocked = ((targetLocationsLocked[move.targetContainer] and targetLocationsLocked[move.targetContainer][move.targetSlot]) or ContainerFunctions[addon.Locations.Bag].IsLocked(move.targetContainer, move.targetSlot));
Zerotorescue@88 449
Zerotorescue@110 450 if move and not isSourceLocked and not isTargetLocked and (not ContainerFunctions[movesSource].Synchronous or not hasMoved) then
Zerotorescue@98 451 addon:Print(("Moving %dx%s."):format(move.num, IdToItemLink(move.itemId)));
Zerotorescue@80 452
Zerotorescue@89 453 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 454
Zerotorescue@110 455 -- Check if the source has been changed since out scan
Zerotorescue@109 456 if ContainerFunctions[movesSource].GetItemId(move.sourceContainer, move.sourceSlot) ~= move.itemId then
Zerotorescue@81 457 self:Abort("source changed", "Source (" .. move.sourceContainer .. "," .. move.sourceSlot .. ") is not " .. IdToItemLink(move.itemId));
Zerotorescue@81 458 return;
Zerotorescue@81 459 end
Zerotorescue@81 460
Zerotorescue@80 461 -- Pickup stack
Zerotorescue@109 462 ContainerFunctions[movesSource].PickupItem(move.sourceContainer, move.sourceSlot, move.num);
Zerotorescue@80 463
Zerotorescue@110 464 hasMoved = true;
Zerotorescue@110 465
Zerotorescue@80 466 -- Remember we picked this item up and thus it is now locked
Zerotorescue@80 467 if not sourceLocationsLocked[move.sourceContainer] then
Zerotorescue@80 468 sourceLocationsLocked[move.sourceContainer] = {};
Zerotorescue@80 469 end
Zerotorescue@80 470 sourceLocationsLocked[move.sourceContainer][move.sourceSlot] = true;
Zerotorescue@80 471
Zerotorescue@110 472 if not ContainerFunctions[movesSource].DoNotDrop then
Zerotorescue@110 473 -- Some sources don't actually pick items up but just move them, this makes the code below unnessary
Zerotorescue@110 474
Zerotorescue@110 475 if movesSource ~= addon.Locations.Bank or CursorHasItem() then -- CursorHasItem only works when moving from the bank
Zerotorescue@110 476 -- We are moving into our local bags, so the below must check normal bags
Zerotorescue@110 477
Zerotorescue@110 478 -- Check if the target has been changed since out scan
Zerotorescue@110 479 local targetItemId = ContainerFunctions[addon.Locations.Bag].GetItemId(move.targetContainer, move.targetSlot);
Zerotorescue@110 480 if targetItemId and targetItemId ~= move.itemId then
Zerotorescue@110 481 self:Abort("target changed", "Target (" .. move.targetContainer .. "," .. move.targetSlot .. ") is not " .. IdToItemLink(move.itemId) .. " nor empty");
Zerotorescue@110 482 return;
Zerotorescue@110 483 end
Zerotorescue@110 484
Zerotorescue@110 485 -- And drop it (this is always a local bag so no need to do any guild-checks)
Zerotorescue@110 486 PickupContainerItem(move.targetContainer, move.targetSlot);
Zerotorescue@110 487
Zerotorescue@110 488 -- Remember we dropped an item here and thus this is now locked
Zerotorescue@110 489 if not targetLocationsLocked[move.targetContainer] then
Zerotorescue@110 490 targetLocationsLocked[move.targetContainer] = {};
Zerotorescue@110 491 end
Zerotorescue@110 492 targetLocationsLocked[move.targetContainer][move.targetSlot] = true;
Zerotorescue@110 493
Zerotorescue@110 494 -- This move was processed
Zerotorescue@122 495 tremove(combinedMoves, numCurrentMove);
Zerotorescue@110 496 else
Zerotorescue@110 497 self:Abort("item disappeared from mouse", "Couldn't move " .. IdToItemLink(move.itemId) .. ", CursorHasItem() is false");
Zerotorescue@81 498 return;
Zerotorescue@81 499 end
Zerotorescue@110 500 else
Zerotorescue@110 501 -- When items are deposit automatically we still need to remember when a move has been processed
Zerotorescue@80 502
Zerotorescue@80 503 -- This move was processed
Zerotorescue@122 504 tremove(combinedMoves, numCurrentMove);
Zerotorescue@80 505 end
Zerotorescue@80 506 end
Zerotorescue@80 507
Zerotorescue@80 508 -- Proceed with the next element (or previous considering we're going from last to first)
Zerotorescue@80 509 numCurrentMove = (numCurrentMove - 1);
Zerotorescue@80 510 end
Zerotorescue@81 511
Zerotorescue@89 512 addon:Debug("%d moves processed. %d moves remaining.", (combinedMovesOriginalLength - #combinedMoves), #combinedMoves);
Zerotorescue@82 513
Zerotorescue@81 514 if #combinedMoves == 0 then
Zerotorescue@98 515 addon:Print("Finished.", addon.Colors.Green);
Zerotorescue@81 516
Zerotorescue@81 517 self:Abort();
Zerotorescue@81 518
Zerotorescue@81 519 return;
Zerotorescue@81 520 end
Zerotorescue@80 521 end
Zerotorescue@80 522
Zerotorescue@80 523 local tmrProcessNext;
Zerotorescue@110 524 function mod:SourceUpdated()
Zerotorescue@81 525 self:CancelTimer(tmrProcessNext, true); -- silent
Zerotorescue@89 526 tmrProcessNext = self:ScheduleTimer("ProcessMove", .5);
Zerotorescue@80 527 end
Zerotorescue@80 528
Zerotorescue@81 529 function IdToItemLink(itemId)
Zerotorescue@81 530 local itemLink = select(2, GetItemInfo(itemId));
Zerotorescue@81 531 itemLink = itemLink or "Unknown (" .. itemId .. ")";
Zerotorescue@81 532 return itemLink;
Zerotorescue@81 533 end
Zerotorescue@81 534
Zerotorescue@81 535 function mod:UI_ERROR_MESSAGE(e, errorMessage)
Zerotorescue@81 536 if errorMessage == ERR_SPLIT_FAILED then
Zerotorescue@81 537 self:Abort("splitting failed", "Splitting failed.");
Zerotorescue@110 538 elseif errorMessage == ERR_GUILD_WITHDRAW_LIMIT then
Zerotorescue@110 539 self:Abort("at guild withdrawal limit", "At guild withdrawal limit");
Zerotorescue@80 540 end
Zerotorescue@80 541 end
Zerotorescue@80 542
Zerotorescue@81 543 function mod:Abort(simple, debugMsg)
Zerotorescue@110 544 -- Announce
Zerotorescue@81 545 if debugMsg then
Zerotorescue@89 546 addon:Debug("Aborting:%s", debugMsg);
Zerotorescue@81 547 end
Zerotorescue@81 548 if simple then
Zerotorescue@98 549 addon:Print(("Aborting: %s."):format(simple), addon.Colors.Red);
Zerotorescue@81 550 end
Zerotorescue@80 551
Zerotorescue@98 552 -- Make sure nothing is at the mouse
Zerotorescue@98 553 ClearCursor();
Zerotorescue@98 554
Zerotorescue@81 555 -- Stop timer
Zerotorescue@110 556 self:UnregisterEvent(ContainerFunctions[movesSource].Event);
Zerotorescue@81 557 self:CancelTimer(tmrProcessNext, true); -- silent
Zerotorescue@80 558
Zerotorescue@81 559 self:UnregisterEvent("UI_ERROR_MESSAGE");
Zerotorescue@110 560
Zerotorescue@110 561 -- Reset vars
Zerotorescue@122 562 twipe(combinedMoves);
Zerotorescue@110 563 movesSource = nil;
Zerotorescue@110 564 if MailAddonBusy == addon:GetName() then
Zerotorescue@110 565 MailAddonBusy = nil;
Zerotorescue@110 566 end
Zerotorescue@80 567 end
Zerotorescue@80 568
Zerotorescue@80 569 function mod:OnEnable()
Zerotorescue@80 570 Scanner = addon:GetModule("Scanner");
Zerotorescue@80 571 end
Zerotorescue@80 572
Zerotorescue@80 573 function mod:OnDisable()
Zerotorescue@80 574 Scanner = nil;
Zerotorescue@81 575
Zerotorescue@81 576 self:Abort();
Zerotorescue@80 577 end