vanilla-wow-addons – Rev 1

Subversion Repositories:
Rev:
--[[
  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