annotate Modules/Mover.lua @ 207:5d6b3d116b80

Queueing without a profession open should no longer spam your chat window with ?No tradeskill window detected.? messages.
author Zerotorescue
date Sat, 05 Feb 2011 20:01:57 +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