Files
Outfitter/Outfitter.lua
T
2026-10-30 16:59:08 +00:00

7387 lines
209 KiB
Lua

gOutfitter_Settings = nil;
Outfitter_cTitleVersion = GetAddOnMetadata( "Outfitter", "Title" ) .. " " .. GetAddOnMetadata( "Outfitter", "Version" )
local AceEvent = AceLibrary:HasInstance("AceEvent-2.0") and AceLibrary("AceEvent-2.0")
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 = 84507, SubCode = 0 }, -- Outfitter_cBarkskinFisher
{ Code = 19970, SubCode = 0 }, -- Outfitter_cArcaniteFishingPole
{ Code = 19022, SubCode = 0 }, -- Outfitter_cNatPaglesFishingPole
{ Code = 12225, SubCode = 0 }, -- Outfitter_cBlumpFishingPole
{ Code = 6367, SubCode = 0 }, -- Outfitter_cBigIronFishingPole
{ Code = 6366, SubCode = 0 }, -- Outfitter_cDarkwoodFishingPole
{ Code = 6365, SubCode = 0 }, -- Outfitter_cStrongFishingPole
{ Code = 6256, SubCode = 0 }, -- Outfitter_cFishingPole
};
local Outfitter_cRidingItems = {
{ Code = 60501, SubCode = 0 }, -- Outfitter_cWhipOfEncouragement
{ Code = 50525, SubCode = 0 }, -- Outfitter_cGoblinCarKey
{ Code = 50524, SubCode = 0 }, -- Outfitter_cGnomeCarKey
{ 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_cAspectOfTheBeast] = "Beast",
[Outfitter_cEvocate] = "Evocate",
};
local gOutfitter_AuraIconSpecialID = {
["INV_Misc_Fork&Knife"] = "Dining",
["INV_Misc_Food_28"] = "Dining",
["Spell_Shadow_Shadowform"] = "Shadowform",
["Spell_Nature_SpiritWolf"] = "GhostWolf",
["Ability_Rogue_FeignDeath"] = "Feigning",
["Ability_Hunter_AspectOfTheMonkey"] = "Monkey",
["Spell_Nature_RavenForm"] = "Hawk",
["Ability_Mount_Pinktiger"] = "Beast",
};
local Outfitter_cSpecialOutfitDescriptions = {
ArgentDawn = Outfitter_cArgentDawnOutfitDescription,
Riding = Outfitter_cRidingOutfitDescription,
Dining = Outfitter_cDiningOutfitDescription,
Battleground = Outfitter_cBattlegroundOutfitDescription,
AB = Outfitter_cArathiBasinOutfitDescription,
AV = Outfitter_cAlteracValleyOutfitDescription,
WSG = Outfitter_cWarsongGulchOutfitDescription,
BR = Outfitter_cBloodRingOutfitDescription,
SGV = Outfitter_cSunnygladeValleyOutfitDescription,
City = Outfitter_cCityOutfitDescription,
Boss = Outfitter_cBossOutfitDescription,
Lvl63 = Outfitter_cLvl63OutfitDescription,
Trash = Outfitter_cTrashOutfitDescription,
Critter = Outfitter_cCritterOutfitDescription,
BeastTrash = Outfitter_cBeastTrashOutfitDescription,
UndeadTrash = Outfitter_cUndeadTrashOutfitDescription,
DemonTrash = Outfitter_cDemonTrashOutfitDescription,
Swimming = Outfitter_cSwimmingOutfitDescription,
};
-- 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",
"BR",
"SGV",
"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_cBloodRing] = { "Battleground", "BR" },
[Outfitter_cSunnygladeValley] = { "Battleground", "SGV" },
[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" },
[Outfitter_cES] = { "Instance" },
[Outfitter_cBM] = { "Instance" },
[Outfitter_cK40] = { "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_CurrentProfessionOutfit = nil;
local gOutfitter_EquippableItems = nil;
local gOutfitter_Initialized = false;
local gOutfitter_Suspended = false;
-- False until the equipped items have been read back reliably after login or
-- zoning. While false, Outfitter neither records inventory differences nor
-- equips anything. See Outfitter_BeginInventoryVerification.
local gOutfitter_InventoryVerified = false;
local gOutfitter_CurrentTarget = UnitName("target");
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_UPDATE"] =
{
text = TEXT(Outfitter_cConfirmUpdateMsg),
button1 = TEXT(Outfitter_cUpdate),
button2 = TEXT(CANCEL),
OnAccept = function() Outfitter_UpdateSelectedOutfit(); 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,
};
local function Outfitter_CreateCurrentOutfitFrame()
local frame = CreateFrame("Frame", "OutfitterCurrentOutfit", UIParent)
frame:SetWidth(150) -- Adjust size as needed
frame:SetHeight(25)
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0) -- Position at center of the parent frame
frame:SetBackdrop({
bgFile = "Interface/Tooltips/UI-Tooltip-Background",
edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 16,
insets = { left = 4, right = 4, top = 4, bottom = 4 }
})
frame:SetBackdropColor(0, 0, 0, 1) -- Black background with full opacity
frame:SetMovable(true)
frame:EnableMouse(true)
frame:SetFrameStrata("HIGH")
-- Create a FontString for currently equipped outfits
frame.equippedOutfits = frame:CreateFontString(nil, "DIALOG", "GameFontNormal")
frame.equippedOutfits:SetWidth(140)
frame.equippedOutfits:SetHeight(15)
frame.equippedOutfits:SetJustifyH("CENTER")
frame.equippedOutfits:SetNonSpaceWrap(false)
frame.equippedOutfits:SetPoint("LEFT", frame, "LEFT", 5, 0) -- Position at left center
frame:RegisterForDrag("LeftButton") -- Only start dragging with the left mouse button
frame:SetScript("OnDragStart", function()
frame:StartMoving()
end)
frame:SetScript("OnDragStop", function()
frame:StopMovingOrSizing()
end)
frame:SetScript("OnMouseUp", function()
if arg1 == "RightButton" then
if not this.dropdown then
this.dropdown = CreateFrame("Frame", "OutfitterCurrentOutfitDropDown");
OutfitterMinimapDropDown_OnLoad( this.dropdown );
this.dropdown.ChangedValueFunc = OutfitterMinimapButton_ItemSelected;
end
ToggleDropDownMenu(nil, nil, this.dropdown, this:GetName(), 2, 2);
end
end)
frame:Hide()
return frame
end
local currentOutfitFrame = Outfitter_CreateCurrentOutfitFrame();
local function Outfitter_UpdateCurrentOutfit()
if gOutfitter_Settings.Options.ShowCurrentOutfit then
local equippedNames = "";
for vCategoryID, vOutfits in gOutfitter_Settings.Outfits do
for vIndex, vOutfit in vOutfits do
if Outfitter_WearingOutfit(vOutfit) then
if equippedNames ~= "" then
equippedNames = vOutfit.Name .. ", " .. equippedNames;
else
equippedNames = vOutfit.Name;
end
end
end
end
currentOutfitFrame.equippedOutfits:SetText(equippedNames)
end
end
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_DetectClientMods();
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");
-- For boss/trash outfit
Outfitter_RegisterEvent(this, "PLAYER_TARGET_CHANGED", Outfitter_TargetChanged);
-- For the swimming outfit. With SuperWoW/ClassicAPI the client swim flag
-- is polled (catches surface swimming); otherwise fall back to the
-- breath timer, which only shows while submerged
if not Outfitter_ClientMods.HasIsSwimming then
Outfitter_RegisterEvent(this, "MIRROR_TIMER_START", Outfitter_MirrorTimerChanged);
Outfitter_RegisterEvent(this, "MIRROR_TIMER_STOP", Outfitter_MirrorTimerChanged);
end
-- For auto-equip/unequip of profession outfits
Outfitter_RegisterEvent(this, "UI_ERROR_MESSAGE", Outfitter_ProfessionCheck);
Outfitter_RegisterEvent(this, "LOOT_CLOSED", Outfitter_ProfessionUnequip);
-- 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;
gOutfitter_InventoryVerified = false;
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_pfUISkin()
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_BeginInventoryVerification();
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_ProfessionCheck(pEvent)
local profession
if string.find(arg1, Outfitter_cRequiresSkinning) then
profession = "Skinning";
elseif string.find(arg1, Outfitter_cRequiresMining) then
profession = "Mining";
elseif string.find(arg1, Outfitter_cRequiresHerbalism) then
profession = "Herbalism";
end
if profession then
local vOutfit = Outfitter_FindOutfitByStatID(profession)
if vOutfit and vOutfit.AutoEquip then
gOutfitter_CurrentProfessionOutfit = vOutfit;
Outfitter_WearOutfit(gOutfitter_CurrentProfessionOutfit);
end
end
end
function Outfitter_ProfessionUnequip(pEvent)
if gOutfitter_CurrentProfessionOutfit then
if gOutfitter_CurrentProfessionOutfit.AutoUnEquip and Outfitter_WearingOutfit(gOutfitter_CurrentProfessionOutfit) then
Outfitter_RemoveOutfit(gOutfitter_CurrentProfessionOutfit);
end
gOutfitter_CurrentProfessionOutfit = nil;
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_TargetChanged()
-- Delay the event to avoid triggering when addons briefly change targets
AceEvent:ScheduleEvent("OutfitterDelayedTargetChange", Outfitter_TargetChangedDelayedEvent, 0.1);
end
function Outfitter_TargetChangedDelayedEvent()
-- GUID with SuperWoW/ClassicAPI, otherwise name + level. The old
-- name-only check skipped re-evaluation when a different mob shared the
-- previous target's name.
local newTarget = Outfitter_GetUnitIdentity("target");
-- check if target actually changed
if newTarget == gOutfitter_CurrentTarget or UnitIsDead("target") then
return ;
end
gOutfitter_CurrentTarget = newTarget;
-- check level of target
-- -1 indicates ? (lvl 63+) unit. Also honour the worldboss
-- classification, which covers bosses whose level is shown.
local level = UnitLevel("target");
if level == -1 or (level ~= 0 and UnitClassification("target") == "worldboss") then
Outfitter_SetSpecialOutfitEnabled("Boss", true);
Outfitter_SetSpecialOutfitEnabled("Lvl63", true);
Outfitter_SetSpecialOutfitEnabled("Trash", false);
Outfitter_SetSpecialOutfitEnabled("Critter", false);
elseif level >= 63 then
Outfitter_SetSpecialOutfitEnabled("Boss", false);
Outfitter_SetSpecialOutfitEnabled("Lvl63", true);
Outfitter_SetSpecialOutfitEnabled("Trash", false);
Outfitter_SetSpecialOutfitEnabled("Critter", false);
elseif UnitCreatureType("target") == Outfitter_cCritter then
Outfitter_SetSpecialOutfitEnabled("Critter", true);
Outfitter_SetSpecialOutfitEnabled("Lvl63", false);
Outfitter_SetSpecialOutfitEnabled("Boss", false);
Outfitter_SetSpecialOutfitEnabled("BeastTrash", false);
Outfitter_SetSpecialOutfitEnabled("UndeadTrash", false);
Outfitter_SetSpecialOutfitEnabled("DemonTrash", false);
elseif level > 0 then
-- check if boss trash
Outfitter_SetSpecialOutfitEnabled("Critter", false);
if gBossTrashNames[UnitName("target")] then
Outfitter_SetSpecialOutfitEnabled("Boss", true);
Outfitter_SetSpecialOutfitEnabled("Lvl63", true);
Outfitter_SetSpecialOutfitEnabled("Trash", false);
Outfitter_SetSpecialOutfitEnabled("BeastTrash", false);
Outfitter_SetSpecialOutfitEnabled("UndeadTrash", false);
Outfitter_SetSpecialOutfitEnabled("DemonTrash", false);
else
Outfitter_SetSpecialOutfitEnabled("Boss", false);
Outfitter_SetSpecialOutfitEnabled("Lvl63", false);
Outfitter_SetSpecialOutfitEnabled("Trash", true);
local creatureType = UnitCreatureType("target");
-- check if undead trash
if creatureType == Outfitter_cCreatureBeast then
Outfitter_SetSpecialOutfitEnabled("BeastTrash", true);
Outfitter_SetSpecialOutfitEnabled("UndeadTrash", false);
Outfitter_SetSpecialOutfitEnabled("DemonTrash", false);
elseif creatureType == Outfitter_cCreatureUndead then
Outfitter_SetSpecialOutfitEnabled("BeastTrash", false);
Outfitter_SetSpecialOutfitEnabled("UndeadTrash", true);
Outfitter_SetSpecialOutfitEnabled("DemonTrash", false);
elseif creatureType == Outfitter_cCreatureDemon then
Outfitter_SetSpecialOutfitEnabled("BeastTrash", false);
Outfitter_SetSpecialOutfitEnabled("UndeadTrash", false);
Outfitter_SetSpecialOutfitEnabled("DemonTrash", true);
else
Outfitter_SetSpecialOutfitEnabled("BeastTrash", false);
Outfitter_SetSpecialOutfitEnabled("UndeadTrash", false);
Outfitter_SetSpecialOutfitEnabled("DemonTrash", false);
end
end
else
Outfitter_SetSpecialOutfitEnabled("Boss", false);
Outfitter_SetSpecialOutfitEnabled("Lvl63", false);
Outfitter_SetSpecialOutfitEnabled("Trash", false);
Outfitter_SetSpecialOutfitEnabled("BeastTrash", false);
Outfitter_SetSpecialOutfitEnabled("UndeadTrash", false);
Outfitter_SetSpecialOutfitEnabled("DemonTrash", false);
Outfitter_SetSpecialOutfitEnabled("Critter", false);
end
end
function Outfitter_InventoryChanged(pEvent)
if arg1 ~= "player" then
return ;
end
-- debounce the event to avoid doing the same logic many times when swapping sets
AceEvent:ScheduleEvent("OutfitterUnitInventoryChanged", Outfitter_InventoryChanged2, 0.5)
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
-- Until the equipped items can be read reliably, any difference may just
-- be item data that hasn't loaded; verification does this pass later
if not gOutfitter_InventoryVerified then
return ;
end
local vNewItemsOutfit, vCurrentOutfit = Outfitter_GetNewItemsOutfit(gOutfitter_CurrentOutfit);
-- Several slots emptying in one update means the read failed (e.g. a
-- disconnect is in progress). Re-verify instead of recording it.
if vNewItemsOutfit
and Outfitter_IsSuspiciousStripOutfit(vNewItemsOutfit) then
local vUnexpected = Outfitter_NewEmptyOutfit();
for vInventorySlot, vItem in vNewItemsOutfit.Items do
vUnexpected.Items[vInventorySlot] = vItem;
end
Outfitter_SubtractOutfit(vUnexpected, gOutfitter_ExpectedOutfit);
if Outfitter_IsSuspiciousStripOutfit(vUnexpected) then
Outfitter_BeginInventoryVerification();
return ;
end
end
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)
local 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 },
mods = { useOutfit = false, func = Outfitter_ClientModsSummary },
}
local vStartIndex, vEndIndex, vCommand, vParameter = string.find(pCommand, "(%w+) ?(.*)");
if not vCommand then
Outfitter_NoteMessage(HIGHLIGHT_FONT_COLOR_CODE .. "/outfitter wear <outfit name>" .. NORMAL_FONT_COLOR_CODE .. ": Wear an outfit");
Outfitter_NoteMessage(HIGHLIGHT_FONT_COLOR_CODE .. "/outfitter unwear <outfit name>" .. NORMAL_FONT_COLOR_CODE .. ": Remove an outfit");
Outfitter_NoteMessage(HIGHLIGHT_FONT_COLOR_CODE .. "/outfitter toggle <outfit name>" .. NORMAL_FONT_COLOR_CODE .. ": Wears or removes an outfit");
Outfitter_NoteMessage(HIGHLIGHT_FONT_COLOR_CODE .. "/outfitter mods" .. NORMAL_FONT_COLOR_CODE .. ": Show which client mods Outfitter is using");
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_AskUpdateOutfit(pOutfit)
gOutfitter_OutfitToUpdate = pOutfit;
StaticPopup_Show("OUTFITTER_CONFIRM_UPDATE", gOutfitter_OutfitToUpdate.Name);
end
function Outfitter_UpdateSelectedOutfit()
if not gOutfitter_OutfitToUpdate then
return;
end
Outfitter_UpdateOutfit(gOutfitter_OutfitToUpdate);
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_AddMenuItem(vFrame, Outfitter_cUpdateToCurrent, "UPDATE");
if vOutfit.StatID == "Mining" or vOutfit.StatID == "Skinning" or vOutfit.StatID == "Herbalism" then
Outfitter_AddMenuItem(vFrame, Outfitter_cAutoEquip, "AUTOEQUIP", vOutfit.AutoEquip);
Outfitter_AddMenuItem(vFrame, Outfitter_cAutoUnEquip, "AUTOUNEQUIP", vOutfit.AutoUnEquip);
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 Outfitter_SetShowCurrentOutfit(pShowCurrentOutfit)
gOutfitter_Settings.Options.ShowCurrentOutfit = pShowCurrentOutfit;
if gOutfitter_Settings.Options.ShowCurrentOutfit then
currentOutfitFrame:Show();
Outfitter_UpdateCurrentOutfit();
else
currentOutfitFrame:Hide();
end
end
function Outfitter_SetHideDisabledOutfits(pHideDisabledOutfits)
gOutfitter_Settings.Options.HideDisabledOutfits = pHideDisabledOutfits;
Outfitter_Update(false);
end
function OutfitterMinimapDropDown_OnLoad(dropdown)
if ( not dropdown ) then
dropdown = this;
end
UIDropDownMenu_SetAnchor(3, -7, dropdown, "TOPRIGHT", this:GetName(), "TOPLEFT");
UIDropDownMenu_Initialize(dropdown, 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
-- skip disabled outfits if the option is set
if not vOutfit.Disabled or not gOutfitter_Settings.Options.HideDisabledOutfits then
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
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);
OutfitterShowCurrentOutfit:SetChecked(gOutfitter_Settings.Options.ShowCurrentOutfit);
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
-- A temporary outfit that only empties several slots is the footprint of
-- an earlier login where the gear hadn't loaded yet. Wearing it would strip
-- the character, so drop it.
local vTemporaryOutfit = OutfitterStack_GetTemporaryOutfit();
if vTemporaryOutfit and Outfitter_IsSuspiciousStripOutfit(vTemporaryOutfit) then
local vStackSize = table.getn(gOutfitter_OutfitStack);
table.remove(gOutfitter_OutfitStack, vStackSize);
gOutfitter_Settings.LastOutfitStack = {};
for _, vOutfit in gOutfitter_OutfitStack do
if vOutfit.Name then
table.insert(gOutfitter_Settings.LastOutfitStack, { Name = vOutfit.Name });
else
table.insert(gOutfitter_Settings.LastOutfitStack, vOutfit);
end
end
Outfitter_NoteMessage(Outfitter_cDiscardedStripOutfit);
end
gOutfitter_ExpectedOutfit = Outfitter_GetCompiledOutfit();
-- Comparing the saved outfit with what's equipped is deferred until the
-- equipped items can be read (Outfitter_InventoryVerified)
if gOutfitter_Settings.Options.ShowStackContents then
OutfitterStack_DumpStackContents("Restore saved stack");
end
end
-- An outfit counts as a suspicious strip when it has no items at all, only
-- emptied slots, and empties at least three of them. Players don't take off
-- three pieces within one inventory update by hand; a failed read does.
function Outfitter_IsSuspiciousStripOutfit(pOutfit)
local vEmptySlots = 0;
for vInventorySlot, vItem in pOutfit.Items do
if vInventorySlot ~= "AmmoSlot" then
if vItem.Code and vItem.Code ~= 0 then
return false;
end
vEmptySlots = vEmptySlots + 1;
end
end
return vEmptySlots >= 3;
end
-- Inventory verification
--
-- After a crash the client's item cache (WDB) is usually discarded, so for a
-- while after login GetInventoryItemLink() returns nil for equipped items.
-- Outfitter used to read that as "every slot is empty", record it as a
-- temporary outfit on top of the stack and, on the next equipment update,
-- move all the gear into the bags. Now nothing is compared or equipped until
-- two consecutive reads agree and the item data is available.
local cOutfitter_VerifyInterval = 0.5;
local cOutfitter_VerifyMaxAttempts = 60; -- 30 seconds
local gOutfitter_VerifyAttempts = 0;
local gOutfitter_VerifyLastSignature = nil;
function Outfitter_ReadInventorySignature()
local vSignature = "";
local vNumItems = 0;
local vNumUnreadable = 0;
for _, vInventorySlot in Outfitter_cSlotNames do
if vInventorySlot ~= "AmmoSlot" then
local vSlotID = GetInventorySlotInfo(vInventorySlot);
local vItemInfo = Outfitter_GetItemInfoFromLink(GetInventoryItemLink("player", vSlotID));
if vItemInfo then
vNumItems = vNumItems + 1;
vSignature = vSignature .. vItemInfo.Code .. ",";
else
-- An icon with no readable link means the slot is occupied
-- but the item data hasn't arrived yet
if GetInventoryItemTexture("player", vSlotID) then
vNumUnreadable = vNumUnreadable + 1;
end
vSignature = vSignature .. "0,";
end
end
end
return vSignature, vNumItems, vNumUnreadable;
end
-- True when the client has item data for everything the saved outfit
-- expects to be worn. GetItemInfo() returns nil for uncached items.
function Outfitter_ExpectedItemsAreCached()
if not gOutfitter_ExpectedOutfit then
return true;
end
for vInventorySlot, vItem in gOutfitter_ExpectedOutfit.Items do
if vItem.Code and vItem.Code ~= 0 and not GetItemInfo(vItem.Code) then
return false;
end
end
return true;
end
function Outfitter_ExpectedOutfitIsEmpty()
if not gOutfitter_ExpectedOutfit then
return true;
end
for vInventorySlot, vItem in gOutfitter_ExpectedOutfit.Items do
if vInventorySlot ~= "AmmoSlot" and vItem.Code and vItem.Code ~= 0 then
return false;
end
end
return true;
end
function Outfitter_IsInventoryVerified()
return gOutfitter_InventoryVerified;
end
function Outfitter_BeginInventoryVerification()
gOutfitter_InventoryVerified = false;
gOutfitter_VerifyAttempts = 0;
gOutfitter_VerifyLastSignature = nil;
if AceEvent then
AceEvent:ScheduleEvent("OutfitterVerifyInventory", Outfitter_VerifyInventoryTick, cOutfitter_VerifyInterval);
else
Outfitter_InventoryVerifiedNow();
end
end
function Outfitter_VerifyInventoryTick()
if gOutfitter_InventoryVerified then
return ;
end
gOutfitter_VerifyAttempts = gOutfitter_VerifyAttempts + 1;
local vSignature, vNumItems, vNumUnreadable = Outfitter_ReadInventorySignature();
local vStable = vSignature == gOutfitter_VerifyLastSignature;
local vTimedOut = gOutfitter_VerifyAttempts >= cOutfitter_VerifyMaxAttempts;
gOutfitter_VerifyLastSignature = vSignature;
local vReady = vStable
and vNumUnreadable == 0
and Outfitter_ExpectedItemsAreCached()
and (vNumItems > 0 or Outfitter_ExpectedOutfitIsEmpty());
if vReady or vTimedOut then
Outfitter_InventoryVerifiedNow();
else
AceEvent:ScheduleEvent("OutfitterVerifyInventory", Outfitter_VerifyInventoryTick, cOutfitter_VerifyInterval);
end
end
function Outfitter_InventoryVerifiedNow()
gOutfitter_InventoryVerified = true;
Outfitter_BeginEquipmentUpdate();
-- Re-read everything now that the item data is trustworthy
OutfitterItemList_FlushEquippableItems();
gOutfitter_CurrentOutfit = Outfitter_GetInventoryOutfit();
if gOutfitter_ExpectedOutfit then
local vNewItemsOutfit = Outfitter_GetNewItemsOutfit(gOutfitter_ExpectedOutfit);
-- Even after waiting, never turn a mass disappearance of gear into a
-- temporary outfit; let the saved outfit re-equip instead
if vNewItemsOutfit
and not Outfitter_IsSuspiciousStripOutfit(vNewItemsOutfit) then
Outfitter_UpdateTemporaryOutfit(vNewItemsOutfit);
end
end
gOutfitter_DisplayIsDirty = true;
Outfitter_EndEquipmentUpdate("Outfitter_InventoryVerifiedNow");
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");
Outfitter_UpdateCurrentOutfit();
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);
Outfitter_UpdateCurrentOutfit();
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
-- Don't equip anything until the current gear has been read reliably;
-- the pending flags stay set and verification runs this again
if not gOutfitter_InventoryVerified 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
-- Tooltip results keyed by aura spell ID. Only filled when a client mod
-- (SuperWoW / ClassicAPI) supplies spell IDs; each distinct aura is then
-- scanned once per session instead of on every PLAYER_AURAS_CHANGED.
local gOutfitter_AuraTooltipCache = {};
local function Outfitter_GetBuffTooltipTextCached(pBuffIndex, pSpellID, pAuraName)
if not pSpellID then
return Outfitter_GetBuffTooltipText(pBuffIndex);
end
local vCached = gOutfitter_AuraTooltipCache[pSpellID];
if not vCached then
local vText1, vText2 = Outfitter_GetBuffTooltipText(pBuffIndex);
-- Don't cache an empty scan; the tooltip may not have been ready
if not vText1 then
return nil;
end
-- If the mod's aura name disagrees with the tooltip, the two APIs
-- indexed different auras; use the scan but don't cache it
if pAuraName and pAuraName ~= vText1 then
return vText1, vText2;
end
vCached = { Text1 = vText1, Text2 = vText2 };
gOutfitter_AuraTooltipCache[pSpellID] = vCached;
end
return vCached.Text1, vCached.Text2;
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
};
-- A client mount flag is locale-independent and recognises every mount,
-- so when it's available the buff tooltip heuristic is skipped entirely
local vClientMounted = Outfitter_ClientIsMounted();
if vClientMounted ~= nil then
vAuraStates.Riding = vClientMounted;
end
local vBuffIndex = 1;
while true do
local vTexture, vSpellID, vAuraName = Outfitter_GetPlayerBuffInfo(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_GetBuffTooltipTextCached(vBuffIndex, vSpellID, vAuraName);
if vTextLine1 then
local vSpecialID = gOutfitter_SpellNameSpecialID[vTextLine1];
if vSpecialID then
vAuraStates[vSpecialID] = true;
elseif vClientMounted == nil
and 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()
Outfitter_ApplyAuraStates();
-- The client mount flag can lag the aura event by a server update, so
-- check once more shortly afterwards (debounced; only with a mount flag)
if Outfitter_ClientMods.HasIsMounted and AceEvent then
AceEvent:ScheduleEvent("OutfitterAuraRecheck", Outfitter_ApplyAuraStates, 0.3);
end
end
function Outfitter_ApplyAuraStates()
-- 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
-- Swimming outfit
function Outfitter_SetSwimmingState(pIsSwimming)
if not gOutfitter_Initialized then
return ;
end
pIsSwimming = pIsSwimming and true or false;
if gOutfitter_SpecialState.Swimming == nil then
gOutfitter_SpecialState.Swimming = Outfitter_WearingSpecialOutfit("Swimming") and true or false;
end
if gOutfitter_SpecialState.Swimming ~= pIsSwimming then
gOutfitter_SpecialState.Swimming = pIsSwimming;
Outfitter_SetSpecialOutfitEnabled("Swimming", pIsSwimming);
end
end
function Outfitter_MirrorTimerChanged(pEvent)
if arg1 ~= "BREATH" then
return ;
end
Outfitter_SetSwimmingState(pEvent == "MIRROR_TIMER_START");
end
local gOutfitter_SwimPollElapsed = 0;
local gOutfitter_SwimDryTime = 0;
function Outfitter_SwimPoll_OnUpdate()
gOutfitter_SwimPollElapsed = gOutfitter_SwimPollElapsed + arg1;
if gOutfitter_SwimPollElapsed < 0.25 then
return ;
end
gOutfitter_SwimPollElapsed = 0;
if not gOutfitter_Initialized
or gOutfitter_Suspended
or gOutfitter_IsDead then
return ;
end
-- Nothing to do unless there's a usable swimming outfit
local vOutfit = Outfitter_GetSpecialOutfit("Swimming");
if not vOutfit
or vOutfit.Disabled
or Outfitter_IsEmptyOutfit(vOutfit) then
return ;
end
local vIsSwimming = Outfitter_ClientIsSwimming();
if vIsSwimming == nil then
return ;
end
-- Enter immediately, but only leave after being out of the water for a
-- second so bobbing at a shoreline doesn't thrash equipment
if vIsSwimming then
gOutfitter_SwimDryTime = 0;
Outfitter_SetSwimmingState(true);
elseif gOutfitter_SpecialState.Swimming then
gOutfitter_SwimDryTime = gOutfitter_SwimDryTime + 0.25;
if gOutfitter_SwimDryTime >= 1 then
gOutfitter_SwimDryTime = 0;
Outfitter_SetSwimmingState(false);
end
end
end
function Outfitter_StartSwimPolling()
if not Outfitter_ClientMods.HasIsSwimming then
return ;
end
if not gOutfitter_SwimPollFrame then
gOutfitter_SwimPollFrame = CreateFrame("Frame", "OutfitterSwimPollFrame");
gOutfitter_SwimPollFrame:SetScript("OnUpdate", Outfitter_SwimPoll_OnUpdate);
end
gOutfitter_SwimPollFrame:Show();
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()
-- IsInInstance() covers every dungeon and raid; the zone list below only
-- knew a handful of raids (no MC, BWL, Onyxia, Naxxramas or 5-mans)
if IsInInstance then
local vInInstance, vInstanceType = IsInInstance();
if vInInstance then
if vInstanceType then
return vInstanceType ~= "pvp";
end
return not Outfitter_InBattlegroundZone();
end
end
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_UpdateOutfit(pOutfit)
local vOutfitCategoryID, vOutfitIndex = Outfitter_FindOutfit(pOutfit);
local name = pOutfit.Name
if not vOutfitCategoryID then
return;
end
local UpdatedpOutfit = Outfitter_GetInventoryOutfit()
pOutfit.Items = UpdatedpOutfit.Items
gOutfitter_Settings.Outfits[vOutfitCategoryID][vOutfitIndex] = 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_StartSwimPolling();
Outfitter_DispatchOutfitEvent("OUTFITTER_INIT")
if gOutfitter_Settings.Options.ShowCurrentOutfit then
currentOutfitFrame:Show();
Outfitter_UpdateCurrentOutfit();
end
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)
local 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);
Outfitter_CreateEmptySpecialOccassionOutfit("BR", Outfitter_cBROutfit);
Outfitter_CreateEmptySpecialOccassionOutfit("SGV", Outfitter_cSGVOutfit);
-- Create the swimming outfit
Outfitter_CreateEmptySpecialOccassionOutfit("Swimming", Outfitter_cSwimmingOutfit);
-- Create the city outfit
Outfitter_CreateEmptySpecialOccassionOutfit("City", Outfitter_cCityOutfit);
-- Create class-specific outfits
Outfitter_InitializeClassOutfits();
-- Create boss outfit if needed
vOutfit = Outfitter_GetSpecialOutfit("Boss");
if not vOutfit then
Outfitter_CreateEmptySpecialOccassionOutfit("Boss", Outfitter_cBossOutfit);
vOutfit = Outfitter_GetSpecialOutfit("Boss");
vOutfit.Disabled = true; -- Disable it by default
end
-- Create Lvl63+ outfit if needed
vOutfit = Outfitter_GetSpecialOutfit("Lvl63");
if not vOutfit then
Outfitter_CreateEmptySpecialOccassionOutfit("Lvl63", Outfitter_cLvl63Outfit);
vOutfit = Outfitter_GetSpecialOutfit("Lvl63");
vOutfit.Disabled = true; -- Disable it by default
end
-- Create trash outfit if needed
vOutfit = Outfitter_GetSpecialOutfit("Trash");
if not vOutfit then
Outfitter_CreateEmptySpecialOccassionOutfit("Trash", Outfitter_cTrashOutfit);
vOutfit = Outfitter_GetSpecialOutfit("Trash");
vOutfit.Disabled = true; -- Disable it by default
end
-- Create critter outfit if needed
vOutfit = Outfitter_GetSpecialOutfit("Critter");
if not vOutfit then
Outfitter_CreateEmptySpecialOccassionOutfit("Critter", Outfitter_cCritterOutfit);
vOutfit = Outfitter_GetSpecialOutfit("Critter");
vOutfit.Disabled = true; -- Disable it by default
end
-- Create beast trash outfit if needed
vOutfit = Outfitter_GetSpecialOutfit("BeastTrash");
if not vOutfit then
Outfitter_CreateEmptySpecialOccassionOutfit("BeastTrash", Outfitter_cBeastTrashOutfit);
vOutfit = Outfitter_GetSpecialOutfit("BeastTrash");
vOutfit.Disabled = true; -- Disable it by default
end
-- Create Undead trash outfit if needed
vOutfit = Outfitter_GetSpecialOutfit("UndeadTrash");
if not vOutfit then
Outfitter_CreateEmptySpecialOccassionOutfit("UndeadTrash", Outfitter_cUndeadTrashOutfit);
vOutfit = Outfitter_GetSpecialOutfit("UndeadTrash");
vOutfit.Disabled = true; -- Disable it by default
end
-- Create Demon trash outfit if needed
vOutfit = Outfitter_GetSpecialOutfit("DemonTrash");
if not vOutfit then
Outfitter_CreateEmptySpecialOccassionOutfit("DemonTrash", Outfitter_cDemonTrashOutfit);
vOutfit = Outfitter_GetSpecialOutfit("DemonTrash");
vOutfit.Disabled = true; -- Disable it by default
end
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 == "UPDATE" then
Outfitter_AskUpdateOutfit(vOutfit, vCategoryID);
elseif pValue == "DEPOSIT" then
Outfitter_DepositOutfit(vOutfit);
elseif pValue == "DEPOSITUNIQUE" then
Outfitter_DepositOutfit(vOutfit, true);
elseif pValue == "WITHDRAW" then
Outfitter_WithdrawOutfit(vOutfit);
elseif pValue == "AUTOEQUIP" then
if vOutfit.AutoEquip then
vOutfit.AutoEquip = nil;
else
vOutfit.AutoEquip = true;
end
elseif pValue == "AUTOUNEQUIP" then
if vOutfit.AutoUnEquip then
vOutfit.AutoUnEquip = nil;
else
vOutfit.AutoUnEquip = true;
end
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()
if not OutfitterMinimapButton.CursorStartX
or not OutfitterMinimapButton.CursorStartY then
OutfitterMinimapButton.IsDragging = false;
return;
end
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();
if not OutfitterMinimapButton.CursorStartX
or not OutfitterMinimapButton.CursorStartY then
return;
end
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 = 27;
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;
OutfitterQuickSlotsBackEnd1:Show();
OutfitterQuickSlotsBackEnd2:Show();
OutfitterQuickSlotsBackStart1:Show();
OutfitterQuickSlotsBackStart2:Show();
for vIndex = 1, pNumSlots do
local vSlotItem = getglobal("OutfitterQuickSlotsItem" .. vIndex);
vSlotItem:ClearAllPoints();
if vIndex == 1 then
vSlotItem:SetPoint("TOPLEFT", "OutfitterQuickSlots", "TOPLEFT", 6, -6);
elseif vIndex == 10 then
vSlotItem:SetPoint("TOPLEFT", "OutfitterQuickSlots", "TOPLEFT", 6, vSlotWidth * -1 - 12);
elseif vIndex == 19 then
vSlotItem:SetPoint("TOPLEFT", "OutfitterQuickSlots", "TOPLEFT", 6, -102);
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
if vIndex == 10 then
OutfitterQuickSlotsBackEnd1:Hide();
OutfitterQuickSlotsBackStart1:Hide();
end
if vIndex == 19 then
OutfitterQuickSlotsBackEnd2:Hide();
OutfitterQuickSlotsBackStart2:Hide();
end
local vSlotItem = getglobal("OutfitterQuickSlotsItem" .. vIndex);
vSlotItem:Hide();
end
-- Size the frame
local pNumSlotsTemp = pNumSlots
if pNumSlots > 9 then
pNumSlotsTemp = 9
end
OutfitterQuickSlots:SetWidth(vBaseWidth + vSlotWidth * pNumSlotsTemp);
-- Fix the background
if pNumSlots > 0 then
for vIndex = 1, pNumSlots - 1 do
getglobal("OutfitterQuickSlotsBack" .. vIndex):Show();
end
if pNumSlots > 18 then
pNumSlotsTemp = pNumSlots - 2
elseif pNumSlots > 9 then
pNumSlotsTemp = pNumSlots - 1
end
for vIndex = pNumSlotsTemp, Outfitter_cMaxNumQuickSlots - 1 do
getglobal("OutfitterQuickSlotsBack" .. vIndex):Hide();
end
if pNumSlots == 19 then
OutfitterQuickSlotsBackEnd2:SetPoint("LEFT", "OutfitterQuickSlotsBackStart2", "RIGHT", 0, 0);
else
OutfitterQuickSlotsBackEnd2:SetPoint("LEFT", "OutfitterQuickSlotsBack" .. (pNumSlotsTemp - 1), "RIGHT", 0, 0);
end
if pNumSlots == 10 then
OutfitterQuickSlotsBackEnd1:SetPoint("LEFT", "OutfitterQuickSlotsBackStart1", "RIGHT", 0, 0);
elseif pNumSlots == 1 then
OutfitterQuickSlotsBackEnd1:SetPoint("LEFT", "OutfitterQuickSlotsBack16", "RIGHT", 0, 0);
elseif pNumSlots < 18 then
OutfitterQuickSlotsBackEnd1:SetPoint("LEFT", "OutfitterQuickSlotsBack" .. (pNumSlots - 2), "RIGHT", 0, 0);
else
OutfitterQuickSlotsBackEnd1:SetPoint("LEFT", "OutfitterQuickSlotsBack16", "RIGHT", 0, 0);
end
if pNumSlots < 9 then
OutfitterQuickSlotsBackEnd:SetPoint("LEFT", "OutfitterQuickSlotsBack" .. (pNumSlots - 1), "RIGHT", 0, 0);
else
OutfitterQuickSlotsBackEnd:SetPoint("LEFT", "OutfitterQuickSlotsBack8", "RIGHT", 0, 0);
end
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
function Outfitter_pfUISkin()
if IsAddOnLoaded( "pfUI" ) and pfUI and pfUI.api and pfUI.env and pfUI.env.C then
pfUI:RegisterSkin( "Outfitter", "vanilla", function()
OutfitterButton:SetPoint( "TOPRIGHT", -28, -40 )
OutfitterButton:SetNormalTexture("Interface\\Addons\\Outfitter\\Textures\\Outfitter-Button-pfUI")
OutfitterButton:SetPushedTexture("Interface\\Addons\\Outfitter\\Textures\\Outfitter-Button-pfUI")
pfUI.api.StripTextures( OutfitterFrame )
pfUI.api.CreateBackdrop( OutfitterFrame, nil, nil, .75 )
pfUI.api.CreateBackdropShadow( OutfitterFrame )
OutfitterFrame:SetPoint( "TOPLEFT", OutfitterButtonFrame, "TOPRIGHT", -28, -40 )
OutfitterFrameTitle:SetPoint( "TOP", 0, -6 )
OutfitterMainFrameButtonBarBackground:SetTexture( nil )
pfUI.api.SkinCloseButton( OutfitterCloseButton )
OutfitterCloseButton:SetPoint( "TOPRIGHT", -4, -4 )
pfUI.api.SkinButton( OutfitterNewButton )
OutfitterNewButton:SetPoint( "BOTTOMRIGHT", -9, 4 )
pfUI.api.SkinButton( OutfitterEnableAll )
OutfitterEnableAll:SetPoint("TOP", -73, -80)
pfUI.api.SkinButton( OutfitterEnableNone )
OutfitterEnableNone:SetPoint("TOP", 18, -80)
pfUI.api.StripTextures( OutfitterMainFrameScrollbarTrench )
pfUI.api.SkinScrollbar( OutfitterMainFrameScrollFrameScrollBar )
pfUI.api.SkinTab( OutfitterFrameTab1 )
OutfitterFrameTab1:ClearAllPoints()
OutfitterFrameTab1:SetPoint( "TOPRIGHT", OutfitterFrame, "BOTTOMRIGHT", 0, -6 )
pfUI.api.SkinTab( OutfitterFrameTab2 )
OutfitterFrameTab2:ClearAllPoints()
OutfitterFrameTab2:SetPoint( "TOPRIGHT", OutfitterFrameTab1, "TOPLEFT", -6, 0 )
pfUI.api.SkinTab( OutfitterFrameTab3 )
OutfitterFrameTab3:ClearAllPoints()
OutfitterFrameTab3:SetPoint( "TOPRIGHT", OutfitterFrameTab2, "TOPLEFT", -6, 0 )
pfUI.api.StripTextures( OutfitterNameOutfitDialog )
pfUI.api.CreateBackdrop( OutfitterNameOutfitDialog )
OutfitterNameOutfitDialogTitle:SetPoint( "TOP", 0, -8 )
pfUI.api.StripTextures( OutfitterNameOutfitDialogName, true, "BACKGROUND" )
pfUI.api.CreateBackdrop( OutfitterNameOutfitDialogName )
OutfitterNameOutfitDialogName:SetWidth( 165 )
pfUI.api.SkinDropDown( OutfitterNameOutfitDialogCreateUsing )
OutfitterNameOutfitDialogCreateUsing:SetPoint( "TOPLEFT", OutfitterNameOutfitDialogName, "TOPLEFT", -17, -30 )
OutfitterNameOutfitDialogCreateUsingTitle:SetPoint( "RIGHT", OutfitterNameOutfitDialogCreateUsing, "LEFT", 5, 0)
pfUI.api.SkinButton( OutfitterNameOutfitDialogDoneButton )
pfUI.api.SkinButton( OutfitterNameOutfitDialogCancelButton )
pfUI.api.StripTextures( OutfitterCurrentOutfit )
pfUI.api.CreateBackdrop( OutfitterCurrentOutfit )
OutfitterCurrentOutfit:SetWidth( 140 )
local function skin_checkbox(cb)
pfUI.api.SkinCheckbox( cb )
local tex = cb:GetCheckedTexture()
tex:ClearAllPoints()
tex:SetPoint( "TOPLEFT", 4, -4 )
tex:SetPoint( "BOTTOMRIGHT", -4, 4 )
end
for i = 0, 13 do
local m = getglobal( "OutfitterItem" .. i .. "OutfitMenu")
pfUI.api.SkinArrowButton( m, "down" )
local cb = getglobal( "OutfitterItem" .. i .. "OutfitSelected")
skin_checkbox( cb )
local c = getglobal( "OutfitterItem" .. i .. "CategoryExpand")
pfUI.api.SkinCollapseButton( c )
c.icon.backdrop:SetPoint("TOPLEFT", -2, 1 )
c.icon.backdrop:SetPoint("BOTTOMRIGHT", 1, -2 )
end
OutfitterShowMinimapButton:SetPoint( "TOPLEFT", 15, -90 )
for _, v in { "ShowMinimapButton", "RememberVisibility", "ShowHotkeyMessages", "ShowCurrentOutfit", "HideDisabledOutfits" } do
local cb = getglobal( "Outfitter" .. v )
skin_checkbox( cb )
end
for _, v in { "Head", "Neck", "Shoulder", "Back", "Chest", "Shirt", "Tabard", "Wrist", "Hands", "Waist", "Legs", "Feet", "Finger0", "Finger1", "Trinket0", "Trinket1", "MainHand", "SecondaryHand", "Ranged", "Ammo" } do
local cb = getglobal( "OutfitterEnable" .. v .. "Slot")
skin_checkbox( cb )
end
end )
end
end