annotate Modules/Mover.lua @ 110:67bd5057ecb7

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