vanilla-wow-addons – Rev 1
?pathlinks?
gOutfitter_Settings = nil;
local Outfitter_cInitializationEvent = "PLAYER_ENTERING_WORLD";
local BANKED_FONT_COLOR = {r = 0.25, g = 0.2, b = 1.0};
local BANKED_FONT_COLOR_CODE = "|cff4033ff";
local OUTFIT_MESSAGE_COLOR = {r = 0.2, g = 0.75, b = 0.3};
local Outfitter_cSlotNames =
{
-- First priority goes to armor
"HeadSlot",
"ShoulderSlot",
"ChestSlot",
"WristSlot",
"HandsSlot",
"WaistSlot",
"LegsSlot",
"FeetSlot",
-- Second priority goes to weapons
"MainHandSlot",
"SecondaryHandSlot",
"RangedSlot",
"AmmoSlot",
-- Last priority goes to items with no durability
"BackSlot",
"NeckSlot",
"ShirtSlot",
"TabardSlot",
"Finger0Slot",
"Finger1Slot",
"Trinket0Slot",
"Trinket1Slot",
};
local Outfitter_cSlotDisplayNames =
{
HeadSlot = HEADSLOT,
NeckSlot = NECKSLOT,
ShoulderSlot = SHOULDERSLOT,
BackSlot = BACKSLOT,
ChestSlot = CHESTSLOT,
ShirtSlot = SHIRTSLOT,
TabardSlot = TABARDSLOT,
WristSlot = WRISTSLOT,
HandsSlot = HANDSSLOT,
WaistSlot = WAISTSLOT,
LegsSlot = LEGSSLOT,
FeetSlot = FEETSLOT,
Finger0Slot = Outfitter_cFinger0SlotName,
Finger1Slot = Outfitter_cFinger1SlotName,
Trinket0Slot = Outfitter_cTrinket0SlotName,
Trinket1Slot = Outfitter_cTrinket1SlotName,
MainHandSlot = MAINHANDSLOT,
SecondaryHandSlot = SECONDARYHANDSLOT,
RangedSlot = RANGEDSLOT,
AmmoSlot = AMMOSLOT,
};
local Outfitter_cInvTypeToSlotName =
{
INVTYPE_2HWEAPON = {SlotName = "MainHandSlot", MetaSlotName = "TwoHandSlot"},
INVTYPE_BAG = {SlotName = "Bag"},
INVTYPE_BODY = {SlotName = "ShirtSlot"},
INVTYPE_CHEST = {SlotName = "ChestSlot"},
INVTYPE_CLOAK = {SlotName = "BackSlot"},
INVTYPE_FEET = {SlotName = "FeetSlot"},
INVTYPE_FINGER = {SlotName = "Finger0Slot"},
INVTYPE_HAND = {SlotName = "HandsSlot"},
INVTYPE_HEAD = {SlotName = "HeadSlot"},
INVTYPE_HOLDABLE = {SlotName = "SecondaryHandSlot"},
INVTYPE_LEGS = {SlotName = "LegsSlot"},
INVTYPE_NECK = {SlotName = "NeckSlot"},
INVTYPE_RANGED = {SlotName = "RangedSlot"},
INVTYPE_ROBE = {SlotName = "ChestSlot"},
INVTYPE_SHIELD = {SlotName = "SecondaryHandSlot"},
INVTYPE_SHOULDER = {SlotName = "ShoulderSlot"},
INVTYPE_TABARD = {SlotName = "TabardSlot"},
INVTYPE_TRINKET = {SlotName = "Trinket0Slot"},
INVTYPE_WAIST = {SlotName = "WaistSlot"},
INVTYPE_WEAPON = {SlotName = "MainHandSlot", MetaSlotName = "Weapon0Slot"},
INVTYPE_WEAPONMAINHAND = {SlotName = "MainHandSlot"},
INVTYPE_WEAPONOFFHAND = {SlotName = "SecondaryHandSlot"},
INVTYPE_WRIST = {SlotName = "WristSlot"},
INVTYPE_RANGEDRIGHT = {SlotName = "RangedSlot"},
INVTYPE_AMMO = {SlotName = "AmmoSlot"},
INVTYPE_THROWN = {SlotName = "RangedSlot"},
INVTYPE_RELIC = {SlotName = "RangedSlot"},
};
local Outfitter_cHalfAlternateStatSlot =
{
Trinket0Slot = "Trinket1Slot",
Finger0Slot = "Finger1Slot",
Weapon0Slot = "Weapon1Slot",
};
local Outfitter_cFullAlternateStatSlot =
{
Trinket0Slot = "Trinket1Slot",
Trinket1Slot = "Trinket0Slot",
Finger0Slot = "Finger1Slot",
Finger1Slot = "Finger0Slot",
Weapon0Slot = "Weapon1Slot",
Weapon1Slot = "Weapon0Slot",
};
local gOutfitter_cCategoryOrder =
{
"Complete",
"Partial",
"Accessory",
"Special"
};
local gOutfitter_Collapsed = {};
local gOutfitter_BankFrameOpened = false;
local Outfitter_cItemAliases =
{
[18608] = 18609, -- Benediction -> Anathema
[18609] = 18608, -- Anathema -> Benediction
[17223] = 17074, -- Thunderstrike -> Shadowstrike
[17074] = 17223, -- Shadowstrike -> Thunderstrike
};
local Outfitter_cSpecialtyBags =
{
[21340] = {Name = "Soul Pouch", Type = "ShardBag"},
[21341] = {Name = "Felcloth Bag", Type = "ShardBag"},
[21342] = {Name = "Core Felcloth Bag", Type = "ShardBag"},
[22243] = {Name = "Small Soul Pouch", Type = "ShardBag"},
[22244] = {Name = "Box of Souls", Type = "ShardBag"},
[2102] = {Name = "Small Ammo Pouch", Type = "AmmoPouch"},
[7279] = {Name = "Small Leather Ammo Pouch", Type = "AmmoPouch"},
[8218] = {Name = "Thick Leather Ammo Pouch", Type = "AmmoPouch"},
[7372] = {Name = "Heavy Leather Ammo Pouch", Type = "AmmoPouch"},
[3574] = {Name = "Hunting Ammo Sack", Type = "AmmoPouch"},
[3604] = {Name = "Bandolier of the Night Watch", Type = "AmmoPouch"},
[5441] = {Name = "Small Shot Pouch", Type = "AmmoPouch"},
[2663] = {Name = "Ribbly's Bandolier", Type = "AmmoPouch"},
[19320] = {Name = "Gnoll Skin Bandolier", Type = "AmmoPouch"},
[19319] = {Name = "Harpy Hide Quiver", Type = "Quiver"},
[7371] = {Name = "Heavy Quiver", Type = "Quiver"},
[3573] = {Name = "Hunting Quiver", Type = "Quiver"},
[7278] = {Name = "Light Leather Quiver", Type = "Quiver"},
[2101] = {Name = "Light Quiver", Type = "Quiver"},
[11362] = {Name = "Medium Quiver", Type = "Quiver"},
[8217] = {Name = "Quickdraw Quiver", Type = "Quiver"},
[3605] = {Name = "Quiver of the Night Watch", Type = "Quiver"},
[2662] = {Name = "Ribbly's Quiver", Type = "Quiver"},
[5439] = {Name = "Small Quiver", Type = "Quiver"},
[18714] = {Name = "Ancient Sinew Wrapped Lamina", Type = "Quiver"},
[22246] = {Name = "Enchanted Mageweave Pouch", Type = "Enchant"},
[22248] = {Name = "Enchanted Runecloth Bag", Type = "Enchant"},
[22249] = {Name = "Big Bag of Enchantment", Type = "Enchant"},
[22250] = {Name = "Herb Pouch", Type = "Herb"},
[22251] = {Name = "Cenarian Herb Bag", Type = "Herb"},
[22252] = {Name = "Satchel of Cenarious", Type = "Herb"},
};
local Outfitter_cFishingPoles =
{
{Code = 19970, SubCode = 0}, -- Outfitter_cArcaniteFishingPole
{Code = 19022, SubCode = 0}, -- Outfitter_cNatPaglesFishingPole
{Code = 12224, SubCode = 0}, -- Outfitter_cBlumpFishingPole
{Code = 6367, SubCode = 0}, -- Outfitter_cBigIronFishingPole
{Code = 6365, SubCode = 0}, -- Outfitter_cStrongFishingPole
{Code = 6256, SubCode = 0}, -- Outfitter_cFishingPole
};
local Outfitter_cRidingItems =
{
{Code = 11122, SubCode = 0}, -- Outfitter_cCarrotOnAStick
};
local Outfitter_cArgentDawnTrinkets =
{
{Code = 13209, SubCode = 0}, -- Outfitter_cSealOfTheDawn
{Code = 19812, SubCode = 0}, -- Outfitter_cRuneOfTheDawn
{Code = 12846, SubCode = 0}, -- Outfitter_cArgentDawnCommission
};
local Outfitter_cStatIDItems =
{
Fishing = Outfitter_cFishingPoles,
Riding = Outfitter_cRidingItems,
ArgentDawn = Outfitter_cArgentDawnTrinkets,
};
local Outfitter_cIgnoredUnusedItems =
{
[2901] = "Mining Pick",
[5956] = "Blacksmith hammer",
[6219] = "Arclight Spanner",
[7005] = "Skinning Knife",
[7297] = "Morbent's Bane",
[10696] = "Enchanted Azsharite Felbane Sword",
[10697] = "Enchanted Azsharite Felbane Dagger",
[10698] = "Enchanted Azsharite Felbane Staff",
[20406] = "Twilight Cultist Mantle",
[20407] = "Twilight Cultist Robe",
[20408] = "Twilight Cultist Cowl",
};
local Outfitter_cSmartOutfits =
{
{Name = Outfitter_cFishingOutfit, StatID = "Fishing", IsAccessory = true},
{Name = Outfitter_cHerbalismOutfit, StatID = "Herbalism", IsAccessory = true},
{Name = Outfitter_cMiningOutfit, StatID = "Mining", IsAccessory = true},
{Name = Outfitter_cSkinningOutfit, StatID = "Skinning", IsAccessory = true},
{Name = Outfitter_cFireResistOutfit, StatID = "FireResist"},
{Name = Outfitter_cNatureResistOutfit, StatID = "NatureResist"},
{Name = Outfitter_cShadowResistOutfit, StatID = "ShadowResist"},
{Name = Outfitter_cArcaneResistOutfit, StatID = "ArcaneResist"},
{Name = Outfitter_cFrostResistOutfit, StatID = "FrostResist"},
};
local Outfitter_cStatCategoryInfo =
{
{Category = "Stat", Name = Outfitter_cStatsCategory},
{Category = "Melee", Name = Outfitter_cMeleeCategory},
{Category = "Spell", Name = Outfitter_cSpellsCategory},
{Category = "Regen", Name = Outfitter_cRegenCategory},
{Category = "Resist", Name = Outfitter_cResistCategory},
{Category = "Trade", Name = Outfitter_cTradeCategory},
};
local Outfitter_cItemStatInfo =
{
{ID = "Agility", Name = Outfitter_cAgilityStatName, Category = "Stat"},
{ID = "Armor", Name = Outfitter_cArmorStatName, Category = "Stat"},
{ID = "Defense", Name = Outfitter_cDefenseStatName, Category = "Stat"},
{ID = "Intellect", Name = Outfitter_cIntellectStatName, Category = "Stat"},
{ID = "Spirit", Name = Outfitter_cSpiritStatName, Category = "Stat"},
{ID = "Stamina", Name = Outfitter_cStaminaStatName, Category = "Stat"},
{ID = "Strength", Name = Outfitter_cStrengthStatName, Category = "Stat"},
{ID = "ManaRegen", Name = Outfitter_cManaRegenStatName, Category = "Regen"},
{ID = "HealthRegen", Name = Outfitter_cHealthRegenStatName, Category = "Regen"},
{ID = "SpellCrit", Name = Outfitter_cSpellCritStatName, Category = "Spell"},
{ID = "SpellHit", Name = Outfitter_cSpellHitStatName, Category = "Spell"},
{ID = "SpellDmg", Name = Outfitter_cSpellDmgStatName, Category = "Spell"},
{ID = "FrostDmg", Name = Outfitter_cFrostDmgStatName, Category = "Spell"},
{ID = "FireDmg", Name = Outfitter_cFireDmgStatName, Category = "Spell"},
{ID = "ArcaneDmg", Name = Outfitter_cArcaneDmgStatName, Category = "Spell"},
{ID = "ShadowDmg", Name = Outfitter_cShadowDmgStatName, Category = "Spell"},
{ID = "NatureDmg", Name = Outfitter_cNatureDmgStatName, Category = "Spell"},
{ID = "Healing", Name = Outfitter_cHealingStatName, Category = "Spell"},
{ID = "MeleeCrit", Name = Outfitter_cMeleeCritStatName, Category = "Melee"},
{ID = "MeleeHit", Name = Outfitter_cMeleeHitStatName, Category = "Melee"},
{ID = "MeleeDmg", Name = Outfitter_cMeleeDmgStatName, Category = "Melee"},
{ID = "Dodge", Name = Outfitter_cDodgeStatName, Category = "Melee"},
{ID = "Attack", Name = Outfitter_cAttackStatName, Category = "Melee"},
{ID = "ArcaneResist", Name = Outfitter_cArcaneResistStatName, Category = "Resist"},
{ID = "FireResist", Name = Outfitter_cFireResistStatName, Category = "Resist"},
{ID = "FrostResist", Name = Outfitter_cFrostResistStatName, Category = "Resist"},
{ID = "NatureResist", Name = Outfitter_cNatureResistStatName, Category = "Resist"},
{ID = "ShadowResist", Name = Outfitter_cShadowResistStatName, Category = "Resist"},
{ID = "Fishing", Name = Outfitter_cFishingStatName, Category = "Trade"},
{ID = "Herbalism", Name = Outfitter_cHerbalismStatName, Category = "Trade"},
{ID = "Mining", Name = Outfitter_cMiningStatName, Category = "Trade"},
{ID = "Skinning", Name = Outfitter_cSkinningStatName, Category = "Trade"},
};
local Outfitter_cNormalizedClassName =
{
[Outfitter_cDruidClassName] = "Druid",
[Outfitter_cHunterClassName] = "Hunter",
[Outfitter_cMageClassName] = "Mage",
[Outfitter_cPaladinClassName] = "Paladin",
[Outfitter_cPriestClassName] = "Priest",
[Outfitter_cRogueClassName] = "Rogue",
[Outfitter_cShamanClassName] = "Shaman",
[Outfitter_cWarlockClassName] = "Warlock",
[Outfitter_cWarriorClassName] = "Warrior",
};
local Outfitter_cClassSpecialOutfits =
{
Warrior =
{
{Name = Outfitter_cWarriorBattleStance, SpecialID = "Battle"},
{Name = Outfitter_cWarriorDefensiveStance, SpecialID = "Defensive"},
{Name = Outfitter_cWarriorBerserkerStance, SpecialID = "Berserker"},
},
Druid =
{
{Name = Outfitter_cDruidBearForm, SpecialID = "Bear"},
{Name = Outfitter_cDruidCatForm, SpecialID = "Cat"},
{Name = Outfitter_cDruidAquaticForm, SpecialID = "Aquatic"},
{Name = Outfitter_cDruidTravelForm, SpecialID = "Travel"},
{Name = Outfitter_cDruidMoonkinForm, SpecialID = "Moonkin"},
},
Priest =
{
{Name = Outfitter_cPriestShadowform, SpecialID = "Shadowform"},
},
Rogue =
{
{Name = Outfitter_cRogueStealth, SpecialID = "Stealth"},
},
Shaman =
{
{Name = Outfitter_cShamanGhostWolf, SpecialID = "GhostWolf"},
},
Hunter =
{
{Name = Outfitter_cHunterMonkey, SpecialID = "Monkey"},
{Name = Outfitter_cHunterHawk, SpecialID = "Hawk"},
{Name = Outfitter_cHunterCheetah, SpecialID = "Cheetah"},
{Name = Outfitter_cHunterPack, SpecialID = "Pack"},
{Name = Outfitter_cHunterBeast, SpecialID = "Beast"},
{Name = Outfitter_cHunterWild, SpecialID = "Wild"},
},
Mage =
{
{Name = Outfitter_cMageEvocate, SpecialID = "Evocate"},
},
};
local gOutfitter_SpellNameSpecialID =
{
[Outfitter_cAspectOfTheCheetah] = "Cheetah",
[Outfitter_cAspectOfThePack] = "Pack",
[Outfitter_cAspectOfTheBeast] = "Beast",
[Outfitter_cAspectOfTheWild] = "Wild",
[Outfitter_cEvocate] = "Evocate",
};
local gOutfitter_AuraIconSpecialID =
{
["INV_Misc_Fork&Knife"] = "Dining",
["Spell_Shadow_Shadowform"] = "Shadowform",
["Spell_Nature_SpiritWolf"] = "GhostWolf",
["Ability_Rogue_FeignDeath"] = "Feigning",
["Ability_Hunter_AspectOfTheMonkey"] = "Monkey",
["Spell_Nature_RavenForm"] = "Hawk",
};
local Outfitter_cSpecialOutfitDescriptions =
{
ArgentDawn = Outfitter_cArgentDawnOutfitDescription,
Riding = Outfitter_cRidingOutfitDescription,
Dining = Outfitter_cDiningOutfitDescription,
Battleground = Outfitter_cBattlegroundOutfitDescription,
AB = Outfitter_cArathiBasinOutfitDescription,
AV = Outfitter_cAlteracValleyOutfitDescription,
WSG = Outfitter_cWarsongGulchOutfitDescription,
City = Outfitter_cCityOutfitDescription,
};
-- Note that zone special outfits will be worn in the order
-- the are listed here, with later outfits being worn over
-- earlier outfits (when they're being applied at the same time)
-- This allows BG-specific outfits to take priority of the generic
-- BG outfit
local Outfitter_cZoneSpecialIDs =
{
"ArgentDawn",
"City",
"Battleground",
"AV",
"AB",
"WSG",
};
local Outfitter_cZoneSpecialIDMap =
{
[Outfitter_cWesternPlaguelands] = {"ArgentDawn"},
[Outfitter_cEasternPlaguelands] = {"ArgentDawn"},
[Outfitter_cStratholme] = {"ArgentDawn"},
[Outfitter_cScholomance] = {"ArgentDawn"},
[Outfitter_cNaxxramas] = {"ArgentDawn"},
[Outfitter_cAlteracValley] = {"Battleground", "AV"},
[Outfitter_cArathiBasin] = {"Battleground", "AB"},
[Outfitter_cWarsongGulch] = {"Battleground", "WSG"},
[Outfitter_cIronforge] = {"City"},
[Outfitter_cCityOfIronforge] = {"City"},
[Outfitter_cDarnassus] = {"City"},
[Outfitter_cStormwind] = {"City"},
[Outfitter_cOrgrimmar] = {"City"},
[Outfitter_cThunderBluff] = {"City"},
[Outfitter_cUndercity] = {"City"},
};
local gOutfitter_StatDistribution =
{
DRUID =
{
Agility = {Armor = {Coeff = 2}, Dodge = {Coeff = 1 / 20}},
Stamina = {Health = {Coeff = 10}},
Intellect = {Mana = {Coeff = 15}, SpellCrit = {Coeff = 1 / 30}},
Spirit = {ManaRegen = {Coeff = 0.25 * 2.5}}, -- * 2.5 to convert from ticks to per-five-seconds
Strength = {BlockAmount = {Coeff = 1 / 22}},
},
HUNTER =
{
Agility = {Armor = {Coeff = 2}, Dodge = {Coeff = 1 / 26.5}, MeleeCrit = {Coeff = 1 / 53}},
Stamina = {Health = {Coeff = 10}},
Intellect = {Mana = {Coeff = 15}},
Spirit = {ManaRegen = {Coeff = 0.25 * 2.5}}, -- * 2.5 to convert from ticks to per-five-seconds
Strength = {BlockAmount = {Coeff = 1 / 22}},
},
MAGE =
{
Agility = {Armor = {Coeff = 2}, Dodge = {Coeff = 1 / 20}, MeleeCrit = {Coeff = 1 / 20}},
Stamina = {Health = {Coeff = 10}},
Intellect = {Mana = {Coeff = 15}, SpellCrit = {Coeff = 1.0 / 59.5}},
Spirit = {ManaRegen = {Coeff = 0.25 * 2.5}}, -- * 2.5 to convert from ticks to per-five-seconds
Strength = {BlockAmount = {Coeff = 1 / 22}},
},
PALADIN =
{
Agility = {Armor = {Coeff = 2}, Dodge = {Coeff = 1 / 20}, MeleeCrit = {Coeff = 1 / 20}},
Stamina = {Health = {Coeff = 10}},
Intellect = {Mana = {Coeff = 15}, SpellCrit = {Coeff = 1.0 / 20}},
Spirit = {ManaRegen = {Coeff = 0.25 * 2.5}}, -- * 2.5 to convert from ticks to per-five-seconds
Strength = {BlockAmount = {Coeff = 1 / 22}},
},
PRIEST =
{
Agility = {Armor = {Coeff = 2}, Dodge = {Coeff = 1 / 20}, MeleeCrit = {Coeff = 1 / 20}},
Stamina = {Health = {Coeff = 10}},
Intellect = {Mana = {Coeff = 15}, SpellCrit = {Coeff = 1.0 / 30}},
Spirit = {ManaRegen = {Coeff = 0.25 * 2.5}}, -- * 2.5 to convert from ticks to per-five-seconds
Strength = {BlockAmount = {Coeff = 1 / 22}},
},
ROGUE =
{
Agility = {Armor = {Coeff = 2}, Dodge = {Coeff = 1 / 14.5}, MeleeCrit = {Coeff = 1 / 29}},
Stamina = {Health = {Coeff = 10}},
Intellect = {Mana = {Coeff = 15}},
Spirit = {ManaRegen = {Coeff = 0.25 * 2.5}}, -- * 2.5 to convert from ticks to per-five-seconds
Strength = {BlockAmount = {Coeff = 1 / 22}},
},
SHAMAN =
{
Agility = {Armor = {Coeff = 2}, Dodge = {Coeff = 1 / 20}, MeleeCrit = {Coeff = 1 / 20}},
Stamina = {Health = {Coeff = 10}},
Intellect = {Mana = {Coeff = 15}, SpellCrit = {Coeff = 1.0 / 20}},
Spirit = {ManaRegen = {Coeff = 0.25 * 2.5}}, -- * 2.5 to convert from ticks to per-five-seconds
Strength = {BlockAmount = {Coeff = 1 / 22}},
},
WARLOCK =
{
Agility = {Armor = {Coeff = 2}, Dodge = {Coeff = 1 / 20}, MeleeCrit = {Coeff = 1 / 20}},
Stamina = {Health = {Coeff = 10}},
Intellect = {Mana = {Coeff = 15}, SpellCrit = {Coeff = 1.0 / 30}},
Spirit = {ManaRegen = {Coeff = 0.25 * 2.5}}, -- * 2.5 to convert from ticks to per-five-seconds
Strength = {BlockAmount = {Coeff = 1 / 22}},
},
WARRIOR =
{
Agility = {Armor = {Coeff = 2}, Dodge = {Coeff = 1 / 20}, MeleeCrit = {Coeff = 1 / 20}},
Stamina = {Health = {Coeff = 10}},
Intellect = {Mana = {Coeff = 15}},
Spirit = {ManaRegen = {Coeff = 0.25 * 2.5}}, -- * 2.5 to convert from ticks to per-five-seconds
Strength = {BlockAmount = {Coeff = 1 / 22}},
},
};
local Outfitter_cCombatEquipmentSlots =
{
MainHandSlot = true,
SecondaryHandSlot = true,
RangedSlot = true,
AmmoSlot = true,
};
local gOutfitter_OutfitStack = {};
local gOutfitter_SelectedOutfit = nil;
local gOutfitter_DisplayIsDirty = true;
local gOutfitter_CurrentZone = nil;
local gOutfitter_InCombat = false;
local gOutfitter_IsDead = false;
local gOutfitter_IsFeigning = false;
local gOutfitter_EquippedNeedsUpdate = false;
local gOutfitter_WeaponsNeedUpdate = false;
local gOutfitter_LastEquipmentUpdateTime = 0;
local Outfitter_cMinEquipmentUpdateInterval = 1.5;
local gOutfitter_CurrentOutfit = nil;
local gOutfitter_ExpectedOutfit = nil;
local gOutfitter_CurrentInventoryOutfit = nil;
local gOutfitter_EquippableItems = nil;
local gOutfitter_Initialized = false;
local gOutfitter_Suspended = false;
local Outfitter_cMaxDisplayedItems = 14;
local gOutfitter_PanelFrames =
{
"OutfitterMainFrame",
"OutfitterOptionsFrame",
"OutfitterAboutFrame",
};
local gOutfitter_CurrentPanel = 0;
local Outfitter_cShapeshiftSpecialIDs =
{
-- Warriors
[Outfitter_cBattleStance] = {ID = "Battle"},
[Outfitter_cDefensiveStance] = {ID = "Defensive"},
[Outfitter_cBerserkerStance] = {ID = "Berserker"},
-- Druids
[Outfitter_cBearForm] = {ID = "Bear"},
[Outfitter_cCatForm] = {ID = "Cat"},
[Outfitter_cAquaticForm] = {ID = "Aquatic"},
[Outfitter_cTravelForm] = {ID = "Travel"},
[Outfitter_cDireBearForm] = {ID = "Bear"},
[Outfitter_cMoonkinForm] = {ID = "Moonkin"},
-- Rogues
[Outfitter_cStealth] = {ID = "Stealth"},
};
local gOutfitter_SpecialState = {};
StaticPopupDialogs["OUTFITTER_CONFIRM_DELETE"] =
{
text = TEXT(Outfitter_cConfirmDeleteMsg),
button1 = TEXT(DELETE),
button2 = TEXT(CANCEL),
OnAccept = function() Outfitter_DeleteSelectedOutfit(); end,
timeout = 0,
whileDead = 1,
hideOnEscape = 1
};
StaticPopupDialogs["OUTFITTER_CONFIRM_REBUILD"] =
{
text = TEXT(Outfitter_cConfirmRebuildMsg),
button1 = TEXT(Outfitter_cRebuild),
button2 = TEXT(CANCEL),
OnAccept = function() Outfitter_RebuildSelectedOutfit(); end,
timeout = 0,
whileDead = 1,
hideOnEscape = 1,
};
function Outfitter_ToggleOutfitterFrame()
if Outfitter_IsOpen() then
OutfitterFrame:Hide();
else
OutfitterFrame:Show();
end
end
function Outfitter_IsOpen()
return OutfitterFrame:IsVisible();
end
function Outfitter_OnLoad()
Outfitter_RegisterEvent(this, "PLAYER_ENTERING_WORLD", Outfitter_PlayerEnteringWorld);
Outfitter_RegisterEvent(this, "PLAYER_LEAVING_WORLD", Outfitter_PlayerLeavingWorld);
Outfitter_RegisterEvent(this, "VARIABLES_LOADED", Outfitter_VariablesLoaded);
-- For monitoring mounted, dining and shadowform states
Outfitter_RegisterEvent(this, "PLAYER_AURAS_CHANGED", Outfitter_UpdateAuraStates);
-- For monitoring plaguelands and battlegrounds
Outfitter_RegisterEvent(this, "ZONE_CHANGED_NEW_AREA", Outfitter_UpdateZone);
-- For monitoring player combat state
Outfitter_RegisterEvent(this, "PLAYER_REGEN_ENABLED", Outfitter_RegenEnabled);
Outfitter_RegisterEvent(this, "PLAYER_REGEN_DISABLED", Outfitter_RegenDisabled);
-- For monitoring player dead/alive stat
Outfitter_RegisterEvent(this, "PLAYER_DEAD", Outfitter_PlayerDead);
Outfitter_RegisterEvent(this, "PLAYER_ALIVE", Outfitter_PlayerAlive);
Outfitter_RegisterEvent(this, "PLAYER_UNGHOST", Outfitter_PlayerAlive);
Outfitter_RegisterEvent(this, "UNIT_INVENTORY_CHANGED", Outfitter_InventoryChanged);
-- For indicating which outfits are missing items
Outfitter_RegisterEvent(this, "BAG_UPDATE", Outfitter_BagUpdate);
Outfitter_RegisterEvent(this, "PLAYERBANKSLOTS_CHANGED", Outfitter_BankSlotsChanged);
-- For monitoring bank bags
Outfitter_RegisterEvent(this, "BANKFRAME_OPENED", Outfitter_BankFrameOpened);
Outfitter_RegisterEvent(this, "BANKFRAME_CLOSED", Outfitter_BankFrameClosed);
-- For unequipping the dining outfit
Outfitter_RegisterEvent(this, "UNIT_HEALTH", Outfitter_UnitHealthOrManaChanged);
Outfitter_RegisterEvent(this, "UNIT_MANA", Outfitter_UnitHealthOrManaChanged);
Outfitter_SuspendEvent(this, "UNIT_HEALTH"); -- Don't actually care until the dining outfit equips
Outfitter_SuspendEvent(this, "UNIT_MANA");
-- Tabs
PanelTemplates_SetNumTabs(this, table.getn(gOutfitter_PanelFrames));
OutfitterFrame.selectedTab = gOutfitter_CurrentPanel;
PanelTemplates_UpdateTabs(this);
-- Install the /outfit command handler
SlashCmdList["OUTFITTER"] = Outfitter_ExecuteCommand;
SLASH_OUTFITTER1 = "/outfitter";
-- Fake a leaving world event to suspend inventory/bag
-- updating until loading is completed
Outfitter_PlayerLeavingWorld();
end
function Outfitter_OnShow()
Outfitter_ShowPanel(1); -- Always switch to the main view when showing the window
end
function Outfitter_OnHide()
Outfitter_ClearSelection();
OutfitterQuickSlots_Close();
OutfitterFrame:Hide(); -- This seems redundant, but the OnHide handler gets called
-- in response to the parent being hidden (the character window)
-- so calling Hide() on the frame here ensures that when the
-- character window is hidden then Outfitter won't be displayed
-- next time it's opened
end
function Outfitter_OnEvent(pEvent)
-- Ignore all events except for entering world until initialization is
-- completed
if not gOutfitter_Initialized
and pEvent ~= "VARIABLES_LOADED" then
if pEvent ~= Outfitter_cInitializationEvent then
return;
end
Outfitter_Initialize();
end
--
Outfitter_DispatchEvent(this, pEvent);
Outfitter_Update(false);
end
function Outfitter_PlayerLeavingWorld()
-- To improve load screen performance, suspend events which are
-- fired repeatedly and rapidly during zoning
gOutfitter_Suspended = true;
Outfitter_SuspendEvent(OutfitterFrame, "BAG_UPDATE");
Outfitter_SuspendEvent(OutfitterFrame, "UNIT_INVENTORY_CHANGED");
Outfitter_SuspendEvent(OutfitterFrame, "UPDATE_INVENTORY_ALERTS");
Outfitter_SuspendEvent(OutfitterFrame, "SPELLS_CHANGED");
Outfitter_SuspendEvent(OutfitterFrame, "PLAYER_AURAS_CHANGED");
Outfitter_SuspendEvent(OutfitterFrame, "PLAYERBANKSLOTS_CHANGED");
end
function Outfitter_PlayerEnteringWorld()
OutfitterItemList_FlushEquippableItems();
Outfitter_RegenEnabled();
Outfitter_UpdateAuraStates();
Outfitter_SetSpecialOutfitEnabled("Riding", false);
Outfitter_ResumeLoadScreenEvents();
end
function Outfitter_ResumeLoadScreenEvents()
if gOutfitter_Suspended then
-- To improve load screen performance, suspend events which are
-- fired repeatedly and rapidly during zoning
gOutfitter_Suspended = false;
Outfitter_ResumeEvent(OutfitterFrame, "BAG_UPDATE");
Outfitter_ResumeEvent(OutfitterFrame, "UNIT_INVENTORY_CHANGED");
Outfitter_ResumeEvent(OutfitterFrame, "UPDATE_INVENTORY_ALERTS");
Outfitter_ResumeEvent(OutfitterFrame, "SPELLS_CHANGED");
Outfitter_ResumeEvent(OutfitterFrame, "PLAYER_AURAS_CHANGED");
Outfitter_ResumeEvent(OutfitterFrame, "PLAYERBANKSLOTS_CHANGED");
Outfitter_InventoryChanged2();
end
end
function Outfitter_VariablesLoaded()
-- Change the initialization event to PLAYER_ALIVE if this is the first use
-- This will ensure that the bags and inventory info has been loaded before
-- trying to generate the automatic outfits
if not gOutfitter_Settings
or not gOutfitter_Settings.Outfits then
Outfitter_cInitializationEvent = "PLAYER_ALIVE";
end
end
function Outfitter_BankSlotsChanged()
OutfitterItemList_FlushBagFromEquippableItems(-1);
for vBagIndex = NUM_BAG_SLOTS + 1, NUM_BAG_SLOTS + NUM_BANKBAGSLOTS do
OutfitterItemList_FlushBagFromEquippableItems(vBagIndex);
end
-- Force the bank bags to update since they now exist
if gOutfitter_EquippableItems then
gOutfitter_EquippableItems.NeedsUpdate = true;
end
gOutfitter_DisplayIsDirty = true;
Outfitter_Update(false);
end
function Outfitter_BagUpdate()
local vBagIndex = arg1;
OutfitterItemList_FlushBagFromEquippableItems(vBagIndex);
-- This is a messy hack to ensure the database gets updated properly
-- after an upgrade. WoW doesn't always have the players items
-- loaded on PLAYER_ENTERING_WORLD so once the bag update fires
-- we check the databases again if necessary
if gOutfitter_NeedItemCodesUpdated then
gOutfitter_NeedItemCodesUpdated = gOutfitter_NeedItemCodesUpdated - 1;
if gOutfitter_NeedItemCodesUpdated == 0 then
gOutfitter_NeedItemCodesUpdated = nil;
end
if Outfitter_UpdateDatabaseItemCodes() then
gOutfitter_NeedItemCodesUpdated = nil;
end
end
--
gOutfitter_DisplayIsDirty = true;
Outfitter_Update(false);
end
local gOutfitter_OutfitEvents = {};
function Outfitter_RegisterOutfitEvent(pEvent, pFunction)
local vHandlers = gOutfitter_OutfitEvents[pEvent];
if not vHandlers then
vHandlers = {};
gOutfitter_OutfitEvents[pEvent] = vHandlers;
end
table.insert(vHandlers, pFunction);
end
function Outfitter_UnregisterOutfitEvent(pEvent, pFunction)
local vHandlers = gOutfitter_OutfitEvents[pEvent];
if not vHandlers then
return;
end
for vIndex, vFunction in vHandlers do
if vFunction == pFunction then
table.remove(vHandlers, vIndex);
return;
end
end
end
function Outfitter_DispatchOutfitEvent(pEvent, pParameter1, pParameter2)
-- Don't send out events until we're initialized
if not gOutfitter_Initialized then
return;
end
--
OutfitterMinimapDropDown_OutfitEvent(pEvent, pParameter1, pParameter2);
local vHandlers = gOutfitter_OutfitEvents[pEvent];
if not vHandlers then
return;
end
for _, vFunction in vHandlers do
-- Call in protected mode so that if they fail it doesn't
-- screw up Outfitter or other addons wishing to be notified
pcall(vFunction, pEvent, pParameter1, pParameter2);
end
end
function Outfitter_BankFrameOpened()
gOutfitter_BankFrameOpened = true;
gOutfitter_DisplayIsDirty = true;
Outfitter_BankSlotsChanged();
Outfitter_Update(false);
end
function Outfitter_BankFrameClosed()
gOutfitter_BankFrameOpened = false;
Outfitter_BankSlotsChanged();
gOutfitter_DisplayIsDirty = true;
Outfitter_Update(false);
end
function Outfitter_RegenEnabled(pEvent)
gOutfitter_InCombat = false;
end
function Outfitter_RegenDisabled(pEvent)
gOutfitter_InCombat = true;
end
function Outfitter_PlayerDead(pEvent)
gOutfitter_IsDead = true;
end
function Outfitter_PlayerAlive(pEvent)
if not UnitIsDeadOrGhost("player") then
gOutfitter_IsDead = false;
end
end
function Outfitter_UnitHealthOrManaChanged()
if arg1 ~= "player" then
return;
end
local vHealth = UnitHealth("player");
local vMaxHealth = UnitHealthMax("player");
local vFullHealth = false;
local vFullMana = false;
if vHealth > (vMaxHealth * 0.99) then
vFullHealth = true;
end
if UnitPowerType("player") == 0 then
local vMana = UnitMana("player");
local vMaxMana = UnitManaMax("player");
if vMana > (vMaxMana * 0.99) then
vFullMana = true;
end
else
vFullMana = true;
end
if vFullHealth and vFullMana then
Outfitter_SetSpecialOutfitEnabled("Dining", false);
end
end
function Outfitter_InventoryChanged(pEvent)
if arg1 ~= "player" then
return;
end
Outfitter_InventoryChanged2();
end
function Outfitter_InventoryChanged2()
OutfitterItemList_FlushEquippableItems(); -- Flush everything because the game sends bag update
-- events for an item after it's already appeared in the inventory. This
-- creates a brief situation in which the item appears to be both in the
-- bank and in the inventory which causes various problems for Outfitter
-- when checking for items.
gOutfitter_DisplayIsDirty = true; -- Update the list so the checkboxes reflect the current state
local vNewItemsOutfit, vCurrentOutfit = Outfitter_GetNewItemsOutfit(gOutfitter_CurrentOutfit);
if vNewItemsOutfit then
if not gOutfitter_Settings.Options.DisableAutoVisibility then
-- If the cloak is changing, remember the visibility for the old one and set
-- it for the new one
if vNewItemsOutfit.Items.BackSlot then
if gOutfitter_CurrentOutfit.Items.BackSlot.Code then
if ShowingCloak() then
gOutfitter_Settings.HideCloak[gOutfitter_CurrentOutfit.Items.BackSlot.Code] = false;
else
gOutfitter_Settings.HideCloak[gOutfitter_CurrentOutfit.Items.BackSlot.Code] = true;
end
end
if gOutfitter_Settings.HideCloak[vNewItemsOutfit.Items.BackSlot.Code] ~= nil then
if gOutfitter_Settings.HideCloak[vNewItemsOutfit.Items.BackSlot.Code] then
ShowCloak(0);
else
ShowCloak(1);
end
end
end
-- If the helm is changing, remember the visibility for the old one and set
-- it for the new one
if vNewItemsOutfit.Items.HeadSlot then
if gOutfitter_CurrentOutfit.Items.HeadSlot.Code then
if ShowingHelm() then
gOutfitter_Settings.HideHelm[gOutfitter_CurrentOutfit.Items.HeadSlot.Code] = false;
else
gOutfitter_Settings.HideHelm[gOutfitter_CurrentOutfit.Items.HeadSlot.Code] = true;
end
end
if gOutfitter_Settings.HideHelm[vNewItemsOutfit.Items.HeadSlot.Code] ~= nil then
if gOutfitter_Settings.HideHelm[vNewItemsOutfit.Items.HeadSlot.Code] then
ShowHelm(0);
else
ShowHelm(1);
end
end
end
end
-- Save the new outfit
gOutfitter_CurrentOutfit = vCurrentOutfit;
-- Close QuickSlots if there's an inventory change (except if the only
-- change is with the ammo slots)
if OutfitterQuickSlots.SlotName ~= "AmmoSlot"
or not Outfitter_OutfitIsAmmoOnly(vNewItemsOutfit) then
OutfitterQuickSlots_Close();
end
-- Update the selected outfit or temporary outfit
Outfitter_SubtractOutfit(vNewItemsOutfit, gOutfitter_ExpectedOutfit);
if gOutfitter_SelectedOutfit then
Outfitter_UpdateOutfitFromInventory(gOutfitter_SelectedOutfit, vNewItemsOutfit);
else
Outfitter_UpdateTemporaryOutfit(vNewItemsOutfit);
end
end
Outfitter_Update(true);
end
function Outfitter_OutfitIsAmmoOnly(pOutfit)
local vHasAmmoItem = false;
for vInventorySlot, vItem in pOutfit.Items do
if vInventorySlot ~= "AmmoSlot" then
return false;
else
vHasAmmoItem = true;
end
end
return vHasAmmoItem;
end
function Outfitter_ExecuteCommand(pCommand)
vCommands =
{
wear = {useOutfit = true, func = Outfitter_WearOutfit},
unwear = {useOutfit = true, func = Outfitter_RemoveOutfit},
toggle = {useOutfit = true, func = Outfitter_ToggleOutfit},
summary = {useOutfit = false, func = Outfitter_OutfitSummary},
}
local vStartIndex, vEndIndex, vCommand, vParameter = string.find(pCommand, "(%w+) ?(.*)");
if not vCommand then
Outfitter_NoteMessage(HIGHLIGHT_FONT_COLOR_CODE.."/outfitter wear <outfit name>"..NORMAL_FONT_COLOR_CODE..": Wear an outfit");
Outfitter_NoteMessage(HIGHLIGHT_FONT_COLOR_CODE.."/outfitter unwear <outfit name>"..NORMAL_FONT_COLOR_CODE..": Remove an outfit");
Outfitter_NoteMessage(HIGHLIGHT_FONT_COLOR_CODE.."/outfitter toggle <outfit name>"..NORMAL_FONT_COLOR_CODE..": Wears or removes an outfit");
return;
end
vCommand = strlower(vCommand);
local vCommandInfo = vCommands[vCommand];
if not vCommandInfo then
Outfitter_ErrorMessage("Outfitter: Expected command");
return;
end
local vOutfit = nil;
local vCategoryID = nil;
if vCommandInfo.useOutfit then
if not vParameter then
Outfitter_ErrorMessage("Outfitter: Expected outfit name for "..vCommand.." command");
return;
end
vOutfit, vCategoryID = Outfitter_FindOutfitByName(vParameter);
if not vOutfit then
Outfitter_ErrorMessage("Outfitter: Couldn't find outfit named "..vParameter);
return;
end
vCommandInfo.func(vOutfit, vCategoryID);
else
vCommandInfo.func();
end
end
function Outfitter_AskRebuildOutfit(pOutfit, pCategoryID)
gOutfitter_OutfitToRebuild = pOutfit;
gOutfitter_OutfitCategoryToRebuild = pCategoryID;
StaticPopup_Show("OUTFITTER_CONFIRM_REBUILD", gOutfitter_OutfitToRebuild.Name);
end
function Outfitter_RebuildSelectedOutfit()
if not gOutfitter_OutfitToRebuild then
return;
end
local vOutfit = Outfitter_GenerateSmartOutfit("temp", gOutfitter_OutfitToRebuild.StatID, OutfitterItemList_GetEquippableItems(true));
if vOutfit then
gOutfitter_OutfitToRebuild.Items = vOutfit.Items;
Outfitter_UpdateOutfitCategory(gOutfitter_OutfitToRebuild);
Outfitter_WearOutfit(gOutfitter_OutfitToRebuild, gOutfitter_OutfitCategoryToRebuild);
Outfitter_Update(true);
end
gOutfitter_OutfitToRebuild = nil;
gOutfitter_OutfitCategoryToRebuild = nil;
end
function Outfitter_AskDeleteOutfit(pOutfit)
gOutfitter_OutfitToDelete = pOutfit;
StaticPopup_Show("OUTFITTER_CONFIRM_DELETE", gOutfitter_OutfitToDelete.Name);
end
function Outfitter_DeleteSelectedOutfit()
if not gOutfitter_OutfitToDelete then
return;
end
Outfitter_DeleteOutfit(gOutfitter_OutfitToDelete);
Outfitter_Update(true);
end
function Outfitter_ShowPanel(pPanelIndex)
Outfitter_CancelDialogs(); -- Force any dialogs to close if they're open
if gOutfitter_CurrentPanel > 0
and gOutfitter_CurrentPanel ~= pPanelIndex then
Outfitter_HidePanel(gOutfitter_CurrentPanel);
end
-- NOTE: Don't check for redundant calls since this function
-- will be called to reset the field values as well as to
-- actually show the panel when it's hidden
gOutfitter_CurrentPanel = pPanelIndex;
getglobal(gOutfitter_PanelFrames[pPanelIndex]):Show();
PanelTemplates_SetTab(OutfitterFrame, pPanelIndex);
-- Update the control values
if pPanelIndex == 1 then
-- Main panel
elseif pPanelIndex == 2 then
-- Options panel
elseif pPanelIndex == 3 then
-- About panel
else
Outfitter_ErrorMessage("Outfitter: Unknown index ("..pPanelIndex..") in ShowPanel()");
end
Outfitter_Update(false);
end
function Outfitter_HidePanel(pPanelIndex)
if gOutfitter_CurrentPanel ~= pPanelIndex then
return;
end
getglobal(gOutfitter_PanelFrames[pPanelIndex]):Hide();
gOutfitter_CurrentPanel = 0;
end
function Outfitter_CancelDialogs()
end
function OutfitterItemDropDown_OnLoad()
UIDropDownMenu_SetAnchor(0, 0, this, "TOPLEFT", this:GetName(), "CENTER");
UIDropDownMenu_Initialize(this, OutfitterItemDropDown_Initialize);
--UIDropDownMenu_Refresh(this); -- Don't refresh on menus which don't have a text portion
this:SetHeight(this.SavedHeight);
end
function Outfitter_AddDividerMenuItem()
UIDropDownMenu_AddButton({text = " ", notCheckable = true, notClickable = true});
end
function Outfitter_AddCategoryMenuItem(pName)
UIDropDownMenu_AddButton({text = pName, notCheckable = true, notClickable = true});
end
function Outfitter_AddMenuItem(pFrame, pName, pValue, pChecked, pLevel, pColor, pDisabled)
if not pColor then
pColor = NORMAL_FONT_COLOR;
end
UIDropDownMenu_AddButton({text = pName, value = pValue, owner = pFrame, checked = pChecked, func = OutfitterDropDown_OnClick2, textR = pColor.r, textG = pColor.g, textB = pColor.b, disabled = pDisabled}, pLevel);
end
function Outfitter_AddSubmenuItem(pFrame, pName, pValue)
UIDropDownMenu_AddButton({text = pName, owner = pFrame, hasArrow = 1, value = pValue, textR = NORMAL_FONT_COLOR.r, textG = NORMAL_FONT_COLOR.g, textB = NORMAL_FONT_COLOR.b});
end
function OutfitterItemDropDown_Initialize()
local vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
local vItem = vFrame:GetParent():GetParent();
local vOutfit, vCategoryID = Outfitter_GetOutfitFromListItem(vItem);
if not vOutfit then
return;
end
if UIDROPDOWNMENU_MENU_LEVEL == 1 then
local vIsSpecialOutfit = vCategoryID == "Special";
Outfitter_AddCategoryMenuItem(vOutfit.Name);
if vIsSpecialOutfit then
Outfitter_AddMenuItem(vFrame, Outfitter_cDisableOutfit, "DISABLE", vOutfit.Disabled);
Outfitter_AddMenuItem(vFrame, Outfitter_cDisableOutfitInBG, "BGDISABLE", vOutfit.BGDisabled);
else
Outfitter_AddMenuItem(vFrame, PET_RENAME, "RENAME");
end
if not vIsSpecialOutfit
and vOutfit.StatID then
local vStatName = Outfitter_GetStatIDName(vOutfit.StatID);
if vStatName then
Outfitter_AddMenuItem(vFrame, format(Outfitter_cRebuildOutfitFormat, vStatName), "REBUILD");
end
end
Outfitter_AddSubmenuItem(vFrame, Outfitter_cKeyBinding, "BINDING");
if not vIsSpecialOutfit then
Outfitter_AddMenuItem(vFrame, DELETE, "DELETE");
end
Outfitter_AddCategoryMenuItem(Outfitter_cBankCategoryTitle);
Outfitter_AddMenuItem(vFrame, Outfitter_cDepositToBank, "DEPOSIT", nil, nil, nil, not gOutfitter_BankFrameOpened);
Outfitter_AddMenuItem(vFrame, Outfitter_cDepositUniqueToBank, "DEPOSITUNIQUE", nil, nil, nil, not gOutfitter_BankFrameOpened);
Outfitter_AddMenuItem(vFrame, Outfitter_cWithdrawFromBank, "WITHDRAW", nil, nil, nil, not gOutfitter_BankFrameOpened);
if not vIsSpecialOutfit
and vCategoryID ~= "Complete" then
Outfitter_AddCategoryMenuItem(Outfitter_cOutfitCategoryTitle);
Outfitter_AddMenuItem(vFrame, Outfitter_cPartialOutfits, "PARTIAL", vCategoryID == "Partial");
Outfitter_AddMenuItem(vFrame, Outfitter_cAccessoryOutfits, "ACCESSORY", vCategoryID == "Accessory");
end
elseif UIDROPDOWNMENU_MENU_LEVEL == 2 then
if UIDROPDOWNMENU_MENU_VALUE == "BINDING" then
for vIndex = 1, 10 do
Outfitter_AddMenuItem(vFrame, getglobal("BINDING_NAME_OUTFITTER_OUTFIT"..vIndex), "BINDING"..vIndex, vOutfit.BindingIndex == vIndex, UIDROPDOWNMENU_MENU_LEVEL);
end
end
end
vFrame:SetHeight(vFrame.SavedHeight);
end
function Outfitter_SetShowMinimapButton(pShowButton)
gOutfitter_Settings.Options.HideMinimapButton = not pShowButton;
if gOutfitter_Settings.Options.HideMinimapButton then
OutfitterMinimapButton:Hide();
else
OutfitterMinimapButton:Show();
end
Outfitter_Update(false);
end
function Outfitter_SetRememberVisibility(pRememberVisibility)
gOutfitter_Settings.Options.DisableAutoVisibility = not pRememberVisibility;
Outfitter_Update(false);
end
function Outfitter_SetShowHotkeyMessages(pShowHotkeyMessages)
gOutfitter_Settings.Options.DisableHotkeyMessages = not pShowHotkeyMessages;
Outfitter_Update(false);
end
function OutfitterMinimapDropDown_OnLoad()
UIDropDownMenu_SetAnchor(3, -7, this, "TOPRIGHT", this:GetName(), "TOPLEFT");
UIDropDownMenu_Initialize(this, OutfitterMinimapDropDown_Initialize);
--UIDropDownMenu_Refresh(this); -- Don't refresh on menus which don't have a text portion
Outfitter_RegisterOutfitEvent("WEAR_OUTFIT", OutfitterMinimapDropDown_OutfitEvent);
Outfitter_RegisterOutfitEvent("UNWEAR_OUTFIT", OutfitterMinimapDropDown_OutfitEvent);
end
function OutfitterMinimapDropDown_OutfitEvent(pEvent, pParameter1, pParameter2)
if UIDROPDOWNMENU_OPEN_MENU ~= "OutfitterMinimapButton" then
return;
end
UIDropDownMenu_Initialize(OutfitterMinimapButton, OutfitterMinimapDropDown_Initialize);
end
function OutfitterMinimapDropDown_AdjustScreenPosition(pMenu)
local vListFrame = getglobal("DropDownList1");
if not vListFrame:IsVisible() then
return;
end
local vCenterX, vCenterY = pMenu:GetCenter();
local vScreenWidth, vScreenHeight = GetScreenWidth(), GetScreenHeight();
local vAnchor;
local vOffsetX, vOffsetY;
if vCenterY < vScreenHeight / 2 then
vAnchor = "BOTTOM";
vOffsetY = -8;
else
vAnchor = "TOP";
vOffsetY = -17;
end
if vCenterX < vScreenWidth / 2 then
vAnchor = vAnchor.."LEFT";
vOffsetX = 21;
else
vAnchor = vAnchor.."RIGHT";
vOffsetX = 3;
end
vListFrame:ClearAllPoints();
vListFrame:SetPoint(vAnchor, pMenu.relativeTo, pMenu.relativePoint, vOffsetX, vOffsetY);
end
function Outfitter_OutfitIsVisible(pOutfit)
return not pOutfit.Disabled
and not Outfitter_IsEmptyOutfit(pOutfit);
end
function Outfitter_HasVisibleOutfits(pOutfits)
if not pOutfits then
return false;
end
for vIndex, vOutfit in pOutfits do
if Outfitter_OutfitIsVisible(vOutfit) then
return true;
end
end
return false;
end
function OutfitterMinimapDropDown_Initialize()
-- Just return if not initialized yet
if not gOutfitter_Initialized then
return;
end
--
local vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
Outfitter_AddCategoryMenuItem(Outfitter_cTitleVersion);
Outfitter_AddMenuItem(vFrame, Outfitter_cOpenOutfitter, 0);
OutfitterMinimapDropDown_InitializeOutfitList();
end
function Outfitter_GetCategoryOrder()
return gOutfitter_cCategoryOrder;
end
function Outfitter_GetOutfitsByCategoryID(pCategoryID)
return gOutfitter_Settings.Outfits[pCategoryID];
end
function OutfitterMinimapDropDown_InitializeOutfitList()
-- Just return if not initialized yet
if not gOutfitter_Initialized then
return;
end
--
local vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
local vEquippableItems = OutfitterItemList_GetEquippableItems();
local vCategoryOrder = Outfitter_GetCategoryOrder();
for vCategoryIndex, vCategoryID in vCategoryOrder do
local vCategoryName = getglobal("Outfitter_c"..vCategoryID.."Outfits");
local vOutfits = Outfitter_GetOutfitsByCategoryID(vCategoryID);
if Outfitter_HasVisibleOutfits(vOutfits) then
Outfitter_AddCategoryMenuItem(vCategoryName);
for vIndex, vOutfit in vOutfits do
if Outfitter_OutfitIsVisible(vOutfit) then
local vWearingOutfit = Outfitter_WearingOutfit(vOutfit);
local vMissingItems, vBankedItems = OutfitterItemList_GetMissingItems(vEquippableItems, vOutfit);
local vItemColor = NORMAL_FONT_COLOR;
if vMissingItems then
vItemColor = RED_FONT_COLOR;
elseif vBankedItems then
vItemColor = BANKED_FONT_COLOR;
end
Outfitter_AddMenuItem(vFrame, vOutfit.Name, {CategoryID = vCategoryID, Index = vIndex}, vWearingOutfit, nil, vItemColor);
end
end
end
end
end
function OutfitterDropDown_OnClick()
UIDropDownMenu_SetSelectedValue(this.owner, this.value);
OutfitterDropDown_OnClick2();
end
function OutfitterDropDown_OnClick2()
if this.owner.ChangedValueFunc then
this.owner.ChangedValueFunc(this.owner, this.value);
end
CloseDropDownMenus();
end
function OutfitterItem_SetTextColor(pItem, pRed, pGreen, pBlue)
local vItemNameField;
if pItem.isCategory then
vItemNameField = getglobal(pItem:GetName().."CategoryName");
else
vItemNameField = getglobal(pItem:GetName().."OutfitName");
end
vItemNameField:SetTextColor(pRed, pGreen, pBlue);
end
Outfitter_cCategoryDescriptions =
{
Complete = Outfitter_cCompleteCategoryDescripton,
Partial = Outfitter_cPartialCategoryDescription,
Accessory = Outfitter_cAccessoryCategoryDescription,
Special = Outfitter_cSpecialCategoryDescription,
OddsNEnds = Outfitter_cOddsNEndsCategoryDescription,
};
Outfitter_cMissingItemsSeparator = ", ";
function Outfitter_GenerateItemListString(pLabel, pListColorCode, pItems)
local vItemList = nil;
for vIndex, vOutfitItem in pItems do
if not vItemList then
vItemList = HIGHLIGHT_FONT_COLOR_CODE..pLabel..pListColorCode..vOutfitItem.Name;
else
vItemList = vItemList..Outfitter_cMissingItemsSeparator..vOutfitItem.Name;
end
end
return vItemList;
end
function OutfitterItem_OnEnter(pItem)
OutfitterItem_SetTextColor(pItem, HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b);
if pItem.isCategory then
local vDescription = Outfitter_cCategoryDescriptions[pItem.categoryID];
if vDescription then
local vCategoryName = getglobal("Outfitter_c"..pItem.categoryID.."Outfits");
GameTooltip_AddNewbieTip(vCategoryName, 1.0, 1.0, 1.0, vDescription, 1);
end
ResetCursor();
elseif pItem.isOutfitItem then
local vHasCooldown, vRepairCost;
GameTooltip:SetOwner(pItem, "ANCHOR_TOP");
if pItem.outfitItem.Location.SlotName then
if not pItem.outfitItem.Location.SlotID then
local vSlotID, vEmptySlotTexture = GetInventorySlotInfo(pItem.outfitItem.Location.SlotName);
pItem.outfitItem.Location.SlotID = vSlotID;
end
GameTooltip:SetInventoryItem("player", pItem.outfitItem.Location.SlotID);
else
vHasCooldown, vRepairCost = GameTooltip:SetBagItem(pItem.outfitItem.Location.BagIndex, pItem.outfitItem.Location.BagSlotIndex);
end
GameTooltip:Show();
if InRepairMode() and (vRepairCost and vRepairCost > 0) then
GameTooltip:AddLine(TEXT(REPAIR_COST), "", 1, 1, 1);
SetTooltipMoney(GameTooltip, vRepairCost);
GameTooltip:Show();
elseif MerchantFrame:IsShown() and MerchantFrame.selectedTab == 1 then
if pItem.outfitItem.Location.BagIndex then
ShowContainerSellCursor(pItem.outfitItem.Location.BagIndex, pItem.outfitItem.Location.BagSlotIndex);
end
else
ResetCursor();
end
else
local vOutfit = Outfitter_GetOutfitFromListItem(pItem);
if pItem.MissingItems
or pItem.BankedItems then
GameTooltip:SetOwner(pItem, "ANCHOR_LEFT");
GameTooltip:AddLine(vOutfit.Name);
if pItem.MissingItems then
local vItemList = Outfitter_GenerateItemListString(Outfitter_cMissingItemsLabel, RED_FONT_COLOR_CODE, pItem.MissingItems);
GameTooltip:AddLine(vItemList, RED_FONT_COLOR.r, RED_FONT_COLOR.g, RED_FONT_COLOR.b, true);
end
if pItem.BankedItems then
local vItemList = Outfitter_GenerateItemListString(Outfitter_cBankedItemsLabel, BANKED_FONT_COLOR_CODE, pItem.BankedItems);
GameTooltip:AddLine(vItemList, RED_FONT_COLOR.r, RED_FONT_COLOR.g, RED_FONT_COLOR.b, true);
end
GameTooltip:Show();
elseif vOutfit.SpecialID then
local vDescription = Outfitter_cSpecialOutfitDescriptions[vOutfit.SpecialID];
if vDescription then
GameTooltip_AddNewbieTip(vOutfit.Name, 1.0, 1.0, 1.0, vDescription, 1);
end
end
ResetCursor();
end
end
function OutfitterItem_OnLeave(pItem)
if pItem.isCategory then
OutfitterItem_SetTextColor(pItem, 1, 1, 1);
else
OutfitterItem_SetTextColor(pItem, pItem.DefaultColor.r, pItem.DefaultColor.g, pItem.DefaultColor.b);
end
GameTooltip:Hide();
end
function OutfitterItem_OnClick(pItem, pButton, pIgnoreModifiers)
if pItem.isCategory then
local vCategoryOutfits = gOutfitter_Settings.Outfits[pItem.categoryID];
gOutfitter_Collapsed[pItem.categoryID] = not gOutfitter_Collapsed[pItem.categoryID];
gOutfitter_DisplayIsDirty = true;
elseif pItem.isOutfitItem then
if pButton == "LeftButton" then
Outfitter_PickupItemLocation(pItem.outfitItem.Location);
StackSplitFrame:Hide();
else
if MerchantFrame:IsShown() and MerchantFrame.selectedTab == 2 then
-- Don't sell the item if the buyback tab is selected
return;
else
if pItem.outfitItem.Location.BagIndex then
UseContainerItem(pItem.outfitItem.Location.BagIndex, pItem.outfitItem.Location.BagSlotIndex);
StackSplitFrame:Hide();
end
end
end
else
local vOutfit = Outfitter_GetOutfitFromListItem(pItem);
if not vOutfit then
-- Error: outfit not found
return;
end
vOutfit.Disabled = nil;
Outfitter_WearOutfit(vOutfit, pItem.categoryID);
end
Outfitter_Update(true);
end
function OutfitterItem_CheckboxClicked(pItem)
if pItem.isCategory then
return;
end
local vOutfits = gOutfitter_Settings.Outfits[pItem.categoryID];
if not vOutfits then
-- Error: outfit category not found
return;
end
local vOutfit = vOutfits[pItem.outfitIndex];
if not vOutfit then
-- Error: outfit not found
return;
end
local vCheckbox = getglobal(pItem:GetName().."OutfitSelected");
if vCheckbox:GetChecked() then
vOutfit.Disabled = nil;
Outfitter_WearOutfit(vOutfit, pItem.categoryID);
else
Outfitter_RemoveOutfit(vOutfit);
end
Outfitter_Update(true);
end
function OutfitterItem_SetToOutfit(pItemIndex, pOutfit, pCategoryID, pOutfitIndex, pEquippableItems)
local vItemName = "OutfitterItem"..pItemIndex;
local vItem = getglobal(vItemName);
local vOutfitFrameName = vItemName.."Outfit";
local vOutfitFrame = getglobal(vOutfitFrameName);
local vItemFrame = getglobal(vItemName.."Item");
local vCategoryFrame = getglobal(vItemName.."Category");
local vMissingItems, vBankedItems = OutfitterItemList_GetMissingItems(pEquippableItems, pOutfit);
vOutfitFrame:Show();
vCategoryFrame:Hide();
vItemFrame:Hide();
local vItemSelectedCheckmark = getglobal(vOutfitFrameName.."Selected");
local vItemNameField = getglobal(vOutfitFrameName.."Name");
local vItemMenu = getglobal(vOutfitFrameName.."Menu");
vItemSelectedCheckmark:Show();
if Outfitter_WearingOutfit(pOutfit) then
vItemSelectedCheckmark:SetChecked(true);
else
vItemSelectedCheckmark:SetChecked(nil);
end
vItem.MissingItems = vMissingItems;
vItem.BankedItems = vBankedItems;
if pOutfit.Disabled then
vItemNameField:SetText(format(Outfitter_cDisabledOutfitName, pOutfit.Name));
vItem.DefaultColor = GRAY_FONT_COLOR;
else
vItemNameField:SetText(pOutfit.Name);
if vMissingItems then
vItem.DefaultColor = RED_FONT_COLOR;
elseif vBankedItems then
vItem.DefaultColor = BANKED_FONT_COLOR;
else
vItem.DefaultColor = NORMAL_FONT_COLOR;
end
end
vItemNameField:SetTextColor(vItem.DefaultColor.r, vItem.DefaultColor.g, vItem.DefaultColor.b);
vItemMenu:Show();
vItem.isCategory = false;
vItem.isOutfitItem = false;
vItem.outfitItem = nil;
vItem.categoryID = pCategoryID;
vItem.outfitIndex = pOutfitIndex;
vItem:Show();
-- Update the highlighting
if gOutfitter_SelectedOutfit == pOutfit then
OutfitterMainFrameHighlight:SetPoint("TOPLEFT", vItem, "TOPLEFT", 0, 0);
OutfitterMainFrameHighlight:Show();
end
end
function OutfitterItem_SetToItem(pItemIndex, pOutfitItem)
local vItemName = "OutfitterItem"..pItemIndex;
local vItem = getglobal(vItemName);
local vCategoryFrameName = vItemName.."Category";
local vItemFrameName = vItemName.."Item";
local vItemFrame = getglobal(vItemFrameName);
local vOutfitFrame = getglobal(vItemName.."Outfit");
local vCategoryFrame = getglobal(vCategoryFrameName);
vItem.isOutfitItem = true;
vItem.isCategory = false;
vItem.outfitItem = pOutfitItem;
vItemFrame:Show();
vOutfitFrame:Hide();
vCategoryFrame:Hide();
local vItemNameField = getglobal(vItemFrameName.."Name");
local vItemIcon = getglobal(vItemFrameName.."Icon");
vItemNameField:SetText(pOutfitItem.Name);
if pOutfitItem.Quality then
vItem.DefaultColor = ITEM_QUALITY_COLORS[pOutfitItem.Quality];
else
vItem.DefaultColor = GRAY_FONT_COLOR;
end
if pOutfitItem.Texture then
vItemIcon:SetTexture(pOutfitItem.Texture);
vItemIcon:Show();
else
vItemIcon:Hide();
end
vItemNameField:SetTextColor(vItem.DefaultColor.r, vItem.DefaultColor.g, vItem.DefaultColor.b);
vItem:Show();
end
function OutfitterItem_SetToCategory(pItemIndex, pCategoryID)
local vCategoryName = getglobal("Outfitter_c"..pCategoryID.."Outfits");
local vItemName = "OutfitterItem"..pItemIndex;
local vItem = getglobal(vItemName);
local vCategoryFrameName = vItemName.."Category";
local vOutfitFrame = getglobal(vItemName.."Outfit");
local vItemFrame = getglobal(vItemName.."Item");
local vCategoryFrame = getglobal(vCategoryFrameName);
vOutfitFrame:Hide();
vCategoryFrame:Show();
vItemFrame:Hide();
local vItemNameField = getglobal(vCategoryFrameName.."Name");
local vExpandButton = getglobal(vCategoryFrameName.."Expand");
vItem.MissingItems = nil;
vItem.BankedItems = nil;
if gOutfitter_Collapsed[pCategoryID] then
vExpandButton:SetNormalTexture("Interface\\Buttons\\UI-PlusButton-Up");
else
vExpandButton:SetNormalTexture("Interface\\Buttons\\UI-MinusButton-Up");
end
vItemNameField:SetText(vCategoryName);
vItem.isCategory = true;
vItem.isOutfitItem = false;
vItem.outfitItem = nil;
vItem.categoryID = pCategoryID;
vItem:Show();
end
function Outfitter_AddOutfitsToList(pOutfits, pCategoryID, pItemIndex, pFirstItemIndex, pEquippableItems)
local vOutfits = pOutfits[pCategoryID];
local vItemIndex = pItemIndex;
local vFirstItemIndex = pFirstItemIndex;
if vFirstItemIndex == 0 then
OutfitterItem_SetToCategory(vItemIndex, pCategoryID, false);
vItemIndex = vItemIndex + 1;
else
vFirstItemIndex = vFirstItemIndex - 1;
end
if vItemIndex >= Outfitter_cMaxDisplayedItems then
return vItemIndex, vFirstItemIndex;
end
if not gOutfitter_Collapsed[pCategoryID]
and vOutfits then
for vIndex, vOutfit in vOutfits do
if vFirstItemIndex == 0 then
OutfitterItem_SetToOutfit(vItemIndex, vOutfit, pCategoryID, vIndex, pEquippableItems);
vItemIndex = vItemIndex + 1;
if vItemIndex >= Outfitter_cMaxDisplayedItems then
return vItemIndex, vFirstItemIndex;
end
else
vFirstItemIndex = vFirstItemIndex - 1;
end
end
end
return vItemIndex, vFirstItemIndex;
end
function Outfitter_AddOutfitItemsToList(pOutfitItems, pCategoryID, pItemIndex, pFirstItemIndex)
local vItemIndex = pItemIndex;
local vFirstItemIndex = pFirstItemIndex;
if vFirstItemIndex == 0 then
OutfitterItem_SetToCategory(vItemIndex, pCategoryID, false);
vItemIndex = vItemIndex + 1;
else
vFirstItemIndex = vFirstItemIndex - 1;
end
if vItemIndex >= Outfitter_cMaxDisplayedItems then
return vItemIndex, vFirstItemIndex;
end
if not gOutfitter_Collapsed[pCategoryID] then
for vIndex, vOutfitItem in pOutfitItems do
if vFirstItemIndex == 0 then
OutfitterItem_SetToItem(vItemIndex, vOutfitItem);
vItemIndex = vItemIndex + 1;
if vItemIndex >= Outfitter_cMaxDisplayedItems then
return vItemIndex, vFirstItemIndex;
end
else
vFirstItemIndex = vFirstItemIndex - 1;
end
end
end
return vItemIndex, vFirstItemIndex;
end
function Outfitter_SortOutfits()
for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
table.sort(vOutfits, Outfiter_CompareOutfitNames);
end
end
function Outfiter_CompareOutfitNames(pOutfit1, pOutfit2)
return pOutfit1.Name < pOutfit2.Name;
end
function Outfitter_Update(pUpdateSlotEnables)
if not OutfitterFrame:IsVisible() then
return;
end
if gOutfitter_CurrentPanel == 1 then
-- Main panel
if not gOutfitter_DisplayIsDirty then
return;
end
gOutfitter_DisplayIsDirty = false;
-- Sort the outfits
Outfitter_SortOutfits();
-- Get the equippable items so outfits can be marked if they're missing anything
local vEquippableItems = OutfitterItemList_GetEquippableItems();
-- Update the slot enables if they're shown
if pUpdateSlotEnables
and OutfitterSlotEnables:IsVisible() then
Outfitter_UpdateSlotEnables(gOutfitter_SelectedOutfit, vEquippableItems);
end
OutfitterItemList_CompiledUnusedItemsList(vEquippableItems);
-- Update the list
OutfitterMainFrameHighlight:Hide();
local vFirstItemIndex = FauxScrollFrame_GetOffset(OutfitterMainFrameScrollFrame);
local vItemIndex = 0;
OutfitterItemList_ResetIgnoreItemFlags(vEquippableItems);
for vCategoryIndex, vCategoryID in gOutfitter_cCategoryOrder do
vItemIndex, vFirstItemIndex = Outfitter_AddOutfitsToList(gOutfitter_Settings.Outfits, vCategoryID, vItemIndex, vFirstItemIndex, vEquippableItems);
if vItemIndex >= Outfitter_cMaxDisplayedItems then
break;
end
end
if vItemIndex < Outfitter_cMaxDisplayedItems
and vEquippableItems.UnusedItems then
vItemIndex, vFirstItemIndex = Outfitter_AddOutfitItemsToList(vEquippableItems.UnusedItems, "OddsNEnds", vItemIndex, vFirstItemIndex);
end
-- Hide any unused items
for vItemIndex2 = vItemIndex, (Outfitter_cMaxDisplayedItems - 1) do
local vItemName = "OutfitterItem"..vItemIndex2;
local vItem = getglobal(vItemName);
vItem:Hide();
end
local vTotalNumItems = 0;
for vCategoryIndex, vCategoryID in gOutfitter_cCategoryOrder do
vTotalNumItems = vTotalNumItems + 1;
local vOutfits = gOutfitter_Settings.Outfits[vCategoryID];
if not gOutfitter_Collapsed[vCategoryID]
and vOutfits then
vTotalNumItems = vTotalNumItems + table.getn(vOutfits);
end
end
if vEquippableItems.UnusedItems then
vTotalNumItems = vTotalNumItems + 1;
if not gOutfitter_Collapsed["OddsNEnds"] then
vTotalNumItems = vTotalNumItems + table.getn(vEquippableItems.UnusedItems);
end
end
FauxScrollFrame_Update(
OutfitterMainFrameScrollFrame,
vTotalNumItems, -- numItems
Outfitter_cMaxDisplayedItems, -- numToDisplay
18, -- valueStep
nil, nil, nil, -- button, smallWidth, bigWidth
nil, -- highlightFrame
0, 0); -- smallHighlightWidth, bigHighlightWidth
elseif gOutfitter_CurrentPanel == 2 then -- Options panel
OutfitterShowMinimapButton:SetChecked(not gOutfitter_Settings.Options.HideMinimapButton);
OutfitterRememberVisibility:SetChecked(not gOutfitter_Settings.Options.DisableAutoVisibility);
OutfitterShowHotkeyMessages:SetChecked(not gOutfitter_Settings.Options.DisableHotkeyMessages);
end
end
function Outfitter_OnVerticalScroll()
gOutfitter_DisplayIsDirty = true;
Outfitter_Update(false);
end
function Outfitter_SelectOutfit(pOutfit, pCategoryID)
if not Outfitter_IsOpen() then
return;
end
gOutfitter_SelectedOutfit = pOutfit;
-- Get the equippable items so outfits can be marked if they're missing anything
local vEquippableItems = OutfitterItemList_GetEquippableItems();
-- Update the slot enables
Outfitter_UpdateSlotEnables(pOutfit, vEquippableItems);
OutfitterSlotEnables:Show();
-- Done, rebuild the list
gOutfitter_DisplayIsDirty = true;
end
function Outfitter_UpdateSlotEnables(pOutfit, pEquippableItems)
if UnitHasRelicSlot("player") then
OutfitterEnableAmmoSlot:Hide();
else
OutfitterEnableAmmoSlot:Show();
end
for _, vInventorySlot in Outfitter_cSlotNames do
local vOutfitItem = pOutfit.Items[vInventorySlot];
local vCheckbox = getglobal("OutfitterEnable"..vInventorySlot);
if not vOutfitItem then
vCheckbox:SetChecked(false);
else
if OutfitterItemList_InventorySlotContainsItem(pEquippableItems, vInventorySlot, vOutfitItem) then
vCheckbox:SetCheckedTexture("Interface\\Buttons\\UI-CheckBox-Check");
vCheckbox.IsUnknown = false;
else
vCheckbox:SetCheckedTexture("Interface\\Addons\\Outfitter\\Textures\\CheckboxUnknown");
vCheckbox.IsUnknown = true;
end
vCheckbox:SetChecked(true);
end
end
end
function Outfitter_ClearSelection()
gOutfitter_SelectedOutfit = nil;
gOutfitter_DisplayIsDirty = true;
OutfitterSlotEnables:Hide();
end
function Outfitter_FindOutfitItemIndex(pOutfit)
local vOutfitCategoryID, vOutfitIndex = Outfitter_FindOutfit(pOutfit);
if not vOutfitCategoryID then
return nil;
end
local vItemIndex = 0;
for vCategoryIndex, vCategoryID in gOutfitter_cCategoryOrder do
vItemIndex = vItemIndex + 1;
if not gOutfitter_Collapsed[vCategoryID] then
if vOutfitCategoryID == vCategoryID then
return vItemIndex + vOutfitIndex - 1;
else
vItemIndex = vItemIndex + table.getn(gOutfitter_Settings.Outfits[vCategoryID]);
end
end
end
return nil;
end
function OutfitterStack_FindOutfit(pOutfit)
for vIndex, vOutfit in gOutfitter_OutfitStack do
if vOutfit == pOutfit then
return true, vIndex;
end
end
return false, nil;
end
function OutfitterStack_FindOutfitByCategory(pCategoryID)
for vIndex, vOutfit in gOutfitter_OutfitStack do
if vOutfit.CategoryID == pCategoryID then
return true, vIndex;
end
end
return false, nil;
end
function OutfitterStack_Clear()
for vIndex, vOutfit in gOutfitter_OutfitStack do
Outfitter_DispatchOutfitEvent("UNWEAR_OUTFIT", vOutfit.Name, vOutfit)
end
gOutfitter_OutfitStack = {};
gOutfitter_Settings.LastOutfitStack = {};
gOutfitter_DisplayIsDirty = true;
if gOutfitter_Settings.Options.ShowStackContents then
Outfitter_DebugMessage("Outfitter stack cleared");
end
end
function OutfitterStack_ClearCategory(pCategoryID)
local vIndex = 1;
local vStackLength = table.getn(gOutfitter_OutfitStack);
local vChanged = false;
while vIndex <= vStackLength do
local vOutfit = gOutfitter_OutfitStack[vIndex];
if vOutfit
and vOutfit.CategoryID == pCategoryID then
Outfitter_DispatchOutfitEvent("UNWEAR_OUTFIT", vOutfit.Name, vOutfit)
table.remove(gOutfitter_OutfitStack, vIndex);
table.remove(gOutfitter_Settings.LastOutfitStack, vIndex);
vStackLength = vStackLength - 1;
vChanged = true;
else
vIndex = vIndex + 1;
end
end
OutfitterStack_CollapseTemporaryOutfits();
if vChanged then
if gOutfitter_Settings.Options.ShowStackContents then
OutfitterStack_DumpStackContents("Clear category "..pCategoryID);
end
gOutfitter_DisplayIsDirty = true;
end
end
function OutfitterStack_GetTemporaryOutfit()
local vStackSize = table.getn(gOutfitter_OutfitStack);
if vStackSize == 0 then
return nil;
end
local vOutfit = gOutfitter_OutfitStack[vStackSize];
if vOutfit.Name then
return nil;
end
return vOutfit;
end
function OutfitterStack_CollapseTemporaryOutfits()
local vIndex = 1;
local vStackLength = table.getn(gOutfitter_OutfitStack);
local vTemporaryOutfit1 = nil;
while vIndex <= vStackLength do
local vOutfit = gOutfitter_OutfitStack[vIndex];
if vOutfit
and vOutfit.Name == nil then
if vTemporaryOutfit1 then
-- Copy the items up
for vInventorySlot, vItem in vTemporaryOutfit1.Items do
if not vOutfit.Items[vInventorySlot] then
vOutfit.Items[vInventorySlot] = vItem;
end
end
-- Remove the lower temp outfit
table.remove(gOutfitter_OutfitStack, vIndex - 1);
vStackLength = vStackLength - 1;
else
vIndex = vIndex + 1;
end
vTemporaryOutfit1 = vOutfit;
else
vTemporaryOutfit1 = nil;
vIndex = vIndex + 1;
end
end
end
function OutfitterStack_IsTopmostOutfit(pOutfit)
local vStackLength = table.getn(gOutfitter_OutfitStack);
if vStackLength == 0 then
return false;
end
return gOutfitter_OutfitStack[vStackLength] == pOutfit;
end
function OutfitterStack_AddOutfit(pOutfit, pBelowOutfit)
local vFound, vIndex = OutfitterStack_FindOutfit(pOutfit);
-- If it's already on then remove it from the stack
-- so it can be added to the end
if vFound then
table.remove(gOutfitter_OutfitStack, vIndex);
table.remove(gOutfitter_Settings.LastOutfitStack, vIndex);
Outfitter_DispatchOutfitEvent("UNWEAR_OUTFIT", pOutfit.Name, pOutfit);
end
-- Figure out the position to insert at
local vStackLength = table.getn(gOutfitter_OutfitStack);
local vInsertIndex = vStackLength + 1;
if pBelowOutfit then
local vFound2, vIndex = OutfitterStack_FindOutfit(pBelowOutfit);
if vFound2 then
vInsertIndex = vIndex;
end
end
--[[ Always insert below the temporary outfit
local vTemporaryOutfit;
if vStackLength > 0 then
vTemporaryOutfit = gOutfitter_OutfitStack[vStackLength];
end
if vTemporaryOutfit and vTemporaryOutfit.Name == nil then
-- Knock out any slots used by the new outfit if it's being inserted at the top
if vInsertIndex >= vStackLength then
for vInventorySlot, vItem in pOutfit.Items do
vTemporaryOutfit.Items[vInventorySlot] = nil;
end
-- Remove the temporary outfit if it's empty now
if Outfitter_IsEmptyOutfit(vTemporaryOutfit) then
table.remove(gOutfitter_OutfitStack, vStackLength);
table.remove(gOutfitter_Settings.LastOutfitStack, vStackLength);
vInsertIndex = vStackLength;
vStackLength = vStackLength - 1;
else
vInsertIndex = vStackLength;
end
end
end
]]--
-- Add the outfit
table.insert(gOutfitter_OutfitStack, vInsertIndex, pOutfit);
if pOutfit.Name then
table.insert(gOutfitter_Settings.LastOutfitStack, vInsertIndex, {Name = pOutfit.Name});
else
table.insert(gOutfitter_Settings.LastOutfitStack, vInsertIndex, pOutfit);
end
gOutfitter_DisplayIsDirty = true;
if gOutfitter_Settings.Options.ShowStackContents then
OutfitterStack_DumpStackContents("Add outfit");
end
if vFound then
OutfitterStack_CollapseTemporaryOutfits();
end
Outfitter_DispatchOutfitEvent("WEAR_OUTFIT", pOutfit.Name, pOutfit);
end
function OutfitterStack_RemoveOutfit(pOutfit)
local vFound, vIndex = OutfitterStack_FindOutfit(pOutfit);
if not vFound then
return false;
end
-- Remove the outfit
table.remove(gOutfitter_OutfitStack, vIndex);
table.remove(gOutfitter_Settings.LastOutfitStack, vIndex);
OutfitterStack_CollapseTemporaryOutfits();
gOutfitter_DisplayIsDirty = true;
if gOutfitter_Settings.Options.ShowStackContents then
OutfitterStack_DumpStackContents("Remove outfit");
end
return true;
end
function OutfitterStack_RestoreSavedStack()
if not gOutfitter_Settings.LastOutfitStack then
gOutfitter_Settings.LastOutfitStack = {};
end
for vIndex, vOutfit in gOutfitter_Settings.LastOutfitStack do
if vOutfit.Name then
vOutfit = Outfitter_FindOutfitByName(vOutfit.Name);
end
if vOutfit then
table.insert(gOutfitter_OutfitStack, vOutfit);
end
end
gOutfitter_ExpectedOutfit = Outfitter_GetCompiledOutfit();
Outfitter_UpdateTemporaryOutfit(Outfitter_GetNewItemsOutfit(gOutfitter_ExpectedOutfit));
if gOutfitter_Settings.Options.ShowStackContents then
OutfitterStack_DumpStackContents("Restore saved stack");
end
end
function OutfitterStack_DumpStackContents(pOperation)
Outfitter_DebugMessage("Outfitter Stack Contents: "..pOperation);
for vIndex, vOutfit in gOutfitter_OutfitStack do
if vOutfit.Name then
Outfitter_DebugMessage("Slot "..vIndex..": "..vOutfit.Name);
else
Outfitter_DebugMessage("Slot "..vIndex..": Temporaray outfit");
end
end
end
function Outfitter_WearOutfit(pOutfit, pCategoryID, pWearBelowOutfit)
if pOutfit.Disabled then
return;
end
--
Outfitter_BeginEquipmentUpdate();
if pCategoryID == "Complete" then
OutfitterStack_Clear();
elseif pCategoryID == "Partial" then
OutfitterStack_ClearCategory(pCategoryID);
OutfitterStack_ClearCategory("Accessory");
end
OutfitterStack_AddOutfit(pOutfit, pWearBelowOutfit);
-- If outfitter is open then also select the outfit
if Outfitter_IsOpen() then
if OutfitterStack_IsTopmostOutfit(pOutfit) then
Outfitter_SelectOutfit(pOutfit, pCategoryID);
else
Outfitter_ClearSelection();
end
end
-- Update the equipment
gOutfitter_EquippedNeedsUpdate = true;
gOutfitter_WeaponsNeedUpdate = true;
Outfitter_EndEquipmentUpdate("Outfitter_WearOutfit");
end
function Outfitter_SetOutfitBindingIndex(pOutfit, pBindingIndex)
for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
for vOutfitIndex, vOutfit in vOutfits do
if vOutfit.BindingIndex == pBindingIndex then
vOutfit.BindingIndex = nil;
end
end
end
pOutfit.BindingIndex = pBindingIndex;
end
local gOutfitter_LastBindingIndex = nil;
local gOutfitter_LastBindingTime = nil;
local Outfitter_cMinBindingTime = 0.75;
function Outfitter_WearBoundOutfit(pBindingIndex)
-- Check for the user spamming the button so prevent the outfit from
-- toggling if they're panicking
local vTime = GetTime();
if gOutfitter_LastBindingIndex == pBindingIndex then
local vElapsed = vTime - gOutfitter_LastBindingTime;
if vElapsed < Outfitter_cMinBindingTime then
gOutfitter_LastBindingTime = vTime;
return;
end
end
--
for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
for vOutfitIndex, vOutfit in vOutfits do
if vOutfit.BindingIndex == pBindingIndex then
vOutfit.Disabled = nil;
if vCategoryID == "Complete" then
Outfitter_WearOutfit(vOutfit, vCategoryID);
if not gOutfitter_Settings.Options.DisableHotkeyMessages then
UIErrorsFrame:AddMessage(format(Outfitter_cEquipOutfitMessageFormat, vOutfit.Name), OUTFIT_MESSAGE_COLOR.r, OUTFIT_MESSAGE_COLOR.g, OUTFIT_MESSAGE_COLOR.b);
end
else
local vEquipped = Outfitter_ToggleOutfit(vOutfit, vCategoryID);
if not gOutfitter_Settings.Options.DisableHotkeyMessages then
if vEquipped then
UIErrorsFrame:AddMessage(format(Outfitter_cEquipOutfitMessageFormat, vOutfit.Name), OUTFIT_MESSAGE_COLOR.r, OUTFIT_MESSAGE_COLOR.g, OUTFIT_MESSAGE_COLOR.b);
else
UIErrorsFrame:AddMessage(format(Outfitter_cUnequipOutfitMessageFormat, vOutfit.Name), OUTFIT_MESSAGE_COLOR.r, OUTFIT_MESSAGE_COLOR.g, OUTFIT_MESSAGE_COLOR.b);
end
end
end
-- Remember the binding used to filter for button spam
gOutfitter_LastBindingIndex = pBindingIndex;
gOutfitter_LastBindingTime = vTime;
return;
end
end
end
end
function Outfitter_FindOutfit(pOutfit)
for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
for vOutfitIndex, vOutfit in vOutfits do
if vOutfit == pOutfit then
return vCategoryID, vOutfitIndex;
end
end
end
return nil, nil;
end
function Outfitter_FindOutfitByName(pName)
if not pName
or pName == "" then
return nil;
end
local vLowerName = strlower(pName);
for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
for vOutfitIndex, vOutfit in vOutfits do
if strlower(vOutfit.Name) == vLowerName then
return vOutfit, vCategoryID, vOutfitIndex;
end
end
end
return nil, nil;
end
-- Outfitter doesn't use this function, but other addons such as
-- Fishing Buddy might use it to locate specific generated outfits
function Outfitter_FindOutfitByStatID(pStatID)
if not pStatID or pStatID == "" then
return nil;
end
for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
for vOutfitIndex, vOutfit in vOutfits do
if vOutfit.StatID and vOutfit.StatID == pStatID then
return vOutfit, vCategoryID, vOutfitIndex;
end
end
end
return nil;
end
function Outfitter_RemoveOutfit(pOutfit)
if not OutfitterStack_RemoveOutfit(pOutfit) then
return;
end
-- Stop monitoring health and mana if it's the dining outfit
if pOutfit.SpecialID == "Dining" then
Outfitter_SuspendEvent(OutfitterFrame, "UNIT_HEALTH");
Outfitter_SuspendEvent(OutfitterFrame, "UNIT_MANA");
end
--
Outfitter_BeginEquipmentUpdate();
-- Clear the selection if the outfit being removed
-- is selected too
if gOutfitter_SelectedOutfit == pOutfit then
Outfitter_ClearSelection();
end
-- Update the list
gOutfitter_EquippedNeedsUpdate = true;
gOutfitter_WeaponsNeedUpdate = true;
Outfitter_EndEquipmentUpdate("Outfitter_RemoveOutfit");
Outfitter_DispatchOutfitEvent("UNWEAR_OUTFIT", pOutfit.Name, pOutfit);
end
function Outfitter_ToggleOutfit(pOutfit, pCategoryID)
if Outfitter_WearingOutfit(pOutfit) then
Outfitter_RemoveOutfit(pOutfit);
return false;
else
Outfitter_WearOutfit(pOutfit, pCategoryID);
return true;
end
end
function Outfitter_OutfitSummary()
local _, vPlayerClass = UnitClass("player");
local vStatDistribution = gOutfitter_StatDistribution[vPlayerClass];
local vCurrentOutfitStats = OutfitterTankPoints_GetCurrentOutfitStats(vStatDistribution);
Outfitter_DumpArray("Current Stats", vCurrentOutfitStats);
end
function Outfitter_GetCompiledOutfit()
local vCompiledOutfit = Outfitter_NewEmptyOutfit();
vCompiledOutfit.SourceOutfit = {};
for vStackIndex, vOutfit in gOutfitter_OutfitStack do
for vInventorySlot, vOutfitItem in vOutfit.Items do
vCompiledOutfit.Items[vInventorySlot] = vOutfitItem;
vCompiledOutfit.SourceOutfit[vInventorySlot] = vOutfit.Name;
end
end
return vCompiledOutfit;
end
function Outfitter_GetExpectedOutfit(pExcludeOutfit)
local vCompiledOutfit = Outfitter_NewEmptyOutfit();
vCompiledOutfit.SourceOutfit = {};
for vStackIndex, vOutfit in gOutfitter_OutfitStack do
if vOutfit ~= pExcludeOutfit then
for vInventorySlot, vOutfitItem in vOutfit.Items do
vCompiledOutfit.Items[vInventorySlot] = vOutfitItem;
vCompiledOutfit.SourceOutfit[vInventorySlot] = vOutfit.Name;
end
end
end
return vCompiledOutfit;
end
function Outfitter_GetEmptyBagSlot(pStartBagIndex, pStartBagSlotIndex, pIncludeBank)
local vStartBagIndex = pStartBagIndex;
local vStartBagSlotIndex = pStartBagSlotIndex;
if not vStartBagIndex then
vStartBagIndex = NUM_BAG_SLOTS;
end
if not vStartBagSlotIndex then
vStartBagSlotIndex = 1;
end
local vEndBagIndex = 0;
if pIncludeBank then
vEndBagIndex = -1;
end
for vBagIndex = vStartBagIndex, vEndBagIndex, -1 do
-- Skip the bag if it's a specialty bag (ammo pouch, quiver, shard bag)
local vSkipBag = false;
if vBagIndex > 0 then -- Don't worry about the backpack
local vItemLink = GetInventoryItemLink("player", ContainerIDToInventoryID(vBagIndex));
local vItemInfo = Outfitter_GetItemInfoFromLink(vItemLink);
if vItemInfo
and Outfitter_cSpecialtyBags[vItemInfo.Code] ~= nil then
vSkipBag = true;
end
end
-- Search the bag for empty slots
if not vSkipBag then
local vNumBagSlots = GetContainerNumSlots(vBagIndex);
if vNumBagSlots > 0 then
for vSlotIndex = vStartBagSlotIndex, vNumBagSlots do
local vItemInfo = Outfitter_GetBagItemInfo(vBagIndex, vSlotIndex);
if not vItemInfo then
return {BagIndex = vBagIndex, BagSlotIndex = vSlotIndex};
end
end
end
end
vStartBagSlotIndex = 1;
end
return nil;
end
function Outfitter_GetEmptyBagSlotList()
local vEmptyBagSlots = {};
local vBagIndex = NUM_BAG_SLOTS;
local vBagSlotIndex = 1;
while true do
local vBagSlotInfo = Outfitter_GetEmptyBagSlot(vBagIndex, vBagSlotIndex);
if not vBagSlotInfo then
return vEmptyBagSlots;
end
table.insert(vEmptyBagSlots, vBagSlotInfo);
vBagIndex = vBagSlotInfo.BagIndex;
vBagSlotIndex = vBagSlotInfo.BagSlotIndex + 1;
end
end
function Outfitter_GetEmptyBankSlotList()
local vEmptyBagSlots = {};
local vBagIndex = NUM_BAG_SLOTS + NUM_BANKBAGSLOTS;
local vBagSlotIndex = 1;
while true do
local vBagSlotInfo = Outfitter_GetEmptyBagSlot(vBagIndex, vBagSlotIndex, true);
if not vBagSlotInfo then
return vEmptyBagSlots;
elseif vBagSlotInfo.BagIndex > NUM_BAG_SLOTS
or vBagSlotInfo.BagIndex < 0 then
table.insert(vEmptyBagSlots, vBagSlotInfo);
end
vBagIndex = vBagSlotInfo.BagIndex;
vBagSlotIndex = vBagSlotInfo.BagSlotIndex + 1;
end
end
function Outfitter_FindItemsInBagsForSlot(pSlotName)
local vInventorySlot = pSlotName;
-- Alias the slot names down for finger and trinket
if vInventorySlot == "Finger1Slot" then
vInventorySlot = "Finger0Slot";
elseif vInventorySlot == "Trinket1Slot" then
vInventorySlot = "Trinket0Slot";
end
--
local vItems = {};
local vNumBags, vFirstBagIndex = Outfitter_GetNumBags();
for vBagIndex = vFirstBagIndex, vNumBags do
local vNumBagSlots = GetContainerNumSlots(vBagIndex);
if vNumBagSlots > 0 then
for vSlotIndex = 1, vNumBagSlots do
local vItemInfo = Outfitter_GetBagItemInfo(vBagIndex, vSlotIndex);
if vItemInfo then
local vItemSlotName = vItemInfo.ItemSlotName;
if vItemInfo.MetaSlotName then
vItemSlotName = vItemInfo.MetaSlotName;
end
if vItemSlotName == "TwoHandSlot" then
vItemSlotName = "MainHandSlot";
elseif vItemSlotName == "Weapon0Slot" then
if vInventorySlot == "MainHandSlot"
or vInventorySlot == "SecondaryHandSlot" then
vItemSlotName = vInventorySlot;
end
end
if vItemSlotName == vInventorySlot then
table.insert(vItems, {BagIndex = vBagIndex, BagSlotIndex = vSlotIndex, Code = vItemInfo.Code, Name = vItemInfo.Name});
end
end
end
end
end
if table.getn(vItems) == 0 then
return nil;
end
return vItems;
end
function Outfitter_PickupItemLocation(pItemLocation)
if pItemLocation == nil then
Outfitter_ErrorMessage("Outfitter: nil location in PickupItemLocation");
return;
end
if pItemLocation.BagIndex then
PickupContainerItem(pItemLocation.BagIndex, pItemLocation.BagSlotIndex);
elseif pItemLocation.SlotName then
local vSlotID, vEmptySlotTexture = GetInventorySlotInfo(pItemLocation.SlotName);
PickupInventoryItem(vSlotID);
else
Outfitter_ErrorMessage("Outfitter: Unknown location in PickupItemLocation");
return;
end
end
function Outfitter_BuildUnequipChangeList(pOutfit, pEquippableItems)
local vEquipmentChangeList = {};
for vInventorySlot, vOutfitItem in pOutfit.Items do
local vItem, vIgnoredItem = OutfitterItemList_FindItemOrAlt(pEquippableItems, vOutfitItem, true);
if vItem then
table.insert(vEquipmentChangeList, {FromLocation = vItem.Location, Item = vItem, ToLocation = nil});
end
end -- for
return vEquipmentChangeList;
end
function Outfitter_BuildEquipmentChangeList(pOutfit, pEquippableItems)
local vEquipmentChangeList = {};
OutfitterItemList_ResetIgnoreItemFlags(pEquippableItems);
-- Remove items which are already in the correct slot from the outfit and from the
-- equippable items list
for vInventorySlot, vOutfitItem in pOutfit.Items do
local vContainsItem, vItem = OutfitterItemList_InventorySlotContainsItem(pEquippableItems, vInventorySlot, vOutfitItem);
if vContainsItem then
pOutfit.Items[vInventorySlot] = nil;
if vItem then
vItem.IgnoreItem = true;
end
end
end
-- Scan the outfit using the Outfitter_cSlotNames array as an index so that changes
-- are executed in the specified order
for _, vInventorySlot in Outfitter_cSlotNames do
local vOutfitItem = pOutfit.Items[vInventorySlot];
if vOutfitItem then
local vSlotID, vEmptySlotTexture = GetInventorySlotInfo(vInventorySlot);
local vCurrentItemInfo = Outfitter_GetInventoryItemInfo(vInventorySlot);
-- Empty the slot if it's supposed to be blank
if vOutfitItem.Code == 0 then
if vCurrentItemInfo then
table.insert(vEquipmentChangeList, {SlotName = vInventorySlot, SlotID = vSlotID, ItemName = vOutfitItem.Name, ItemLocation = nil});
end
else
-- Find the item
local vItem, vIgnoredItem = OutfitterItemList_FindItemOrAlt(pEquippableItems, vOutfitItem, true);
-- If the item wasn't found then show an appropriate error message
if not vItem then
if vOutfitItem.Name then
if vIgnoredItem then
local vSlotDisplayName = Outfitter_cSlotDisplayNames[vInventorySlot];
if not vSlotDisplayName then
vSlotDisplayName = vInventorySlot;
end
Outfitter_ErrorMessage(format(Outfitter_cItemAlreadyUsedError, vOutfitItem.Name, vSlotDisplayName));
else
Outfitter_ErrorMessage(format(Outfitter_cItemNotFoundError, vOutfitItem.Name));
end
else
Outfitter_ErrorMessage(format(Outfitter_cItemNotFoundError, "unknown"));
end
-- Generate a change to move the item from its present location to the correct slot
else
pOutfit.Items[vInventorySlot].MetaSlotName = vItem.MetaSlotName;
table.insert(vEquipmentChangeList, {SlotName = vInventorySlot, SlotID = vSlotID, ItemName = vOutfitItem.Name, ItemMetaSlotName = vItem.MetaSlotName, ItemLocation = vItem});
end
end
end -- if
end -- for
if table.getn(vEquipmentChangeList) == 0 then
return nil;
end
Outfitter_OptimizeEquipmentChangeList(vEquipmentChangeList);
return vEquipmentChangeList;
end
function Outfitter_FindEquipmentChangeForSlot(pEquipmentChangeList, pSlotName)
for vChangeIndex, vEquipmentChange in pEquipmentChangeList do
if vEquipmentChange.SlotName == pSlotName then
return vChangeIndex, vEquipmentChange;
end
end
return nil, nil;
end
function Outfitter_FixSlotSwapChange(pEquipmentList, pChangeIndex1, pEquipmentChange1, pSlotName1, pChangeIndex2, pEquipmentChange2, pSlotName2)
-- No problem if both slots will be emptied
if not pEquipmentChange1.ItemLocation
and not pEquipmentChange2.ItemLocation then
return;
end
-- No problem if neither slot is being moved to the other one
local vSlot2ToSlot1 = pEquipmentChange1.ItemLocation ~= nil
and pEquipmentChange1.ItemLocation.SlotName == pSlotName2;
local vSlot1ToSlot2 = pEquipmentChange2.ItemLocation ~= nil
and pEquipmentChange2.ItemLocation.SlotName == pSlotName1;
-- No problem if the slots are swapping with each other
-- or not moving between each other at all
if vSlot2ToSlot1 == vSlot1ToSlot2 then
return;
end
-- Slot 1 is moving to slot 2
if vSlot1ToSlot2 then
if pEquipmentChange1.ItemLocation then
-- Swap change 1 and change 2 around
pEquipmentList[pChangeIndex1] = pEquipmentChange2;
pEquipmentList[pChangeIndex2] = pEquipmentChange1;
-- Insert a change to empty slot 2
table.insert(pEquipmentList, pChangeIndex1, {SlotName = pEquipmentChange2.SlotName, SlotID = pEquipmentChange2.SlotID, ItemLocation = nil});
else
-- Slot 1 is going to be empty, so empty slot 2 instead
-- and then when slot 1 is moved it'll swap the empty space
pEquipmentChange1.SlotName = pSlotName2;
pEquipmentChange1.SlotID = pEquipmentChange2.SlotID;
pEquipmentChange1.ItemLocation = nil;
end
-- Slot 2 is moving to slot 1
else
if pEquipmentChange2.ItemLocation then
-- Insert a change to empty slot 1 first
table.insert(pEquipmentList, pChangeIndex1, {SlotName = pEquipmentChange1.SlotName, SlotID = pEquipmentChange1.SlotID, ItemLocation = nil});
else
-- Slot 2 is going to be empty, so empty slot 1 instead
-- and then when slot 2 is moved it'll swap the empty space
pEquipmentChange2.SlotName = pSlotName1;
pEquipmentChange2.SlotID = pEquipmentChange1.SlotID;
pEquipmentChange2.ItemLocation = nil;
-- Change the order so that slot 1 gets emptied before the move
pEquipmentList[pChangeIndex1] = pEquipmentChange2;
pEquipmentList[pChangeIndex2] = pEquipmentChange1;
end
end
end
function Outfitter_OptimizeEquipmentChangeList(pEquipmentChangeList)
local vSwapList =
{
{Slot1 = "Finger0Slot", Slot2 = "Finger1Slot"},
{Slot1 = "Trinket0Slot", Slot2 = "Trinket1Slot"},
{Slot1 = "MainHandSlot", Slot2 = "SecondaryHandSlot"},
};
local vDidSlot = {};
local vChangeIndex = 1;
local vNumChanges = table.getn(pEquipmentChangeList);
while vChangeIndex <= vNumChanges do
local vEquipmentChange = pEquipmentChangeList[vChangeIndex];
-- If a two-hand weapon is being equipped, remove the change event
-- for removing the offhand slot
if vEquipmentChange.ItemMetaSlotName == "TwoHandSlot" then
local vChangeIndex2, vEquipmentChange2 = Outfitter_FindEquipmentChangeForSlot(pEquipmentChangeList, "SecondaryHandSlot");
-- If there's a change for the offhand slot, remove it
if vChangeIndex2 then
table.remove(pEquipmentChangeList, vChangeIndex2);
if vChangeIndex2 < vChangeIndex then
vChangeIndex = vChangeIndex - 1;
end
vNumChanges = vNumChanges - 1;
end
-- Insert a new change for the offhand slot to empty it ahead
-- of equipping the two-hand item
local vSlotID, vEmptySlotTexture = GetInventorySlotInfo("SecondaryHandSlot");
table.insert(pEquipmentChangeList, vChangeIndex, {SlotName = "SecondaryHandSlot", SlotID = vSlotID, ItemLocation = nil});
-- Otherwise see if the change needs to be re-arranged so that slot
-- swapping works correctly
else
for vSwapListIndex, vSwapSlotInfo in vSwapList do
if vEquipmentChange.SlotName == vSwapSlotInfo.Slot1
and not vDidSlot[vEquipmentChange.SlotName] then
local vChangeIndex2, vEquipmentChange2 = Outfitter_FindEquipmentChangeForSlot(pEquipmentChangeList, vSwapSlotInfo.Slot2);
if vChangeIndex2 then
Outfitter_FixSlotSwapChange(pEquipmentChangeList, vChangeIndex, vEquipmentChange, vSwapSlotInfo.Slot1, vChangeIndex2, vEquipmentChange2, vSwapSlotInfo.Slot2);
end
vDidSlot[vEquipmentChange.SlotName] = true;
vNumChanges = table.getn(pEquipmentChangeList);
end
end
end
vChangeIndex = vChangeIndex + 1;
end
end
function Outfitter_ExecuteEquipmentChangeList(pEquipmentChangeList, pEmptyBagSlots, pExpectedEquippableItems)
for vChangeIndex, vEquipmentChange in pEquipmentChangeList do
if vEquipmentChange.ItemLocation then
Outfitter_PickupItemLocation(vEquipmentChange.ItemLocation);
EquipCursorItem(vEquipmentChange.SlotID);
if pExpectedEquippableItems then
OutfitterItemList_SwapLocationWithInventorySlot(pExpectedEquippableItems, vEquipmentChange.ItemLocation, vEquipmentChange.SlotName);
end
else
-- Remove the item
if not pEmptyBagSlots
or table.getn(pEmptyBagSlots) == 0 then
local vItemInfo = Outfitter_GetInventoryItemInfo(vEquipmentChange.SlotName);
if not vItemInfo then
Outfitter_ErrorMessage("Outfitter internal error: Can't empty slot "..vEquipmentChange.SlotName.." because bags are full but slot is empty");
else
Outfitter_ErrorMessage(format(Outfitter_cBagsFullError, vItemInfo.Name));
end
else
local vBagIndex = pEmptyBagSlots[1].BagIndex;
local vBagSlotIndex = pEmptyBagSlots[1].BagSlotIndex;
table.remove(pEmptyBagSlots, 1);
PickupInventoryItem(vEquipmentChange.SlotID);
PickupContainerItem(vBagIndex, vBagSlotIndex);
if pExpectedEquippableItems then
OutfitterItemList_SwapBagSlotWithInventorySlot(pExpectedEquippableItems, vBagIndex, vBagSlotIndex, vEquipmentChange.SlotName);
end
end
end
end
end
function Outfitter_ExecuteEquipmentChangeList2(pEquipmentChangeList, pEmptySlots, pBagsFullErrorFormat, pExpectedEquippableItems)
for vChangeIndex, vEquipmentChange in pEquipmentChangeList do
if vEquipmentChange.ToLocation then
Outfitter_PickupItemLocation(vEquipmentChange.FromLocation);
EquipCursorItem(vEquipmentChange.SlotID);
if pExpectedEquippableItems then
OutfitterItemList_SwapLocationWithInventorySlot(pExpectedEquippableItems, vEquipmentChange.ToLocation, vEquipmentChange.SlotName);
end
else
-- Remove the item
if not pEmptySlots
or table.getn(pEmptySlots) == 0 then
Outfitter_ErrorMessage(format(pBagsFullErrorFormat, vEquipmentChange.Item.Name));
else
local vToLocation = {BagIndex = pEmptySlots[1].BagIndex, BagSlotIndex = pEmptySlots[1].BagSlotIndex};
table.remove(pEmptySlots, 1);
Outfitter_PickupItemLocation(vEquipmentChange.FromLocation);
Outfitter_PickupItemLocation(vToLocation);
if pExpectedEquippableItems then
OutfitterItemList_SwapLocations(pExpectedEquippableItems, vEquipmentChange.FromLocation, vToLocation);
end
end
end
end
end
function Outfitter_OutfitHasCombatEquipmentSlots(pOutfit)
for vEquipmentSlot, _ in Outfitter_cCombatEquipmentSlots do
if pOutfit.Items[vEquipmentSlot] then
return true;
end
end
return false;
end
function Outfitter_OutfitOnlyHasCombatEquipmentSlots(pOutfit)
for vEquipmentSlot, _ in pOutfit.Items do
if not Outfitter_cCombatEquipmentSlots[vEquipmentSlot] then
return false;
end
end
return true;
end
local gOutfitter_EquipmentUpdateCount = 0;
function Outfitter_BeginEquipmentUpdate()
gOutfitter_EquipmentUpdateCount = gOutfitter_EquipmentUpdateCount + 1;
end
function Outfitter_EndEquipmentUpdate(pCallerName)
gOutfitter_EquipmentUpdateCount = gOutfitter_EquipmentUpdateCount - 1;
if gOutfitter_EquipmentUpdateCount == 0 then
Outfitter_UpdateEquippedItems();
Outfitter_Update(false);
end
end
function Outfitter_UpdateEquippedItems()
if not gOutfitter_EquippedNeedsUpdate
and not gOutfitter_WeaponsNeedUpdate then
return;
end
-- Delay all changes until they're alive
if gOutfitter_IsDead then
-- or gOutfitter_IsFeigning then -- no longer disabling outfit changes during FD
return;
end
local vCurrentTime = GetTime();
if vCurrentTime - gOutfitter_LastEquipmentUpdateTime < Outfitter_cMinEquipmentUpdateInterval then
OutfitterTimer_AdjustTimer();
return;
end
gOutfitter_LastEquipmentUpdateTime = vCurrentTime;
local vWeaponsNeedUpdate = gOutfitter_WeaponsNeedUpdate;
gOutfitter_EquippedNeedsUpdate = false;
gOutfitter_WeaponsNeedUpdate = false;
-- Compile the outfit
local vEquippableItems = OutfitterItemList_GetEquippableItems();
local vCompiledOutfit = Outfitter_GetCompiledOutfit();
-- If the outfit contains non-weapon changes then
-- delay the change until they're out of combat but go
-- ahead and swap the weapon slots if there are any
if gOutfitter_InCombat then
if vWeaponsNeedUpdate
and Outfitter_OutfitHasCombatEquipmentSlots(vCompiledOutfit) then
-- Allow the weapon change to proceed but defer the rest
-- until they're out of combat
local vWeaponOutfit = Outfitter_NewEmptyOutfit();
for vEquipmentSlot, _ in Outfitter_cCombatEquipmentSlots do
vWeaponOutfit.Items[vEquipmentSlot] = vCompiledOutfit.Items[vEquipmentSlot];
end
-- Still need to update the rest once they exit combat
-- if there are non-equipment slot items
if not Outfitter_OutfitOnlyHasCombatEquipmentSlots(vCompiledOutfit) then
gOutfitter_EquippedNeedsUpdate = true;
end
-- Switch to the weapons-only part
vCompiledOutfit = vWeaponOutfit;
else
-- No weapon changes, just defer the whole outfit change
gOutfitter_EquippedNeedsUpdate = true;
return;
end
end
-- Equip it
local vEquipmentChangeList = Outfitter_BuildEquipmentChangeList(vCompiledOutfit, vEquippableItems);
if vEquipmentChangeList then
-- local vExpectedEquippableItems = OutfitterItemList_New();
Outfitter_ExecuteEquipmentChangeList(vEquipmentChangeList, Outfitter_GetEmptyBagSlotList(), vExpectedEquippableItems);
-- Outfitter_DumpArray("ExpectedEquippableItems", vExpectedEquippableItems);
end
-- Update the outfit we're expecting to see on the player
for vInventorySlot, vItem in vCompiledOutfit.Items do
gOutfitter_ExpectedOutfit.Items[vInventorySlot] = vCompiledOutfit.Items[vInventorySlot];
end
end
function Outfitter_InitDebugging()
if gOutfitter_InitializedDebug then
return;
end
gOutfitter_InitializedDebug = true;
-- Find the debug frame if there is one
for vChatIndex = 1, NUM_CHAT_WINDOWS do
local vChatFrame = getglobal("ChatFrame"..vChatIndex);
if vChatFrame
and (vChatFrame:IsVisible() or vChatFrame.isDocked) then
local vTab = getglobal("ChatFrame"..vChatIndex.."Tab");
local vName = vTab:GetText();
if vName == "Debug" then
gOutfitter_DebugFrame = vChatFrame;
if gOutfitter_DebugFrame:GetMaxLines() < 1000 then
gOutfitter_DebugFrame:SetMaxLines(1000);
end
_ERRORMESSAGE = function(message) Outfitter_DebugMessage(message); end;
end
end
end
if gOutfitter_DebugFrame then
Outfitter_DebugMessage("Found debugging chat frame");
end
end
function Outfitter_DebugMessage(pMessage)
if gOutfitter_DebugFrame then
gOutfitter_DebugFrame:AddMessage("DEBUG: "..pMessage, 0.7, 0.3, 1.0);
local vTabFlash = getglobal(gOutfitter_DebugFrame:GetName().."TabFlash");
vTabFlash:Show();
UIFrameFlash(vTabFlash, 0.25, 0.25, 60, nil, 0.5, 0.5);
else
DEFAULT_CHAT_FRAME:AddMessage("DEBUG: "..pMessage, 0.7, 0.3, 1.0);
end
end
function Outfitter_ErrorMessage(pMessage)
DEFAULT_CHAT_FRAME:AddMessage(pMessage, 0.8, 0.3, 0.5);
end
function Outfitter_TestMessage(pMessage)
if gOutfitter_DebugFrame then
gOutfitter_DebugFrame:AddMessage("TEST: "..pMessage, 0.7, 0.3, 1.0);
else
DEFAULT_CHAT_FRAME:AddMessage("TEST: "..pMessage, 0.7, 0.3, 1.0);
end
end
function Outfitter_NoteMessage(pMessage)
DEFAULT_CHAT_FRAME:AddMessage(pMessage, 0.6, 1.0, 0.3);
end
function Outfitter_RenameLink(pLink, pName)
local vMessage = string.gsub(pLink, "%[.*%]", "["..pName.."]");
-- local vMessage = string.gsub(pMessage, "||", "|");
DEFAULT_CHAT_FRAME:AddMessage(vMessage);
end
function Outfitter_DumpArray(pPrefixString, pArray)
if not pArray then
Outfitter_DebugMessage(pPrefixString.." is nil");
return;
end
local vFoundElement = false;
for vIndex, vElement in pArray do
vFoundElement = true;
local vType = type(vElement);
local vPrefix;
if type(vIndex) == "number" then
vPrefix = pPrefixString.."["..vIndex.."]";
else
vPrefix = pPrefixString.."."..vIndex;
end
if vType == "number" then
Outfitter_DebugMessage(vPrefix.." = "..vElement);
elseif vType == "string" then
Outfitter_DebugMessage(vPrefix.." = \""..vElement.."\"");
elseif vType == "boolean" then
if vElement then
Outfitter_DebugMessage(vPrefix.." = true");
else
Outfitter_DebugMessage(vPrefix.." = false");
end
elseif vType == "table" then
Outfitter_DumpArray(vPrefix, vElement);
else
Outfitter_DebugMessage(vPrefix.." "..vType);
end
end
if not vFoundElement then
Outfitter_DebugMessage(pPrefixString.." is empty");
end
end
function Outfitter_InventorySlotIsEmpty(pInventorySlot)
return Outfitter_GetInventoryItemInfo(pInventorySlot) == nil;
end
function Outfitter_GetBagItemInfo(pBagIndex, pSlotIndex)
local vItemLink = GetContainerItemLink(pBagIndex, pSlotIndex);
local vItemInfo = Outfitter_GetItemInfoFromLink(vItemLink);
if not vItemInfo then
return nil;
end
vItemInfo.Texture, _, _, vItemInfo.Quality, _ = GetContainerItemInfo(pBagIndex, pSlotIndex);
return vItemInfo;
end
local gOutfitter_AmmoSlotInfoCache = nil;
function Outfitter_FindAmmoSlotItem(pName, pTexture)
if gOutfitter_AmmoSlotInfoCache
and gOutfitter_AmmoSlotInfoCache.Name == pName
and gOutfitter_AmmoSlotInfoCache.Texture == pTexture then
return gOutfitter_AmmoSlotInfoCache.ItemInfo;
end
for vBagIndex = 0, NUM_BAG_SLOTS do
local vNumBagSlots = GetContainerNumSlots(vBagIndex);
if vNumBagSlots > 0 then
for vBagSlotIndex = 1, vNumBagSlots do
local vTexture = GetContainerItemInfo(vBagIndex, vBagSlotIndex);
if vTexture == pTexture then
local vItemInfo = Outfitter_GetBagItemInfo(vBagIndex, vBagSlotIndex);
if vItemInfo.Name == pName then
if not gOutfitter_AmmoSlotInfoCache then
gOutfitter_AmmoSlotInfoCache = {};
end
gOutfitter_AmmoSlotInfoCache.Name = pName;
gOutfitter_AmmoSlotInfoCache.Texture = pTexture;
gOutfitter_AmmoSlotInfoCache.ItemInfo = vItemInfo;
return vItemInfo;
end
end
end -- for vBagSlotIndex
end -- if vNumBagSlots
end -- for vBagIndex
return nil;
end
function Outfitter_GetInventoryItemInfo(pInventorySlot)
local vSlotID = GetInventorySlotInfo(pInventorySlot);
local vItemLink = GetInventoryItemLink("player", vSlotID);
-- GetInventoryItemLink doesn't work for the ammo slot, so instead get the icon
-- for the slot and then search for a matching icon in the bags
if vItemLink == nil
and pInventorySlot == "AmmoSlot" then
OutfitterTooltip:SetOwner(OutfitterFrame, "ANCHOR_BOTTOMRIGHT", 0, 0);
OutfitterTooltip:SetInventoryItem("player", vSlotID);
if not OutfitterTooltipTextLeft1:IsShown() then
OutfitterTooltip:Hide();
return nil;
end
local vAmmoItemName = OutfitterTooltipTextLeft1:GetText();
OutfitterTooltip:Hide();
local vAmmoItemTexture = GetInventoryItemTexture("player", vSlotID);
return Outfitter_FindAmmoSlotItem(vAmmoItemName, vAmmoItemTexture);
end
local vItemInfo = Outfitter_GetItemInfoFromLink(vItemLink);
if not vItemInfo then
return nil;
end
vItemInfo.Quality = GetInventoryItemQuality("player", vSlotID);
vItemInfo.Texture = GetInventoryItemTexture("player", vSlotID);
return vItemInfo;
end
function Outfitter_GetItemInfoFromLink(pItemLink)
if not pItemLink then
return nil;
end
-- |cff1eff00|Hitem:1465:803:0:0|h[Tigerbane]|h|r
-- |cff1eff00|Hitem:1465:803:0:0|h[Tigerbane]|h|r
-- |(hex code for item color)|Hitem:(item ID code):(enchant code):(added stats code):0|h[(item name)]|h|r
local vStartIndex, vEndIndex, vLinkColor, vItemCode, vItemEnchantCode, vItemSubCode, vUnknownCode, vItemName = strfind(pItemLink, "|(%x+)|Hitem:(%d+):(%d+):(%d+):(%d+)|h%[([^%]]+)%]|h|r");
if not vStartIndex then
return nil;
end
vItemCode = tonumber(vItemCode);
vItemSubCode = tonumber(vItemSubCode);
vItemEnchantCode = tonumber(vItemEnchantCode);
local vItemFamilyName,
vItemLink,
vItemQuality,
vItemLevel,
vItemType,
vItemSubType,
vItemCount,
vItemInvType = GetItemInfo(vItemCode);
local vItemInfo =
{
Code = vItemCode,
SubCode = vItemSubCode,
Name = vItemName,
EnchantCode = vItemEnchantCode,
Level = vItemLevel,
};
-- Just return if there's no inventory type
if not vItemInvType
or vItemInvType == "" then
return vItemInfo;
end
-- Just return if we don't know anything about the inventory type
local vInvTypeInfo = Outfitter_cInvTypeToSlotName[vItemInvType];
if not vInvTypeInfo then
Outfitter_ErrorMessage("Outfitter error: Unknown slot type "..vItemInvType.." for item "..vItemName);
return vItemInfo;
end
-- Get the slot name
if not vInvTypeInfo.SlotName then
Outfitter_ErrorMessage("Unknown slot name for inventory type "..vItemInvType);
return vItemInfo;
end
vItemInfo.ItemSlotName = vInvTypeInfo.SlotName;
vItemInfo.MetaSlotName = vInvTypeInfo.MetaSlotName;
-- Return the info
return vItemInfo;
end
function Outfitter_CreateNewOutfit()
OutfitterNameOutfit_Open(nil);
end
function Outfitter_NewEmptyOutfit(pName)
return {Name = pName, Items = {}};
end
function Outfitter_IsEmptyOutfit(pOutfit)
return Outfitter_ArrayIsEmpty(pOutfit.Items);
end
function Outfitter_NewNakedOutfit(pName)
local vOutfit = Outfitter_NewEmptyOutfit(pName);
for _, vInventorySlot in Outfitter_cSlotNames do
Outfitter_AddOutfitItem(vOutfit, vInventorySlot, 0, 0, "", 0);
end
return vOutfit;
end
function Outfitter_AddOutfitItem(pOutfit, pSlotName, pItemCode, pItemSubCode, pItemName, pItemEnchantCode)
pOutfit.Items[pSlotName] = {Code = pItemCode, SubCode = pItemSubCode, Name = pItemName, EnchantCode = pItemEnchantCode};
end
function Outfitter_AddOutfitStatItem(pOutfit, pSlotName, pItemCode, pItemSubCode, pItemName, pItemEnchantCode, pStatID, pStatValue)
if not pSlotName then
Outfitter_ErrorMessage("AddOutfitStatItem: SlotName is nil for "..pItemName);
return;
end
if not pStatID then
Outfitter_ErrorMessage("AddOutfitStatItem: StatID is nil for "..pItemName);
return;
end
Outfitter_AddOutfitItem(pOutfit, pSlotName, pItemCode, pItemSubCode, pItemName, pItemEnchantCode);
pOutfit.Items[pSlotName][pStatID] = pStatValue;
end
function Outfitter_AddOutfitStatItemIfBetter(pOutfit, pSlotName, pItemCode, pItemSubCode, pItemName, pItemEnchantCode, pStatID, pStatValue)
local vCurrentItem = pOutfit.Items[pSlotName];
local vAlternateSlotName = Outfitter_cHalfAlternateStatSlot[pSlotName];
if not vCurrentItem
or not vCurrentItem[pStatID]
or vCurrentItem[pStatID] < pStatValue then
-- If we're bumping the current item, see if it should be moved to the alternate slot
if vCurrentItem
and vCurrentItem[pStatID]
and vAlternateSlotName then
Outfitter_AddOutfitStatItemIfBetter(pOutfit, vAlternateSlotName, vCurrentItem.Code, vCurrentItem.SubCode, vCurrentItem.Name, vCurrentItem.EnchantCode, pStatID, vCurrentItem[pStatID])
end
Outfitter_AddOutfitStatItem(pOutfit, pSlotName, pItemCode, pItemSubCode, pItemName, pItemEnchantCode, pStatID, pStatValue);
else
if not vAlternateSlotName then
return;
end
return Outfitter_AddOutfitStatItemIfBetter(pOutfit, vAlternateSlotName, pItemCode, pItemSubCode, pItemName, pItemEnchantCode, pStatID, pStatValue);
end
end
function Outfitter_AddStats(pItem1, pItem2, pStatID)
local vStat = 0;
if pItem1
and pItem1[pStatID] then
vStat = pItem1[pStatID];
end
if pItem2
and pItem2[pStatID] then
vStat = vStat + pItem2[pStatID];
end
return vStat;
end
function Outfitter_CollapseMetaSlotsIfBetter(pOutfit, pStatID)
-- Compare the weapon slot with the 1H/OH slots
local vWeapon0Item = pOutfit.Items.Weapon0Slot;
local vWeapon1Item = pOutfit.Items.Weapon1Slot;
if vWeapon0Item or vWeapon1Item then
-- Try the various combinations of MH/OH/W0/W1
local v1HItem = pOutfit.Items.MainHandSlot;
local vOHItem = pOutfit.Items.SecondaryHandSlot;
local vCombinations =
{
{MainHand = v1HItem, SecondaryHand = vOHItem, AllowEmptyMainHand = true},
{MainHand = v1HItem, SecondaryHand = vWeapon0Item, AllowEmptyMainHand = false},
{MainHand = v1HItem, SecondaryHand = vWeapon1Item, AllowEmptyMainHand = false},
{MainHand = vWeapon0Item, SecondaryHand = vOHItem, AllowEmptyMainHand = true},
{MainHand = vWeapon1Item, SecondaryHand = vOHItem, AllowEmptyMainHand = true},
{MainHand = vWeapon0Item, SecondaryHand = vWeapon1Item, AllowEmptyMainHand = false},
};
local vBestCombinationIndex = nil;
local vBestCombinationValue = nil;
for vIndex = 1, 6 do
local vCombination = vCombinations[vIndex];
-- Ignore combinations where the main hand is empty if
-- that's not allowed in this combinations
if vCombination.AllowEmptyMainHand
or vCombination.MainHand then
local vCombinationValue = Outfitter_AddStats(vCombination.MainHand, vCombination.SecondaryHand, pStatID);
if not vBestCombinationIndex
or vCombinationValue > vBestCombinationValue then
vBestCombinationIndex = vIndex;
vBestCombinationValue = vCombinationValue;
end
end
end
if vBestCombinationIndex then
local vCombination = vCombinations[vBestCombinationIndex];
pOutfit.Items.MainHandSlot = vCombination.MainHand;
pOutfit.Items.SecondaryHandSlot = vCombination.SecondaryHand;
end
pOutfit.Items.Weapon0Slot = nil;
pOutfit.Items.Weapon1Slot = nil;
end
-- Compare the 2H slot with the 1H/OH slots
local v2HItem = pOutfit.Items.TwoHandSlot;
if v2HItem then
local v1HItem = pOutfit.Items.MainHandSlot;
local vOHItem = pOutfit.Items.SecondaryHandSlot;
local v1HOHTotalStat = Outfitter_AddStats(v1HItem, vOHItem, pStatID);
if v2HItem[pStatID]
and v2HItem[pStatID] > v1HOHTotalStat then
pOutfit.Items.MainHandSlot = v2HItem;
pOutfit.Items.SecondaryHandSlot = nil;
end
pOutfit.Items.TwoHandSlot = nil;
end
end
function Outfitter_RemoveOutfitItem(pOutfit, pSlotName)
pOutfit.Items[pSlotName] = nil;
end
function Outfitter_GetInventoryOutfit(pName, pOutfit)
local vOutfit;
if pOutfit then
vOutfit = pOutfit;
else
vOutfit = Outfitter_NewEmptyOutfit(pName);
end
for _, vInventorySlot in Outfitter_cSlotNames do
local vItemInfo = Outfitter_GetInventoryItemInfo(vInventorySlot);
-- To avoid extra memory operations, only update the item if it's different
local vExistingItem = vOutfit.Items[vInventorySlot];
if not vItemInfo then
if not vExistingItem
or vExistingItem.Code ~= 0 then
Outfitter_AddOutfitItem(vOutfit, vInventorySlot, 0, 0, "", 0);
end
else
if not vExistingItem
or vExistingItem.Code ~= vItemInfo.Code
or vExistingItem.SubCode ~= vItemInfo.SubCode
or vExistingItem.EnchantCode ~= vItemInfo.EnchantCode then
Outfitter_AddOutfitItem(vOutfit, vInventorySlot, vItemInfo.Code, vItemInfo.SubCode, vItemInfo.Name, vItemInfo.EnchantCode);
end
end
end
return vOutfit;
end
function Outfitter_UpdateOutfitFromInventory(pOutfit, pNewItemsOutfit)
if not pNewItemsOutfit then
return;
end
for vInventorySlot, vItem in pNewItemsOutfit.Items do
-- Only update slots which aren't in an unknown state
local vCheckbox = getglobal("OutfitterEnable"..vInventorySlot);
if not vCheckbox:GetChecked()
or not vCheckbox.IsUnknown then
pOutfit.Items[vInventorySlot] = vItem;
Outfitter_NoteMessage(format(Outfitter_cAddingItem, vItem.Name, pOutfit.Name));
Outfitter_UpdateOutfitCategory(pOutfit);
end
end
-- Add the new items to the current compiled outfit
for vInventorySlot, vItem in pNewItemsOutfit.Items do
gOutfitter_ExpectedOutfit.Items[vInventorySlot] = pNewItemsOutfit.Items[vInventorySlot];
end
gOutfitter_DisplayIsDirty = true;
end
function Outfitter_SubtractOutfit(pOutfit1, pOutfit2, pCheckAlternateSlots)
local vEquippableItems = OutfitterItemList_GetEquippableItems();
-- Remove items from pOutfit1 if they match the item in pOutfit2
for _, vInventorySlot in Outfitter_cSlotNames do
local vItem1 = pOutfit1.Items[vInventorySlot];
local vItem2 = pOutfit2.Items[vInventorySlot];
if OutfitterItemList_ItemsAreSame(vEquippableItems, vItem1, vItem2) then
pOutfit1.Items[vInventorySlot] = nil;
elseif pCheckAlternateSlots then
local vAlternateSlotName = Outfitter_cFullAlternateStatSlot[vInventorySlot];
vItem2 = pOutfit2.Items[vAlternateSlotName];
if OutfitterItemList_ItemsAreSame(vEquippableItems, vItem1, vItem2) then
pOutfit1.Items[vInventorySlot] = nil;
end
end
end
end
function Outfitter_GetNewItemsOutfit(pPreviousOutfit)
-- Get the current outfit and the list
-- of equippable items
gOutfitter_CurrentInventoryOutfit = Outfitter_GetInventoryOutfit(gOutfitter_CurrentInventoryOutfit);
local vEquippableItems = OutfitterItemList_GetEquippableItems();
-- Create a temporary outfit from the differences
local vNewItemsOutfit = Outfitter_NewEmptyOutfit();
local vOutfitHasItems = false;
for _, vInventorySlot in Outfitter_cSlotNames do
local vCurrentItem = gOutfitter_CurrentInventoryOutfit.Items[vInventorySlot];
local vPreviousItem = pPreviousOutfit.Items[vInventorySlot];
local vSkipSlot = false;
if vInventorySlot == "SecondaryHandSlot" then
local vMainHandItem = pPreviousOutfit.Items["MainHandSlot"];
if vMainHandItem
and vMainHandItem.MetaSlotName == "TwoHandSlot" then
vSkipSlot = true;
end
elseif vInventorySlot == "AmmoSlot"
and (not vCurrentItem or vCurrentItem.Code == 0) then
vSkipSlot = true;
end
if not vSkipSlot
and not OutfitterItemList_InventorySlotContainsItem(vEquippableItems, vInventorySlot, vPreviousItem) then
vNewItemsOutfit.Items[vInventorySlot] = vCurrentItem;
vOutfitHasItems = true;
end
end
if not vOutfitHasItems then
return nil;
end
return vNewItemsOutfit, gOutfitter_CurrentInventoryOutfit;
end
function Outfitter_UpdateTemporaryOutfit(pNewItemsOutfit)
-- Just return if nothing has changed
if not pNewItemsOutfit then
return;
end
-- Merge the new items with an existing temporary outfit
local vTemporaryOutfit = OutfitterStack_GetTemporaryOutfit();
local vUsingExistingTempOutfit = false;
if vTemporaryOutfit then
for vInventorySlot, vItem in pNewItemsOutfit.Items do
vTemporaryOutfit.Items[vInventorySlot] = vItem;
end
vUsingExistingTempOutfit = true;
-- Otherwise add the new items as the temporary outfit
else
vTemporaryOutfit = pNewItemsOutfit;
end
-- Subtract out items which are expected to be in the outfit
local vExpectedOutfit = Outfitter_GetExpectedOutfit(vTemporaryOutfit);
Outfitter_SubtractOutfit(vTemporaryOutfit, vExpectedOutfit);
if Outfitter_IsEmptyOutfit(vTemporaryOutfit) then
if vUsingExistingTempOutfit then
Outfitter_RemoveOutfit(vTemporaryOutfit);
end
else
if not vUsingExistingTempOutfit then
OutfitterStack_AddOutfit(vTemporaryOutfit);
end
end
-- Add the new items to the current compiled outfit
for vInventorySlot, vItem in pNewItemsOutfit.Items do
gOutfitter_ExpectedOutfit.Items[vInventorySlot] = vItem;
end
end
function Outfitter_SetSlotEnable(pSlotName, pEnable)
if not gOutfitter_SelectedOutfit then
return;
end
if pEnable then
local vItemInfo = Outfitter_GetInventoryItemInfo(pSlotName);
if vItemInfo then
gOutfitter_SelectedOutfit.Items[pSlotName] = {Code = vItemInfo.Code, SubCode = vItemInfo.SubCode, Name = vItemInfo.Name, EnchantCode = vItemInfo.EnchantCode};
else
gOutfitter_SelectedOutfit.Items[pSlotName] = {Code = 0, SubCode = 0, Name = "", EnchantCode = 0};
end
else
gOutfitter_SelectedOutfit.Items[pSlotName] = nil;
end
gOutfitter_DisplayIsDirty = true;
end
function Outfitter_GetSpecialOutfit(pSpecialID)
for vOutfitIndex, vOutfit in gOutfitter_Settings.Outfits.Special do
if vOutfit.SpecialID == pSpecialID then
return vOutfit;
end
end
return nil;
end
function Outfitter_GetPlayerAuraStates()
local vAuraStates =
{
Dining = false,
Shadowform = false,
Riding = false,
GhostWolf = false,
Feigning = false,
Evocate = false,
Monkey = false,
Hawk = false,
Cheetah = false,
Pack = false,
Beast = false,
Wild = false
};
local vBuffIndex = 1;
while true do
vTexture = UnitBuff("player", vBuffIndex);
if not vTexture then
return vAuraStates;
end
local vStartIndex, vEndIndex, vTextureName = string.find(vTexture, "([^%\\]*)$");
--
local vSpecialID = gOutfitter_AuraIconSpecialID[vTextureName];
if vSpecialID then
vAuraStates[vSpecialID] = true;
--
elseif not vAuraStates.Dining
and string.find(vTextureName, "INV_Drink") then
vAuraStates.Dining = true;
--
else
local vTextLine1, vTextLine2 = Outfitter_GetBuffTooltipText(vBuffIndex);
if vTextLine1 then
local vSpecialID = gOutfitter_SpellNameSpecialID[vTextLine1];
if vSpecialID then
vAuraStates[vSpecialID] = true;
elseif vTextLine2
and string.find(vTextLine2, Outfitter_cMountSpeedFormat) then
vAuraStates.Riding = true;
end
end
end
vBuffIndex = vBuffIndex + 1;
end
end
function Outfitter_GetBuffTooltipText(pBuffIndex)
OutfitterTooltip:SetOwner(OutfitterFrame, "ANCHOR_BOTTOMRIGHT", 0, 0);
OutfitterTooltip:SetUnitBuff("player", pBuffIndex);
local vText1, vText2;
if OutfitterTooltipTextLeft1:IsShown() then
vText1 = OutfitterTooltipTextLeft1:GetText();
end -- if IsShown
if OutfitterTooltipTextLeft2:IsShown() then
vText2 = OutfitterTooltipTextLeft2:GetText();
end -- if IsShown
OutfitterTooltip:Hide();
return vText1, vText2;
end
function Outfitter_UpdateAuraStates()
-- Check for special aura outfits
local vAuraStates = Outfitter_GetPlayerAuraStates();
for vSpecialID, vIsActive in vAuraStates do
if vSpecialID == "Feigning" then
gOutfitter_IsFeigning = vIsActive;
else
if not gOutfitter_SpecialState[vSpecialID] then
gOutfitter_SpecialState[vSpecialID] = false;
end
if gOutfitter_SpecialState[vSpecialID] ~= vIsActive then
gOutfitter_SpecialState[vSpecialID] = vIsActive;
Outfitter_SetSpecialOutfitEnabled(vSpecialID, vIsActive);
end
end
end
-- As of 1.12 aura changes are the only way to detect shapeshifts, so update those too
Outfitter_UpdateShapeshiftState();
end
function Outfitter_UpdateShapeshiftState()
local vNumForms = GetNumShapeshiftForms();
for vIndex = 1, vNumForms do
local vTexture, vName, vIsActive, vIsCastable = GetShapeshiftFormInfo(vIndex);
local vSpecialID = Outfitter_cShapeshiftSpecialIDs[vName];
if vSpecialID then
if not vIsActive then
vIsActive = false;
end
if gOutfitter_SpecialState[vSpecialID.ID] == nil then
gOutfitter_SpecialState[vSpecialID.ID] = Outfitter_WearingSpecialOutfit(vSpecialID.ID);
end
if gOutfitter_SpecialState[vSpecialID.ID] ~= vIsActive then
gOutfitter_SpecialState[vSpecialID.ID] = vIsActive;
Outfitter_SetSpecialOutfitEnabled(vSpecialID.ID, vIsActive);
end
end
end
end
function Outfitter_SetSpecialOutfitEnabled(pSpecialID, pEnable)
local vOutfit = Outfitter_GetSpecialOutfit(pSpecialID);
if not vOutfit
or vOutfit.Disabled
or (pEnable and vOutfit.BGDisabled and Outfitter_InBattlegroundZone()) then
return;
end
if pEnable then
-- Start monitoring health and mana if it's the dining outfit
if pSpecialID == "Dining" then
Outfitter_ResumeEvent(OutfitterFrame, "UNIT_HEALTH");
Outfitter_ResumeEvent(OutfitterFrame, "UNIT_MANA");
end
--
local vWearBelowOutfit = nil;
-- If it's the ArgentDawn outfit, wear it below the
-- riding outfit. Once the player dismounts then
-- overlapping items from the ArgentDawn outfit will equip.
-- This will prevent the Argent Dawn trinket from interfering
-- with the carrot trinket when riding into the plaguelands
if pSpecialID == "ArgentDawn" then
vWearBelowOutfit = Outfitter_GetSpecialOutfit("Riding");
end
--
Outfitter_WearOutfit(vOutfit, "Special", vWearBelowOutfit);
else
Outfitter_RemoveOutfit(vOutfit);
end
end
function Outfitter_WearingSpecialOutfit(pSpecialID)
for vIndex, vOutfit in gOutfitter_OutfitStack do
if vOutfit.SpecialID == pSpecialID then
return true, vIndex;
end
end
end
function Outfitter_UpdateZone()
local vCurrentZone = GetZoneText();
local vPVPType, vFactionName, vIsArena = GetZonePVPInfo();
if vCurrentZone == gOutfitter_CurrentZone then
return;
end
gOutfitter_CurrentZone = vCurrentZone;
local vZoneSpecialIDMap = Outfitter_cZoneSpecialIDMap[vCurrentZone];
local vSpecialZoneStates = {};
if vZoneSpecialIDMap then
for _, vZoneSpecialID in vZoneSpecialIDMap do
if vZoneSpecialID ~= "City" or vPVPType ~= "hostile" then
vSpecialZoneStates[vZoneSpecialID] = true;
end
end
end
for _, vSpecialID in Outfitter_cZoneSpecialIDs do
local vIsActive = vSpecialZoneStates[vSpecialID];
if vIsActive == nil then
vIsActive = false;
end
local vCurrentIsActive = gOutfitter_SpecialState[vSpecialID];
if vCurrentIsActive == nil then
vCurrentIsActive = Outfitter_WearingSpecialOutfit(vSpecialID);
gOutfitter_SpecialState[vSpecialID] = vCurrentIsActive;
end
if vCurrentIsActive ~= vIsActive then
gOutfitter_SpecialState[vSpecialID] = vIsActive;
Outfitter_SetSpecialOutfitEnabled(vSpecialID, vIsActive);
end
end
end
function Outfitter_InBattlegroundZone()
local vZoneSpecialIDMap = Outfitter_cZoneSpecialIDMap[gOutfitter_CurrentZone];
return vZoneSpecialIDMap and vZoneSpecialIDMap[1] == "Battleground";
end
function Outfitter_SetAllSlotEnables(pEnable)
for _, vInventorySlot in Outfitter_cSlotNames do
Outfitter_SetSlotEnable(vInventorySlot, pEnable);
end
Outfitter_UpdateOutfitCategory(gOutfitter_SelectedOutfit);
Outfitter_Update(true);
end
function Outfitter_OutfitIsComplete(pOutfit, pIgnoreAmmoSlot)
for _, vInventorySlot in Outfitter_cSlotNames do
if not pOutfit.Items[vInventorySlot]
and (not pIgnoreAmmoSlot or vInventorySlot ~= "AmmoSlot") then
return false;
end
end
return true;
end
function Outfitter_CalculateOutfitCategory(pOutfit)
local vIgnoreAmmoSlot = UnitHasRelicSlot("player");
if Outfitter_OutfitIsComplete(pOutfit, vIgnoreAmmoSlot) then
return "Complete";
elseif pOutfit.IsAccessory then
return "Accessory";
else
return "Partial";
end
end
function Outfitter_UpdateOutfitCategory(pOutfit)
if not pOutfit then
return;
end
local vTargetCategoryID = Outfitter_CalculateOutfitCategory(pOutfit);
local vOutfitCategoryID, vOutfitIndex = Outfitter_FindOutfit(pOutfit);
-- Don't move special outfits around
if vOutfitCategoryID == "Special" then
return;
end
-- Move the outfit if necessary
if vTargetCategoryID ~= vOutfitCategoryID then
table.remove(gOutfitter_Settings.Outfits[vOutfitCategoryID], vOutfitIndex);
Outfitter_AddOutfit(pOutfit);
end
end
function Outfitter_DeleteOutfit(pOutfit)
local vWearingOutfit = Outfitter_WearingOutfit(pOutfit);
local vOutfitCategoryID, vOutfitIndex = Outfitter_FindOutfit(pOutfit);
if not vOutfitCategoryID then
return;
end
-- Delete the outfit
table.remove(gOutfitter_Settings.Outfits[vOutfitCategoryID], vOutfitIndex);
-- Deselect the outfit
if pOutfit == gOutfitter_SelectedOutfit then
Outfitter_ClearSelection();
end
-- Remove the outfit if it's being worn
Outfitter_RemoveOutfit(pOutfit);
--
gOutfitter_DisplayIsDirty = true;
end
function Outfitter_AddOutfit(pOutfit)
local vCategoryID;
if pOutfit.SpecialID then
vCategoryID = "Special"
else
vCategoryID = Outfitter_CalculateOutfitCategory(pOutfit);
end
if not gOutfitter_Settings.Outfits then
gOutfitter_Settings.Outfits = {};
end
if not gOutfitter_Settings.Outfits[vCategoryID] then
gOutfitter_Settings.Outfits[vCategoryID] = {};
end
table.insert(gOutfitter_Settings.Outfits[vCategoryID], pOutfit);
pOutfit.CategoryID = vCategoryID;
gOutfitter_DisplayIsDirty = true;
return vCategoryID;
end
function Outfitter_SlotEnableClicked(pCheckbox, pButton)
-- If the user is attempting to drop an item put it in the slot for them
if CursorHasItem() then
local vSlotID, vEmptySlotTexture = GetInventorySlotInfo(pCheckbox.SlotName);
PickupInventoryItem(vSlotID);
return;
end
--
local vChecked = pCheckbox:GetChecked();
if pCheckbox.IsUnknown then
pCheckbox.IsUnknown = false;
pCheckbox:SetCheckedTexture("Interface\\Buttons\\UI-CheckBox-Check");
vChecked = true;
end
Outfitter_SetSlotEnable(pCheckbox.SlotName, vChecked);
Outfitter_UpdateOutfitCategory(gOutfitter_SelectedOutfit);
Outfitter_Update(true);
end
function Outfitter_FindMultipleItemLocation(pItems, pEquippableItems)
for vListIndex, vListItem in pItems do
local vItem = OutfitterItemList_FindItemOrAlt(pEquippableItems, vListItem);
if vItem then
return vItem, vListItem;
end
end
return nil, nil;
end
function Outfitter_FindAndAddItemsToOutfit(pOutfit, pSlotName, pItems, pEquippableItems)
vItemLocation, vItem = Outfitter_FindMultipleItemLocation(pItems, pEquippableItems);
if vItemLocation then
local vInventorySlot = pSlotName;
if not vInventorySlot then
vInventorySlot = vItemLocation.ItemSlotName;
end
Outfitter_AddOutfitItem(pOutfit, vInventorySlot, vItem.Code, vItem.SubCode, vItem.Name, vItem.EnchantCOde);
end
end
function Outfitter_AddItemsWithStatToOutfit(pOutfit, pStatID, pEquippableItems)
local vItemStats;
if not pEquippableItems then
return;
end
for vInventorySlot, vItems in pEquippableItems.ItemsBySlot do
for vIndex, vItem in vItems do
local vStatValue = vItem.Stats[pStatID];
if vStatValue then
local vSlotName = vItem.MetaSlotName;
if not vSlotName then
vSlotName = vItem.ItemSlotName;
end
Outfitter_AddOutfitStatItemIfBetter(pOutfit, vSlotName, vItem.Code, vItem.SubCode, vItem.Name, vItem.EnchantCode, pStatID, vStatValue);
end
end
end
-- Collapse the meta slots (currently just 2H vs. 1H/OH)
Outfitter_CollapseMetaSlotsIfBetter(pOutfit, pStatID);
end
function Outfitter_IsInitialized()
return gOutfitter_Initialized;
end
function Outfitter_Initialize()
if gOutfitter_Initialized then
return;
end
--
if not gOutfitter_Settings then
gOutfitter_Settings = {};
gOutfitter_Settings.Version = 7;
gOutfitter_Settings.Options = {};
gOutfitter_Settings.LastOutfitStack = {};
gOutfitter_Settings.HideHelm = {};
gOutfitter_Settings.HideCloak = {};
end
if not gOutfitter_Settings.HideHelm then
gOutfitter_Settings.HideHelm = {};
end
if not gOutfitter_Settings.HideCloak then
gOutfitter_Settings.HideCloak = {};
end
--
Outfitter_InitDebugging();
-- Initialize the outfits and outfit stack
gOutfitter_CurrentOutfit = Outfitter_GetInventoryOutfit();
if not gOutfitter_Settings.Outfits then
Outfitter_InitializeOutfits();
end
Outfitter_CheckDatabase();
Outfitter_InitializeSpecialOccassionOutfits(); -- Make sure the special occassion outfits are intact
-- since the user has no way of creating them himself
OutfitterStack_RestoreSavedStack();
-- Set the minimap button
if gOutfitter_Settings.Options.HideMinimapButton then
OutfitterMinimapButton:Hide();
else
OutfitterMinimapButton:Show();
end
if not gOutfitter_Settings.Options.MinimapButtonAngle then
gOutfitter_Settings.Options.MinimapButtonAngle = -1.5708;
end
OutfitterMinimapButton_SetPositionAngle(gOutfitter_Settings.Options.MinimapButtonAngle);
-- Hook QuickSlots into the paper doll frame
Outfitter_HookPaperDollFrame();
-- Done initializing
gOutfitter_Initialized = true;
-- Make sure the outfit state is good
Outfitter_SetSpecialOutfitEnabled("Riding", false);
Outfitter_SetSpecialOutfitEnabled("Spirit", false);
Outfitter_UpdateAuraStates();
Outfitter_ResumeLoadScreenEvents();
Outfitter_DispatchOutfitEvent("OUTFITTER_INIT")
end
function Outfitter_InitializeOutfits()
local vOutfit, vItemLocation, vItem;
local vEquippableItems = OutfitterItemList_GetEquippableItems(true);
-- Create the outfit categories
gOutfitter_Settings.Outfits = {};
for vCategoryIndex, vCategoryID in gOutfitter_cCategoryOrder do
gOutfitter_Settings.Outfits[vCategoryID] = {};
end
-- Create the normal outfit using the current
-- inventory and set it as the currently equipped outfit
vOutfit = Outfitter_GetInventoryOutfit(Outfitter_cNormalOutfit);
Outfitter_AddOutfit(vOutfit);
gOutfitter_Settings.LastOutfitStack = {{Name = Outfitter_cNormalOutfit}};
gOutfitter_OutfitStack = {vOutfit};
-- Create the naked outfit
vOutfit = Outfitter_NewNakedOutfit(Outfitter_cNakedOutfit);
Outfitter_AddOutfit(vOutfit);
-- Generate the smart outfits
for vSmartIndex, vSmartOutfit in Outfitter_cSmartOutfits do
vOutfit = Outfitter_GenerateSmartOutfit(vSmartOutfit.Name, vSmartOutfit.StatID, vEquippableItems);
if vOutfit then
vOutfit.IsAccessory = vSmartOutfit.IsAccessory;
Outfitter_AddOutfit(vOutfit);
end
end
Outfitter_InitializeSpecialOccassionOutfits();
end
function Outfitter_CreateEmptySpecialOccassionOutfit(pSpecialID, pName)
vOutfit = Outfitter_GetSpecialOutfit(pSpecialID);
if not vOutfit then
vOutfit = Outfitter_NewEmptyOutfit(pName);
vOutfit.SpecialID = pSpecialID;
Outfitter_AddOutfit(vOutfit);
end
end
function Outfitter_InitializeSpecialOccassionOutfits()
local vEquippableItems = OutfitterItemList_GetEquippableItems(true);
local vOutfit;
-- Find an argent dawn trinket and set the argent dawn outfit
vOutfit = Outfitter_GetSpecialOutfit("ArgentDawn");
if not vOutfit then
vOutfit = Outfitter_GenerateSmartOutfit(Outfitter_cArgentDawnOutfit, "ArgentDawn", vEquippableItems, true);
vOutfit.SpecialID = "ArgentDawn";
Outfitter_AddOutfit(vOutfit);
end
-- Find riding items
vOutfit = Outfitter_GetSpecialOutfit("Riding");
if not vOutfit then
vOutfit = Outfitter_GenerateSmartOutfit(Outfitter_cRidingOutfit, "Riding", vEquippableItems, true);
vOutfit.SpecialID = "Riding";
vOutfit.BGDisabled = true;
Outfitter_AddOutfit(vOutfit);
end
-- Create the dining outfit
Outfitter_CreateEmptySpecialOccassionOutfit("Dining", Outfitter_cDiningOutfit);
-- Create the Battlegrounds outfits
Outfitter_CreateEmptySpecialOccassionOutfit("Battleground", Outfitter_cBattlegroundOutfit);
Outfitter_CreateEmptySpecialOccassionOutfit("AB", Outfitter_cABOutfit);
Outfitter_CreateEmptySpecialOccassionOutfit("AV", Outfitter_cAVOutfit);
Outfitter_CreateEmptySpecialOccassionOutfit("WSG", Outfitter_cWSGOutfit);
-- Create the city outfit
Outfitter_CreateEmptySpecialOccassionOutfit("City", Outfitter_cCityOutfit);
-- Create class-specific outfits
Outfitter_InitializeClassOutfits();
end
function Outfitter_InitializeClassOutfits()
local vClassName = Outfitter_cNormalizedClassName[UnitClass("player")];
local vOutfits = Outfitter_cClassSpecialOutfits[vClassName];
if not vOutfits then
return;
end
for vIndex, vOutfitInfo in vOutfits do
Outfitter_CreateEmptySpecialOccassionOutfit(vOutfitInfo.SpecialID, vOutfitInfo.Name);
end
end
function Outfitter_IsStatText(pText)
for vStatIndex, vStatInfo in Outfitter_cItemStatFormats do
local vStartIndex, vEndIndex, vValue = string.find(pText, vStatInfo.Format);
if vStartIndex then
vValue = tonumber(vValue);
if not vValue then
vValue = vStatInfo.Value;
end
if not vValue then
vValue = 0;
end
return vStatInfo.Types, vValue;
end
end
return nil, nil;
end
function Outfitter_GetItemStatsFromTooltip(pTooltip, pDistribution)
local vStats = {};
local vTooltipName = pTooltip:GetName();
for vLineIndex = 1, 30 do
local vLeftText = getglobal(vTooltipName.."TextLeft"..vLineIndex):GetText();
-- local vRightText = getglobal(vTooltipName.."TextRight"..vLineIndex):GetText();
if vLeftText then
-- Check for the start of the set bonus section
local vStartIndex, vEndIndex, vValue = string.find(vLeftText, "%(%d/%d%)");
if vStartIndex then
break;
end
--
for vStatString in string.gfind(vLeftText, "([^/]+)") do
local vStatIDs, vValue = Outfitter_IsStatText(vStatString);
if vStatIDs then
for vStatIDIndex, vStatID in vStatIDs do
OutfitterStats_AddStatValue(vStats, vStatID, vValue, pDistribution);
end
end
end
end
end -- for vLineIndex
return vStats;
end
function Outfitter_TooltipContainsText(pTooltip, pText)
local vTooltipName = pTooltip:GetName();
for vLineIndex = 1, 30 do
local vLeftText = getglobal(vTooltipName.."TextLeft"..vLineIndex):GetText();
if vLeftText
and string.find(vLeftText, pText) then
return true;
end
end -- for vLineIndex
return false;
end
function Outfitter_CanEquipBagItem(pBagIndex, pBagSlotIndex)
local vItemInfo = Outfitter_GetBagItemInfo(pBagIndex, pBagSlotIndex);
if vItemInfo
and vItemInfo.Level
and UnitLevel("player") < vItemInfo.Level then
return false;
end
return true;
end
function Outfitter_BagItemWillBind(pBagIndex, pBagSlotIndex)
local vItemLink = GetContainerItemLink(pBagIndex, pBagSlotIndex);
if not vItemLink then
return nil;
end
OutfitterTooltip:SetOwner(OutfitterFrame, "ANCHOR_BOTTOMRIGHT", 0, 0);
OutfitterTooltip:SetBagItem(pBagIndex, pBagSlotIndex);
local vIsBOE = Outfitter_TooltipContainsText(OutfitterTooltip, ITEM_BIND_ON_EQUIP);
OutfitterTooltip:Hide();
return vIsBOE;
end
function Outfitter_GenerateSmartOutfit(pName, pStatID, pEquippableItems, pAllowEmptyOutfit)
local vOutfit = Outfitter_NewEmptyOutfit(pName);
if pStatID == "TANKPOINTS" then
return;
end
local vItems = Outfitter_cStatIDItems[pStatID];
OutfitterItemList_ResetIgnoreItemFlags(pEquippableItems);
if vItems then
Outfitter_FindAndAddItemsToOutfit(vOutfit, nil, vItems, pEquippableItems);
end
Outfitter_AddItemsWithStatToOutfit(vOutfit, pStatID, pEquippableItems);
if not pAllowEmptyOutfit
and Outfitter_IsEmptyOutfit(vOutfit) then
return nil;
end
vOutfit.StatID = pStatID;
return vOutfit;
end
function Outfitter_ArrayIsEmpty(pArray)
if not pArray then
return true;
end
for vIndex, vValue in pArray do
return false;
end
return true;
end
function OutfitterNameOutfit_Open(pOutfit)
gOutfitter_OutfitToRename = pOutfit;
if gOutfitter_OutfitToRename then
OutfitterNameOutfitDialogTitle:SetText(Outfitter_cRenameOutfit);
OutfitterNameOutfitDialogName:SetText(gOutfitter_OutfitToRename.Name);
OutfitterNameOutfitDialogCreateUsing:Hide();
OutfitterNameOutfitDialog:SetHeight(OutfitterNameOutfitDialog.baseHeight - 35);
else
OutfitterNameOutfitDialogTitle:SetText(Outfitter_cNewOutfit);
OutfitterNameOutfitDialogName:SetText("");
OutfitterDropDown_SetSelectedValue(OutfitterNameOutfitDialogCreateUsing, 0);
OutfitterNameOutfitDialogCreateUsing:Show();
OutfitterNameOutfitDialog:SetHeight(OutfitterNameOutfitDialog.baseHeight);
OutfitterNameOutfitDialogCreateUsing.ChangedValueFunc = OutfitterNameOutfit_CheckForStatOutfit;
end
OutfitterNameOutfitDialog:Show();
OutfitterNameOutfitDialogName:SetFocus();
end
function OutfitterNameOutfit_CheckForStatOutfit(pMenu, pValue)
OutfitterNameOutfit_Update(true);
end
function OutfitterNameOutfit_Done()
local vName = OutfitterNameOutfitDialogName:GetText();
if vName
and vName ~= "" then
if gOutfitter_OutfitToRename then
local vWearingOutfit = Outfitter_WearingOutfit(gOutfitter_OutfitToRename);
if vWearingOutfit then
Outfitter_DispatchOutfitEvent("UNWEAR_OUTFIT", gOutfitter_OutfitToRename.Name, gOutfitter_OutfitToRename)
end
gOutfitter_OutfitToRename.Name = vName;
gOutfitter_DisplayIsDirty = true;
if vWearingOutfit then
Outfitter_DispatchOutfitEvent("WEAR_OUTFIT", gOutfitter_OutfitToRename.Name, gOutfitter_OutfitToRename)
end
else
-- New outift
local vStatID = UIDropDownMenu_GetSelectedValue(OutfitterNameOutfitDialogCreateUsing);
local vOutfit;
if not vStatID
or vStatID == 0 then
vOutfit = Outfitter_GetInventoryOutfit(vName);
elseif vStatID == "EMPTY" then
vOutfit = Outfitter_NewEmptyOutfit(vName);
else
vOutfit = Outfitter_GenerateSmartOutfit(vName, vStatID, OutfitterItemList_GetEquippableItems(true));
end
if not vOutfit then
vOutfit = Outfitter_NewEmptyOutfit(vName);
end
local vCategoryID = Outfitter_AddOutfit(vOutfit);
Outfitter_WearOutfit(vOutfit, vCategoryID);
end
end
OutfitterNameOutfitDialog:Hide();
Outfitter_Update(true);
end
function OutfitterNameOutfit_Cancel()
OutfitterNameOutfitDialog:Hide();
end
function OutfitterNameOutfit_Update(pCheckForStatOutfit)
local vEnableDoneButton = true;
local vErrorMessage = nil;
-- If there's no name entered then disable the okay button
local vName = OutfitterNameOutfitDialogName:GetText();
if not vName
or vName == "" then
vEnableDoneButton = false;
else
local vOutfit = Outfitter_FindOutfitByName(vName);
if vOutfit
and vOutfit ~= gOutfitter_OutfitToRename then
vErrorMessage = Outfitter_cNameAlreadyUsedError;
vEnableDoneButton = false;
end
end
--
if not vErrorMessage
and pCheckForStatOutfit then
local vStatID = UIDropDownMenu_GetSelectedValue(OutfitterNameOutfitDialogCreateUsing);
if vStatID
and vStatID ~= 0
and vStatID ~= "EMPTY" then
local vOutfit = Outfitter_GenerateSmartOutfit("temp outfit", vStatID, OutfitterItemList_GetEquippableItems(true));
if not vOutfit
or Outfitter_IsEmptyOutfit(vOutfit) then
vErrorMessage = Outfitter_cNoItemsWithStatError;
end
end
end
if vErrorMessage then
OutfitterNameOutfitDialogError:SetText(vErrorMessage);
OutfitterNameOutfitDialogError:Show();
else
OutfitterNameOutfitDialogError:Hide();
end
Outfitter_SetButtonEnable(OutfitterNameOutfitDialogDoneButton, vEnableDoneButton);
end
function Outfitter_SetButtonEnable(pButton, pEnabled)
if pEnabled then
pButton:Enable();
pButton:SetAlpha(1.0);
pButton:EnableMouse(true);
--getglobal(pButton:GetName().."Text"):SetAlpha(1.0);
else
pButton:Disable();
pButton:SetAlpha(0.7);
pButton:EnableMouse(false);
--getglobal(pButton:GetName().."Text"):SetAlpha(0.7);
end
end
function Outfitter_GetOutfitFromListItem(pItem)
if pItem.isCategory then
return nil;
end
if not gOutfitter_Settings.Outfits then
return nil;
end
local vOutfits = gOutfitter_Settings.Outfits[pItem.categoryID];
if not vOutfits then
-- Error: outfit category not found
return nil;
end
return vOutfits[pItem.outfitIndex], pItem.categoryID;
end
function Outfitter_OutfitItemSelected(pMenu, pValue)
local vItem = pMenu:GetParent():GetParent();
local vOutfit, vCategoryID = Outfitter_GetOutfitFromListItem(vItem);
if not vOutfit then
Outfitter_ErrorMessage("Outfitter Error: Outfit for menu item "..vItem:GetName().." not found");
return;
end
-- Perform the selected action
if pValue == "DELETE" then
Outfitter_AskDeleteOutfit(vOutfit);
elseif pValue == "RENAME" then
OutfitterNameOutfit_Open(vOutfit);
elseif pValue == "DISABLE" then
if vOutfit.Disabled then
vOutfit.Disabled = nil;
else
vOutfit.Disabled = true;
end
gOutfitter_DisplayIsDirty = true;
elseif pValue == "BGDISABLE" then
if vOutfit.BGDisabled then
vOutfit.BGDisabled = nil;
else
vOutfit.BGDisabled = true;
end
gOutfitter_DisplayIsDirty = true;
elseif pValue == "ACCESSORY" then
vOutfit.IsAccessory = true;
Outfitter_UpdateOutfitCategory(vOutfit);
elseif pValue == "PARTIAL" then
vOutfit.IsAccessory = nil;
Outfitter_UpdateOutfitCategory(vOutfit);
elseif string.sub(pValue, 1, 7) == "BINDING" then
Outfitter_SetOutfitBindingIndex(vOutfit, tonumber(string.sub(pValue, 8)));
elseif pValue == "REBUILD" then
Outfitter_AskRebuildOutfit(vOutfit, vCategoryID);
elseif pValue == "DEPOSIT" then
Outfitter_DepositOutfit(vOutfit);
elseif pValue == "DEPOSITUNIQUE" then
Outfitter_DepositOutfit(vOutfit, true);
elseif pValue == "WITHDRAW" then
Outfitter_WithdrawOutfit(vOutfit);
end
Outfitter_Update(true);
end
function OutfitterStatDropdown_OnLoad()
UIDropDownMenu_Initialize(this, OutfitterStatDropdown_Initialize);
UIDropDownMenu_SetWidth(150);
UIDropDownMenu_Refresh(this);
end
function Outfitter_GetStatIDName(pStatID)
for vStatIndex, vStatInfo in Outfitter_cItemStatInfo do
if vStatInfo.ID == pStatID then
return vStatInfo.Name;
end
end
return nil;
end
function OutfitterStatDropdown_Initialize()
local vFrame = getglobal(UIDROPDOWNMENU_INIT_MENU);
if UIDROPDOWNMENU_MENU_LEVEL == 2 then
for vStatIndex, vStatInfo in Outfitter_cItemStatInfo do
if vStatInfo.Category == UIDROPDOWNMENU_MENU_VALUE then
UIDropDownMenu_AddButton({text = vStatInfo.Name, value = vStatInfo.ID, owner = vFrame, func = OutfitterDropDown_OnClick}, UIDROPDOWNMENU_MENU_LEVEL);
end
end
else
UIDropDownMenu_AddButton({text = Outfitter_cUseCurrentOutfit, value = 0, owner = vFrame, func = OutfitterDropDown_OnClick});
UIDropDownMenu_AddButton({text = Outfitter_cUseEmptyOutfit, value = "EMPTY", owner = vFrame, func = OutfitterDropDown_OnClick});
UIDropDownMenu_AddButton({text = " ", notCheckable = true, notClickable = true});
for vCategoryIndex, vCategoryInfo in Outfitter_cStatCategoryInfo do
UIDropDownMenu_AddButton({text = vCategoryInfo.Name, owner = vFrame, hasArrow = 1, value = vCategoryInfo.Category});
end
if false and IsAddOnLoaded("TankPoints") then
UIDropDownMenu_AddButton({text = " ", notCheckable = true, notClickable = true});
UIDropDownMenu_AddButton({text = Outfitter_cTankPoints, value="TANKPOINTS", owner = vFrame, func = OutfitterDropDown_OnClick});
end
end
end
function OutfitterDropDown_SetSelectedValue(pDropDown, pValue)
UIDropDownMenu_SetText("", pDropDown); -- Set to empty in case the selected value isn't there
UIDropDownMenu_Initialize(pDropDown, pDropDown.initialize);
UIDropDownMenu_SetSelectedValue(pDropDown, pValue);
-- All done if the item text got set successfully
local vItemText = UIDropDownMenu_GetText(pDropDown);
if vItemText and vItemText ~= "" then
return;
end
-- Scan for submenus
local vRootListFrameName = "DropDownList1";
local vRootListFrame = getglobal(vRootListFrameName);
local vRootNumItems = vRootListFrame.numButtons;
for vRootItemIndex = 1, vRootNumItems do
local vItem = getglobal(vRootListFrameName.."Button"..vRootItemIndex);
if vItem.hasArrow then
local vSubMenuFrame = getglobal("DropDownList2");
UIDROPDOWNMENU_OPEN_MENU = pDropDown:GetName();
UIDROPDOWNMENU_MENU_VALUE = vItem.value;
UIDROPDOWNMENU_MENU_LEVEL = 2;
UIDropDownMenu_Initialize(pDropDown, pDropDown.initialize, nil, 2);
UIDropDownMenu_SetSelectedValue(pDropDown, pValue);
-- All done if the item text got set successfully
local vItemText = UIDropDownMenu_GetText(pDropDown);
if vItemText and vItemText ~= "" then
return;
end
-- Switch back to the root menu
UIDROPDOWNMENU_OPEN_MENU = nil;
UIDropDownMenu_Initialize(pDropDown, pDropDown.initialize, nil, 1);
end
end
end
function OutfitterScrollbarTrench_SizeChanged(pScrollbarTrench)
local vScrollbarTrenchName = pScrollbarTrench:GetName();
local vScrollbarTrenchMiddle = getglobal(vScrollbarTrenchName.."Middle");
local vMiddleHeight= pScrollbarTrench:GetHeight() - 51;
vScrollbarTrenchMiddle:SetHeight(vMiddleHeight);
end
function OutfitterInputBox_OnLoad(pChildDepth)
if not pChildDepth then
pChildDepth = 0;
end
local vParent = this:GetParent();
for vDepthIndex = 1, pChildDepth do
vParent = vParent:GetParent();
end
if vParent.lastEditBox then
this.prevEditBox = vParent.lastEditBox;
this.nextEditBox = vParent.lastEditBox.nextEditBox;
this.prevEditBox.nextEditBox = this;
this.nextEditBox.prevEditBox = this;
else
this.prevEditBox = this;
this.nextEditBox = this;
end
vParent.lastEditBox = this;
end
function OutfitterInputBox_TabPressed()
local vReverse = IsShiftKeyDown();
local vEditBox = this;
for vIndex = 1, 50 do
local vNextEditBox;
if vReverse then
vNextEditBox = vEditBox.prevEditBox;
else
vNextEditBox = vEditBox.nextEditBox;
end
if vNextEditBox:IsVisible()
and not vNextEditBox.isDisabled then
vNextEditBox:SetFocus();
return;
end
vEditBox = vNextEditBox;
end
end
function OutfitterTimer_AdjustTimer()
local vNeedTimer = false;
if OutfitterMinimapButton.IsDragging then
vNeedTimer = true;
end
if gOutfitter_EquippedNeedsUpdate
or gOutfitter_WeaponsNeedUpdate then
vNeedTimer = true;
end
if vNeedTimer then
OutfitterUpdateFrame:Show();
else
OutfitterUpdateFrame:Hide();
OutfitterUpdateFrame.Elapsed = nil;
end
end
function OutfitterUpdateFrame_OnUpdate(pElapsed)
if OutfitterMinimapButton.IsDragging then
OutfitterMinimapButton_UpdateDragPosition();
end
if not OutfitterUpdateFrame.Elapsed then
OutfitterUpdateFrame.Elapsed = 0;
else
OutfitterUpdateFrame.Elapsed = OutfitterUpdateFrame.Elapsed + pElapsed;
if OutfitterUpdateFrame.Elapsed > 0.25 then
Outfitter_UpdateEquippedItems();
OutfitterUpdateFrame.Elapsed = 0;
end
end
OutfitterTimer_AdjustTimer();
end
function OutfitterMinimapButton_MouseDown()
-- Remember where the cursor was in case the user drags
local vCursorX, vCursorY = GetCursorPosition();
vCursorX = vCursorX / this:GetEffectiveScale();
vCursorY = vCursorY / this:GetEffectiveScale();
OutfitterMinimapButton.CursorStartX = vCursorX;
OutfitterMinimapButton.CursorStartY = vCursorY;
local vCenterX, vCenterY = OutfitterMinimapButton:GetCenter();
local vMinimapCenterX, vMinimapCenterY = Minimap:GetCenter();
OutfitterMinimapButton.CenterStartX = vCenterX - vMinimapCenterX;
OutfitterMinimapButton.CenterStartY = vCenterY - vMinimapCenterY;
end
function OutfitterMinimapButton_DragStart()
OutfitterMinimapButton.IsDragging = true;
OutfitterTimer_AdjustTimer();
end
function OutfitterMinimapButton_DragEnd()
OutfitterMinimapButton.IsDragging = false;
OutfitterTimer_AdjustTimer();
end
function OutfitterMinimapButton_UpdateDragPosition()
-- Remember where the cursor was in case the user drags
local vCursorX, vCursorY = GetCursorPosition();
vCursorX = vCursorX / this:GetEffectiveScale();
vCursorY = vCursorY / this:GetEffectiveScale();
local vCursorDeltaX = vCursorX - OutfitterMinimapButton.CursorStartX;
local vCursorDeltaY = vCursorY - OutfitterMinimapButton.CursorStartY;
--
local vCenterX = OutfitterMinimapButton.CenterStartX + vCursorDeltaX;
local vCenterY = OutfitterMinimapButton.CenterStartY + vCursorDeltaY;
-- Calculate the angle
local vAngle = math.atan2(vCenterX, vCenterY);
-- Set the new position
OutfitterMinimapButton_SetPositionAngle(vAngle);
end
function Outfitter_RestrictAngle(pAngle, pRestrictStart, pRestrictEnd)
if pAngle <= pRestrictStart
or pAngle >= pRestrictEnd then
return pAngle;
end
local vDistance = (pAngle - pRestrictStart) / (pRestrictEnd - pRestrictStart);
if vDistance > 0.5 then
return pRestrictEnd;
else
return pRestrictStart;
end
end
function OutfitterMinimapButton_SetPositionAngle(pAngle)
local vAngle = pAngle;
-- Restrict the angle from going over the date/time icon or the zoom in/out icons
local vRestrictedStartAngle = nil;
local vRestrictedEndAngle = nil;
if GameTimeFrame:IsVisible() then
if MinimapZoomIn:IsVisible()
or MinimapZoomOut:IsVisible() then
vAngle = Outfitter_RestrictAngle(vAngle, 0.4302272732931596, 2.930420793963121);
else
vAngle = Outfitter_RestrictAngle(vAngle, 0.4302272732931596, 1.720531504573905);
end
elseif MinimapZoomIn:IsVisible()
or MinimapZoomOut:IsVisible() then
vAngle = Outfitter_RestrictAngle(vAngle, 1.720531504573905, 2.930420793963121);
end
-- Restrict it from the tracking icon area
vAngle = Outfitter_RestrictAngle(vAngle, -1.290357134304173, -0.4918423429923585);
--
local vRadius = 80;
vCenterX = math.sin(vAngle) * vRadius;
vCenterY = math.cos(vAngle) * vRadius;
OutfitterMinimapButton:SetPoint("CENTER", "Minimap", "CENTER", vCenterX - 1, vCenterY - 1);
gOutfitter_Settings.Options.MinimapButtonAngle = vAngle;
end
function OutfitterMinimapButton_ItemSelected(pMenu, pValue)
local vType = type(pValue);
if vType == "table" then
local vCategoryID = pValue.CategoryID;
local vIndex = pValue.Index;
local vOutfit = gOutfitter_Settings.Outfits[vCategoryID][vIndex];
local vDoToggle = vCategoryID ~= "Complete";
if vDoToggle
and Outfitter_WearingOutfit(vOutfit) then
Outfitter_RemoveOutfit(vOutfit);
else
Outfitter_WearOutfit(vOutfit, vCategoryID);
end
if vDoToggle then
return true;
end
else
if pValue == 0 then -- Open Outfitter
ShowUIPanel(CharacterFrame);
CharacterFrame_ShowSubFrame("PaperDollFrame");
OutfitterFrame:Show();
end
end
return false;
end
function Outfitter_WearingOutfit(pOutfit)
return OutfitterStack_FindOutfit(pOutfit);
end
function Outfitter_GetCurrentOutfitInfo()
if not gOutfitter_Initialized then
return "", nil;
end
local vStackLength = table.getn(gOutfitter_OutfitStack);
if vStackLength == 0 then
return "", nil;
end
local vOutfit = gOutfitter_OutfitStack[vStackLength];
if vOutfit and vOutfit.Name then
return vOutfit.Name, vOutfit;
else
return Outfitter_cCustom, vOutfit;
end
end
function Outfitter_CheckDatabase()
local vOutfit;
if not gOutfitter_Settings.Version then
local vOutfits = gOutfitter_Settings.Outfits[vCategoryID];
if gOutfitter_Settings.Outfits then
for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
for vIndex, vOutfit in vOutfits do
if Outfitter_OutfitIsComplete(vOutfit, true) then
Outfitter_AddOutfitItem(vOutfit, "AmmoSlot", 0, 0, "", 0);
end
end
end
end
gOutfitter_Settings.Version = 1;
end
-- Versions 1 and 2 both simply add class outfits
-- so just reinitialize those
if gOutfitter_Settings.Version < 3 then
Outfitter_InitializeClassOutfits();
gOutfitter_Settings.Version = 3;
end
-- Version 4 sets the BGDisabled flag for the mounted outfit
if gOutfitter_Settings.Version < 4 then
local vRidingOutfit = Outfitter_GetSpecialOutfit("Riding");
if vRidingOutfit then
vRidingOutfit.BGDisabled = true;
end
gOutfitter_Settings.Version = 4;
end
-- Version 5 adds moonkin form, just reinitialize class outfits
if gOutfitter_Settings.Version < 5 then
Outfitter_InitializeClassOutfits();
gOutfitter_Settings.Version = 5;
end
-- Make sure all outfits have an associated category ID
if gOutfitter_Settings.Outfits then
for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
for vIndex, vOutfit in vOutfits do
vOutfit.CategoryID = vCategoryID;
end
end
end
-- Version 6 and 7 adds item sub-code and enchantment codes
-- (7 tries to clean up failed updates from 6)
if gOutfitter_Settings.Version < 7 then
if not Outfitter_UpdateDatabaseItemCodes() then
gOutfitter_NeedItemCodesUpdated = 5; -- Do up to five attempts at updated the item codes
end
gOutfitter_Settings.Version = 7;
end
-- Scan the outfits and make sure everything is in order
for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
for vIndex, vOutfit in vOutfits do
Outfitter_CheckOutfit(vOutfit);
end
end
end
function Outfitter_CheckOutfit(pOutfit)
if not pOutfit.Name then
pOutfit.Name = "Damaged outfit";
end
if not pOutfit.Items then
pOutfit.Items = {};
end
for vInventorySlot, vItem in pOutfit.Items do
if not vItem.Code then
vItem.Code = 0;
end
if not vItem.SubCode then
vItem.SubCode = 0;
end
if not vItem.Name then
vItem.Name = "";
end
if not vItem.EnchantCode then
vItem.EnchantCode = 0;
end
end
end
function Outfitter_UpdateDatabaseItemCodes()
local vEquippableItems = OutfitterItemList_GetEquippableItems();
local vResult = true;
for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
for vIndex, vOutfit in vOutfits do
for vInventorySlot, vOutfitItem in vOutfit.Items do
if vOutfitItem.Code ~= 0 then
local vItem = OutfitterItemList_FindItemOrAlt(vEquippableItems, vOutfitItem, false, true);
if vItem then
vOutfitItem.SubCode = vItem.SubCode;
vOutfitItem.Name = vItem.Name;
vOutfitItem.EnchantCode = vItem.EnchantCode;
vOutfitItem.Checksum = nil;
else
vResult = false;
end
end
end
end
end
return vResult;
end
local gOutfitter_PaperDollItemSlotButton_OnClick;
function Outfitter_HookPaperDollFrame()
gOutfitter_PaperDollItemSlotButton_OnClick = PaperDollItemSlotButton_OnClick;
PaperDollItemSlotButton_OnClick = Outfitter_PaperDollItemSlotButton_OnClick
end
local Outfitter_cMaxNumQuickSlots = 9;
local Outfitter_cSlotIDToInventorySlot = nil;
function Outfitter_PaperDollItemSlotButton_OnClick(pButton, pIgnoreModifiers)
-- Build the table to convert from slot ID to inventory slot name
if not Outfitter_cSlotIDToInventorySlot then
Outfitter_cSlotIDToInventorySlot = {};
for _, vInventorySlot in Outfitter_cSlotNames do
local vSlotID = GetInventorySlotInfo(vInventorySlot);
Outfitter_cSlotIDToInventorySlot[vSlotID] = vInventorySlot;
end
end
--
local vSlotID = this:GetID();
local vInventorySlot = Outfitter_cSlotIDToInventorySlot[vSlotID];
local vItemLink = GetInventoryItemLink("player", vSlotID);
local vSlotIsEmpty = vItemLink == nil;
-- Call the original function
gOutfitter_PaperDollItemSlotButton_OnClick(pButton, pIgnoreModifiers);
-- If there's an item on the cursor then open the slots otherwise
-- make sure they're closed
if not OutfitterQuickSlots:IsVisible()
and (CursorHasItem() or vSlotIsEmpty) then
-- Hide the tooltip so that it isn't in the way
GameTooltip:Hide();
-- Open QuickSlots
OutfitterQuickSlots_Open(vInventorySlot);
else
OutfitterQuickSlots_Close();
end
end
function OutfitterItemList_AddItem(pItemList, pItem)
-- Add the item to the code list
local vItemFamily = pItemList.ItemsByCode[pItem.Code];
if not vItemFamily then
vItemFamily = {};
pItemList.ItemsByCode[pItem.Code] = vItemFamily;
end
table.insert(vItemFamily, pItem);
-- Add the item to the slot list
local vItemSlot = pItemList.ItemsBySlot[pItem.ItemSlotName];
if not vItemSlot then
vItemSlot = {};
pItemList.ItemsBySlot[pItem.ItemSlotName] = vItemSlot;
end
table.insert(vItemSlot, pItem);
-- Add the item to the bags
if pItem.Location.BagIndex then
local vBagItems = pItemList.BagItems[pItem.Location.BagIndex];
if not vBagItems then
vBagItems = {};
pItemList.BagItems[pItem.Location.BagIndex] = vBagItems;
end
vBagItems[pItem.Location.BagSlotIndex] = pItem;
-- Add the item to the inventory
elseif pItem.Location.SlotName then
pItemList.InventoryItems[pItem.Location.SlotName] = pItem;
end
end
function Outfitter_GetNumBags()
if gOutfitter_BankFrameOpened then
return NUM_BAG_SLOTS + NUM_BANKBAGSLOTS, -1;
else
return NUM_BAG_SLOTS, 0;
end
end
function OutfitterItemList_FlushEquippableItems()
gOutfitter_EquippableItems = nil;
end
function OutfitterItemList_FlushBagFromEquippableItems(pBagIndex)
if gOutfitter_EquippableItems
and gOutfitter_EquippableItems.BagItems[pBagIndex] then
for vBagSlotIndex, vItem in gOutfitter_EquippableItems.BagItems[pBagIndex] do
OutfitterItemList_RemoveItem(gOutfitter_EquippableItems, vItem);
end
gOutfitter_EquippableItems.NeedsUpdate = true;
gOutfitter_EquippableItems.BagItems[pBagIndex] = nil;
end
end
function OutfitterItemList_FlushInventoryFromEquippableItems()
if gOutfitter_EquippableItems then
for vInventorySlot, vItem in gOutfitter_EquippableItems.InventoryItems do
OutfitterItemList_RemoveItem(gOutfitter_EquippableItems, vItem);
end
gOutfitter_EquippableItems.NeedsUpdate = true;
gOutfitter_EquippableItems.InventoryItems = nil;
end
end
function OutfitterItemList_New()
return {ItemsByCode = {}, ItemsBySlot = {}, InventoryItems = nil, BagItems = {}};
end
function OutfitterItemList_RemoveItem(pItemList, pItem)
-- Remove the item from the code list
local vItems = pItemList.ItemsByCode[pItem.Code];
for vIndex, vItem in vItems do
if vItem == pItem then
table.remove(vItems, vIndex);
break;
end
end
-- Remove the item from the slot list
local vItemSlot = pItemList.ItemsBySlot[pItem.ItemSlotName];
if vItemSlot then
for vIndex, vItem in vItemSlot do
if vItem == pItem then
table.remove(vItemSlot, vIndex);
break;
end
end
end
-- Remove the item from the bags list
if pItem.Location.BagIndex then
local vBagItems = pItemList.BagItems[pItem.Location.BagIndex];
if vBagItems then
vBagItems[pItem.Location.BagSlotIndex] = nil;
end
-- Remove the item from the inventory list
elseif pItem.Location.SlotName then
pItemList.InventoryItems[pItem.Location.SlotName] = nil;
end
end
function OutfitterItemList_GetInventoryOutfit(pEquippableItems)
return pEquippableItems.InventoryItems;
end
function OutfitterItemList_ResetIgnoreItemFlags(pItemList)
for vItemCode, vItemFamily in pItemList.ItemsByCode do
for _, vItem in vItemFamily do
vItem.IgnoreItem = nil;
end
end
end
function OutfitterItemList_GetEquippableItems(pIncludeItemStats)
-- If there's a cached copy just clear the IgnoreItem flags and return it
-- (never used cached copy if the caller wants stats)
if gOutfitter_EquippableItems
and not gOutfitter_EquippableItems.NeedsUpdate
and not pIncludeItemStats then
OutfitterItemList_ResetIgnoreItemFlags(gOutfitter_EquippableItems);
return gOutfitter_EquippableItems;
end
if not gOutfitter_EquippableItems
or pIncludeItemStats then
gOutfitter_EquippableItems = OutfitterItemList_New();
end
local _, vPlayerClass = UnitClass("player");
local vStatDistribution = gOutfitter_StatDistribution[vPlayerClass];
if not gOutfitter_EquippableItems.InventoryItems
or pIncludeItemStats then
gOutfitter_EquippableItems.InventoryItems = {};
for _, vInventorySlot in Outfitter_cSlotNames do
local vItemInfo = Outfitter_GetInventoryItemInfo(vInventorySlot);
if vItemInfo
and vItemInfo.ItemSlotName
and vItemInfo.Code ~= 0 then
vItemInfo.SlotName = vInventorySlot;
vItemInfo.Location = {SlotName = vInventorySlot};
if pIncludeItemStats then
OutfitterItemList_GetItemStats(vItemInfo, vStatDistribution);
end
OutfitterItemList_AddItem(gOutfitter_EquippableItems, vItemInfo);
end
end
else
for vInventorySlot, vItem in gOutfitter_EquippableItems.InventoryItems do
vItem.IgnoreItem = nil;
end
end
local vNumBags, vFirstBagIndex = Outfitter_GetNumBags();
for vBagIndex = vFirstBagIndex, vNumBags do
local vBagItems = gOutfitter_EquippableItems.BagItems[vBagIndex];
if not vBagItems
or pIncludeItemStats then
gOutfitter_EquippableItems.BagItems[vBagIndex] = {};
local vNumBagSlots = GetContainerNumSlots(vBagIndex);
if vNumBagSlots > 0 then
for vBagSlotIndex = 1, vNumBagSlots do
local vItemInfo = Outfitter_GetBagItemInfo(vBagIndex, vBagSlotIndex);
if vItemInfo
and vItemInfo.Code ~= 0
and vItemInfo.ItemSlotName
and Outfitter_CanEquipBagItem(vBagIndex, vBagSlotIndex)
and not Outfitter_BagItemWillBind(vBagIndex, vBagSlotIndex) then
vItemInfo.BagIndex = vBagIndex;
vItemInfo.BagSlotIndex = vBagSlotIndex;
vItemInfo.Location = {BagIndex = vBagIndex, BagSlotIndex = vBagSlotIndex};
if pIncludeItemStats then
OutfitterItemList_GetItemStats(vItemInfo, vStatDistribution);
end
OutfitterItemList_AddItem(gOutfitter_EquippableItems, vItemInfo);
end
end -- for vBagSlotIndex
end -- if vNumBagSlots > 0
else -- if not BagItems
for vBagSlotIndex, vItem in vBagItems do
vItem.IgnoreItem = nil;
end
end -- if not BagItems
end -- for vBagIndex
gOutfitter_EquippableItems.NeedsUpdate = false;
return gOutfitter_EquippableItems;
end
function OutfitterItemList_SwapLocations(pItemList, pLocation1, pLocation2)
-- if pLocation1.BagIndex then
-- Outfitter_TestMessage("OutfitterItemList_SwapLocations: Swapping bag "..pLocation1.BagIndex..", "..pLocation1.BagSlotIndex);
-- elseif pLocation1.SlotName then
-- Outfitter_TestMessage("OutfitterItemList_SwapLocations: Swapping slot "..pLocation1.SlotName);
-- end
-- if pLocation2.BagIndex then
-- Outfitter_TestMessage("OutfitterItemList_SwapLocations: with bag "..pLocation2.BagIndex..", "..pLocation2.BagSlotIndex);
-- elseif pLocation2.SlotName then
-- Outfitter_TestMessage("OutfitterItemList_SwapLocations: with slot "..pLocation2.SlotName);
-- end
end
function OutfitterItemList_SwapLocationWithInventorySlot(pItemList, pLocation, pSlotName)
-- if pLocation.BagIndex then
-- Outfitter_TestMessage("OutfitterItemList_SwapLocationWithInventorySlot: Swapping bag "..pLocation.BagIndex..", "..pLocation.BagSlotIndex.." with slot "..pSlotName);
-- elseif pLocation.SlotName then
-- Outfitter_TestMessage("OutfitterItemList_SwapLocationWithInventorySlot: Swapping slot "..pLocation.SlotName.." with slot "..pSlotName);
-- end
end
function OutfitterItemList_SwapBagSlotWithInventorySlot(pItemList, pBagIndex, pBagSlotIndex, pSlotName)
-- Outfitter_TestMessage("OutfitterItemList_SwapBagSlotWithInventorySlot: Swapping bag "..pBagIndex..", "..pBagSlotIndex.." with slot "..pSlotName);
end
function OutfitterItemList_FindItemOrAlt(pItemList, pOutfitItem, pMarkAsInUse, pAllowSubCodeWildcard)
local vItem, vIgnoredItem = OutfitterItemList_FindItem(pItemList, pOutfitItem, pMarkAsInUse, pAllowSubCodeWildcard);
if vItem then
return vItem;
end
-- See if there's an alias for the item if it wasn't found
local vAltCode = Outfitter_cItemAliases[pOutfitItem.Code];
if not vAltCode then
return nil, vIgnoredItem;
end
return OutfitterItemList_FindItem(pItemList, {Code = vAltCode}, pMarkAsInUse, true);
end
function OutfitterItemList_FindItem(pItemList, pOutfitItem, pMarkAsInUse, pAllowSubCodeWildcard)
local vItem, vIndex, vItemFamily, vIgnoredItem = OutfitterItemList_FindItemIndex(pItemList, pOutfitItem, pAllowSubCodeWildcard);
if not vItem then
return nil, vIgnoredItem;
end
if pMarkAsInUse then
vItem.IgnoreItem = true;
end
return vItem;
end
function OutfitterItemList_FindAllItemsOrAlt(pItemList, pOutfitItem, pAllowSubCodeWildcard, rItems)
local vNumItems = OutfitterItemList_FindAllItems(pItemList, pOutfitItem, pAllowSubCodeWildcard, rItems);
local vAltCode = Outfitter_cItemAliases[pOutfitItem.Code];
if vAltCode then
vNumItems = vNumItems + OutfitterItemList_FindAllItems(pItemList, {Code = vAltCode}, true, rItems);
end
return vNumItems;
end
function OutfitterItemList_FindAllItems(pItemList, pOutfitItem, pAllowSubCodeWildcard, rItems)
if not pItemList then
return 0;
end
local vItemFamily = pItemList.ItemsByCode[pOutfitItem.Code];
if not vItemFamily then
return 0;
end
local vNumItemsFound = 0;
for vIndex, vItem in vItemFamily do
if (pAllowSubCodeWildcard and not pOutfitItem.SubCode)
or vItem.SubCode == pOutfitItem.SubCode then
table.insert(rItems, vItem);
vNumItemsFound = vNumItemsFound + 1;
end
end
return vNumItemsFound;
end
function OutfitterItemList_FindItemIndex(pItemList, pOutfitItem, pAllowSubCodeWildcard)
if not pItemList then
return nil, nil, nil, nil;
end
local vItemFamily = pItemList.ItemsByCode[pOutfitItem.Code];
if not vItemFamily then
return nil, nil, nil, nil;
end
local vBestMatch = nil;
local vBestMatchIndex = nil;
local vNumItemsFound = 0;
local vFoundIgnoredItem = nil;
for vIndex, vItem in vItemFamily do
if pAllowSubCodeWildcard
and not pOutfitItem.SubCode then
if vItem.IgnoreItem then
vFoundIgnoredItem = vItem;
else
return vItem, vIndex, vItemFamily, nil;
end
-- If the subcode matches then check for an enchant match
elseif vItem.SubCode == pOutfitItem.SubCode then
-- If the enchant matches then we're all done
if vItem.EnchantCode == pOutfitItem.EnchantCode then
if vItem.IgnoreItem then
vFoundIgnoredItem = vItem;
else
return vItem, vIndex, vItemFamily;
end
-- Otherwise save the match in case a better one can
-- be found
else
if vItem.IgnoreItem then
if not vFoundIgnoredItem then
vFoundIgnoredItem = vItem;
end
else
vBestMatch = vItem;
vBestMatchIndex = vIndex;
vNumItemsFound = vNumItemsFound + 1;
end
end
end
end
-- Return the match if only one item was found
if vNumItemsFound == 1
and not vBestMatch.IgnoreItem then
return vBestMatch, vBestMatchIndex, vItemFamily, nil;
end
return nil, nil, nil, vFoundIgnoredItem;
end
function OutfitterItemList_GetItemStats(pItem, pDistribution)
if pItem.Stats then
return pItem.Stats;
end
OutfitterTooltip:SetOwner(OutfitterFrame, "ANCHOR_BOTTOMRIGHT", 0, 0);
if pItem.SlotName then
local vHasItem = OutfitterTooltip:SetInventoryItem("player", GetInventorySlotInfo(pItem.SlotName));
if not vHasItem then
OutfitterTooltip:Hide();
return nil;
end
elseif pItem.BagIndex == -1 then
OutfitterTooltip:SetInventoryItem("player", BankButtonIDToInvSlotID(pItem.BagSlotIndex));
else
OutfitterTooltip:SetBagItem(pItem.BagIndex, pItem.BagSlotIndex);
end
local vStats = Outfitter_GetItemStatsFromTooltip(OutfitterTooltip, pDistribution);
OutfitterTooltip:Hide();
if not vStats then
return nil;
end
pItem.Stats = vStats;
return vStats;
end
function Outfitter_IsBankBagIndex(pBagIndex)
return pBagIndex and (pBagIndex > NUM_BAG_SLOTS or pBagIndex < 0);
end
function OutfitterItemList_GetMissingItems(pEquippableItems, pOutfit)
local vMissingItems = nil;
local vBankedItems = nil;
for vInventorySlot, vOutfitItem in pOutfit.Items do
if vOutfitItem.Code ~= 0 then
local vItem = OutfitterItemList_FindItemOrAlt(pEquippableItems, vOutfitItem);
if not vItem then
if not vMissingItems then
vMissingItems = {};
end
table.insert(vMissingItems, vOutfitItem);
elseif Outfitter_IsBankBagIndex(vItem.Location.BagIndex) then
if not vBankedItems then
vBankedItems = {};
end
table.insert(vBankedItems, vOutfitItem);
end
end
end
return vMissingItems, vBankedItems;
end
function OutfitterItemList_CompiledUnusedItemsList(pEquippableItems)
OutfitterItemList_ResetIgnoreItemFlags(pEquippableItems);
for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
for vOutfitIndex, vOutfit in vOutfits do
for vInventorySlot, vOutfitItem in vOutfit.Items do
if vOutfitItem.Code ~= 0 then
local vItem = OutfitterItemList_FindItemOrAlt(pEquippableItems, vOutfitItem, true);
if vItem then
vItem.UsedInOutfit = true;
end
end
end
end
end
local vUnusedItems = nil;
for vCode, vFamilyItems in pEquippableItems.ItemsByCode do
for vIndex, vOutfitItem in vFamilyItems do
if not vOutfitItem.UsedInOutfit
and vOutfitItem.ItemSlotName ~= "AmmoSlot"
and Outfitter_cIgnoredUnusedItems[vOutfitItem.Code] == nil then
if not vUnusedItems then
vUnusedItems = {};
end
table.insert(vUnusedItems, vOutfitItem);
end
end
end
pEquippableItems.UnusedItems = vUnusedItems;
end
function OutfitterItemList_ItemsAreSame(pEquippableItems, pItem1, pItem2)
if not pItem1 then
return pItem2 == nil;
end
if not pItem2 then
return false;
end
if pItem1.Code == 0 then
return pItem2.Code == 0;
end
if pItem1.Code ~= pItem2.Code
or pItem1.SubCode ~= pItem2.SubCode then
return false;
end
local vItems = {};
local vNumItems = OutfitterItemList_FindAllItemsOrAlt(pEquippableItems, pItem1, nil, vItems);
if vNumItems == 0 then
-- Shouldn't ever get here
Outfitter_ErrorMessage("OutfitterItemList_ItemsAreSame: Item not found");
return false;
elseif vNumItems == 1 then
-- If there's only one of that item then the enchant code
-- is disregarded so just make sure it's the same
return true;
else
return pItem1.EnchantCode == pItem2.EnchantCode;
end
end
function OutfitterItemList_InventorySlotContainsItem(pEquippableItems, pInventorySlot, pOutfitItem)
-- Nil items are supposed to be ignored, so never claim the slot contains them
if pOutfitItem == nil then
return false, nil;
end
-- If the item specifies and empty slot check to see if the slot is actually empty
if pOutfitItem.Code == 0 then
return pEquippableItems.InventoryItems[pInventorySlot] == nil;
end
local vItems = {};
local vNumItems = OutfitterItemList_FindAllItemsOrAlt(pEquippableItems, pOutfitItem, nil, vItems);
if vNumItems == 0 then
return false;
elseif vNumItems == 1 then
-- If there's only one of that item then the enchant code
-- is disregarded so just make sure it's in the slot
return vItems[1].SlotName == pInventorySlot, vItems[1];
else
-- See if one of the items is in the slot
for vIndex, vItem in vItems do
if vItem.SlotName == pInventorySlot then
-- Must match the enchant code if there are multiple items
-- in order to be considered a perfect match
return vItem.EnchantCode == pOutfitItem.EnchantCode, vItem;
end
end
-- No items in the slot
return false, nil;
end
end
function OutfitterQuickSlots_Open(pSlotName)
local vPaperDollSlotName = "Character"..pSlotName;
-- Hide the tooltip so that it isn't in the way
GameTooltip:Hide();
-- Position the window
if pSlotName == "MainHandSlot"
or pSlotName == "SecondaryHandSlot"
or pSlotName == "RangedSlot"
or pSlotName == "AmmoSlot" then
OutfitterQuickSlots:SetPoint("TOPLEFT", vPaperDollSlotName, "BOTTOMLEFT", 0, 0);
else
OutfitterQuickSlots:SetPoint("TOPLEFT", vPaperDollSlotName, "TOPRIGHT", 5, 6);
end
OutfitterQuickSlots.SlotName = pSlotName;
-- Populate the items
local vItems = Outfitter_FindItemsInBagsForSlot(pSlotName);
local vNumSlots = 0;
if vItems then
for vItemInfoIndex, vItemInfo in vItems do
if vNumSlots >= Outfitter_cMaxNumQuickSlots then
break;
end
vNumSlots = vNumSlots + 1;
OutfitterQuickSlots_SetSlotToBag(vNumSlots, vItemInfo.BagIndex, vItemInfo.BagSlotIndex);
end
end
-- If the slot isn't empty, offer an empty slot to put the item in
if vNumSlots < Outfitter_cMaxNumQuickSlots
and not Outfitter_InventorySlotIsEmpty(pSlotName) then
local vBagSlotInfo = Outfitter_GetEmptyBagSlot();
if vBagSlotInfo then
vNumSlots = vNumSlots + 1;
OutfitterQuickSlots_SetSlotToBag(vNumSlots, vBagSlotInfo.BagIndex, vBagSlotInfo.BagSlotIndex);
end
end
-- Resize the window and show it
OutfitterQuickSlots_SetNumSlots(vNumSlots);
if vNumSlots == 0 then
OutfitterQuickSlots:Hide();
else
OutfitterQuickSlots:Show();
end
end
function OutfitterQuickSlots_Close()
OutfitterQuickSlots:Hide();
end
function OutfitterQuickSlots_OnLoad()
table.insert(UIMenus, this:GetName());
end
function OutfitterQuickSlots_OnShow()
end
function OutfitterQuickSlots_OnHide()
end
function OutfitterQuickSlots_OnEvent(pEvent)
end
function OutfitterQuickSlotItem_OnLoad()
this.size = 1; -- one-slot container
end
function OutfitterQuickSlotItem_OnShow()
this:RegisterEvent("BAG_UPDATE");
this:RegisterEvent("BAG_UPDATE_COOLDOWN");
this:RegisterEvent("ITEM_LOCK_CHANGED");
this:RegisterEvent("UPDATE_INVENTORY_ALERTS");
end
function OutfitterQuickSlotItem_OnHide()
this:UnregisterEvent("BAG_UPDATE");
this:UnregisterEvent("BAG_UPDATE_COOLDOWN");
this:UnregisterEvent("ITEM_LOCK_CHANGED");
this:UnregisterEvent("UPDATE_INVENTORY_ALERTS");
end
function OutfitterQuickSlotItemButton_OnEnter(button)
GameTooltip:SetOwner(button, "ANCHOR_RIGHT");
local vBagIndex = button:GetParent():GetID();
local vBagSlotIndex = button:GetID();
local hasItem, hasCooldown, repairCost;
if vBagIndex == -1 then
hasItem, hasCooldown, repairCost = GameTooltip:SetInventoryItem("player", BankButtonIDToInvSlotID(vBagSlotIndex));
else
hasCooldown, repairCost = GameTooltip:SetBagItem(vBagIndex, vBagSlotIndex);
end
if ( InRepairMode() and (repairCost and repairCost > 0) ) then
GameTooltip:AddLine(TEXT(REPAIR_COST), "", 1, 1, 1);
SetTooltipMoney(GameTooltip, repairCost);
GameTooltip:Show();
elseif ( this.readable or (IsControlKeyDown() and button.hasItem) ) then
ShowInspectCursor();
elseif ( MerchantFrame:IsVisible() and MerchantFrame.selectedTab == 1 ) then
ShowContainerSellCursor(button:GetParent():GetID(),button:GetID());
else
ResetCursor();
end
end
function OutfitterQuickSlots_SetNumSlots(pNumSlots)
if pNumSlots > Outfitter_cMaxNumQuickSlots then
pNumSlots = Outfitter_cMaxNumQuickSlots;
end
local vBaseWidth = 11;
local vSlotWidth = 42;
for vIndex = 1, pNumSlots do
local vSlotItem = getglobal("OutfitterQuickSlotsItem"..vIndex);
vSlotItem:ClearAllPoints();
if vIndex == 1 then
vSlotItem:SetPoint("TOPLEFT", "OutfitterQuickSlots", "TOPLEFT", 6, -6);
else
vSlotItem:SetPoint("TOPLEFT", "OutfitterQuickSlotsItem"..(vIndex - 1), "TOPLEFT", vSlotWidth, 0);
end
vSlotItem:Show();
end
-- Hide the unused slots
for vIndex = pNumSlots + 1, Outfitter_cMaxNumQuickSlots do
local vSlotItem = getglobal("OutfitterQuickSlotsItem"..vIndex);
vSlotItem:Hide();
end
-- Size the frame
OutfitterQuickSlots:SetWidth(vBaseWidth + vSlotWidth * pNumSlots);
-- Fix the background
if pNumSlots > 0 then
for vIndex = 1, pNumSlots - 1 do
getglobal("OutfitterQuickSlotsBack"..vIndex):Show();
end
for vIndex = pNumSlots, Outfitter_cMaxNumQuickSlots - 1 do
getglobal("OutfitterQuickSlotsBack"..vIndex):Hide();
end
OutfitterQuickSlotsBackEnd:SetPoint("LEFT", "OutfitterQuickSlotsBack"..(pNumSlots - 1), "RIGHT", 0, 0);
end
end
function OutfitterQuickSlots_SetSlotToBag(pQuickSlotIndex, pBagIndex, pBagSlotIndex)
local vQuickSlotItem = getglobal("OutfitterQuickSlotsItem"..pQuickSlotIndex);
local vQuickSlotItemButton = getglobal("OutfitterQuickSlotsItem"..pQuickSlotIndex.."Item1");
vQuickSlotItem:SetID(pBagIndex);
vQuickSlotItemButton:SetID(pBagSlotIndex);
ContainerFrame_Update(vQuickSlotItem);
end
function Outfitter_RegisterEvent(pFrame, pEvent, pHandler)
if not pFrame.EventHandlers then
pFrame.EventHandlers = {};
end
pFrame.EventHandlers[pEvent] = pHandler;
pFrame:RegisterEvent(pEvent);
end
function Outfitter_UnregisterEvent(pFrame, pEvent)
if pFrame.EventHandlers then
pFrame.EventHandlers[pEvent] = nil;
end
pFrame:UnregisterEvent(pEvent);
end
function Outfitter_SuspendEvent(pFrame, pEvent)
if not pFrame.EventHandlers
or not pFrame.EventHandlers[pEvent] then
return;
end
pFrame:UnregisterEvent(pEvent);
end
function Outfitter_ResumeEvent(pFrame, pEvent)
if not pFrame.EventHandlers
or not pFrame.EventHandlers[pEvent] then
return;
end
pFrame:RegisterEvent(pEvent);
end
function Outfitter_DispatchEvent(pFrame, pEvent)
if not pFrame.EventHandlers then
return false;
end
local vEventHandler = pFrame.EventHandlers[pEvent];
if not vEventHandler then
return false;
end
Outfitter_BeginEquipmentUpdate();
vEventHandler(pEvent);
Outfitter_EndEquipmentUpdate("Outfitter_DispatchEvent("..pEvent..")");
return true;
end
function Outfitter_GetPlayerStat(pStatIndex)
local _, vEffectiveValue, vPosValue, vNegValue = UnitStat("player", pStatIndex);
return vEffectiveValue - vPosValue - vNegValue, vPosValue + vNegValue;
end
function Outfitter_DepositOutfit(pOutfit, pUniqueItemsOnly)
-- Deselect any outfits to avoid them from being updated when
-- items get put away
Outfitter_ClearSelection();
-- Build a list of items for the outfit
local vEquippableItems = OutfitterItemList_GetEquippableItems();
OutfitterItemList_ResetIgnoreItemFlags(vEquippableItems);
-- Make a copy of the outfit
local vUnequipOutfit = Outfitter_NewEmptyOutfit();
for vInventorySlot, vItem in pOutfit.Items do
vUnequipOutfit.Items[vInventorySlot] = vItem;
end
-- Subtract out items from other outfits if unique is specified
if pUniqueItemsOnly then
for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
for vOutfitIndex, vOutfit in vOutfits do
if vOutfit ~= pOutfit then
local vMissingItems, vBankedItems = OutfitterItemList_GetMissingItems(vEquippableItems, vOutfit);
-- Only subtract out items from outfits which aren't themselves partialy banked
if vBankedItems == nil then
Outfitter_SubtractOutfit(vUnequipOutfit, vOutfit, true);
end
end -- if vOutfit
end -- for vOutfitIndex
end -- for vCategoryID
end -- if pUniqueItemsOnly
-- Build the change list
OutfitterItemList_ResetIgnoreItemFlags(vEquippableItems);
local vEquipmentChangeList = Outfitter_BuildUnequipChangeList(vUnequipOutfit, vEquippableItems);
if not vEquipmentChangeList then
return;
end
-- Eliminate items which are already banked
local vChangeIndex = 1;
local vNumChanges = table.getn(vEquipmentChangeList);
while vChangeIndex <= vNumChanges do
vEquipmentChange = vEquipmentChangeList[vChangeIndex];
if Outfitter_IsBankBagIndex(vEquipmentChange.FromLocation.BagIndex) then
table.remove(vEquipmentChangeList, vChangeIndex);
vNumChanges = vNumChanges - 1;
else
vChangeIndex = vChangeIndex + 1;
end
end
-- Get the list of empty bank slots
local vEmptyBankSlots = Outfitter_GetEmptyBankSlotList();
-- Execute the changes
Outfitter_ExecuteEquipmentChangeList2(vEquipmentChangeList, vEmptyBankSlots, Outfitter_cDepositBagsFullError, vExpectedEquippableItems);
end
function Outfitter_WithdrawOutfit(pOutfit)
local vEquippableItems = OutfitterItemList_GetEquippableItems();
-- Build a list of items for the outfit
OutfitterItemList_ResetIgnoreItemFlags(vEquippableItems);
local vEquipmentChangeList = Outfitter_BuildUnequipChangeList(pOutfit, vEquippableItems);
if not vEquipmentChangeList then
return;
end
-- Eliminate items which aren't in the bank
local vChangeIndex = 1;
local vNumChanges = table.getn(vEquipmentChangeList);
while vChangeIndex <= vNumChanges do
vEquipmentChange = vEquipmentChangeList[vChangeIndex];
if not Outfitter_IsBankBagIndex(vEquipmentChange.FromLocation.BagIndex) then
table.remove(vEquipmentChangeList, vChangeIndex);
vNumChanges = vNumChanges - 1;
else
vChangeIndex = vChangeIndex + 1;
end
end
-- Get the list of empty bag slots
local vEmptyBagSlots = Outfitter_GetEmptyBagSlotList();
-- Execute the changes
Outfitter_ExecuteEquipmentChangeList2(vEquipmentChangeList, vEmptyBagSlots, Outfitter_cWithdrawBagsFullError, vExpectedEquippableItems);
end
function Outfitter_TestOutfitCombinations()
local vEquippableItems = OutfitterItemList_GetEquippableItems(true);
local vFilterStats = {["FireResist"] = true};
local vOutfit = Outfitter_FindOutfitCombination(vEquippableItems, vFilterStats, Outfitter_OutfitTestEval, {});
end
function Outfitter_OutfitTestEval(pOpcode, pParams, pOutfit1, pOutfit2)
if pOpcode == "INIT" then
Outfitter_TestMessage("Outfitter_OutfitTestEval: INIT");
elseif pOpcode == "COMPARE" then
Outfitter_TestMessage("Outfitter_OutfitTestEval: COMPARE");
end
end
function Outfitter_FindOutfitCombination(pEquippableItems, pFilterStats, pOutfitEvalFunc, pOutfitEvalParams)
local vSlotIterators = OutfitterSlotIterators_New(pEquippableItems, pFilterStats);
Outfitter_DumpArray("vSlotIterators", vSlotIterators);
local vBestOutfit = nil;
local vNumIterations = 0;
pOutfitEvalFunc("INIT", pOutfitEvalParams);
while vSlotIterators:Increment() do
local vOutfit = vSlotIterators:GetOutfit();
if pOutfitEvalFunc("COMPARE", pOutfitEvalParams, vBestOutfit, vOutfit) then
vBestOutfit = vOutfit;
end
vNumIterations = vNumIterations + 1;
if vNumIterations > 20 then
return vBestOutfit;
end
end
return vBestOutfit;
end
function Outfitter_ItemContainsStats(pItem, pFilterStats)
for vStatID, _ in pFilterStats do
if pItem.Stats[vStatID] then
return true;
end
end
return false;
end
function OutfitterSlotIterators_New(pEquippableItems, pFilterStats)
local vSlotIterators = {Slots = {}};
local vNumCombinations = 1;
for vInventorySlot, vItems in pEquippableItems.ItemsBySlot do
local vNumItems = table.getn(vItems);
if vInventorySlot ~= "AmmoSlot"
and vNumItems > 0 then
-- Filter the items by stat
local vFilteredItems = nil;
if pFilterStats then
vNumItems = 0;
for vItemIndex, vItem in vItems do
if Outfitter_ItemContainsStats(vItem, pFilterStats) then
if not vFilteredItems then
vFilteredItems = {};
end
table.insert(vFilteredItems, vItem);
vNumItems = vNumItems + 1;
end
end
else
vFilteredItems = vItems;
end
-- Add the filtered list
if vFilteredItems then
table.insert(vSlotIterators.Slots, {ItemSlotName = vInventorySlot, Items = vItems, Index = 0, MaxIndex = vNumItems});
vNumCombinations = vNumCombinations * (vNumItems + 1);
Outfitter_TestMessage("OutfitterSlotIterators_New: "..vInventorySlot.." has "..vNumItems.." items. Combinations "..vNumCombinations);
end
end
end
vSlotIterators.Increment = OutfitterSlotIterators_Increment;
vSlotIterators.GetOutfit = OutfitterSlotIterators_GetOutfit;
vSlotIterators.NumCombinations = vNumCombinations;
Outfitter_TestMessage("OutfitterSlotIterators_New: Total combinations "..vNumCombinations);
return vSlotIterators;
end
function OutfitterSlotIterators_Increment(pSlotIterators)
for vSlotIndex, vSlotIterator in pSlotIterators.Slots do
vSlotIterator.Index = vSlotIterator.Index + 1;
if vSlotIterator.Index <= vSlotIterator.MaxIndex then
return true;
end
vSlotIterator.Index = 0;
end
return false; -- Couldn't increment
end
function OutfitterSlotIterators_GetOutfit(pSlotIterators)
local vOutfit = Outfitter_NewEmptyOutfit();
for _, vItems in pSlotIterators.Slots do
-- if vItems.Index > 0 then
-- local vItem = vItems.Items[vItems.Index];
--
-- Outfitter_AddOutfitItem(vOutfit, vItems.ItemSlotName, vItem.Code, vItem.SubCode, vItem.Name, vItem.EnchantCode);
-- end
end
return vOutfit;
end
function OutfitterStats_AddStatValue(pStats, pStat, pValue, pDistribution)
if not pStats[pStat] then
pStats[pStat] = pValue;
else
pStats[pStat] = pStats[pStat] + pValue;
end
if not pDistribution then
return;
end
local vStatDistribution = pDistribution[pStat];
if not vStatDistribution then
return;
end
for vSecondaryStat, vFactors in vStatDistribution do
local vSecondaryValue = pValue * vFactors.Coeff;
if vFactors.Const then
vSecondaryValue = vSecondaryValue + vFactors.Const;
end
if pStats[vSecondaryStat] then
pStats[vSecondaryStat] = pStats[vSecondaryStat] + vSecondaryValue;
else
pStats[vSecondaryStat] = vSecondaryValue;
end
end
end
function OutfitterStats_SubtractStats(pStats, pStats2)
for vStat, vValue in pStats2 do
if pStats[vStat] then
pStats[vStat] = pStats[vStat] - vValue;
end
end
end
function OutfitterStats_AddStats(pStats, pStats2)
for vStat, vValue in pStats2 do
if pStats[vStat] then
pStats[vStat] = pStats[vStat] + vValue;
else
pStats[vStat] = vValue;
end
end
end
function OutfitterTankPoints_New()
local vTankPointData = {};
local _, vPlayerClass = UnitClass("player");
local vStatDistribution = gOutfitter_StatDistribution[vPlayerClass];
if not vStatDistribution then
Outfitter_ErrorMessage("Outfitter: Missing stat distribution data for "..vPlayerClass);
end
vTankPointData.PlayerLevel = UnitLevel("player");
vTankPointData.StaminaFactor = 1.0; -- Warlocks with demonic embrace = 1.15
-- Get the base stats
vTankPointData.BaseStats = {};
OutfitterStats_AddStatValue(vTankPointData.BaseStats, "Strength", UnitStat("player", 1), vStatDistribution);
OutfitterStats_AddStatValue(vTankPointData.BaseStats, "Agility", UnitStat("player", 2), vStatDistribution);
OutfitterStats_AddStatValue(vTankPointData.BaseStats, "Stamina", UnitStat("player", 3), vStatDistribution);
OutfitterStats_AddStatValue(vTankPointData.BaseStats, "Intellect", UnitStat("player", 4), vStatDistribution);
OutfitterStats_AddStatValue(vTankPointData.BaseStats, "Spirit", UnitStat("player", 5), vStatDistribution);
OutfitterStats_AddStatValue(vTankPointData.BaseStats, "Health", UnitHealthMax("player"), vStatDistribution);
vTankPointData.BaseStats.Health = vTankPointData.BaseStats.Health - vTankPointData.BaseStats.Stamina * 10;
vTankPointData.BaseStats.Dodge = GetDodgeChance();
vTankPointData.BaseStats.Parry = GetParryChance();
vTankPointData.BaseStats.Block = GetBlockChance();
local vBaseDefense, vBuffDefense = UnitDefense("player");
OutfitterStats_AddStatValue(vTankPointData.BaseStats, "Defense", vBaseDefense + vBuffDefense, vStatDistribution);
-- Replace the armor with the current value since that already includes various factors
local vBaseArmor, vEffectiveArmor, vArmor, vArmorPosBuff, vArmorNegBuff = UnitArmor("player");
vTankPointData.BaseStats.Armor = vEffectiveArmor;
Outfitter_TestMessage("------------------------------------------");
Outfitter_DumpArray("vTankPointData", vTankPointData);
-- Subtract out the current outfit
local vCurrentOutfitStats = OutfitterTankPoints_GetCurrentOutfitStats(vStatDistribution);
Outfitter_TestMessage("------------------------------------------");
Outfitter_DumpArray("vCurrentOutfitStats", vCurrentOutfitStats);
OutfitterStats_SubtractStats(vTankPointData.BaseStats, vCurrentOutfitStats);
-- Calculate the buff stats (stuff from auras/spell buffs/whatever)
vTankPointData.BuffStats = {};
-- Reset the cumulative values
OutfitterTankPoints_Reset(vTankPointData);
Outfitter_TestMessage("------------------------------------------");
Outfitter_DumpArray("vTankPointData", vTankPointData);
Outfitter_TestMessage("------------------------------------------");
return vTankPointData;
end
function OutfitterTankPoints_Reset(pTankPointData)
pTankPointData.AdditionalStats = {};
end
function OutfitterTankPoints_GetTotalStat(pTankPointData, pStat)
local vTotalStat = pTankPointData.BaseStats[pStat];
if not vTotalStat then
vTotalStat = 0;
end
local vAdditionalStat = pTankPointData.AdditionalStats[pStat];
if vAdditionalStat then
vTotalStat = vTotalStat + vAdditionalStat;
end
local vBuffStat = pTankPointData.BuffStats[pStat];
if vBuffStat then
vTotalStat = vTotalStat + vBuffStat;
end
--
return vTotalStat;
end
function OutfitterTankPoints_CalcTankPoints(pTankPointData, pStanceModifier)
if not pStanceModifier then
pStanceModifier = 1;
end
Outfitter_DumpArray("pTankPointData", pTankPointData);
local vEffectiveArmor = OutfitterTankPoints_GetTotalStat(pTankPointData, "Armor");
Outfitter_TestMessage("Armor: "..vEffectiveArmor);
local vArmorReduction = vEffectiveArmor / ((85 * pTankPointData.PlayerLevel) + 400);
vArmorReduction = vArmorReduction / (vArmorReduction + 1);
local vEffectiveHealth = OutfitterTankPoints_GetTotalStat(pTankPointData, "Health");
Outfitter_TestMessage("Health: "..vEffectiveHealth);
Outfitter_TestMessage("Stamina: "..OutfitterTankPoints_GetTotalStat(pTankPointData, "Stamina"));
--
local vEffectiveDodge = OutfitterTankPoints_GetTotalStat(pTankPointData, "Dodge") * 0.01;
local vEffectiveParry = OutfitterTankPoints_GetTotalStat(pTankPointData, "Parry") * 0.01;
local vEffectiveBlock = OutfitterTankPoints_GetTotalStat(pTankPointData, "Block") * 0.01;
local vEffectiveDefense = OutfitterTankPoints_GetTotalStat(pTankPointData, "Defense");
-- Add agility and defense to dodge
-- defenseInputBox:GetNumber() * 0.04 + agiInputBox:GetNumber() * 0.05
Outfitter_TestMessage("Dodge: "..vEffectiveDodge);
Outfitter_TestMessage("Parry: "..vEffectiveParry);
Outfitter_TestMessage("Block: "..vEffectiveBlock);
Outfitter_TestMessage("Defense: "..vEffectiveDefense);
local vDefenseModifier = (vEffectiveDefense - pTankPointData.PlayerLevel * 5) * 0.04 * 0.01;
Outfitter_TestMessage("Crit reduction: "..vDefenseModifier);
local vMobCrit = max(0, 0.05 - vDefenseModifier);
local vMobMiss = 0.05 + vDefenseModifier;
local vMobDPS = 1;
local vTotalReduction = 1 - (vMobCrit * 2 + (1 - vMobCrit - vMobMiss - vEffectiveDodge - vEffectiveParry)) * (1 - vArmorReduction) * pStanceModifier;
Outfitter_TestMessage("Total reduction: "..vTotalReduction);
local vTankPoints = vEffectiveHealth / (vMobDPS * (1 - vTotalReduction));
return vTankPoints;
--[[
Stats used in TankPoints calculation:
Health
Dodge
Parry
Block
Defense
Armor
]]--
end
function OutfitterTankPoints_GetCurrentOutfitStats(pStatDistribution)
local vTotalStats = {};
for _, vSlotName in Outfitter_cSlotNames do
local vStats = OutfitterItemList_GetItemStats({SlotName = vSlotName});
if vStats then
for vStat, vValue in vStats do
OutfitterStats_AddStatValue(vTotalStats, vStat, vValue, pStatDistribution);
end
end
end
return vTotalStats;
end
function OutfitterTankPoints_Test()
local _, vPlayerClass = UnitClass("player");
local vStatDistribution = gOutfitter_StatDistribution[vPlayerClass];
local vTankPointData = OutfitterTankPoints_New();
local vStats = OutfitterTankPoints_GetCurrentOutfitStats(vStatDistribution);
OutfitterStats_AddStats(vTankPointData.AdditionalStats, vStats);
local vTankPoints = OutfitterTankPoints_CalcTankPoints(vTankPointData);
Outfitter_TestMessage("TankPoints = "..vTankPoints);
end
function Outfitter_TestAmmoSlot()
local vItemInfo = Outfitter_GetInventoryItemInfo("AmmoSlot");
local vSlotID = GetInventorySlotInfo("AmmoSlot");
local vItemLink = GetInventoryItemLink("player", vSlotID);
Outfitter_TestMessage("SlotID: "..vSlotID);
Outfitter_TestMessage("ItemLink: "..vItemLink);
Outfitter_DumpArray("vItemInfo", vItemInfo);
end