Mercurial > wow > inventory
comparison Modules/Mover.lua @ 84:3bec0ea44607
Cleaned the Inventorium folder; moved all classes to classes directory, modules to modules directory and support addons to plugins directory. In addition support addons are now references within XML files rather than inside the TOC.
Fixed the default local item count setting, you can now exclude bag and AH data from it.
Fixed some mover algorithm bugs. Mover can no longer freeze the game but instead will terminate the process after a 1000 passes.
Now reversing the moves table after making it, rather than every single time it is used.
Fixed guild bank support.
Now displaying the amount of items moved.
Scanner now scans all guild bank tabs rather than only the current.
Fixed a bug with local item data not being retrieved properly.
Disabled ?enterClicksFirstButton? within dialogs as this causes the dialog to consume all keypress.
Events are now at the addon object rather than local.
author | Zerotorescue |
---|---|
date | Thu, 06 Jan 2011 20:05:30 +0100 |
parents | Mover.lua@f885805da5d6 |
children | f1c035694545 |
comparison
equal
deleted
inserted
replaced
83:6b60f7a1410c | 84:3bec0ea44607 |
---|---|
1 local addon = select(2, ...); | |
2 local mod = addon:NewModule("Mover", "AceEvent-3.0", "AceTimer-3.0"); | |
3 | |
4 local Scanner; | |
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 #) | |
7 local movesSource; | |
8 | |
9 function mod:AddMove(itemId, amount) | |
10 table.insert(queuedMoves, { | |
11 id = itemId, | |
12 num = amount, | |
13 }); | |
14 end | |
15 | |
16 function mod:HasMoves() | |
17 return (#queuedMoves ~= 0); | |
18 end | |
19 | |
20 if not table.reverse then | |
21 table.reverse = function(orig) | |
22 local temp = {}; | |
23 local origLength = #orig; | |
24 for i = 1, origLength do | |
25 temp[(origLength - i + 1)] = orig[i]; | |
26 end | |
27 | |
28 -- -- Update the original table (can't do orig = temp as that would change the reference-link instead of the original table) | |
29 -- for i, v in pairs(temp) do | |
30 -- orig[i] = v; | |
31 -- end | |
32 return temp; -- for speed we choose to do a return instead | |
33 end | |
34 end | |
35 | |
36 function mod:BeginMove(location, onFinish) | |
37 addon:Debug("BeginMove"); | |
38 | |
39 -- Find the outgoing moves | |
40 -- 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 | |
41 | |
42 -- Get a list of items in the source container | |
43 local sourceContents = Scanner:CacheLocation(location, false); | |
44 | |
45 local outgoingMoves = {}; | |
46 | |
47 addon:Debug(#queuedMoves .. " moves were queued."); | |
48 | |
49 for _, singleMove in pairs(queuedMoves) do | |
50 local sourceItem = sourceContents[singleMove.id]; | |
51 if not sourceItem then | |
52 print("Can't move " .. IdToItemLink(singleMove.id) .. ", non-existant in source"); | |
53 else | |
54 -- 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) | |
55 table.sort(sourceItem.locations, function(a, b) | |
56 return a.count < b.count; | |
57 end); | |
58 | |
59 for _, itemLocation in pairs(sourceItem.locations) do | |
60 -- if this location has more items than we need, only move what we need, otherwise move everything in this stack | |
61 local movingNum = ((itemLocation.count > singleMove.num and singleMove.num) or itemLocation.count); | |
62 | |
63 table.insert(outgoingMoves, { | |
64 itemId = singleMove.id, | |
65 num = movingNum, | |
66 container = itemLocation.container, | |
67 slot = itemLocation.slot, | |
68 }); | |
69 | |
70 singleMove.num = (singleMove.num - movingNum); | |
71 | |
72 if singleMove.num == 0 then | |
73 -- If we have prepared everything we wanted, go to the next queued move | |
74 break; -- stop the locations-loop | |
75 end | |
76 end | |
77 end | |
78 end | |
79 | |
80 addon:Debug(#outgoingMoves .. " outgoing moves are possible."); | |
81 | |
82 -- No longer needed | |
83 table.wipe(queuedMoves); | |
84 | |
85 -- Process every single outgoing move and find fitting targets | |
86 | |
87 -- Get a list of items already in the target container | |
88 local targetContents = Scanner:CacheLocation(addon.Locations.Bag, false); | |
89 | |
90 -- Find all empty slots | |
91 | |
92 local emptySlots = {}; | |
93 | |
94 local start = 0; | |
95 local stop = NUM_BAG_SLOTS; | |
96 | |
97 -- Go through all our bags, including the backpack | |
98 for bagId = start, stop do | |
99 -- Go through all our slots | |
100 for slotId = 1, GetContainerNumSlots(bagId) do | |
101 local itemId = GetContainerItemID(bagId, slotId); -- we're scanning our local bags here, so no need to get messy with guild bank support | |
102 | |
103 if not itemId then | |
104 table.insert(emptySlots, { | |
105 container = bagId, | |
106 slot = slotId, | |
107 }); | |
108 end | |
109 end | |
110 end | |
111 | |
112 addon:Debug(#emptySlots .. " empty slots are available."); | |
113 | |
114 -- Remember where we're moving from | |
115 movesSource = location; | |
116 | |
117 local backup = 0; | |
118 | |
119 while #outgoingMoves ~= 0 do | |
120 -- A not equal-comparison should be quicker than a larger/smaller than-comparison | |
121 | |
122 for _, outgoingMove in pairs(outgoingMoves) do | |
123 -- itemId will be set to nil when this outgoing move was processed - sanity check | |
124 if outgoingMove.itemId then | |
125 local targetItem = targetContents[outgoingMove.itemId]; | |
126 | |
127 if not targetItem then | |
128 -- grab an empty slot | |
129 -- make new instance of ItemMove | |
130 -- populate targetContents with it so future moves of this item can be put on top of it if this isn't a full stack | |
131 | |
132 local firstAvailableSlot = emptySlots[1]; | |
133 | |
134 if not firstAvailableSlot then | |
135 print("Bags are full. Skipping " .. IdToItemLink(outgoingMove.itemId) .. "."); | |
136 | |
137 outgoingMove.itemId = nil; -- remove this record from the outgoingMoves-table | |
138 else | |
139 table.insert(combinedMoves, { | |
140 itemId = outgoingMove.itemId, | |
141 num = outgoingMove.num, | |
142 sourceContainer = outgoingMove.container, | |
143 sourceSlot = outgoingMove.slot, | |
144 targetContainer = firstAvailableSlot.container, | |
145 targetSlot = firstAvailableSlot.slot, | |
146 }); | |
147 | |
148 -- We filled an empty slot so the target contents now has one more item, | |
149 -- make a new instance of the ItemMove class so any additional items with this id can be stacked on top of it | |
150 local itemMove = addon.ContainerItem:New(); | |
151 itemMove:AddLocation(firstAvailableSlot.container, firstAvailableSlot.slot, outgoingMove.num); | |
152 targetContents[outgoingMove.itemId] = itemMove; | |
153 | |
154 table.remove(emptySlots, 1); -- no longer empty | |
155 | |
156 outgoingMove.num = 0; -- nothing remaining - sanity check | |
157 outgoingMove.itemId = nil; -- remove this record from the outgoingMoves-table | |
158 end | |
159 else | |
160 -- Find the maximum stack size for this item | |
161 local itemStackCount = select(8, GetItemInfo(outgoingMove.itemId)); | |
162 | |
163 -- We want to move to the largest stacks first to keep stuff pretty | |
164 table.sort(targetItem.locations, function(a, b) | |
165 return a.count > b.count; | |
166 end); | |
167 | |
168 for _, itemLocation in pairs(targetItem.locations) do | |
169 if itemLocation.count < itemStackCount and outgoingMove.num > 0 then | |
170 -- Check if this stack isn't already full (and we still need to move this item) | |
171 | |
172 local remainingSpace = (itemStackCount - itemLocation.count); | |
173 if remainingSpace >= outgoingMove.num then | |
174 -- Enough room to move this entire stack | |
175 -- Deposit this item and then forget this outgoing move as everything in it was processed | |
176 | |
177 table.insert(combinedMoves, { | |
178 itemId = outgoingMove.itemId, | |
179 num = outgoingMove.num, | |
180 sourceContainer = outgoingMove.container, | |
181 sourceSlot = outgoingMove.slot, | |
182 targetContainer = itemLocation.container, | |
183 targetSlot = itemLocation.slot, | |
184 }); | |
185 | |
186 itemLocation.count = (itemLocation.count + outgoingMove.num); | |
187 outgoingMove.num = 0; -- nothing remaining | |
188 outgoingMove.itemId = nil; -- remove this record from the outgoingMoves-table | |
189 break; -- stop the locations-loop | |
190 else | |
191 -- Deposit this item but don't remove the outgoing move as there are some items left to move | |
192 | |
193 table.insert(combinedMoves, { | |
194 itemId = outgoingMove.itemId, | |
195 num = outgoingMove.num, | |
196 sourceContainer = outgoingMove.container, | |
197 sourceSlot = outgoingMove.slot, | |
198 targetContainer = itemLocation.container, | |
199 targetSlot = itemLocation.slot, | |
200 }); | |
201 | |
202 -- The target will be full when we complete, but the source will still have remaining items left to be moved | |
203 itemLocation.count = itemStackCount; | |
204 outgoingMove.num = (outgoingMove.num - remainingSpace); | |
205 end | |
206 end | |
207 end | |
208 | |
209 if outgoingMove.num > 0 then | |
210 -- We went through all matching items and checked their stack sizes if we could move this there, no room available | |
211 -- 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 | |
212 targetContents[outgoingMove.itemId] = nil; | |
213 end | |
214 end | |
215 end | |
216 end | |
217 | |
218 -- Loop through the array to find items that should be removed, start with the last element or the loop would break | |
219 local numOutgoingMoves = #outgoingMoves; -- since LUA-tables start at an index of 1, this is actually an existing index (outgoingMoves[#outgoingMoves] would return a value) | |
220 while numOutgoingMoves ~= 0 do | |
221 -- A not equal-comparison should be quicker than a larger/smaller than-comparison | |
222 | |
223 -- Check if the item id is nil, this is set to nil when this outgoing move has been processed | |
224 if not outgoingMoves[numOutgoingMoves].itemId or outgoingMoves[numOutgoingMoves].num == 0 then | |
225 -- Remove this element from the array | |
226 table.remove(outgoingMoves, numOutgoingMoves); | |
227 end | |
228 | |
229 -- Proceed with the next element (or previous considering we're going from last to first) | |
230 numOutgoingMoves = (numOutgoingMoves - 1); | |
231 end | |
232 | |
233 addon:Debug(#outgoingMoves .. " moves remaining."); | |
234 | |
235 backup = (backup + 1); | |
236 if backup > 1000 then | |
237 dump(nil, outgoingMoves); | |
238 table.wipe(outgoingMoves); | |
239 self:Abort("mover crashed", "Error preparing moves, hit an endless loop"); | |
240 onFinish(); | |
241 return; | |
242 end | |
243 end | |
244 | |
245 -- 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 | |
246 combinedMoves = table.reverse(combinedMoves); | |
247 | |
248 addon:Debug(#combinedMoves .. " moves should be possible."); | |
249 | |
250 -- No longer needed | |
251 table.wipe(emptySlots); | |
252 | |
253 self:ProcessMove(); | |
254 | |
255 -- Even though we aren't completely done yet, allow requeueing | |
256 onFinish(); | |
257 end | |
258 | |
259 function mod:ProcessMove() | |
260 addon:Debug("ProcessMove"); | |
261 | |
262 if #combinedMoves == 0 then | |
263 print("Nothing to move."); | |
264 | |
265 self:Abort(); | |
266 | |
267 return; | |
268 end | |
269 | |
270 self:RegisterEvent("BAG_UPDATE"); | |
271 self:RegisterEvent("UI_ERROR_MESSAGE"); | |
272 | |
273 -- combinedMoves now has all moves in it (source -> target) | |
274 -- go through list, move everything inside it | |
275 -- add source and target to lists, if either is already in this list, skip the move | |
276 -- repeat every few seconds until we're completely done | |
277 | |
278 local sourceLocationsLocked = {}; | |
279 local targetLocationsLocked = {}; | |
280 | |
281 local _GetContainerItemId = GetContainerItemID; | |
282 if movesSource == addon.Locations.Guild then | |
283 _GetContainerItemId = function(tabId, slotId) return addon:GetItemID(GetGuildBankItemLink(tabId, slotId)); end; | |
284 end | |
285 | |
286 local combinedMovesOriginalLength = #combinedMoves; | |
287 local numCurrentMove = combinedMovesOriginalLength; | |
288 while numCurrentMove ~= 0 do | |
289 local move = combinedMoves[numCurrentMove]; | |
290 | |
291 -- sourceContainer, sourceSlot, targetContainer, targetSlot, itemId, num | |
292 if move and (not sourceLocationsLocked[move.sourceContainer] or not sourceLocationsLocked[move.sourceContainer][move.sourceSlot]) and | |
293 (not targetLocationsLocked[move.targetContainer] or not targetLocationsLocked[move.targetContainer][move.targetSlot]) then | |
294 | |
295 print(("Moving %dx%s."):format(move.num, IdToItemLink(move.itemId))); | |
296 | |
297 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)); | |
298 | |
299 if _GetContainerItemId(move.sourceContainer, move.sourceSlot) ~= move.itemId then | |
300 self:Abort("source changed", "Source (" .. move.sourceContainer .. "," .. move.sourceSlot .. ") is not " .. IdToItemLink(move.itemId)); | |
301 return; | |
302 end | |
303 | |
304 -- Pickup stack | |
305 if movesSource == addon.Locations.Bank then | |
306 SplitContainerItem(move.sourceContainer, move.sourceSlot, move.num); | |
307 elseif movesSource == addon.Locations.Guild then | |
308 SplitGuildBankItem(move.sourceContainer, move.sourceSlot, move.num); | |
309 end | |
310 | |
311 -- Remember we picked this item up and thus it is now locked | |
312 if not sourceLocationsLocked[move.sourceContainer] then | |
313 sourceLocationsLocked[move.sourceContainer] = {}; | |
314 end | |
315 sourceLocationsLocked[move.sourceContainer][move.sourceSlot] = true; | |
316 | |
317 if movesSource == addon.Locations.Guild or CursorHasItem() then -- CursorHasItem is always false if source is a guild tab | |
318 -- We are moving into our local bags, so the below must check normal | |
319 local targetItemId = GetContainerItemID(move.targetContainer, move.targetSlot); | |
320 if targetItemId and targetItemId ~= move.itemId then | |
321 self:Abort("target changed", "Target (" .. move.targetContainer .. "," .. move.targetSlot .. ") is not " .. IdToItemLink(move.itemId) .. " nor empty"); | |
322 return; | |
323 end | |
324 | |
325 -- And drop it (this is always a local bag so no need to do any guild-checks) | |
326 PickupContainerItem(move.targetContainer, move.targetSlot); | |
327 | |
328 -- Remember we dropped an item here and thus this is now locked | |
329 if not targetLocationsLocked[move.targetContainer] then | |
330 targetLocationsLocked[move.targetContainer] = {}; | |
331 end | |
332 targetLocationsLocked[move.targetContainer][move.targetSlot] = true; | |
333 | |
334 -- This move was processed | |
335 table.remove(combinedMoves, numCurrentMove); | |
336 else | |
337 self:Abort("item disappeared from mouse", "Couldn't move " .. IdToItemLink(move.itemId) .. ", CursorHasItem() is false"); | |
338 return; | |
339 end | |
340 end | |
341 | |
342 -- Proceed with the next element (or previous considering we're going from last to first) | |
343 numCurrentMove = (numCurrentMove - 1); | |
344 end | |
345 | |
346 addon:Debug((combinedMovesOriginalLength - #combinedMoves) .. " moves processed. " .. #combinedMoves .. " moves remaining."); | |
347 | |
348 if #combinedMoves == 0 then | |
349 print("Finished."); | |
350 | |
351 self:Abort(); | |
352 | |
353 return; | |
354 end | |
355 end | |
356 | |
357 local tmrProcessNext; | |
358 function mod:BAG_UPDATE() | |
359 self:CancelTimer(tmrProcessNext, true); -- silent | |
360 tmrProcessNext = self:ScheduleTimer("ProcessMove", 1); | |
361 end | |
362 | |
363 function IdToItemLink(itemId) | |
364 local itemLink = select(2, GetItemInfo(itemId)); | |
365 itemLink = itemLink or "Unknown (" .. itemId .. ")"; | |
366 return itemLink; | |
367 end | |
368 | |
369 function mod:UI_ERROR_MESSAGE(e, errorMessage) | |
370 if errorMessage == ERR_SPLIT_FAILED then | |
371 self:Abort("splitting failed", "Splitting failed."); | |
372 end | |
373 end | |
374 | |
375 function mod:Abort(simple, debugMsg) | |
376 if debugMsg then | |
377 addon:Debug("Aborting:" .. debugMsg); | |
378 end | |
379 if simple then | |
380 print("|cffff0000Aborting: " .. simple .. ".|r"); | |
381 end | |
382 table.wipe(combinedMoves); | |
383 movesSource = nil; | |
384 | |
385 -- Stop timer | |
386 self:UnregisterEvent("BAG_UPDATE"); | |
387 self:CancelTimer(tmrProcessNext, true); -- silent | |
388 | |
389 self:UnregisterEvent("UI_ERROR_MESSAGE"); | |
390 end | |
391 | |
392 function mod:OnEnable() | |
393 Scanner = addon:GetModule("Scanner"); | |
394 end | |
395 | |
396 function mod:OnDisable() | |
397 Scanner = nil; | |
398 | |
399 self:Abort(); | |
400 end |