vanilla-wow-addons – Rev 1

Subversion Repositories:
Rev:
OUTFITDISPLAYFRAME_VERSION = "0.5.6";
OUTFITDISPLAYFRAME_NAME = "Outfit Display Frame";
OUTFITDISPLAYFRAME_TITLE = OUTFITDISPLAYFRAME_NAME.." v"..OUTFITDISPLAYFRAME_VERSION;
OUTFITDISPLAYFRAME_BANKDELAY = 0.5;

local PlayerSlotNames = {
   [1] = { name = "HeadSlot", tooltip = HEADSLOT };
   [2] = { name = "NeckSlot", tooltip = NECKSLOT },
   [3] = { name = "ShoulderSlot", tooltip = SHOULDERSLOT },
   [4] = { name = "BackSlot", tooltip = BACKSLOT },
   [5] = { name = "ChestSlot", tooltip = CHESTSLOT },
   [6] = { name = "ShirtSlot", tooltip = SHIRTSLOT },
   [7] = { name = "TabardSlot", tooltip = TABARDSLOT },
   [8] = { name = "WristSlot", tooltip = WRISTSLOT },
   [9] = { name = "HandsSlot", tooltip = HANDSSLOT },
   [10] = { name = "WaistSlot", tooltip = WAISTSLOT },
   [11] = { name = "LegsSlot", tooltip = LEGSSLOT },
   [12] = { name = "FeetSlot", tooltip = FEETSLOT },
   [13] = { name = "Finger0Slot", tooltip = FINGER0SLOT },
   [14] = { name = "Finger1Slot", tooltip = FINGER1SLOT },
   [15] = { name = "Trinket0Slot", tooltip = TRINKET0SLOT },
   [16] = { name = "Trinket1Slot", tooltip = TRINKET1SLOT },
   [17] = { name = "MainHandSlot", tooltip = MAINHANDSLOT },
   [18] = { name = "SecondaryHandSlot", tooltip = SECONDARYHANDSLOT },
   [19] = { name = "RangedSlot", tooltip = RANGEDSLOT },
}

local SavedPickupContainerItem = nil;
local SavedPickupInventoryItem = nil;

local LastOffSource = nil;
local PerformSlowerSwap = false;
local BankIsOpen = false;

local function tonil(val)
   if ( not val ) then
      return "nil";
   else
      return val;
   end
end

local function Print(msg, r, g, b)
   if ( DEFAULT_CHAT_FRAME ) then
      if ( not r ) then
    DEFAULT_CHAT_FRAME:AddMessage(msg);
      else
    DEFAULT_CHAT_FRAME:AddMessage(msg, r, g, b);
      end
   end
end

-- Based on code in QuickMountEquip
local function SafeHookFunction(func, newfunc)
   local oldValue = getglobal(func);
   if ( oldValue ~= getglobal(newfunc) ) then
      setglobal(func, getglobal(newfunc));
      return true;
   end
   return false;
end

-- this really ought to be in a library somewhere
local function SplitLink(link)
   local _,_, color, item, name = string.find(link, "|c(%x+)|Hitem:(%d+:%d+:%d+:%d+)|h%[(.-)%]|h|r");
   return color, item, name;
end

function OutfitDisplayFrame_OnLoad()
   local temp = PickupContainerItem;
   if ( SafeHookFunction("PickupContainerItem", "OutfitDisplayFrame_PickupContainerItem") ) then
      SavedPickupContainerItem = temp;
   end

         temp = PickupInventoryItem;
   if ( SafeHookFunction("PickupInventoryItem", "OutfitDisplayFrame_PickupInventoryItem") ) then
      SavedPickupInventoryItem = temp;
   end

   local parent = this:GetName();
   for i=1,19,1 do
      local slotName = PlayerSlotNames[i].name;
      local button = getglobal(parent..slotName);
      button.tooltip = PlayerSlotNames[i].tooltip;
      OutfitDisplayItemButton_Draw(button);
      PlayerSlotNames[i].id = GetInventorySlotInfo(slotName);
   end

   -- ideas borrowed from "WeaponQuickSwap - by CapnBry"
   this:RegisterEvent("ITEM_LOCK_CHANGED");
   this:RegisterEvent("BANKFRAME_OPENED");
   this:RegisterEvent("BANKFRAME_CLOSED");

   Print(OUTFITDISPLAYFRAME_TITLE.." loaded.");
end

local function DressUpItem(model, thing)
   if ( not model or not thing or thing == "" ) then
      return;
   end
   local item = gsub(thing, "(%d+).*", "%1", 1);
   model:TryOn(item);
end

local BankFrameClosedTime = 0;

function OutfitDisplayFrame_OnEvent(...)
   if ( arg[1] == "BANKFRAME_OPENED" or arg[1] == "BANKFRAME_CLOSED" ) then
      BankFrameIsOpen = ( arg[1] == "BANKFRAME_OPENED" );
      local frame = getglobal(this:GetName().."MessageUpdateFrame");
      if ( frame ) then
         BankFrameClosedTime = OUTFITDISPLAYFRAME_BANKDELAY;
         frame:Show();
      end
   elseif (arg[1] == "ITEM_LOCK_CHANGED") and not arg[2] then
      return OutfitDisplayFrame_ExecuteSwapIteration();
   end
end

function OutfitDisplayFrame_MessageUpdate(elapsed)
   BankFrameClosedTime = BankFrameClosedTime - elapsed;
   if ( BankFrameClosedTime <= 0 ) then
      this:Hide();
      OutfitDisplayFrame_UpdateMessage(this:GetParent());
      BankFrameClosedTime = 0;
   end
end

local function GetSlotButton(button, slotName)
   local parent = button:GetParent():GetName();
   return getglobal(parent..slotName);
end

local function IsItemOneHanded(item)
   if ( item ) then
      local _,_,_,_,_,_,_,bodyslot = GetItemInfo("item:"..item);
      if ( bodyslot ==  "INVTYPE_2HWEAPON" or bodyslot == INVTYPE_2HWEAPON ) then
         return false;
      end
   end
   return true;
end

function OutfitDisplayFrame_CursorCanGoInSlot(button)
   if ( button.forced ) then
      return false;
   end
   local secondary = GetSlotButton(button, "SecondaryHandSlot");
   local mainbutton = GetSlotButton(button, "MainHandSlot");
   if ( button == secondary and mainbutton and mainbutton.item ) then
      return IsItemOneHanded(mainbutton.item);
   end
   return CursorCanGoInSlot(button:GetID())
end

-- much hackery to know what actually is in the cursor when somebody drops it on us
local OD_Track_Bag = nil;
local OD_Track_Slot = nil;

-- whatever we think the user picked up, apply it to our stuff
local function AcceptCursorItem(button)
   local parent = button:GetParent();
   local pname = parent:GetName();
   if ( not OutfitDisplayFrame_CursorCanGoInSlot(button) ) then
      button = nil;
      for i=1,19,1 do
         local temp = getglobal(pname..PlayerSlotNames[i].name);
         if ( temp and OutfitDisplayFrame_CursorCanGoInSlot(temp) ) then
            button = temp;
         end
      end
   end

   if ( button ) then
      local link, texture;
      if ( OD_Track_Bag ) then
         link = GetContainerItemLink(OD_Track_Bag, OD_Track_Slot);
         texture = GetContainerItemInfo(OD_Track_Bag, OD_Track_Slot);
      else
         link = GetInventoryItemLink("player", OD_Track_Slot);
         texture = GetInventoryItemTexture("player", OD_Track_Slot);
      end
      button.color, button.item, button.name = SplitLink(link);
      button.texture = texture;
      button.used = true;
      button.empty = nil;
      button.missing = false;
      if ( OD_Track_Bag and (OD_Track_Bag == BANK_CONTAINER or
                             OD_Track_Bag > NUM_BAG_SLOTS) ) then
         button.banked = true;
      end
      OutfitDisplayItemButton_Change(button);
   end

   -- clear the cursor item
   if ( OD_Track_Bag ) then
      SavedPickupContainerItem(OD_Track_Bag, OD_Track_Slot);
   elseif ( OD_Track_Slot ) then
      SavedPickupInventoryItem(OD_Track_Slot);
   end
   OD_Track_Bag = nil;
   OD_Track_Slot = nil;
end

-- figure out what the user picked up, so that if they drop it on us we can deal with it
function OutfitDisplayFrame_TrackItemPickup(bag, slot)
   OD_Track_Bag = bag;
   OD_Track_Slot = slot;
end

function OutfitDisplayFrame_PickupContainerItem(bag, slot)
   if ( SavedPickupContainerItem ) then
      SavedPickupContainerItem(bag, slot);
   end
   OutfitDisplayFrame_TrackItemPickup(bag, slot);
end

function OutfitDisplayFrame_PickupInventoryItem(slot)
   if ( SavedPickupInventoryItem ) then
      SavedPickupInventoryItem(slot);
   end
   OutfitDisplayFrame_TrackItemPickup(nil, slot);
end

-- handle making the items in the outfit pane do nice things
function OutfitDisplayItemButton_OnEnter()
   if ( GameTooltip.finished ) then
      return;
   end
   GameTooltip.finished = 1;
   GameTooltip:SetOwner(this, "ANCHOR_RIGHT");
   if (this.item) then
      local link = "item:"..this.item;
      if ( GetItemInfo(link) ) then
         GameTooltip:SetHyperlink(link);
      elseif ( this.name ) then
         GameTooltip:SetText(this.name);
      elseif ( this.tooltip ) then
         GameTooltip:SetText(this.tooltip);
      else
         GameTooltip:SetText("<unlinkable item>", 1, 0, 0);
      end
   elseif ( this.tooltip ) then
      GameTooltip:AddLine(this.tooltip);
   else
      local parentlen = string.len(this:GetParent():GetName())+1;
      local slotName = strsub(this:GetName(), parentlen);
      slotName = strsub(slotName, 1, string.len(slotName) - 4);
      this.tooltip = slotName;
      GameTooltip:AddLine(this.tooltip);
   end
   GameTooltip:AddLine(OUTFITDISPLAYFRAME_ALTCLICK,.8,.8,.8,1);
   GameTooltip:Show();
end

function OutfitDisplayItemButton_OnEvent(event)
   if ( event == "CURSOR_UPDATE" ) then
      if ( not this.forced ) then
         if ((this.CursorCanGoInSlot and this.CursorCanGoInSlot(this)) or
             OutfitDisplayFrame_CursorCanGoInSlot(this)) then
            this:LockHighlight();
         else
            this:UnlockHighlight();
         end
      end
   end
end

function OutfitDisplayItemButton_OnClick(clicked, ignoreModifiers)
   if ( clicked == "LeftButton" ) then
      if( not ignoreModifiers or ignoreModifiers == 0 ) then
         if ( IsShiftKeyDown() ) then
            if ( this.item and ChatFrameEditBox:IsVisible() ) then
               local color = this.color;
               if ( not color ) then
                  color = "ffffffff";
               end
               local link = "|c"..color.."|Hitem"..this.item.."|h["..this.name.."]|h|r";
               ChatFrameEditBox:Insert(link);
               return;
            end
         elseif ( IsAltKeyDown() ) then
            OutfitDisplayItemButton_Clear(this, false, false);
            OutfitDisplayItemButton_Change(this);
            return;
         end
      end
      -- fall through for drags and non-modified clicks
      if ( CursorHasItem() ) then
         AcceptCursorItem(this);
      end
   end
end

function OutfitDisplayItemButton_OnLoad()
   local parentlen = string.len(this:GetParent():GetName())+1;
   local slotName = strsub(this:GetName(), parentlen);
   local id;
   local textureName;
   id, textureName = GetInventorySlotInfo(slotName);
   this:SetID(id);
   this.backgroundTextureName = textureName;
   SetItemButtonTexture(this, this.backgroundTextureName);
   this:RegisterForDrag("LeftButton");
   this:RegisterForClicks("LeftButtonUp", "RightButtonUp");
   this:RegisterEvent("CURSOR_UPDATE");
end

function OutfitDisplayItemButton_Draw(button)
   if ( button.texture ) then
      SetItemButtonTexture(button, button.texture);
   else
      SetItemButtonTexture(button, button.backgroundTextureName);
   end
   if ( button.missing ) then
      SetItemButtonTextureVertexColor(button, 1.0, 0.1, 0.1);
   elseif ( button.banked ) then
      SetItemButtonTextureVertexColor(button, 0.1, 0.1, 1.0);
   else
      SetItemButtonTextureVertexColor(button, 1.0, 1.0, 1.0);
   end
   local checkbox = getglobal(button:GetName().."CheckBox");
   if (checkbox) then
      local pname = button:GetParent():GetName();
      local showhelm = getglobal(pname.."ShowHelm");
      local showcloak = getglobal(pname.."ShowCloak");
      if ( button.forced ) then
         checkbox:SetChecked(true);
         checkbox:Disable();
         checkbox:Show();
      elseif ( button.empty or not button.used ) then
         if ( checkbox.slotName == "HeadSlot" ) then
            showhelm:Hide();
            showhelm:SetChecked(false);
         elseif ( checkbox.slotName == "BackSlot" ) then
            showcloak:Hide();
            showcloak:SetChecked(false);
         end
         checkbox:Enable();
         checkbox:SetChecked(button.empty);
         checkbox:Show();
      elseif ( button.used ) then
         checkbox:Hide();
         if ( checkbox.slotName == "HeadSlot" ) then
            showhelm:Show();
         elseif ( checkbox.slotName == "BackSlot" ) then
            showcloak:Show();
         end
      end
   end
end

function OutfitDisplayItemButton_Change(button)
   OutfitDisplayItemButton_Draw(button);
   -- handle two handed weapons
   local parent = button:GetParent();
   local pname = parent:GetName();
   local parentlen = string.len(pname)+1;
   local slotName = strsub(button:GetName(), parentlen);
   if ( slotName == "MainHandSlot" ) then
      local secondary = getglobal(pname.."SecondaryHandSlot");
      if ( not button.used and secondary.forced ) then
         OutfitDisplayItemButton_Clear(secondary, false, false);
      elseif ( button.used and not IsItemOneHanded(button.item) ) then
         OutfitDisplayItemButton_Clear(secondary, true);
         secondary.forced = true;
      end
      OutfitDisplayItemButton_Draw(secondary);
   end
   OutfitDisplayFrame_UpdateMessage(parent);
   OutfitDisplayFrame_UpdateModel(parent, button);
   if( parent.OutfitChanged ) then
      parent.OutfitChanged( button );
   end
end

function OutfitDisplayItemButton_Clear(button, empty, used)
   button.name = nil;
   button.item = nil;
   button.texture = nil;
   button.color = nil;
   button.missing = nil;
   button.banked = nil;
   button.forced = nil;
   button.empty = empty;
   button.used = used or empty;
end

-- override ShowHelm and ShowCloak
function OutfitDisplayOverrideBox_OnLoad()
   local parentlen = string.len(this:GetParent():GetName())+1;
   this.slotName = strsub(this:GetName(), parentlen);
end

function OutfitDisplayOverrideBox_OnEnter()
   GameTooltip:SetOwner(this, "ANCHOR_RIGHT");
   local text;
   if ( this.slotName == "ShowHelm" ) then
      text = OUTFITDISPLAYFRAME_OVERRIDEHELM;
   else
      text = OUTFITDISPLAYFRAME_OVERRIDECLOAK;
   end
   GameTooltip:SetText(text, nil, nil, nil, nil, 1);
   GameTooltip:Show();
end

-- check box handling
function OutfitDisplayCheckBox_OnEnter()
   GameTooltip:SetOwner(this, "ANCHOR_RIGHT");
   GameTooltip:SetText(OUTFITDISPLAYFRAME_USECHECKBOX, nil, nil, nil, nil, 1);
   GameTooltip:Show();
end

function OutfitDisplayCheckBox_OnLoad()
   local parentlen = string.len(this:GetParent():GetName())+1;
   local name = strsub(this:GetName(), parentlen);
   this.slotName = strsub(name, 1, -9);
end

function OutfitDisplayCheckBox_OnClick()
   local pname = this:GetParent():GetName();
   local button = getglobal(pname..this.slotName);
   OutfitDisplayItemButton_Clear(button, this:GetChecked());
   OutfitDisplayItemButton_Change(button);
end

local function UpdateModel_ThisSlot(pname, model, idx)
   local slotName = PlayerSlotNames[idx].name;
   local what = getglobal(pname..slotName);
   local item = nil;
   if ( what and what.used ) then
      if ( not what.empty ) then
         item = what.item;
      end
   else
      local slot = PlayerSlotNames[idx].id;
      local link = GetInventoryItemLink("player", slot);
      if ( link ) then
         _, item, _ = SplitLink(link);
      end
   end
   if ( item ) then
      DressUpItem(model, item);
   end
end

function OutfitDisplayFrame_UpdateModel(frame, button)
   local pname = frame:GetName();
   local model = getglobal(pname.."Model");
   local empty = false;
   if ( not model ) then
      return;
   end
   if ( button and button.used ) then
      if ( button.empty and not button.forced ) then
         empty = true;
      end
   else
      for i=1,19,1 do
         local what = getglobal(pname..PlayerSlotNames[i].name);
         if ( what and what.empty ) then
            empty = true;
         end
      end
   end
   if ( empty ) then
      model:Undress();
   end
   for i=19,1,-1 do
      UpdateModel_ThisSlot(pname, model, i);
   end
end

function OutfitDisplayFrame_SetOutfit(frame, outfit)
   if not outfit then
      return;
   end
   local pname = frame:GetName();
   for i=1,19,1 do
      local slotName = PlayerSlotNames[i].name;
      local button = getglobal(pname..slotName);
      if ( button and outfit[slotName] and outfit[slotName].used ) then
         OutfitDisplayItemButton_Clear(button);
         if ( outfit[slotName].used ) then
            button.name = outfit[slotName].name;
            button.item = outfit[slotName].item;
            button.texture = outfit[slotName].texture;
            button.color = outfit[slotName].color;
            button.missing = outfit[slotName].missing;
            button.banked = outfit[slotName].banked;
            button.forced = outfit[slotName].forced;
            button.empty = outfit[slotName].empty;
            button.used = true;
         else
            OutfitDisplayItemButton_Clear(button, true, true);
         end
         OutfitDisplayItemButton_Draw(button);
      end
   end
   if ( outfit["Options"] ) then
      local showhelm = getglobal(pname.."ShowHelm");
      local showcloak = getglobal(pname.."ShowCloak");
      showhelm:SetChecked(outfit["Options"].helm);
      showcloak:SetChecked(outfit["Options"].cloak);
   end
   OutfitDisplayFrame_UpdateModel(frame);
   OutfitDisplayFrame_UpdateMessage(frame, outfit);
end

function OutfitDisplayFrame_GetOutfit(frame, puthere)
   local pname = frame:GetName();
   local outfit = puthere or nil;
   local showhelm = getglobal(pname.."ShowHelm");
   local showcloak = getglobal(pname.."ShowCloak");
   local setOptions = false;
   if not outfit then
      outfit = {};
   end
   outfit["Options"] = {};
   for i=1,19,1 do
      local slotName = PlayerSlotNames[i].name;
      local button = getglobal(pname..slotName);

      if ( button and button.used ) then
         outfit[slotName] = {};
         if ( not button.empty ) then
            outfit[slotName].name = button.name;
            outfit[slotName].item = button.item;
            outfit[slotName].texture = button.texture;
            outfit[slotName].color = button.color;
            outfit[slotName].missing = button.missing;
            outfit[slotName].banked = button.banked;
            outfit[slotName].forced = button.forced;
         end
         outfit[slotName].empty = button.empty;
         outfit[slotName].used = true;
         if ( not button.empty ) then
            if ( slotName == "HeadSlot" ) then
               if ( showhelm and showhelm:GetChecked() ) then
                  outfit["Options"].helm = true;
                  setOptions = true;
               end
            elseif ( slotName == "BackSlot" ) then
               if ( showcloak and showcloak:GetChecked() ) then
                  outfit["Options"].cloak = true;
                  setOptions = true;
               end
            end
         end
      elseif ( outfit and outfit.used ) then
         outfit[slotName] = nil;
      end
   end
   if ( not setOptions ) then
      outfit["Options"] = nil;
   end
   if ( outfit ) then
      -- check for two-handed weapon
      local mainhand = outfit["MainHandSlot"];
      if ( mainhand and not IsItemOneHanded(mainhand.item) ) then
         outfit["SecondaryHandSlot"] = {};
         outfit["SecondaryHandSlot"].forced = true;
         outfit["SecondaryHandSlot"].empty = true;
         outfit["SecondaryHandSlot"].used = true;
      end
   end
   return outfit;
end

function OutfitDisplayFrame_GetItemInfo(bag, slot)
   local link;
   local c, i, n;
   if ( bag ) then
      link = GetContainerItemLink (bag,slot);
   else
      link = GetInventoryItemLink("player", slot);
   end
   if (link) then
      c, i, n = SplitLink(link);
   end
   return c, i, n;
end

-- get a snapshot of the current state of the player
function OutfitDisplayFrame_GetBagInfo()
   local contents = {};
   local empties = {};

   local numSlots = 0;
   -- check each of the bags on the player
   for bag=0, NUM_BAG_FRAMES do
      -- get the number of slots in the bag (0 if no bag)
      numSlots = GetContainerNumSlots(bag);
      if (numSlots > 0) then
         -- check each slot in the bag
         for slot=1, numSlots do
            local c, i, n = OutfitDisplayFrame_GetItemInfo(bag, slot);
            if ( i ) then
               contents[i] = {};
               contents[i].bag = bag;
               contents[i].slot = slot;
               contents[i].color = c;
               contents[i].name = n;
            else
               local what = {};
               what.bag = bag;
               what.slot = slot;
               tinsert(empties, what);
            end
         end
      end
   end

   for idx=1,19,1 do
      local slot = PlayerSlotNames[idx].id;
      local c, i, n = OutfitDisplayFrame_GetItemInfo(nil, slot);
      if ( i ) then
         contents[i] = {};
         contents[i].slot = slot;
         contents[i].color = c;
         contents[i].name = n;
      end
   end

   return contents, empties;
end

-- look in a particular bag
local function CheckThisBag(bag, id, skipcount)
   -- get the number of slots in the bag (0 if no bag)
   local numSlots = GetContainerNumSlots(bag);
   if (numSlots > 0) then
      -- check each slot in the bag
      for slot=1, numSlots do
         local c, i, n = OutfitDisplayFrame_GetItemInfo(bag, slot);
         if ( i and id == i ) then
            if ( skipcount == 0 ) then
               return slot, skipcount;
            end
            skipcount = skipcount - 1;
         end
      end
   end
   return nil, skipcount;
end

-- look for the outfit anywhere we can find it
local function FindThisItem(id, skipcount)
   if ( not id ) then
      return nil,nil,nil;
   end
   local skipcount = skipcount or 0;
   -- check each of the bags on the player
   for bag=0, NUM_BAG_FRAMES do
      local slot;
      slot, skipcount = CheckThisBag(bag, id, skipcount);
      if ( slot ) then
         return bag, slot, false;
      end
   end

   for idx=1,19,1 do
      local slot = PlayerSlotNames[idx].id;
      local c, i, n = OutfitDisplayFrame_GetItemInfo(nil, slot);
      if ( (id and i and id == i) or (name and n and name == n) ) then
         if ( skipcount == 0 ) then
            return nil, slot, false;
         end
         skipcount = skipcount - 1;
      end
   end

   -- look in the bank
   -- Geez, this is ugly
   local bankbags = { BANK_CONTAINER, 5, 6, 7, 8, 9, 10 };
   for b=1,7 do
      local slot;
      slot, skipcount = CheckThisBag(bankbags[b], id, skipcount);
      if ( slot ) then
         return bankbags[b], slot, true;
      end
   end
   -- return nil, nil, nil;
end

-- look for the outfit anywhere we can find it
-- we should look in the bank someday
function OutfitDisplayFrame_FindOutfit(outfit)
   -- look for every item, if we find everything return a table of locations
   -- otherwise don't return anything
   if ( not outfit ) then
      return nil;
   end
   local spots = { };
   for slotName in outfit do
      if ( outfit[slotName].use and not outfit[slotName].empty ) then
         local bag, slot = FindThisItem( outfit[slotName].item );
         if ( not bag and not slot ) then
            spots = nil;
            return nil;
         else
            spots[slotName] = { };
            spots[slotName].bag = bag;
            spots[slotName].slot = slot;
         end
      end
   end
   return spots;
end

function OutfitDisplayFrame_CanFindOutfit(outfit)
   if ( not outfit ) then
      return false;
   end
   for slotName in outfit do
      if ( outfit[slotName].used and not outfit[slotName].empty ) then
         local bag, slot = FindThisItem( outfit[slotName].item );
         if ( not bag and not slot ) then
            return false;
         end
      end
   end
   return true;
end

local function CheckSwitchWillFail(frame, outfit)
   local contents, empties = OutfitDisplayFrame_GetBagInfo();
   local numfree = table.getn(empties);
   local needfree = 0;
   local missing = false;
   local banked = false;
   for i=1,19,1 do
      local slotName = PlayerSlotNames[i].name;
      local check;
      if ( outfit ) then
         check = outfit[slotName];
      else
         check = getglobal(frame:GetName()..slotName);
      end
      if ( check and check.used ) then
         if ( not check.empty ) then
            local bag, slot, inbank = FindThisItem( check.item );
            if ( not bag and not slot ) then
               -- pretend it's still in the bank and we just can't get it
               if ( check.banked ) then
                  check.banked = true;
                  banked = true;
               else
                  missing = true;
               end
               check.missing = not check.banked;
            else
               check.missing = nil;
               check.banked = inbank;
            end
         else
            local slot = PlayerSlotNames[i].id;
            if ( OutfitDisplayFrame_GetItemInfo(nil, slot) ) then
               needfree = needfree + 1;
            end
         end
      end
   end
   if ( missing ) then
      return OUTFITDISPLAYFRAME_ITEMSNOTFOUND;
   end
   if ( banked and not BankIsOpen ) then
      return OUTFITDISPLAYFRAME_ITEMSINBANK;
   end
   if ( needfree > numfree ) then
      return OUTFITDISPLAYFRAME_NOTENOUGHFREESPACE;
   end
   return nil;
end

function OutfitDisplayFrame_SwitchWillFail(frame, outfit)
   if ( not outfit ) then
      return OUTFITDISPLAYFRAME_INVALIDOUTFIT;
   end
   -- if we're swapping, fail early
   if ( OutfitDisplayFrame_IsSwapping() ) then
      return OUTFITDISPLAYFRAME_TOOFASTMSG;
   end
   return CheckSwitchWillFail(frame, outfit);
end

function OutfitDisplayFrame_IsWearing(outfit)
   if ( not outfit ) then
      return false;
   end
   for slotName in outfit do
      if ( outfit[slotName].used and not outfit[slotName].empty ) then
         local slot = GetInventorySlotInfo(slotName);
         local link = GetInventoryItemLink("player", slot);
         local foundit = false;
         if ( link ) then
            local color, item, name = SplitLink(link);
            if ( item == outfit[slotName].item ) then
               foundit = true;
            end
         end
         if ( not foundit ) then
            return false;
         end
      end
   end
   return true;
end

function OutfitDisplayFrame_UpdateMessage(frame, outfit)
   if ( frame and outfit ) then
      local msg = CheckSwitchWillFail(frame, outfit);
      local messages = getglobal(frame:GetName().."Message");
      if ( msg ) then
         messages:Show();
         messages:SetText(msg);
         messages:SetTextColor(1, 1, 1);
      else
         messages:SetText("");
         messages:Hide();
      end
   end
end

function OutfitDisplayFrame_GetSlotContents(slotName, returnEmpty)
   if ( slotName ~= "Options" ) then
      local slot = GetInventorySlotInfo(slotName);
      local link = GetInventoryItemLink("player", slot);
      if ( link ) then
         local contents = {};
         local color, item, name = SplitLink(link);
         contents.item = item;
         contents.name = name;
         contents.color = color;
         contents.texture = texture;
         contents.used = true;
         local bag, slot = FindThisItem( item );
         contents.bag = bag;
         contents.slot = slot;
         return contents;
      end
   end
   -- return nil;
end

function OutfitDisplayFrame_GetWearing(check)
   local wearing = {};
   if ( check ) then
      for slotName in check do
         wearing[slotName] = OutfitDisplayFrame_GetSlotContents(slotName);
      end
      -- make sure we pick up the off hand if we're going to put something
      -- two-handed in the main hand slot
      local mainhand = check["MainHandSlot"];
      local offhand = wearing["SecondaryHandSlot"];
      if ( not offhand and mainhand and not IsItemOneHanded(mainhand.item) ) then
         wearing["SecondaryHandSlot"] = OutfitDisplayFrame_GetSlotContents("SecondaryHandSlot");
      end
   else
      for i=1,19,1 do
         local slotName = PlayerSlotNames[i].name;
         wearing[slotName] = OutfitDisplayFrame_GetSlotContents(slotName);
      end
   end
   if ( not ShowingCloak() or not ShowingHelm() ) then
      wearing["Options"] = {}
      wearing["Options"].cloak = not(not ShowingCloak());
      wearing["Options"].helm = not(not ShowingHelm());
   end
   return wearing;
end

function OutfitDisplayFrame_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, check 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 ) then
            local texture = GetInventoryItemTexture("player",
                                                    ContainerIDToInventoryID(i));
            
            if ( string.find(texture, "INV_Misc_Bag_%d") and not string.find(bagName, AMMOSLOT) ) 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 there is a bag
      end -- if not affinity bag
   end  -- for bags

   -- not found return nil,nil implicitly
end

-- okay, let's switch to the specified outfit
-- code liberally borrowed from "WeaponQuickSwap - by CapnBry"

-- list functions
local function swapentry_print(entry)
   if ( DEFAULT_CHAT_FRAME ) then
      local msg = "Entry "..tonil(entry.sb)..","..tonil(entry.si)..","..tonil(entry.db)..","..tonil(entry.di);
      DEFAULT_CHAT_FRAME:AddMessage(msg, 1.0,0,0);
   end      
end

local function swaplist_print(list)
   while ( list ) do
      swapentry_print(list);
      list = list.next;
   end
end

local function swaplist_push(list, sb, si, db, di, helm, cloak)
   list = { next = list, sb = sb, si = si, db = db, di = di,
      helm = helm, cloak = cloak };
   return list;
end

local function swaplist_popfirst(list)
   if not list then return; end
   list = list.next;
   return list;
end

-- Unit variable to hold the stack of swaps
local outfitswap = nil;

function OutfitDisplayFrame_IsSwapping()
   if ( outfitswap or OutfitDisplayFrame_AnyItemLocked() ) then
      return true;
   else
      return false;
   end
end

-- First we do everything except the main hand and secondary hand
-- Then we do the hard stuff

function OutfitDisplayFrame_ItemIsLocked(bag, slot)
   if not bag and not slot then
      return false;
   end

   if not bag then
      return IsInventoryItemLocked(slot);
   else
      local _,_,locked = GetContainerItemInfo(bag,slot);
      return locked;
   end
end

function OutfitDisplayFrame_AnyItemLocked()
-- Checks all the bags and the equipped slots to see if any are still locked
   for i=0,NUM_BAG_FRAMES do
      for j=1,GetContainerNumSlots(i) do
         local _,_,locked = GetContainerItemInfo(i,j);
         if ( locked ) then
            return true;
         end
      end
   end
   for i=1,19,1 do
      if ( IsInventoryItemLocked(PlayerSlotNames[i].id) ) then
         return true;
      end
   end
   return false;
end

function OutfitDisplayFrame_ExecuteSwapIteration()
   if not outfitswap then
      PerformSlowerSwap = false;
      return;
   end

   if OutfitDisplayFrame_ItemIsLocked(outfitswap.sb, outfitswap.si) or
      OutfitDisplayFrame_ItemIsLocked(outfitswap.db, outfitswap.di) then
      return;
   end

   if not outfitswap.sb then
      SavedPickupInventoryItem(outfitswap.si);
   else
      SavedPickupContainerItem(outfitswap.sb, outfitswap.si);
   end

   if not outfitswap.db then
      if not outfitswap.di then
         PutItemInBackpack();
      else
         SavedPickupInventoryItem(outfitswap.di);
      end
   else
      SavedPickupContainerItem(outfitswap.db, outfitswap.di);
   end

   if ( outfitswap.cloak ~= nil ) then
      ShowCloak(outfitswap.cloak);
   elseif ( outfitswap.helm ~= nil ) then
      ShowHelm(outfitswap.helm);
   end

   outfitswap = swaplist_popfirst(outfitswap);
   if ( outfitswap and not PerformSlowerSwap ) then
      return OutfitDisplayFrame_ExecuteSwapIteration();
   end
end

function OutfitDisplayFrame_SwitchOne(outfit, index, skipcount, showhelm, showcloak)
   local slotName = PlayerSlotNames[index].name;
   if ( outfit and outfit[slotName] ) then
      local invslot = GetInventorySlotInfo(slotName);
      if ( outfit[slotName].empty ) then
         local bag, slot = OutfitDisplayFrame_FindLastEmptyBagSlot(skipcount);
         outfitswap = swaplist_push(outfitswap, nil, invslot, bag, slot);
         skipcount = skipcount + 1;
      else
         local helmflag, cloakflag;
         if ( slotName == "HeadSlot" ) then
            helmflag = showhelm;
         elseif ( slotName == "BackSlot" ) then
            cloakflag = showcloak;
         end
         local bag, slot = FindThisItem( outfit[slotName].item );
         -- either we couldn't find it, or it's where it should be
         if ( bag ~= nil or slot ~= invslot ) then
            outfitswap = swaplist_push(outfitswap, bag, slot, nil, invslot, helmflag, cloakflag);
         end
      end
   end
   return skipcount;
end

local function OnSwapError(error)
   outfitswap = nil;
   if ( UIErrorsFrame ) then
      UIErrorsFrame:AddMessage(error, 1.0, 0.1, 0.1, 1.0, UIERRORS_HOLD_TIME);
   end
   return;
end

function OutfitDisplayFrame_SwitchOutfit(outfit)
   if ( CursorHasItem() or OutfitDisplayFrame_IsSwapping() ) then
      return OnSwapError(OUTFITDISPLAYFRAME_TOOFASTMSG); 
   end
   if ( not outfit ) then
      return OnSwapError(OUTFITDISPLAYFRAME_INVALIDOUTFIT);
   end

   -- might not need to swap slowly
   PerformSlowerSwap = false;

   -- need to check to see that we have enough room
   local old = OutfitDisplayFrame_GetWearing(outfit);

   local showcloak;
   local showhelm;
   if ( outfit["Options"] ) then
      showhelm = outfit["Options"].helm;
      showcloak = outfit["Options"].cloak;
   end

   -- do everything except weapon slots
   local skipcount = 0;
   for i=1,16,1 do
      skipcount = OutfitDisplayFrame_SwitchOne(outfit, i, skipcount, showhelm, showcloak);
   end
   OutfitDisplayFrame_SwitchOne(outfit, 19, skipcount);

   if ( not outfit["MainHandSlot"] and not outfit["SecondaryHandSlot"] ) then
      OutfitDisplayFrame_ExecuteSwapIteration(); 
      return old;
   end

   local mainhandslot = GetInventorySlotInfo("MainHandSlot");
   local secondaryslot = GetInventorySlotInfo("SecondaryHandSlot");
   local mainhand = outfit["MainHandSlot"];
   local offhand = outfit["SecondaryHandSlot"];
   -- now do hands
   local m_sb;
   local m_si;
   local o_sb;
   local o_si;
   local m_ok = not mainhand or not mainhand.item;
   local o_ok = not offhand or not offhand.item;

   if ( mainhand and mainhand.item ) then
      m_sb, m_si = FindThisItem(mainhand.item);
      m_ok = ( not m_sb and m_si == mainhandslot );
   end
   if ( offhand and offhand.item ) then
      local multiples = 0;
      if ( mainhand and mainhand.item == offhand.item ) then
         multiples = 1;
      end
      o_sb, o_si = FindThisItem( offhand.item, multiples);
      o_ok = ( not o_sb and o_si == secondaryslot );
   end

   if ( not m_ok ) then
      -- do we need two of these?
      if ( o_ok and not m_sb and m_si == secondaryslot ) then
         m_sb, m_si = FindThisItem( mainhand.item, 1);
      end
   end

   -- moving from bags
   if ( m_sb and o_sb ) then
      -- insert them backwards, since they get popped off
      -- main hand has to get done first in case it's currently
      -- a two hander
      outfitswap = swaplist_push(outfitswap, o_sb, o_si, nil, secondaryslot);
      outfitswap = swaplist_push(outfitswap, m_sb, m_si, nil, mainhandslot);
   elseif ( not m_sb and m_si == secondaryslot and not o_sb and o_si == mainhandslot ) then
      outfitswap = swaplist_push(outfitswap, nil, mainhandslot, nil, secondaryslot);
   else
      -- Install main hand
      if not m_ok then
         -- if nothing going to the main hand
         if ( not m_sb and not m_si ) then
            -- and the main is not going to the off: put it in a bag
            if not ( not o_sb and o_si == mainhandslot) then
               local bb, bi = OutfitDisplayFrame_FindLastEmptyBagSlot(skipcount);
               if not (bb and bi) then 
                  return OnSwapError(OUTFITDISPLAYFRAME_NOTENOUGHFREESPACE); 
               end
               skipcount = skipcount + 1;
               outfitswap = swaplist_push(outfitswap, nil, 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)
               PerformSlowerSwap = not IsItemOneHanded(mainhand.item);
            end
         else
            outfitswap = swaplist_push(outfitswap, m_sb, m_si, nil, mainhandslot);
         end
      end
  
      -- Load offhand if not already there
      if not o_ok then
         if ( not o_sb and not o_si ) then
            if not (not m_sb and m_si == secondaryslot) then
               local bb, bi;
               if LastOffSource then
                  bb, bi = OutfitDisplayFrame_FindLastEmptyBagSlot(skipcount, 
                                                                   LastOffSource.bag, LastOffSource.slot);
               else
                  bb, bi = OutfitDisplayFrame_FindLastEmptyBagSlot(skipcount);
               end
               
               if not (bb and bi) then 
                  return OnSwapError(OUTFITDISPLAYFRAME_NOTENOUGHFREESPACE); 
               end
               skipcount = skipcount + 1;
               outfitswap = swaplist_push(outfitswap, nil, secondaryslot, 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 outfitswap and ( not m_sb and m_si == secondaryslot) then
               outfitswap.sb = o_sb;
               outfitswap.si = o_si;
               -- don't set o_sb, o_si they're tested later
            end
            
            outfitswap = swaplist_push(outfitswap, o_sb, o_si, nil, secondaryslot);
         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 (not m_sb and m_si == secondaryslot) and not ( not o_sb and o_si == mainhandslot) then
         local bb, bi = OutfitDisplayFrame_FindLastEmptyBagSlot(skipcount);
         if not (bb and bi) then 
            return OnSwapError(OUTFITDISPLAYFRAME_NOTENOUGHFREESPACE); 
         end
         skipcount = skipcount + 1;
         outfitswap = swaplist_push(outfitswap, nil, mainhandslot, bb, bi);
      end

      -- Same thing for off hand
      if (not o_sb and o_si == mainhandslot) and not (not m_sb and m_si == secondaryslot) then
         local bb, bi = OutfitDisplayFrame_FindLastEmptyBagSlot(skipcount);
         if not (bb and bi) then 
            return OnSwapError(OUTFITDISPLAYFRAME_NOTENOUGHFREESPACE); 
         end
         skipcount = skipcount + 1;
         outfitswap = swaplist_push(outfitswap, nil, mainhandslot, bb, bi);
      end

      if o_sb then 
         LastOffSource = { bag = o_sb, slot = o_si }; 
      end
   end   
   -- Start moving
   OutfitDisplayFrame_ExecuteSwapIteration(); 
   return old;
end