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