comparison Mover.lua @ 81:58617c7827fa

Item refilling should now be working. Probably very slow if your bags, bank or guild bank is filled with over 200 unique items or so (needs some real testing, but know that it is a known (possible) issue).
author Zerotorescue
date Thu, 06 Jan 2011 01:01:25 +0100
parents c0bf2ddb5288
children f885805da5d6
comparison
equal deleted inserted replaced
80:c0bf2ddb5288 81:58617c7827fa
2 local mod = addon:NewModule("Mover", "AceEvent-3.0", "AceTimer-3.0"); 2 local mod = addon:NewModule("Mover", "AceEvent-3.0", "AceTimer-3.0");
3 3
4 local Scanner; 4 local Scanner;
5 local queuedMoves = {}; -- table storing all queued moves before BeginMove is called 5 local queuedMoves = {}; -- table storing all queued moves before BeginMove is called
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 #) 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 #)
7 local movesSource;
7 8
8 function mod:AddMove(itemId, amount) 9 function mod:AddMove(itemId, amount)
9 table.insert(queuedMoves, { 10 table.insert(queuedMoves, {
10 id = itemId, 11 id = itemId,
11 num = amount, 12 num = amount,
12 }); 13 });
13 end 14 end
14 15
16 function mod:HasMoves()
17 return (#queuedMoves ~= 0);
18 end
19
15 function mod:BeginMove(location, onFinish) 20 function mod:BeginMove(location, onFinish)
21 addon:Debug("BeginMove");
16 22
17 -- Find the outgoing moves 23 -- Find the outgoing moves
18 -- 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 24 -- 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
19 25
20 -- Get a list of items in the source container 26 -- Get a list of items in the source container
21 local sourceContents = Scanner:CacheLocation(location, false); 27 local sourceContents = Scanner:CacheLocation(location, false);
22 28
23 local outgoingMoves = {}; 29 local outgoingMoves = {};
24 30
25 for singleMove in pairs(queuedMoves) do 31 for _, singleMove in pairs(queuedMoves) do
26 local sourceItem = sourceContents[singleMove.id]; 32 local sourceItem = sourceContents[singleMove.id];
27 if not sourceItem then 33 if not sourceItem then
28 print("Can't move " .. IdToItemLink(singleMove.id) .. ", non-existant in source"); 34 print("Can't move " .. IdToItemLink(singleMove.id) .. ", non-existant in source");
29 else 35 else
30 -- We want to move the smallest stacks first to keep stuff pretty 36 -- We want to move the smallest stacks first to keep stuff pretty
31 table.sort(sourceItem.locations, function(a, b) 37 table.sort(sourceItem.locations, function(a, b)
32 return a.count > b.count; 38 return a.count < b.count;
33 end); 39 end);
34 40
35 for itemLocation in pairs(sourceItem.locations) do 41 for _, itemLocation in pairs(sourceItem.locations) do
36 -- if this location has more items than we need, only move what we need, otherwise move everything in this stack 42 -- if this location has more items than we need, only move what we need, otherwise move everything in this stack
37 local movingNum = ((itemLocation.count > singleMove.num and singleMove.num) or itemLocation.count); 43 local movingNum = ((itemLocation.count > singleMove.num and singleMove.num) or itemLocation.count);
38 44
39 table.insert(outgoingMoves, { 45 table.insert(outgoingMoves, {
40 itemId = singleMove.id, 46 itemId = singleMove.id,
45 51
46 singleMove.num = (singleMove.num - movingNum); 52 singleMove.num = (singleMove.num - movingNum);
47 53
48 if singleMove.num == 0 then 54 if singleMove.num == 0 then
49 -- If we have prepared everything we wanted, go to the next queued move 55 -- If we have prepared everything we wanted, go to the next queued move
50 break; 56 break; -- stop the locations-loop
51 end 57 end
52 end 58 end
53 end 59 end
54 end 60 end
55 61
74 for slotId = 1, GetContainerNumSlots(bagId) do 80 for slotId = 1, GetContainerNumSlots(bagId) do
75 local itemId = GetContainerItemID(bagId, slotId); 81 local itemId = GetContainerItemID(bagId, slotId);
76 82
77 if not itemId then 83 if not itemId then
78 table.insert(emptySlots, { 84 table.insert(emptySlots, {
79 container: bagId, 85 container = bagId,
80 slot: slotId, 86 slot = slotId,
81 }); 87 });
82 end 88 end
83 end 89 end
84 end 90 end
91
92 -- Remember where we're moving from
93 movesSource = location;
85 94
86 while #outgoingMoves ~= 0 do 95 while #outgoingMoves ~= 0 do
87 -- A not equal-comparison should be quicker than a larger/smaller than-comparison 96 -- A not equal-comparison should be quicker than a larger/smaller than-comparison
88 97
89 for outgoingMove in pairs(outgoingMoves) do 98 for _, outgoingMove in pairs(outgoingMoves) do
90 -- itemId will be set to nil when this outgoing move was processed - sanity check 99 -- itemId will be set to nil when this outgoing move was processed - sanity check
91 if outgoingMove.itemId then 100 if outgoingMove.itemId then
92 local targetItem = targetContents[outgoingMove.itemId]; 101 local targetItem = targetContents[outgoingMove.itemId];
93 102
94 if not targetItem then 103 if not targetItem then
112 num = outgoingMove.count, 121 num = outgoingMove.count,
113 }); 122 });
114 123
115 -- We filled an empty slot so the target contents now has one more item, 124 -- We filled an empty slot so the target contents now has one more item,
116 -- make a new instance of the ItemMove class so any additional items with this id can be stacked on top of it 125 -- make a new instance of the ItemMove class so any additional items with this id can be stacked on top of it
117 local itemMove = addon.ItemMove:New(); 126 local itemMove = addon.ContainerItem:New();
118 itemMove.AddLocation(firstAvailableSlot.container, firstAvailableSlot.slot, outgoingMove.count); 127 itemMove:AddLocation(firstAvailableSlot.container, firstAvailableSlot.slot, outgoingMove.count);
119 targetContents[outgoingMove.itemId] = itemMove; 128 targetContents[outgoingMove.itemId] = itemMove;
120 129
121 firstAvailableSlot = nil; -- no longer empty 130 table.remove(emptySlots, 1); -- no longer empty
122 131
123 outgoingMove.count = 0; -- nothing remaining - sanity check 132 outgoingMove.count = 0; -- nothing remaining - sanity check
124 outgoingMove.itemId = nil; -- remove this record from the outgoingMoves-table 133 outgoingMove.itemId = nil; -- remove this record from the outgoingMoves-table
125 end 134 end
126 else 135 else
127 -- Find the maximum stack size for this item 136 -- Find the maximum stack size for this item
128 local itemStackCount = select(8, GetItemInfo(outgoingMove.itemId)); 137 local itemStackCount = select(8, GetItemInfo(outgoingMove.itemId));
129 138
130 -- We want to move to the largest stacks first to keep stuff pretty 139 -- We want to move to the largest stacks first to keep stuff pretty
131 table.sort(targetItem.locations, function(a, b) 140 table.sort(targetItem.locations, function(a, b)
132 return a.count < b.count; 141 return a.count > b.count;
133 end); 142 end);
134 143
135 for itemLocation in pairs(targetItem.locations) do 144 for _, itemLocation in pairs(targetItem.locations) do
136 if itemLocation.count < itemStackCount and outgoingMove.count > 0 then 145 if itemLocation.count < itemStackCount and outgoingMove.count > 0 then
137 -- Check if this stack isn't already full (and we still need to move this item) 146 -- Check if this stack isn't already full (and we still need to move this item)
138 147
139 local remainingSpace = (itemStackCount - itemLocation.count); 148 local remainingSpace = (itemStackCount - itemLocation.count);
140 if remainingSpace > outgoingMove.count then 149 if remainingSpace > outgoingMove.count then
199 end 208 end
200 209
201 -- No longer needed 210 -- No longer needed
202 table.wipe(emptySlots); 211 table.wipe(emptySlots);
203 212
204 DoMoveNow(); 213 self:ProcessMove();
205 214
206 --ProcessMove(); 215 self:RegisterEvent("BAG_UPDATE");
207 216
208 --mod:RegisterEvent("BAG_UPDATE", BAG_UPDATE); 217 onFinish();
209 218 end
210 --onFinish(); 219
211 end 220 if not table.reverse then
212 221 -- table.reverse = function(orig)
213 function DoMoveNow() 222 -- local temp = CopyTable(orig);
223 -- local origLength = #temp;
224 -- for i = 1, origLength do
225 -- orig[(origLength - i + 1)] = temp[i];
226 -- end
227 -- end
228 table.reverse = function(orig)
229 local temp = {};
230 local origLength = #orig;
231 for i = 1, origLength do
232 temp[(origLength - i + 1)] = orig[i];
233 end
234
235 -- -- Update the original table (can't do orig = temp as that would change the reference-link instead of the original table)
236 -- for i, v in pairs(temp) do
237 -- orig[i] = v;
238 -- end
239 return temp; -- for speed we choose to do a return instead
240 end
241 end
242
243 function mod:ProcessMove()
244 addon:Debug("ProcessMove");
245
246 if #combinedMoves == 0 then
247 print("Nothing to move.");
248
249 self:Abort();
250
251 return;
252 end
253
254 self:RegisterEvent("UI_ERROR_MESSAGE");
255
214 -- combinedMoves now has all moves in it (source -> target) 256 -- combinedMoves now has all moves in it (source -> target)
215 -- go through list, move everything inside it 257 -- go through list, move everything inside it
216 -- add source and target to one single list 258 -- add source and target to lists, if either is already in this list, skip the move
217 -- if either is already in this list, skip this move 259 -- repeat every few seconds until we're completely done
218 -- repeat every 5 seconds until we're completely done
219 260
220 local sourceLocationsLocked = {}; 261 local sourceLocationsLocked = {};
221 local targetLocationsLocked = {}; 262 local targetLocationsLocked = {};
263
264 -- 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
265 combinedMoves = table.reverse(combinedMoves);
222 266
223 local numCurrentMove = #combinedMoves; 267 local numCurrentMove = #combinedMoves;
224 while numCurrentMove ~= 0 do 268 while numCurrentMove ~= 0 do
225 local move = combinedMoves[numCurrentMove]; 269 local move = combinedMoves[numCurrentMove];
226 270
227 -- sourceContainer, sourceSlot, targetContainer, targetSlot, itemId, num 271 -- sourceContainer, sourceSlot, targetContainer, targetSlot, itemId, num
228 if (not sourceLocationsLocked[move.sourceContainer] or not sourceLocationsLocked[move.sourceContainer][move.sourceSlot]) and 272 if move and (not sourceLocationsLocked[move.sourceContainer] or not sourceLocationsLocked[move.sourceContainer][move.sourceSlot]) and
229 (not targetLocationsLocked[move.targetContainer] or not targetLocationsLocked[move.targetContainer][move.targetSlot]) then 273 (not targetLocationsLocked[move.targetContainer] or not targetLocationsLocked[move.targetContainer][move.targetSlot]) then
230 274
231 print("Moving " .. IdToItemLink(move.itemId)); 275 print("Moving " .. IdToItemLink(move.itemId));
232 276
277 addon:Debug(("Moving %dx%s from (%d,%d) to (%d,%d)"):format(move.num, IdToItemLink(move.itemId), move.sourceContainer, move.sourceSlot, move.targetContainer, move.targetSlot));
278
279 if GetContainerItemID(move.sourceContainer, move.sourceSlot) ~= move.itemId then
280 self:Abort("source changed", "Source (" .. move.sourceContainer .. "," .. move.sourceSlot .. ") is not " .. IdToItemLink(move.itemId));
281
282 return;
283 end
284
233 -- Pickup stack 285 -- Pickup stack
234 SplitGuildBankItem(move.sourceContainer, move.sourceSlot, move.num); 286 if movesSource == addon.Locations.Bank then
287 SplitContainerItem(move.sourceContainer, move.sourceSlot, move.num);
288 elseif movesSource == addon.Locations.Guild then
289 SplitGuildBankItem(move.sourceContainer, move.sourceSlot, move.num);
290 end
235 291
236 -- Remember we picked this item up and thus it is now locked 292 -- Remember we picked this item up and thus it is now locked
237 if not sourceLocationsLocked[move.sourceContainer] then 293 if not sourceLocationsLocked[move.sourceContainer] then
238 sourceLocationsLocked[move.sourceContainer] = {}; 294 sourceLocationsLocked[move.sourceContainer] = {};
239 end 295 end
240 sourceLocationsLocked[move.sourceContainer][move.sourceSlot] = true; 296 sourceLocationsLocked[move.sourceContainer][move.sourceSlot] = true;
241 297
242 if CursorHasItem() then 298 if movesSource == addon.Locations.Guild or CursorHasItem() then -- CursorHasItem is always false if source is a guild tab
299 if GetContainerItemID(move.targetContainer, move.targetSlot) and GetContainerItemID(move.targetContainer, move.targetSlot) ~= move.itemId then
300 self:Abort("target changed", "Target (" .. move.targetContainer .. "," .. move.targetSlot .. ") is not " .. IdToItemLink(move.itemId) .. " nor empty");
301 return;
302 end
303
243 -- And drop it 304 -- And drop it
244 PickupContainerItem(move.targetContainer, move.targetSlot); 305 PickupContainerItem(move.targetContainer, move.targetSlot);
245 306
246 -- Remember we dropped an item here and thus this is now locked 307 -- Remember we dropped an item here and thus this is now locked
247 if not sourceLocationsLocked[move.targetContainer] then 308 if not targetLocationsLocked[move.targetContainer] then
248 sourceLocationsLocked[move.targetContainer] = {}; 309 targetLocationsLocked[move.targetContainer] = {};
249 end 310 end
250 sourceLocationsLocked[move.targetContainer][move.targetSlot] = true; 311 targetLocationsLocked[move.targetContainer][move.targetSlot] = true;
251 312
252 -- This move was processed 313 -- This move was processed
253 table.remove(combinedMoves, numCurrentMove); 314 table.remove(combinedMoves, numCurrentMove);
315 else
316 self:Abort("item disappeared from mouse", "Couldn't move " .. IdToItemLink(move.itemId) .. ", CursorHasItem() is false");
317 return;
254 end 318 end
255 end 319 end
256 320
257 -- Proceed with the next element (or previous considering we're going from last to first) 321 -- Proceed with the next element (or previous considering we're going from last to first)
258 numCurrentMove = (numCurrentMove - 1); 322 numCurrentMove = (numCurrentMove - 1);
259 end 323 end
324
325 if #combinedMoves == 0 then
326 print("Finished.");
327
328 self:Abort();
329
330 return;
331 end
260 end 332 end
261 333
262 local tmrProcessNext; 334 local tmrProcessNext;
263 function BAG_UPDATE() 335 function mod:BAG_UPDATE()
264 mod:CancelTimer(tmrProcessNext, true); -- silent 336 self:CancelTimer(tmrProcessNext, true); -- silent
265 tmrProcessNext = mod:ScheduleTimer(function() 337 tmrProcessNext = self:ScheduleTimer("ProcessMove", 1);
266 ProcessMove();
267 end, 2);
268 end
269
270 function ProcessMove()
271 local currentMove = queuedMoves[1];
272
273 if currentMove then
274 addon:Debug("Moving " .. currentMove.num .. " of " .. IdToItemLink(currentMove.id));
275
276 local requestedMoves = currentMove.num;
277
278 if currentMove.src == addon.Locations.Bank then
279 MoveBankItem(currentMove);
280 elseif currentMove.src == addon.Locations.Guild then
281 MoveGuildItem(currentMove);
282 end
283
284 if requestedMoves == currentMove.num then
285 print("Skipping " .. IdToItemLink(move.id));
286 move.num = 0;
287 elseif currentMove.num > 0 then
288 -- bags are full
289 print("bags are full");
290 end
291
292 if currentMove.num == 0 then
293 table.remove(queuedMoves, 1);
294 end
295 end
296 end
297
298 function MoveGuildItem(move)
299 local tabId = GetCurrentGuildBankTab();
300 local slotId = (MAX_GUILDBANK_SLOTS_PER_TAB or 98); -- start by scanning the last slot
301
302 if tabId == nil or tabId < 1 then return; end
303
304 while slotId ~= 0 do
305 -- A not equal-comparison should be quicker than a larger than-comparison
306
307 local itemLink = GetGuildBankItemLink(tabId, slotId);
308 if itemLink then
309 -- If there is actually an item in this slot
310
311 local itemId = GetItemId(itemLink);
312
313 if itemId and move.id == itemId then
314 -- This is one of the items we're looking for
315
316 local itemCount = select(2, GetGuildBankItemInfo(tabId, slotId));
317
318 if itemCount and itemCount > 0 then
319 -- if the amount we still have to move is more than this stack, move the entire stack, otherwise move the still needed part of the stack
320 local moveable = (move.num > itemCount and itemCount) or move.num;
321
322 -- Pickup stack
323 SplitGuildBankItem(tabId, slotId, moveable);
324
325 -- Find an target slot and put it there
326 local reallyMoved = DropItem(itemId, moveable);
327 if reallyMoved then
328 -- Keep track of how many we have moved
329 moved = (moved + reallyMoved);
330
331 -- Update the required amount of items so it has the remaining num
332 move.num = (move.num - reallyMoved);
333
334 --if reallyMoved ~= moveable then
335 -- -- Scan this slot again because if we moved a 18 stack onto a 16 stack, we'd actually have moved only 4 items and still need to move the remaining 14 (we're capping stacks before using empty slots)
336 -- slotId = (slotId + 1);
337 --end
338
339 --if move.num == 0 then
340 -- if no required items are left to move, then stop and tell the caller function how many we moved
341 return moved;
342 --end
343 end
344 end
345 end
346 end
347
348 -- Continue scanning a different slot
349 slotId = (slotId - 1);
350 end
351 end
352
353 function MoveBankItem(move)
354 local start = 0;
355 local stop = NUM_BAG_SLOTS;
356
357 -- If we requested the bank then we don't want the bag info
358 start = ( NUM_BAG_SLOTS + 1 );
359 stop = ( NUM_BAG_SLOTS + NUM_BANKBAGSLOTS );
360
361 -- Scan the default 100 slots
362 move.num = (move.num - MoveFromContainter(BANK_CONTAINER, move));
363
364 -- Go through all our bags, including the backpack
365 for bagID = start, stop do
366 move.num = (move.num - MoveFromContainter(bagID, move));
367 end
368 end
369
370 -- Go through all slots of this bag and if a match was found, move the items
371 function MoveFromContainter(bagID, move)
372 -- Keep track of how many we have moved
373 local moved = 0;
374
375 -- Go through all slots of this bag
376 for slot = 1, GetContainerNumSlots(bagID) do
377 local itemId = GetContainerItemID(bagID, slot);
378
379 if itemId and move.id == itemId then
380 -- This is one of the items we're looking for
381
382 local itemCount = select(2, GetContainerItemInfo(bagID, slot));
383
384 if itemCount and itemCount > 0 then
385 -- if the amount we still have to move is more than this stack, move the entire stack, otherwise move the still needed part of the stack
386 local moveable = (move.num > itemCount and itemCount) or move.num;
387
388 -- Pickup stack
389 SplitContainerItem(bagID, slot, moveable);
390
391 addon:Debug("Picked " .. IdToItemLink(itemId) .. " up");
392
393 -- Find an target slot and put it there
394 if CursorHasItem() then
395 local reallyMoved = DropItem(itemId, moveable);
396
397 if reallyMoved then
398 addon:Debug("Dropped " .. reallyMoved .. " of " .. IdToItemLink(itemId));
399
400 -- Keep track of how many we have moved
401 moved = (moved + reallyMoved);
402
403 -- Update the required amount of items so it has the remaining num
404 move.num = (move.num - reallyMoved);
405
406 --if reallyMoved ~= moveable then
407 -- Scan this slot again because if we moved a 18 stack onto a 16 stack, we'd actually have moved only 4 items and still need to move the remaining 14 (we're capping stacks before using empty slots)
408 -- slot = (slot - 1);
409 --end
410
411 --if move.num == 0 then
412 -- if no required items are left to move, then stop and tell the caller function how many we moved
413 return moved;
414 --end
415 end
416 end
417 end
418 end
419 end
420
421 return moved;
422 end
423
424
425 -- This currently only uses empty slots, it will have to fill stacks in the future
426 function DropItem(itemId, amount)
427 local start = 0;
428 local stop = NUM_BAG_SLOTS;
429
430 -- Go through all our bags, including the backpack
431 for bagID = start, stop do
432 -- Go through all our slots
433 for slot = 1, GetContainerNumSlots(bagID) do
434 local itemId = GetContainerItemID(bagID, slot);
435
436 if not itemId then
437 -- If this slot is empty, put the item here
438 PickupContainerItem(bagID, slot);
439
440 return amount;
441 end
442 end
443 end
444
445 return;
446 end 338 end
447 339
448 function IdToItemLink(itemId) 340 function IdToItemLink(itemId)
449 return select(2, GetItemInfo(itemId)); 341 local itemLink = select(2, GetItemInfo(itemId));
342 itemLink = itemLink or "Unknown (" .. itemId .. ")";
343 return itemLink;
344 end
345
346 function mod:UI_ERROR_MESSAGE(e, errorMessage)
347 if errorMessage == ERR_SPLIT_FAILED then
348 self:Abort("splitting failed", "Splitting failed.");
349 end
350 end
351
352 function mod:Abort(simple, debugMsg)
353 if debugMsg then
354 addon:Debug("Aborting:" .. debugMsg);
355 end
356 if simple then
357 print("|cffff0000Aborting: " .. simple .. ".|r");
358 end
359 table.wipe(combinedMoves);
360 movesSource = nil;
361
362 -- Stop timer
363 self:UnregisterEvent("BAG_UPDATE");
364 self:CancelTimer(tmrProcessNext, true); -- silent
365
366 self:UnregisterEvent("UI_ERROR_MESSAGE");
450 end 367 end
451 368
452 function mod:OnEnable() 369 function mod:OnEnable()
453 Scanner = addon:GetModule("Scanner"); 370 Scanner = addon:GetModule("Scanner");
454 end 371 end
455 372
456 function mod:OnDisable() 373 function mod:OnDisable()
457 Scanner = nil; 374 Scanner = nil;
458 end 375
376 self:Abort();
377 end