annotate Modules/Mover.lua @ 119:dc6f405c1a5d

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