vanilla-wow-addons – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 --[[
2 WeaponQuickSwap - by CapnBry
3 A script for moving weapons by name between hands, taking slot locking into account.
4  
5 Public Domain. Feel free to use any of this code or ideas in your own mods
6 --]]
7  
8 -- Unit variable to hold the stack of weapon swaps
9 local wswap = nil;
10 local WeaponSwap_IsSwapping = nil;
11 local WeaponSwap_IsInSwapIteration = nil;
12  
13 --
14 -- "Exported" functions for the user
15 --
16 function MageWeaponSwap(...)
17 -- insert them backwards! off, main like a stack
18 table.insert(arg, 1, 18);
19 table.insert(arg, 1, 16);
20 return WeaponQuickSwap_WeaponSwapCommon(arg);
21 end
22  
23 function WeaponSwap(...)
24 -- insert them backwards! off, main like a stack
25 table.insert(arg, 1, 17);
26 table.insert(arg, 1, 16);
27 return WeaponQuickSwap_WeaponSwapCommon(arg);
28 end
29  
30 function WeaponSwap_Reset()
31 wswap = nil;
32 WeaponSwap_IsSwapping = nil;
33 WeaponSwap_IsInSwapIteration = nil;
34 end
35  
36 --
37 -- Internal functions and callbacks
38 --
39 function WeaponQuickSwap_OnLoad()
40 if not Print then Print = function (x)
41 ChatFrame1:AddMessage(x, 1.0, 1.0, 1.0);
42 end
43 end
44  
45 if not WeaponSetsExchange then WeaponSetsExchange = WeaponSwap; end;
46  
47 this:RegisterEvent("PLAYER_ENTERING_WORLD");
48 this:RegisterEvent("PLAYER_LEAVING_WORLD");
49 end
50  
51 function WeaponQuickSwap_OnEvent(...)
52 if arg[1] == "ITEM_LOCK_CHANGED" and not arg[2] then
53 return WeaponQuickSwap_ExecuteSwapIteration();
54 end
55 if arg[1] == "PLAYER_ENTERING_WORLD" then
56 return WeaponSwap_RegisterEvents();
57 end
58 if arg[1] == "PLAYER_LEAVING_WORLD" then
59 return WeaponSwap_UnregisterEvents();
60 end
61 end
62  
63 local function swaplist_push(list, sb, si, db, di)
64 list = { next = list, sb = sb, si = si, db = db, di = di };
65 return list;
66 end
67  
68 local function swaplist_popfirst(list)
69 if not list then return; end
70  
71 list = list.next;
72 return list;
73 end
74  
75 function WeaponSwap_RegisterEvents()
76 WeaponQuickSwapFrame:RegisterEvent("ITEM_LOCK_CHANGED");
77 --this:RegisterEvent("UPDATE_BONUS_ACTIONBAR");
78 --this:RegisterEvent("BAG_UPDATE");
79 --this:RegisterEvent("UNIT_INVENTORY_CHANGED");
80 --this:RegisterEvent("UNIT_MODEL_CHANGED");
81 end
82  
83 function WeaponSwap_UnregisterEvents()
84 WeaponQuickSwapFrame:UnregisterEvent("ITEM_LOCK_CHANGED");
85 end
86  
87 function WeaponQuickSwap_ItemIsLocked(bag,slot)
88 if bag == -1 and slot == -1 then return nil; end
89  
90 if bag == -1 then
91 return IsInventoryItemLocked(slot);
92 else
93 local _,_,retval = GetContainerItemInfo(bag,slot);
94 return retval;
95 end
96 end
97  
98 function WeaponQuickSwap_AnyItemLocked()
99 -- Checks all the bags and the 3 equip slots to see if any slot is still locked
100 for i=0,NUM_BAG_FRAMES do
101 for j=1,GetContainerNumSlots(i) do
102 local _,_,retVal = GetContainerItemInfo(i,j);
103 if retVal then
104 return retVal;
105 end
106 end
107 end
108  
109 return IsInventoryItemLocked(16) or IsInventoryItemLocked(17) or IsInventoryItemLocked(18);
110 end
111  
112 function WeaponQuickSwap_ExecuteSwapIteration()
113 -- I would love to critical section this code:
114 -- It is called from the ITEM_LOCK_CHANGED notify, but calling Pickup*Item()
115 -- creates a notify, so we can get a stack overflow
116 if WeaponSwap_IsInSwapIteration then return; end
117 WeaponSwap_IsInSwapIteration = 1;
118  
119 if not wswap then
120 if WeaponSwap_IsSwapping and not WeaponQuickSwap_AnyItemLocked() then
121 WeaponSwap_IsInSwapIteration = nil;
122 return WeaponQuickSwap_OnSwapComplete();
123 end
124  
125 WeaponSwap_IsInSwapIteration = nil;
126 return;
127 end
128  
129 -- As of 1.10 it seems that sometimes just checking the source and destination
130 -- isn't enough, and we can't move an item if /anything/ is locked
131 --if WeaponQuickSwap_ItemIsLocked(wswap.sb, wswap.si) or
132 -- WeaponQuickSwap_ItemIsLocked(wswap.db, wswap.di) then
133 if WeaponQuickSwap_AnyItemLocked() then
134 WeaponSwap_IsInSwapIteration = nil;
135 return;
136 end
137  
138 if wswap.sb == -1 then
139 PickupInventoryItem(wswap.si);
140 else
141 PickupContainerItem(wswap.sb, wswap.si);
142 end
143  
144 if wswap.db == -1 then
145 if wswap.di == -1 then
146 PutItemInBackpack();
147 else
148 PickupInventoryItem(wswap.di);
149 end
150 else
151 PickupContainerItem(wswap.db, wswap.di);
152 end
153  
154 wswap = swaplist_popfirst(wswap);
155 if wswap and not WeaponQuickSwap_PerformSlowerSwap then
156 WeaponSwap_IsInSwapIteration = nil;
157 return WeaponQuickSwap_ExecuteSwapIteration();
158 end
159  
160 WeaponSwap_IsInSwapIteration = nil;
161 end
162  
163 function WeaponQuickSwap_OnSwapComplete()
164 -- this is just here to allow people to hook the completion event
165  
166 -- DebugStop
167 -- Print("Swap is complete");
168 return WeaponSwap_Reset();
169 end
170  
171 function WeaponQuickSwap_OnSwapError(error)
172 -- this is just here to allow people to hook the completion event
173 Print(error);
174  
175 return WeaponSwap_Reset();
176 end
177  
178 function WeaponQuickSwap_GetItemName(bag, slot)
179 local linktext = nil;
180  
181 if (bag == -1) then
182 linktext = GetInventoryItemLink("player", slot);
183 else
184 linktext = GetContainerItemLink(bag, slot);
185 end
186  
187 if linktext then
188 -- local _,_,name = string.find(linktext, "(%b[])");
189 local _,_,name = string.find(linktext, "^.*%[(.*)%].*$");
190 return name;
191 else
192 return "";
193 end
194 end
195  
196 function WeaponQuickSwap_FindItem(name, skipcount)
197 skipcount = skipcount or 0;
198  
199 -- First check the inventory slots 16, 17 and 18
200 for i= 16,18,1 do
201 if (WeaponQuickSwap_GetItemName(-1,i) == name) then
202 if skipcount == 0 then return -1,i; end
203 skipcount = skipcount - 1;
204 end
205 end
206  
207 -- not found check bags
208 for i=NUM_BAG_FRAMES,0,-1 do
209 for j=GetContainerNumSlots(i),1,-1 do
210 if (WeaponQuickSwap_GetItemName(i,j) == name) then
211 if skipcount == 0 then return i,j; end
212 skipcount = skipcount - 1;
213 end
214 end
215 end
216  
217 -- not found return nil,nil implicitly
218 end
219  
220 function WeaponQuickSwap_FindLastEmptyBagSlot(skipcount, bag_affinity, slot_affinity)
221 skipcount = skipcount or 0;
222  
223 -- try to put the item in the requested affinity, if possible
224 if bag_affinity and slot_affinity and
225 not GetContainerItemInfo(bag_affinity, slot_affinity) then
226 return bag_affinity, slot_affinity;
227 end
228  
229 -- if we couldn't get the bag and slot we wanted, just try the same bag
230 if bag_affinity then
231 for j=GetContainerNumSlots(bag_affinity),1,-1 do
232 if not GetContainerItemInfo(bag_affinity,j) then
233 if skipcount == 0 then return bag_affinity,j; end
234 skipcount = skipcount - 1;
235 end
236 end
237 end
238  
239 -- no affinity, chek all bags
240 for i=NUM_BAG_FRAMES,0,-1 do
241 -- but skip any bag we already have affinity for (because it might have
242 -- already modified skipcount
243 if bag_affinity ~= i then
244 -- Make sure this isn't a quiver, those won't hold shit
245 local bagName = GetBagName(i);
246 if bagName and not (
247 string.find(bagName, "Quiver") or
248 string.find(bagName, "Ammo Pouch") or
249 string.find(bagName, "Shot Pouch")
250 ) then
251 for j=GetContainerNumSlots(i),1,-1 do
252 if not GetContainerItemInfo(i,j) then
253 if skipcount == 0 then return i,j; end
254 skipcount = skipcount - 1;
255 end -- if empty
256 end -- for slots
257 end -- if normal bag
258 end -- if not affinity bag
259 end -- for bags
260  
261 -- not found return nil,nil implicitly
262 end
263  
264 function WeaponQuickSwap_FindCurrentSetIndex(mainhandslot, offhandslot, startIndex, setsList)
265 -- loop through the paramters and find what we have in our hands
266 local main, off;
267 local argidx = startIndex;
268 local retVal;
269 while setsList[argidx] do
270 main, off = setsList[argidx], setsList[argidx+1];
271  
272 if not main then break; end
273  
274 -- don't need to specify the offhand if this is just a single deal
275 if not off then off = ""; end
276  
277 --Print("Checking:"..main..","..off);
278  
279 if WeaponQuickSwap_ItemNameMatches(-1, mainhandslot, main) and
280 WeaponQuickSwap_ItemNameMatches(-1, offhandslot, off) then
281 -- DebugStop
282 --Print("Found currently equipped set idx:"..argidx);
283 retVal = argidx;
284 break;
285 end
286  
287 argidx = argidx + 2;
288 end
289  
290 return retVal;
291 end
292  
293 function WeaponQuickSwap_ItemNameMatches(bag, slot, name)
294 if name == "*" or WeaponQuickSwap_GetItemName(bag, slot) == name then
295 return true;
296 end
297  
298 -- Support for Shadowstrike/Thunderstrike renaming weapons
299 return nil;
300 end
301  
302 function WeaponQuickSwap_WeaponSwapCommon(arg)
303 -- I explicitly use arg as a parameter instead of ... to prevent
304 -- having to call unpack on the arg list from the caller
305  
306 if WeaponSwap_IsSwapping then
307 if not (WeaponSwap_IsInSwapIteration or WeaponQuickSwap_AnyItemLocked()) then
308 Print("IsSwapping == true, IsInIteration == false, no items locked");
309 end
310  
311 -- DebugStop
312 --Print("Bailing out, swap is in progress");
313 return;
314 end
315  
316 WeaponSwap_IsSwapping = 1;
317 wswap = nil;
318 WeaponQuickSwap_PerformSlowerSwap = nil;
319 --if CursorHasItem() then ResetCursor() end
320  
321 local mainhandslot, offhandslot = arg[1], arg[2];
322 local main, off;
323  
324 --Print("Hands are:"..mainhandslot..","..offhandslot);
325 local matchingsetidx = WeaponQuickSwap_FindCurrentSetIndex(mainhandslot, offhandslot, 3, arg);
326  
327 -- if we found a match, and there is at least one weapon speficied in the next
328 -- set, then use that set. Else use the first 2
329 if matchingsetidx and arg[matchingsetidx+2] then
330 main, off = arg[matchingsetidx+2], arg[matchingsetidx+3];
331 else
332 main, off = arg[3], arg[4];
333 end
334  
335 --Print("Picking:"..main..","..off);
336 -- don't need to specify the offhand if this is just a single deal
337 if not off then off = ""; end
338  
339 if not main then
340 return WeaponQuickSwap_OnSwapError("No weapons set found to switch. Not enough arguments?");
341 end
342  
343 -- see what's already set up
344 local m_ok = WeaponQuickSwap_ItemNameMatches(-1, mainhandslot, main);
345 local o_ok = WeaponQuickSwap_ItemNameMatches(-1, offhandslot, off);
346  
347 local m_sb, m_si = -1, mainhandslot;
348 if not m_ok then
349 if main == "" then
350 m_sb, m_si = -1, -1;
351 else
352 m_sb, m_si = WeaponQuickSwap_FindItem(main);
353 -- if FindItem returned the offhand weapon and it is already ok
354 -- don't remove it. Look harder
355 if o_ok and m_sb == -1 and m_si == offhandslot then
356 m_sb, m_si = WeaponQuickSwap_FindItem(main, 1);
357 end
358 end
359 if not (m_sb and m_si) then
360 return WeaponQuickSwap_OnSwapError("Can not find mainhand item: "..main);
361 end
362 end -- if not m_ok
363  
364 local multiinst;
365 -- if you're using 2 of the same weapon FindItem needs to
366 -- know not to just return the first
367 if main == off then multiinst = 1; else multiinst = 0; end
368  
369 local o_sb, o_si = -1, offhandslot;
370 if not o_ok then
371 if off == "" then
372 o_sb, o_si = -1, -1;
373 else
374 o_sb, o_si = WeaponQuickSwap_FindItem(off, multiinst);
375 -- note that here we don't have to "look harder" because
376 -- that would mean that both weapons are the same so multinst
377 -- would already be set to 1
378 end
379 if not (o_sb and o_si) then
380 return WeaponQuickSwap_OnSwapError("Can not find offhand item: "..off);
381 end
382 end -- if not o_ok
383  
384 -- Moving both hands from bags, that's easy
385 if m_sb ~= -1 and o_sb ~= -1 then
386 -- Load main first because if it is a 2h and we try to load offhand
387 -- we get a "Cannot Equip that with a Two-handed weapon" error
388 PickupContainerItem(m_sb, m_si);
389 PickupInventoryItem(mainhandslot);
390 PickupContainerItem(o_sb, o_si);
391 PickupInventoryItem(offhandslot);
392 WeaponQuickSwap_LastOffSource = { bag = o_sb, slot = o_si };
393 return;
394 end
395  
396 -- Simple hand swap
397 if m_sb == -1 and m_si == offhandslot and o_sb == -1 and o_si == mainhandslot then
398 PickupInventoryItem(mainhandslot);
399 PickupInventoryItem(offhandslot);
400 return;
401 end
402  
403 -- Push the list. We want to:
404 -- Take the mainhand weapon out if it isn't going to offhand
405 -- Move from wherever to the offhand. If offhand is supposed to be empty, empty it.
406 -- Install the main hand weapon. No blank main hand is supported unless are going to be.
407 --
408 -- Do it backward, the swaplist is a stack
409  
410 local skipcount = 0;
411  
412 -- Install main hand
413 if not m_ok then
414 -- if nothing going to the main hand
415 if (m_sb == -1 and m_si == -1) then
416 -- and the main is not going to the off: put it in a bag
417 if not (o_sb == -1 and o_si == mainhandslot) then
418 local bb, bi = WeaponQuickSwap_FindLastEmptyBagSlot(skipcount);
419 if not (bb and bi) then
420 return WeaponQuickSwap_OnSwapError("Not enough empty bag slots to perform swap.");
421 end
422 skipcount = skipcount + 1;
423 wswap = swaplist_push(wswap, -1, mainhandslot, bb, bi);
424  
425 -- when moving A,"" -> "",B where A is a 2h, the offhand doesn't lock properly
426 -- so work around it by swapping slowly (only one swap per lock notify)
427 WeaponQuickSwap_PerformSlowerSwap = true;
428 end
429 else
430 wswap = swaplist_push(wswap, m_sb, m_si, -1, mainhandslot);
431 end
432 end
433  
434 -- Load offhand if not already there
435 if not o_ok then
436 if (o_sb == -1 and o_si == -1) then
437 if not (m_sb == -1 and m_si == offhandslot) then
438  
439 local bb, bi;
440 if WeaponQuickSwap_LastOffSource then
441 bb, bi = WeaponQuickSwap_FindLastEmptyBagSlot(skipcount,
442 WeaponQuickSwap_LastOffSource.bag, WeaponQuickSwap_LastOffSource.slot);
443 else
444 bb, bi = WeaponQuickSwap_FindLastEmptyBagSlot(skipcount);
445 end
446  
447 if not (bb and bi) then
448 return WeaponQuickSwap_OnSwapError("Not enough empty bag slots to perform swap.");
449 end
450 skipcount = skipcount + 1;
451 wswap = swaplist_push(wswap, -1, offhandslot, bb, bi);
452 end
453 else
454 -- if the main hand weapon is coming from the offhand slot
455 -- we need to fix up its source to be where the offhand is
456 -- GOING to be after the bag->off swap
457 if wswap and (m_sb == -1 and m_si == offhandslot) then
458 wswap.sb = o_sb;
459 wswap.si = o_si;
460 -- don't set o_sb, o_si they're tested later
461 end
462  
463 wswap = swaplist_push(wswap, o_sb, o_si, -1, offhandslot);
464 end
465 end
466  
467 -- Special Case: Moving off to main, and not main to off
468 -- This is because maybe the main hand weapon is main only
469 if (m_sb == -1 and m_si == offhandslot) and not (o_sb == -1 and o_si == mainhandslot) then
470 local bb, bi = WeaponQuickSwap_FindLastEmptyBagSlot(skipcount);
471 if not (bb and bi) then
472 return WeaponQuickSwap_OnSwapError("Not enough empty bag slots to perform swap.");
473 end
474 skipcount = skipcount + 1;
475 wswap = swaplist_push(wswap, -1, mainhandslot, bb, bi);
476 end
477  
478 -- Same thing for off hand
479 if (o_sb == -1 and o_si == mainhandslot) and not (m_sb == -1 and m_si == offhandslot) then
480 local bb, bi = WeaponQuickSwap_FindLastEmptyBagSlot(skipcount);
481 if not (bb and bi) then
482 return WeaponQuickSwap_OnSwapError("Not enough empty bag slots to perform swap.");
483 end
484 skipcount = skipcount + 1;
485 wswap = swaplist_push(wswap, -1, 17, bb, bi);
486 end
487  
488 if o_sb ~= -1 then
489 WeaponQuickSwap_LastOffSource = { bag = o_sb, slot = o_si };
490 end
491  
492 -- DebugStop
493 --Print("Starting SwapIterations");
494 -- Start moving
495 return WeaponQuickSwap_ExecuteSwapIteration();
496 end