annotate Modules/Mover.lua @ 146:ebe6f90c4bb9

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