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