Mercurial > wow > inventory
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 |