vanilla-wow-addons – Rev 1
?pathlinks?
--[[
WeaponQuickSwap - by CapnBry
A script for moving weapons by name between hands, taking slot locking into account.
Public Domain. Feel free to use any of this code or ideas in your own mods
--]]
-- Unit variable to hold the stack of weapon swaps
local wswap = nil;
local WeaponSwap_IsSwapping = nil;
local WeaponSwap_IsInSwapIteration = nil;
--
-- "Exported" functions for the user
--
function MageWeaponSwap(...)
-- insert them backwards! off, main like a stack
table.insert(arg, 1, 18);
table.insert(arg, 1, 16);
return WeaponQuickSwap_WeaponSwapCommon(arg);
end
function WeaponSwap(...)
-- insert them backwards! off, main like a stack
table.insert(arg, 1, 17);
table.insert(arg, 1, 16);
return WeaponQuickSwap_WeaponSwapCommon(arg);
end
function WeaponSwap_Reset()
wswap = nil;
WeaponSwap_IsSwapping = nil;
WeaponSwap_IsInSwapIteration = nil;
end
--
-- Internal functions and callbacks
--
function WeaponQuickSwap_OnLoad()
if not Print then Print = function (x)
ChatFrame1:AddMessage(x, 1.0, 1.0, 1.0);
end
end
if not WeaponSetsExchange then WeaponSetsExchange = WeaponSwap; end;
this:RegisterEvent("PLAYER_ENTERING_WORLD");
this:RegisterEvent("PLAYER_LEAVING_WORLD");
end
function WeaponQuickSwap_OnEvent(...)
if arg[1] == "ITEM_LOCK_CHANGED" and not arg[2] then
return WeaponQuickSwap_ExecuteSwapIteration();
end
if arg[1] == "PLAYER_ENTERING_WORLD" then
return WeaponSwap_RegisterEvents();
end
if arg[1] == "PLAYER_LEAVING_WORLD" then
return WeaponSwap_UnregisterEvents();
end
end
local function swaplist_push(list, sb, si, db, di)
list = { next = list, sb = sb, si = si, db = db, di = di };
return list;
end
local function swaplist_popfirst(list)
if not list then return; end
list = list.next;
return list;
end
function WeaponSwap_RegisterEvents()
WeaponQuickSwapFrame:RegisterEvent("ITEM_LOCK_CHANGED");
--this:RegisterEvent("UPDATE_BONUS_ACTIONBAR");
--this:RegisterEvent("BAG_UPDATE");
--this:RegisterEvent("UNIT_INVENTORY_CHANGED");
--this:RegisterEvent("UNIT_MODEL_CHANGED");
end
function WeaponSwap_UnregisterEvents()
WeaponQuickSwapFrame:UnregisterEvent("ITEM_LOCK_CHANGED");
end
function WeaponQuickSwap_ItemIsLocked(bag,slot)
if bag == -1 and slot == -1 then return nil; end
if bag == -1 then
return IsInventoryItemLocked(slot);
else
local _,_,retval = GetContainerItemInfo(bag,slot);
return retval;
end
end
function WeaponQuickSwap_AnyItemLocked()
-- Checks all the bags and the 3 equip slots to see if any slot is still locked
for i=0,NUM_BAG_FRAMES do
for j=1,GetContainerNumSlots(i) do
local _,_,retVal = GetContainerItemInfo(i,j);
if retVal then
return retVal;
end
end
end
return IsInventoryItemLocked(16) or IsInventoryItemLocked(17) or IsInventoryItemLocked(18);
end
function WeaponQuickSwap_ExecuteSwapIteration()
-- I would love to critical section this code:
-- It is called from the ITEM_LOCK_CHANGED notify, but calling Pickup*Item()
-- creates a notify, so we can get a stack overflow
if WeaponSwap_IsInSwapIteration then return; end
WeaponSwap_IsInSwapIteration = 1;
if not wswap then
if WeaponSwap_IsSwapping and not WeaponQuickSwap_AnyItemLocked() then
WeaponSwap_IsInSwapIteration = nil;
return WeaponQuickSwap_OnSwapComplete();
end
WeaponSwap_IsInSwapIteration = nil;
return;
end
-- As of 1.10 it seems that sometimes just checking the source and destination
-- isn't enough, and we can't move an item if /anything/ is locked
--if WeaponQuickSwap_ItemIsLocked(wswap.sb, wswap.si) or
-- WeaponQuickSwap_ItemIsLocked(wswap.db, wswap.di) then
if WeaponQuickSwap_AnyItemLocked() then
WeaponSwap_IsInSwapIteration = nil;
return;
end
if wswap.sb == -1 then
PickupInventoryItem(wswap.si);
else
PickupContainerItem(wswap.sb, wswap.si);
end
if wswap.db == -1 then
if wswap.di == -1 then
PutItemInBackpack();
else
PickupInventoryItem(wswap.di);
end
else
PickupContainerItem(wswap.db, wswap.di);
end
wswap = swaplist_popfirst(wswap);
if wswap and not WeaponQuickSwap_PerformSlowerSwap then
WeaponSwap_IsInSwapIteration = nil;
return WeaponQuickSwap_ExecuteSwapIteration();
end
WeaponSwap_IsInSwapIteration = nil;
end
function WeaponQuickSwap_OnSwapComplete()
-- this is just here to allow people to hook the completion event
-- DebugStop
-- Print("Swap is complete");
return WeaponSwap_Reset();
end
function WeaponQuickSwap_OnSwapError(error)
-- this is just here to allow people to hook the completion event
Print(error);
return WeaponSwap_Reset();
end
function WeaponQuickSwap_GetItemName(bag, slot)
local linktext = nil;
if (bag == -1) then
linktext = GetInventoryItemLink("player", slot);
else
linktext = GetContainerItemLink(bag, slot);
end
if linktext then
-- local _,_,name = string.find(linktext, "(%b[])");
local _,_,name = string.find(linktext, "^.*%[(.*)%].*$");
return name;
else
return "";
end
end
function WeaponQuickSwap_FindItem(name, skipcount)
skipcount = skipcount or 0;
-- First check the inventory slots 16, 17 and 18
for i= 16,18,1 do
if (WeaponQuickSwap_GetItemName(-1,i) == name) then
if skipcount == 0 then return -1,i; end
skipcount = skipcount - 1;
end
end
-- not found check bags
for i=NUM_BAG_FRAMES,0,-1 do
for j=GetContainerNumSlots(i),1,-1 do
if (WeaponQuickSwap_GetItemName(i,j) == name) then
if skipcount == 0 then return i,j; end
skipcount = skipcount - 1;
end
end
end
-- not found return nil,nil implicitly
end
function WeaponQuickSwap_FindLastEmptyBagSlot(skipcount, bag_affinity, slot_affinity)
skipcount = skipcount or 0;
-- try to put the item in the requested affinity, if possible
if bag_affinity and slot_affinity and
not GetContainerItemInfo(bag_affinity, slot_affinity) then
return bag_affinity, slot_affinity;
end
-- if we couldn't get the bag and slot we wanted, just try the same bag
if bag_affinity then
for j=GetContainerNumSlots(bag_affinity),1,-1 do
if not GetContainerItemInfo(bag_affinity,j) then
if skipcount == 0 then return bag_affinity,j; end
skipcount = skipcount - 1;
end
end
end
-- no affinity, chek all bags
for i=NUM_BAG_FRAMES,0,-1 do
-- but skip any bag we already have affinity for (because it might have
-- already modified skipcount
if bag_affinity ~= i then
-- Make sure this isn't a quiver, those won't hold shit
local bagName = GetBagName(i);
if bagName and not (
string.find(bagName, "Quiver") or
string.find(bagName, "Ammo Pouch") or
string.find(bagName, "Shot Pouch")
) then
for j=GetContainerNumSlots(i),1,-1 do
if not GetContainerItemInfo(i,j) then
if skipcount == 0 then return i,j; end
skipcount = skipcount - 1;
end -- if empty
end -- for slots
end -- if normal bag
end -- if not affinity bag
end -- for bags
-- not found return nil,nil implicitly
end
function WeaponQuickSwap_FindCurrentSetIndex(mainhandslot, offhandslot, startIndex, setsList)
-- loop through the paramters and find what we have in our hands
local main, off;
local argidx = startIndex;
local retVal;
while setsList[argidx] do
main, off = setsList[argidx], setsList[argidx+1];
if not main then break; end
-- don't need to specify the offhand if this is just a single deal
if not off then off = ""; end
--Print("Checking:"..main..","..off);
if WeaponQuickSwap_ItemNameMatches(-1, mainhandslot, main) and
WeaponQuickSwap_ItemNameMatches(-1, offhandslot, off) then
-- DebugStop
--Print("Found currently equipped set idx:"..argidx);
retVal = argidx;
break;
end
argidx = argidx + 2;
end
return retVal;
end
function WeaponQuickSwap_ItemNameMatches(bag, slot, name)
if name == "*" or WeaponQuickSwap_GetItemName(bag, slot) == name then
return true;
end
-- Support for Shadowstrike/Thunderstrike renaming weapons
return nil;
end
function WeaponQuickSwap_WeaponSwapCommon(arg)
-- I explicitly use arg as a parameter instead of ... to prevent
-- having to call unpack on the arg list from the caller
if WeaponSwap_IsSwapping then
if not (WeaponSwap_IsInSwapIteration or WeaponQuickSwap_AnyItemLocked()) then
Print("IsSwapping == true, IsInIteration == false, no items locked");
end
-- DebugStop
--Print("Bailing out, swap is in progress");
return;
end
WeaponSwap_IsSwapping = 1;
wswap = nil;
WeaponQuickSwap_PerformSlowerSwap = nil;
--if CursorHasItem() then ResetCursor() end
local mainhandslot, offhandslot = arg[1], arg[2];
local main, off;
--Print("Hands are:"..mainhandslot..","..offhandslot);
local matchingsetidx = WeaponQuickSwap_FindCurrentSetIndex(mainhandslot, offhandslot, 3, arg);
-- if we found a match, and there is at least one weapon speficied in the next
-- set, then use that set. Else use the first 2
if matchingsetidx and arg[matchingsetidx+2] then
main, off = arg[matchingsetidx+2], arg[matchingsetidx+3];
else
main, off = arg[3], arg[4];
end
--Print("Picking:"..main..","..off);
-- don't need to specify the offhand if this is just a single deal
if not off then off = ""; end
if not main then
return WeaponQuickSwap_OnSwapError("No weapons set found to switch. Not enough arguments?");
end
-- see what's already set up
local m_ok = WeaponQuickSwap_ItemNameMatches(-1, mainhandslot, main);
local o_ok = WeaponQuickSwap_ItemNameMatches(-1, offhandslot, off);
local m_sb, m_si = -1, mainhandslot;
if not m_ok then
if main == "" then
m_sb, m_si = -1, -1;
else
m_sb, m_si = WeaponQuickSwap_FindItem(main);
-- if FindItem returned the offhand weapon and it is already ok
-- don't remove it. Look harder
if o_ok and m_sb == -1 and m_si == offhandslot then
m_sb, m_si = WeaponQuickSwap_FindItem(main, 1);
end
end
if not (m_sb and m_si) then
return WeaponQuickSwap_OnSwapError("Can not find mainhand item: "..main);
end
end -- if not m_ok
local multiinst;
-- if you're using 2 of the same weapon FindItem needs to
-- know not to just return the first
if main == off then multiinst = 1; else multiinst = 0; end
local o_sb, o_si = -1, offhandslot;
if not o_ok then
if off == "" then
o_sb, o_si = -1, -1;
else
o_sb, o_si = WeaponQuickSwap_FindItem(off, multiinst);
-- note that here we don't have to "look harder" because
-- that would mean that both weapons are the same so multinst
-- would already be set to 1
end
if not (o_sb and o_si) then
return WeaponQuickSwap_OnSwapError("Can not find offhand item: "..off);
end
end -- if not o_ok
-- Moving both hands from bags, that's easy
if m_sb ~= -1 and o_sb ~= -1 then
-- Load main first because if it is a 2h and we try to load offhand
-- we get a "Cannot Equip that with a Two-handed weapon" error
PickupContainerItem(m_sb, m_si);
PickupInventoryItem(mainhandslot);
PickupContainerItem(o_sb, o_si);
PickupInventoryItem(offhandslot);
WeaponQuickSwap_LastOffSource = { bag = o_sb, slot = o_si };
return;
end
-- Simple hand swap
if m_sb == -1 and m_si == offhandslot and o_sb == -1 and o_si == mainhandslot then
PickupInventoryItem(mainhandslot);
PickupInventoryItem(offhandslot);
return;
end
-- Push the list. We want to:
-- Take the mainhand weapon out if it isn't going to offhand
-- Move from wherever to the offhand. If offhand is supposed to be empty, empty it.
-- Install the main hand weapon. No blank main hand is supported unless are going to be.
--
-- Do it backward, the swaplist is a stack
local skipcount = 0;
-- Install main hand
if not m_ok then
-- if nothing going to the main hand
if (m_sb == -1 and m_si == -1) then
-- and the main is not going to the off: put it in a bag
if not (o_sb == -1 and o_si == mainhandslot) then
local bb, bi = WeaponQuickSwap_FindLastEmptyBagSlot(skipcount);
if not (bb and bi) then
return WeaponQuickSwap_OnSwapError("Not enough empty bag slots to perform swap.");
end
skipcount = skipcount + 1;
wswap = swaplist_push(wswap, -1, mainhandslot, bb, bi);
-- when moving A,"" -> "",B where A is a 2h, the offhand doesn't lock properly
-- so work around it by swapping slowly (only one swap per lock notify)
WeaponQuickSwap_PerformSlowerSwap = true;
end
else
wswap = swaplist_push(wswap, m_sb, m_si, -1, mainhandslot);
end
end
-- Load offhand if not already there
if not o_ok then
if (o_sb == -1 and o_si == -1) then
if not (m_sb == -1 and m_si == offhandslot) then
local bb, bi;
if WeaponQuickSwap_LastOffSource then
bb, bi = WeaponQuickSwap_FindLastEmptyBagSlot(skipcount,
WeaponQuickSwap_LastOffSource.bag, WeaponQuickSwap_LastOffSource.slot);
else
bb, bi = WeaponQuickSwap_FindLastEmptyBagSlot(skipcount);
end
if not (bb and bi) then
return WeaponQuickSwap_OnSwapError("Not enough empty bag slots to perform swap.");
end
skipcount = skipcount + 1;
wswap = swaplist_push(wswap, -1, offhandslot, bb, bi);
end
else
-- if the main hand weapon is coming from the offhand slot
-- we need to fix up its source to be where the offhand is
-- GOING to be after the bag->off swap
if wswap and (m_sb == -1 and m_si == offhandslot) then
wswap.sb = o_sb;
wswap.si = o_si;
-- don't set o_sb, o_si they're tested later
end
wswap = swaplist_push(wswap, o_sb, o_si, -1, offhandslot);
end
end
-- Special Case: Moving off to main, and not main to off
-- This is because maybe the main hand weapon is main only
if (m_sb == -1 and m_si == offhandslot) and not (o_sb == -1 and o_si == mainhandslot) then
local bb, bi = WeaponQuickSwap_FindLastEmptyBagSlot(skipcount);
if not (bb and bi) then
return WeaponQuickSwap_OnSwapError("Not enough empty bag slots to perform swap.");
end
skipcount = skipcount + 1;
wswap = swaplist_push(wswap, -1, mainhandslot, bb, bi);
end
-- Same thing for off hand
if (o_sb == -1 and o_si == mainhandslot) and not (m_sb == -1 and m_si == offhandslot) then
local bb, bi = WeaponQuickSwap_FindLastEmptyBagSlot(skipcount);
if not (bb and bi) then
return WeaponQuickSwap_OnSwapError("Not enough empty bag slots to perform swap.");
end
skipcount = skipcount + 1;
wswap = swaplist_push(wswap, -1, 17, bb, bi);
end
if o_sb ~= -1 then
WeaponQuickSwap_LastOffSource = { bag = o_sb, slot = o_si };
end
-- DebugStop
--Print("Starting SwapIterations");
-- Start moving
return WeaponQuickSwap_ExecuteSwapIteration();
end