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", "Instance", }; 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"}, [Outfitter_cAQ20] = {"Instance"}, [Outfitter_cAQ40] = {"Instance"}, [Outfitter_cZG] = {"Instance"}, }; 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 "..NORMAL_FONT_COLOR_CODE..": Wear an outfit"); Outfitter_NoteMessage(HIGHLIGHT_FONT_COLOR_CODE.."/outfitter unwear "..NORMAL_FONT_COLOR_CODE..": Remove an outfit"); Outfitter_NoteMessage(HIGHLIGHT_FONT_COLOR_CODE.."/outfitter toggle "..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); --hax Outfitter_AddMenuItem(vFrame, Outfitter_cDisableOutfitInInstance, "INSTDISABLE", vOutfit.InstDisabled); 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) or --Mount fix by Red Mage Joe string.find(vTextLine2, "Riding") or string.find(vTextLine2, "Slow and steady...") ) 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()) or (pEnable and vOutfit.InstDisabled and Outfitter_InInstanceZone()) 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_InInstanceZone() local vZoneSpecialIDMap = Outfitter_cZoneSpecialIDMap[gOutfitter_CurrentZone]; return vZoneSpecialIDMap and vZoneSpecialIDMap[1] == "Instance"; 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 == "INSTDISABLE" then if vOutfit.InstDisabled then vOutfit.InstDisabled = nil; else vOutfit.InstDisabled = 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; vRidingOutfit.InstDisabled = 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