diff --git a/Bindings.xml b/Bindings.xml new file mode 100644 index 0000000..c3b2715 --- /dev/null +++ b/Bindings.xml @@ -0,0 +1,32 @@ + + + Outfitter_WearBoundOutfit(1); + + + Outfitter_WearBoundOutfit(2); + + + Outfitter_WearBoundOutfit(3); + + + Outfitter_WearBoundOutfit(4); + + + Outfitter_WearBoundOutfit(5); + + + Outfitter_WearBoundOutfit(6); + + + Outfitter_WearBoundOutfit(7); + + + Outfitter_WearBoundOutfit(8); + + + Outfitter_WearBoundOutfit(9); + + + Outfitter_WearBoundOutfit(10); + + diff --git a/BossTrash.lua b/BossTrash.lua new file mode 100644 index 0000000..a96b2bb --- /dev/null +++ b/BossTrash.lua @@ -0,0 +1,34 @@ +gBossTrashNames = { + --MC-- + ["Core Rager"] = true, + ["Firesworn"] = true, + ["Flamewaker"] = true, + ["Flamewaker Protector"] = true, + ["Flamewaker Healer"] = true, + ["Flamewaker Elite"] = true, + ["Flamewaker Priest"] = true, + + --ZG-- + ["Razzashi Cobra"] = true, + ["Zealot Zath"] = true, + ["Zealot Lor'Khan"] = true, + ["Witherbark Speaker"] = true, + ["Vilebranch Speaker"] = true, + + --BWL-- + ["Grethok the Controller"] = true, + ["Blackwing Guardsman"] = true, + + --AQ-- + ["Sartura's Royal Guard"] = true, + + --NAXX-- + ["Crypt Guard"] = true, + ["Deathknight Understudy"] = true, + ["Naxxramas Combat Dummy"] = true, + ["Naxxramas Follower"] = true, + ["Naxxramas Worshipper"] = true, + ["Soldier of the Frozen Wastes"] = true, + ["Soul Weaver"] = true, + ["Unstoppable Abomination"] = true, +} diff --git a/Documentation/FutureFeatures.txt b/Documentation/FutureFeatures.txt new file mode 100644 index 0000000..60d1fcf --- /dev/null +++ b/Documentation/FutureFeatures.txt @@ -0,0 +1,14 @@ +Version 2.0: + +- Outfit analyzer displays complete statistics for the current outfit +- Generate outfits to maximize TankPoints and HealPoints +- Unequipping items will attempt to return them to their original bag +- Dining outfit will automatically unequip once health and mana reach 100% +- Enhanced tooltips in the character window explaining how each stat affects other attributes +- Mage Evocate outfit +- Hunter aspects +- Create custom Special Occassion outfits for particular zones/areas +- Scriptable special occassion outfits with custom event triggers and code +- Shows info in item tooltips stating whether that item would be an upgrade for you +- Show info in item tooltips listing the outfits in which the item is used +- Ability to update an outfit with the currently equipped items diff --git a/Documentation/Images/Background.jpg b/Documentation/Images/Background.jpg new file mode 100644 index 0000000..a2d3f8c Binary files /dev/null and b/Documentation/Images/Background.jpg differ diff --git a/Documentation/Images/Minimap.jpg b/Documentation/Images/Minimap.jpg new file mode 100644 index 0000000..8b06794 Binary files /dev/null and b/Documentation/Images/Minimap.jpg differ diff --git a/Documentation/Images/NewOutfit.jpg b/Documentation/Images/NewOutfit.jpg new file mode 100644 index 0000000..7037b97 Binary files /dev/null and b/Documentation/Images/NewOutfit.jpg differ diff --git a/Documentation/Images/OutfitterIcon.jpg b/Documentation/Images/OutfitterIcon.jpg new file mode 100644 index 0000000..2d4fc3b Binary files /dev/null and b/Documentation/Images/OutfitterIcon.jpg differ diff --git a/Documentation/Images/OutfitterUI.jpg b/Documentation/Images/OutfitterUI.jpg new file mode 100644 index 0000000..378d200 Binary files /dev/null and b/Documentation/Images/OutfitterUI.jpg differ diff --git a/Documentation/Images/QuickSlots.jpg b/Documentation/Images/QuickSlots.jpg new file mode 100644 index 0000000..89f4eba Binary files /dev/null and b/Documentation/Images/QuickSlots.jpg differ diff --git a/Documentation/RevisionHistory.txt b/Documentation/RevisionHistory.txt new file mode 100644 index 0000000..6a09dab --- /dev/null +++ b/Documentation/RevisionHistory.txt @@ -0,0 +1,490 @@ +------------------------------------------- +Having an item on the cursor (ie, clicking a bag item) and then activating an outfit +while the item is still being held will cause the outfit to fail to equip properly +------------------------------------------- +Stance/Shapeshift changing outfits should swap in-place in the outfit stack +------------------------------------------- +Optimizing for +Healing appears to sometimes select gear which has lower damage+healing +stats over gear which has healing only (Peacekeeper Leggings being selected over +Judgement Legplates) +------------------------------------------- +Generating an outfit will not find items in the main bank slots +------------------------------------------- + +Version 1.6.0 Changes + +- Optional SuperWoW / ClassicAPI support: client mount flag for the Riding outfit, + per-spell-ID caching of buff tooltip scans, GUID-based target tracking +- New Swimming special occasion outfit (surface swimming needs SuperWoW or ClassicAPI; + falls back to the breath timer otherwise) +- /outfitter mods reports detected client mods +- Fixed gear being unequipped into bags after logging in following a game crash +- Fixed Sunnyglade Valley outfit never equipping +- "Disable in instances" now covers all dungeons and raids +- World-boss classification counts as Boss target +- Localized creature types for target outfits (deDE, frFR) + +Version 1.4 Changes + +- Special Occassion outfit added for mages when evocating +- Special Occassion outfits added for the various hunter aspects +- Battleground and Around Town outfits now have German translations +- Eastern and Western Plaguelands are correctly identified by German versions +- Titan and FuBar menus will now update when renaming an outfit +- Multipart enchants are supported (ie, Zul'Gurub) +- Korean language support + +Version 1.4b5 Changes + +- Generating an outfit now properly recognizes items in the main bank slots +- Added Korean translation + +Version 1.4b4 Changes + +- Arclight Spanner is no longer counted as an unused item +- Added Small Soul Pouch (12 slot shard bag) +- Ammo slot now ignores unequipping ammo +- Multipart enchants are supported (ie, Zul'Gurub) +- Health regen items of the form "xx health per 5 sec." are supported + +Version 1.4b3 Changes + +- Fixed outfit optimization bug so that 2H weapons are compared correctly to 1H/OH combinations +- Fixed shapeshift/stance detection for patch 1.12 +- Fixed Priest: Shadowform + +Version 1.4b2 Changes + +- Changed cloak to low priority when unequipping since it doesn't have durability +- Fixed inventory caching bug (sometimes caused current outfit to appear as "Custom") + +Version 1.3.3 Changes + +- Added a spaz filter to prevent outfits from toggling repeatedly if the user spams a key binding +- Added an on-screen message when changing outfits using a key binding +- Removed Repair Bags feature +- Added Naxxramas to the Argent Dawn zone list +- Corrected plaguelands zone names for German clients + +Version 1.3.3b2 Changes + +- Added Naxxramas to the Argent Dawn zone list +- Correct plaguelands zone names for German clients + +Version 1.3.2 Changes + +- Patch 1.11 compatibility +- Added ability to deposit only the unique items in an outfit to the bank +- Improved QuickSlot support for ammo slot +- Mining pick, skinning knife, blacksmith hammer, twilight cultist pieces and other items are now excluded + from being displayed in the unused items list +- Dining outfit now automatically unequips once health and mana reach 99% +- Fixed initialization so that Argent Dawn, Riding, Fishing etc. outfits are automatically + set up again +- Added ability to recover from outfit stack corruption errors + +Version 1.3.2b4 Changes + +- Modified behavior of Special Occassion outfits so that when they're disabled in Battlegrounds + they'll still automatically unequip +- Added APIs to aid in external addons building an Outfit menu (third-party FuBar support) +- Added OUTFITTER_INIT message to outfit events which is sent after Outfitter finishes initialization + +Version 1.3.2b3 Changes + +- Fixed lockup when generating outfits which utilize weapons, rings or trinkets + +Version 1.3.2b2 Changes + +- Removed debug messages + +Version 1.3.1 Changes + +- Selected items are no longer accidentally updated if another outfit with overlapping + items is deposited to the bank +- Outfit status is now correctly updated after depositing, withdrawing, opening or closing + the bank window +- Battleground-specific outfits are now given priority over the general Battleground outfit + when there is overlap +- Minimap and Titan outfit menus will now update if they're open during an outfit change +- Removed ammo items from the Unused Items list +- Identical items which are both used in an outfit will no longer list one of those items + as being unused + +Version 1.3.1b2 Changes + +- Fixed bug causing a selected outfit to be modified if another outfit containing some + of the same items was deposited to the bank +- Fixed item cache to update correctly afer the bank is opened + +Version 1.3.1b1 Changes + +- Outfits specific to each battleground will now be equipped on top of the general + Battleground outfit +- Minimap and Titan outfit menus modified to update on outfit changes +- Fixed bugs causing outfit status to be incorrect after depositing or withdrawing + them from the bank or equipping them directly from the bank +- Fixed bug causing identical items to sometimes appear in the unused items list even + though they both were used in an outfit +- Modified unused item list to never list ammo + +Version 1.3 Changes + +- Repair All button for repairing everything including items in backpacks +- Show and hide helm and cloak settings are saved with each helm and cloak +- Ammo slot is now supported in outfits +- Improved performance +- Enabled outfit changes when hunters are feigning death. Changing equipment while feigned + may cause the character to appear to stand up, but does not actually break the feign as + previously assumed +- Titan Outfitter displays the name of the most recently equipped outfit + +Version 1.2 Changes + +- Added Around Town outfit which is equipped automatically in friendly cities +- Added Shaman: Ghost Wolf outfit +- Added battleground-specific outfits for AB, AV, WSG +- Added Chance to Hit with Spells outfit generation +- Gave priority to Rune and Seal of the Dawn so they're selected before the old + commission when there's a choice +- Modified stat parsing to ignore set bonuses when generating outfits +- Modified minimap outfit menu to only list outfits which actually contain items +- Odds 'n Ends (unused items) now show icons and color-coded names + +Version 1.1: + +- Better performance when changing outfits and generating smart outfits +- Supports identical items which only differ by their enchantments +- Outfits with missing items are displayed in red and the missing items for that outfit + are listed in the tooltip +- There is a new category, Odds 'n ends, which lists all equipable items which aren't part + of any outfit +- New support for banked items when the bank window is opened: + QuickSlots will include banked items in the list of equippable items for that slot + Outfits with banked items are displayed in blue and the banked items for that + outfit are listed in the tooltip + Items for an outfit can be moved directly to or from the bank + Outfits can be equipped directly from the bank (note that this will cause existing + equipped items to be placed into the bank) +- Generated outfits now take contributions from other item stats into account, including: + Agility is included in armor, dodge, and melee critical strike calculations + Stamina is included in health calculations + Intellect is included in mana and spell critical strike calculations + Spirit is included in mana regen calculations + Strength is included in blocking calculations +- Support for minimaps which have been repositioned to other corners of the screen +- Improved support for complex item swaps for weapon, trinket and ring slots +- Rings, trinkets, and weapons are now calculated correctly when generating outfits +- On-equip category-specific spell damage is now supported, such as "Equip: Increases + damage done by Shadow spells and effects..." +- Titan Panel support + +Version 1.3b10 Changes + +- Fixed bug in Titan Outfitter addon which caused errors during outfit changes + +Version 1.3b9 Changes + +- Fixed bugs which may have caused the tooltip to appear while repairing items + +Version 1.3b8 Changes + +- Fixed bug causing second finger and second trinket slot to be processed incorrectly + during equipment changes as well as causing nil pointer errors to sometimes be generated + when unequipping items + +Version 1.3b7 Changes + +- Fixed bug causing GetCurrentOutfitInfo to sometimes fail (was possibly causing + problems for Titan Outfitter) + +Version 1.3b6 Changes + +- Suppressed outfit change notifications until after initialization is completed (was + possibly causing problems for Titan Outfitter) +- Changed GetCurrentOutfitInfo to return an empty string for the name instead of nil + when no named outfits are found (was possibly causing problems for Titan Outfitter) + +Version 1.3b5 Changes + +- Fixed typo in detecting Spirit stat on German clients +- Fixed outfit caching bug which could sometimes cause outfit items to be marked as missing + +Version 1.3b4 Changes + +- Turned off debug messages +- Added outfit name display to Titan plugin +- Added Herb Pouch (12 slot herb bag) to list of specialty bags + +Version 1.3b3 Changes + +- Disabled hunter feign death handling introduced in 1.2 +- Changed repair in bags button strata to prevent it from being hidden behind the vendor window +- Fixed item caching bugs which caused empty slots to become unselectable + +Version 1.3b2 Changes + +- Slightly improved responsiveness of repair items in bags button +- Fixed repair items in bags feature so that the main backpack gets examined +- Fixed ammo slot support so ammo is now included in the outfit +- Optimized equipment/bag updates. This should improve performance slightly when changing + items as well as when shooting arrows/bullets. + +Version 1.2b6 Changes + +- Fixed bug which caused multiple errors messages on startup when using Titan Panel +- Added ability to hide label text in Titan Panel + +Version 1.2b5 Changes + +- Fixed broken slot enable checkboxes +- Fixed cursor updating in the outfit list + +Version 1.2b4 Changes + +- Added icons and quality-based name coloring to Odds 'n Ends list to make it easier to find + the item (eventually I'll make it so that you can click the item and open the bag containing + it but that'll have to wait for another day) +- Outfit changes will no longer take place while a hunter is feigning death +- Fixed bug with auto equipping city outfit when entering Ironforge under certain circumstances +- Added support for external addons to be notified when outfits are equipped or removed +- Added Outfitter_FindOutfitByStatID for use by external addons +- Fixed outfit changing to wait until the player is alive before changing gear + +Version 1.2b3 Changes + +- Added caching code to improve performance when changing outfits + +Version 1.2b2 Changes + +- Fixed nil pointer bug +- Fixed Disable in Battlegrounds outfit setting +- Updated French translations + +Version 1.1.11 Changes + +- Remove debug code + +Version 1.1.10 Changes + +- Worked around problem with shaepshift/stance outfits not working after leaving battlegrounds +- Corrected stat-detection strings for French clients +- Fixed bug which caused switching from a 2H to a 1H/OH combo to fail sometimes while in combat +- Changed the order in which items are unequipped so that durabiliity items are removed first + +Version 1.1.9 Changes: + +- Modified QuickSlots for better compatibility with other inventory slot addons +- Fixed detection of items in the main bank slots +- Fixed Rebuild Outfit to update an outfit's category after setting the new items +- Correct mount detection for French clients +- Fixed /outfitter wear and unwear commands so they behave the same as clicking in the UI + +Version 1.1.8 Changes: + +- Suspended updates between leaving/entering world events to slightly improve load + screen performance +- Ammo slot checkbox is now hidden for classes which have a relic slot +- Fixed Quickslots to hide on second click if Quickslot is already opened + +Version 1.1.7 Changes: + +- Added support for herbalism and enchanting bags + +Version 1.1.6 Changes: + +- Fixed bug which caused a nil pointer error when using outfit combinations which + don't specify every slot +- Added support for relic items (untested) + +Version 1.1.5 Changes: + +- Fixed Riding outfit and other features related to reading game tooltip text such as outfit generation + +Version 1.1.4 Changes: + +- Modified behavior so that switching between a 1H/OH weapon combo to a 2H weapon will + put the offhand item into the left-most available bag instead of the right-most to match + the behavior of other items when they're unequipped +- Changed base outfit to be empty instead of naked when compiling the current outfit. This + allows a player who don't use complete outfits to have unused slots which are left alone instead of + being made naked. A side effect of this will be that nothing will happen when a complete + wardrobe is uneqipped, where previously that would've removed all armor/weapons. +- Fixed Priest: Shadowform outfit +- Updated for patch 1.10 + +Version 1.1.3 Changes: + +- Fixed problem with outfit changes when entering or leaving an instance while in combat (again) +- Modified outfit updating to occur after all changes have been processed. This should prevent + problems caused by switching stances/shapeshifting outfit changes +- Changed key binding behavior to toggle the bound outfit instead of only wearing it +- Fixed bug causing toggled outfits to not unequip other outfits when worn + +Version 1.1.2 Changes: + +- Modified database item updating to defer until a BAG_UPDATE event is received in + order to provide a more reliable update and avoid having outfits showing items as missing +- Changed minimap menu to skip populating the menu items if main initialization hasn't + completed yet (fixes first-time initialization error when using Titan Panel) +- Fixed bug causing list items to become hidden when the list doesn't fill the display +- Fixed expand/collapse icons to correctly indicate the category state + +Version 1.1.1 Changes: + +- Removed debug messages + +Version 1.1 Changes: + +- Fixed moonkin form for french clients +- Fixed dire bear form for german clients +- Added TitanOutfitter support for Titan Panel + +Version 1.1b8 Changes: + +- Fixed bug which caused Outfitter to become confused about the combat state if + the player entered an instance while in combat +- Fixed Quickslots so they close when the character window is closed + +Version 1.1b7 Changes: + +- Added deposit/withdraw outfit items +- Added coloring and tooltip to indicate banked items +- Fixed error message when using with characters that have empty bag slots + +Version 1.1b6 Changes: + +- Updated user's manual with descriptions of macro commands and key bindings +- Fixed bug causing weapon changes to fail when moving between main hand and secondary hand +- Improved error message when an outfit combination attempts to use the same item in two + different positions (ie, both ring slots, both trinket slots, or both weapon slots) +- Fixed bugs in handling temporary item changes. This was causing problems with items + not swapping back after dining, dismounting, or in some combat situations +- Fixed frame strata so that outfitter UI doesn't obscure the Quickslots + +Version 1.1b5 Changes: + +- Added outfit availability check whenever the bank window is opened or closed +- Changed outfitter frame strata to "high" so that it always opens on top of the bank window +- Changed initialization back to PLAYER_ENTERING_WORLD +- Added support for thrown weapons +- Added error checks for unrecognized inventory types + +Version 1.1b4 Changes: + +- Fixed initial outfit generation to correctly find items (carrot, argent dawn commission, etc.) +- Added bank slot support to QuickSlots so items can be equipped from the bank when the bank window + is opened +- Fixed problems with switching from a 2H weapon/item to a 1H/OH combo +- Modified initialization to always re-create the special occassion outfits if they're missing +- Fixed QuickSlot behavior so that the slots only open if an item is picked up from the inventory + slot. This will prevent them from opening when applying poisons, oils, enchants, etc. + +Version 1.0.2 Changes: + +- Added Druid Moonkin form +- Modified combat detection to use REGEN_ENABLED/DISABLED instead of ENTER/LEAVE_COMBAT. This should + fix various problems with items not equipping properly when entering/leaving combat. +- Fixed minimap menu position for players who relocate their minimap to another part of the screen +- Improved behavior of QuickSlots to prevent them from spontaneously closing + +Version 1.0.1 Changes: + +- Fixed race condition which caused outfits to be accessed before they were initialized +- Fixed initialization to select the Normal outfit after it's created +- Added User's Manual + +Verison 1.0 Changes: + +- Fixed initialization so that a false temporary outfit isn't generated +- Fixed initialization so that the special outfits will be updated correctly + if the state has changed since logging off (ie, logging off while mounted will + now correctly remove the Riding outfit at the next login) +- Added tooltips explaining what the slot enable checkboxes do + +Version 1.0b16 Changes: + +- Fixed bug which generated a naked base outfit for weapons-only changes instead of a transparent outfit +- Updated french translation + +Version 1.0b15 Changes: + +- Fixed nil pointer bug when unequipping items under some circumstances +- Fixed duplicate messages when adding new items to an outfit +- Added French translation + +Version 1.0b14 Changes: + +- Added "Disable outfit in Battlegrounds" option. This option is selected by default for the Riding outfit. +- Fixed outfit changes made during combat so they occur after combat is exited (again) + +Version 1.0b13 Changes: + +- Fixed "That slot is empty" error message caused when switching to a 2H from a 1H/OH combo +- Fixed outfit initialization so that if no previous outfits were worn the player won't be left naked after dismounting + +Version 1.0b12 Changes: + +- Added quivers to speciality bags +- Modified equipment changes in order to improve in-combat weapon switching + +Version 1.0b11 Changes: + +- Added rogue stealth outfit +- Fixed nil pointer bug when an unrecognized shapeshift type is encountered +- Items which are BoE but not yet soulbound will no longer be chosen for smart outfits + nor selected when attempting to equip items +- Added ability to swap weapon slots during combat +- Fixed equipping dual identical weapons/rings/trinkets (again) +- Modified minimap menu to hide unused categories + +Version 1.0b10 Changes: + +- Fixed nil pointer error when attempting to move the minimap icon while + the game time and minimap zoom in/out icons are hidden +- Fixed nil pointer error when changing outfits with some slots being emptied +- Changed minimap menu to hide disabled outfits + +Version 1.0b9 Changes: + +- Added Option panel with ability to turn off minimap button +- Added ability to create an empty outfit +- Added /outfitter command +- Fixed tooltip position on QuickSlot items +- Fixed minimap menu position +- Fixed support for 1H weapons when optimizing outfits +- Added support for displaying 1H weapons in the offhand QuickSlot +- Fixed problems with QuickSlots not staying open +- Fixed problems with multiple trinkets, rings or weapons of the same kind not equipping correctly +- Added support for druid shapeshift, priest shadowform and warrior stance special occassion outfits + +Version 1.0b8 Changes: + +- Added QuickSlots +- Fixed plus/minus button +- Added ability to disable an outfit +- Removed disable special occassions button +- Added Battleground special occassion outfit +- Added support for ammo pouches + +Version 1.0b2 Changes: + +- Fixed bug which clears the selection when an outfit is moved +- Modified slot enable checkboxes to equip drag-and-drop items +- Fixed initialization bug which caused outfits to not be created properly and errors + +Version 1.0b3 Changes: + +- Now remembers the outfits being worn at logoff +- Defers outfit changes made during combat until combat ends + +Version 1.0b4 Changes: + +- Fixed initialization bugs + +Version 1.0b5 Changes: + +- Added Healing outfits +- Added support for soul shard bags +- Added support for Benediction/Anathema and Thunderstrike/Shadowstrike aliases +- Equipping an item while editing an outfit will now add that item to the outfit diff --git a/Documentation/UsersManual.html b/Documentation/UsersManual.html new file mode 100644 index 0000000..bb79892 --- /dev/null +++ b/Documentation/UsersManual.html @@ -0,0 +1,201 @@ + + + Outfitter User's Manual + + + + Outfitter Instructions +

Contents

+ +

Installing Outfitter

+

Outfitter is installed just like any other addon. If World of Warcraft is + running you should exit from the game before installing. Extract the contents + of the Outfitter.zip file and then copy the resulting Outfitter folder to your + World of Warcraft's Addons folder located inside the Interface folder.

+

Windows users: + The Addons folder can normally be found at + C:\Program + Files\World of Warcraft\Interface\Addons

+

MacOS users:The Addons folder can + normally be found inside the Interface folder which is located inside the World + of Warcraft application folder

+

IMPORTANT: Depending on your unzipping application, you may end up with the + addon folder inside another folder. Look inside the Outfitter folder and verify + that there's a file named Outfitter.toc. If you don't see this file and see + another folder instead, open that folder and look for the file. The folder + containing the .toc file is the one which should be copied to your World of + Warcraft addons folder.

+

The addon is now installed and you can now launch World of Warcraft.

+

Accessing Outfitter

+

To open Outfitter simply open World of Warcrafts character window (usually the + 'c' key on your keyboard. You can open Outfitter from the Character window by + click the button with a robe icon in the top right corner of the window.

+
+

Outfit categories and ready-made outfits

+

One of the first things you will notice is that Outfitter has created several + outfits for you and that these outfits are in categories named Complete + Wardrobes, Mix-n-match, Accessories, and Special Occassions.

+

Complete Wardrobes + are outfits which contain an item for every slot of your inventory. Whenever + you wear a complete wardrobe you will replace any other outfits you're wearing + at the time. The first time you use Outfitter it will create two complete + wardrobes for you. The Normal wardrobe is whatever gear you happened to be + wearing when Outfitter was first run. The Birthday Suit outfit is a naked + outfit which will remove all of your gear when you wear it.

+

Mix-n-match + outfits on the other hand don't have items in every slot. These outfits are + used to overlay special sets of items, such as Fire Resist gear or PvP items, + over a complete wardrobe. Any time you select a Mix-n-match outfit you will + replace all other outfits you're wearing, except for the selected Complete + Wardrobe which will act as a base outfit for the overlay. The first time you + use Outfitter it will attempt to create a set of resistance gear (Fire, Nature, + etc.) for you. If you don't have any items which give you a particular + resistance then Outfitter won't create that outfit automatically.

+

Accessories, like Mix-n-match, are also + only partial outfits. The difference is that you can combine as many accessory + outfits as you like. These outfits can be used for equipping special items such + as Jumper Cables, Lei of Lilies, fishing poles, or other items which you don't + normally have as part of another outfit. The first time you use Outfitter it + will search your bags for tradeskill items, such as fishing poles, gloves with + herbalism, mining or skinning enchants, etc. and create Accessory outfits for + those.

+

The last category is + Special Occassion + outfits which are worn automatically for you at the appropriate time. There are + several of these outfits, some are created for every class and others are only + created for specific classes

+

The + Argent Dawn + outfit will be worn whenever you're in the plaguelands, Scholomance or + Stratholme and will automatically be setup with your Argent Dawn trinket if you + have one.

+

The + Riding + outfit will be worn whenever you are mounted and will automatically be setup + with your Carrot on a Stick, boots with Mithril Spurs, and gloves with the + Riding Skill enchantment if you have those. Note that by default the riding + outfit is setup so that it won't equip when you are in a battleground. You can + change this setting using the menu to the right of the outfit.

+

The + Dining + outfit will be worn whenever you start eating or drinking. This outfit is + initially "transparent" (meaning it has no inventory slots enabled) but can be + edited to include items which boost your spirit or health and mana regen in + order to lower your down time. Choosing items for this outfit can be tricky + though since you don't want to swap in items which reduce your stamina or + intellect.

+

The + Battlegrounds + outfit will be worn whenever you're in one of the three battlegrounds and can + be used to automatically equip your PvP gear.

+

There are also class-specific outfits for priests (shadowform), rogues + (stealth), warriors (all three stances), and druids (all four shapeshift + forms). These outfits are set to transparent by default, so they have no + effect.

+

Any of the Special Occassion outfits can be disabled by using the menu to the + right of the outfit. This is useful if you only temporarily want to prevent + that outfit from auto-equipping.

+

Creating an outfit

+

To create an outfit click the New Outfit button at the bottom of the window. + This will open the New Outfit window where you can name your new outfit and + specify how you want it initially setup.

+
+

By default the Optimize menu will be set to use current outfit. This will create + the new outfit as a Complete Wardrobe using whatever items you currently have + equipped.

+

You may choose Create Empty Outfit which will create an outfit with no slots + enabled.

+

If you want to have Outfitter generate an outfit for you which maximizes some + attribute, you can choose that attribute from the various sub-menus of the + Optimize menu. You can maximize stamina, nature resistance, spell damage, and + many other attributes using this feature. Outfitter will scan your bags and + inventory and choose an item for each slot which has the maximum value for that + attribute. If you don't have any items for a slot which contain that attribute + then that slot will not be enabled in your new outfit.

+

Updating an outfit

+

You can change the contents of an outfit at any time by opening Outfitter and + selecting the outfit from your list of outfits. The selected outfit will equip + and the enabled slots for that outfit will have checkmarks in them. Just equip + an item and it will be added to the selected outfit. If you no longer want that + outfit to include a particular slot just uncheck the box for that slot.

+

When an outfit is selected you will also see two new buttons at the top of your + character window "Enable all" and "Enable none" These buttons can be used to + quickly enable all item slots or disable all item slots to make it easier to + create your outfit.

+

The minimap button

+

The minimap button will initially be located at the center of the left side of + your minimap. If you are using other addons which place buttons around the + minimap you may have to temporarily move them out of the way in order to see + Outfitters button.

+
+

To change the position of Outfitter button just drag it to where you'd like it. + This position will be remembered and set for you each time you start World of + Warcraft.

+

To bring up Outfitters minimap menu click the button. From here you can open the + Outfitter UI in order to edit outfits or you can quickly select an outfit to + wear.

+

QuickSlots

+

QuickSlots are automatically added to your character window for each item slot. + They allow you to quickly find items which go into that slot without having to + dig through all your bags. To access the QuickSlot just click a slot in your + character window. A row of inventory slots will appear containing every item + you have which can go into that slot. An empty slot is also provided so you can + quickly unequip an item without having to open your bags.

+
+

Commands

+

Commands to equip or unequip outfits can be included in macros. Outfitter + supports these commands:

+

+ /outfitter wear outfitname + Equips the outfit named outfitname
+ /outfitter unwear outfitname + Unequips the outfit named outfitname
+ /outfitter toggle outfitname + Toggles the outfit named outfitname (if it is currently equipped it's removed, + otherwise it's equipped
+

+

Key Bindings

+

Outfits can be attached to a key so that they can be equipped or removed using a + key press. Outfitter supports up to 10 outfits being attached to keys. To + attach an outfit to a key on your keyboard, do the following:

+

First, use the menu next to the outfit to choose which of the 10 key bindings + you want to use for your outfit.

+

Next open World of Warcraft's Key Binding settings window and scroll down to the + Outfitter section. In that section you will see the 10 key binding outfits + listed and you can associate a key combination with that outfit.

+ + diff --git a/Libs/AceEvent-2.0/AceEvent-2.0.lua b/Libs/AceEvent-2.0/AceEvent-2.0.lua new file mode 100644 index 0000000..c654568 --- /dev/null +++ b/Libs/AceEvent-2.0/AceEvent-2.0.lua @@ -0,0 +1,973 @@ +--[[ +Name: AceEvent-2.0 +Revision: $Rev: 17803 $ +Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team) +Inspired By: Ace 1.x by Turan (turan@gryphon.com) +Website: http://www.wowace.com/ +Documentation: http://www.wowace.com/index.php/AceEvent-2.0 +SVN: http://svn.wowace.com/root/trunk/Ace2/AceEvent-2.0 +Description: Mixin to allow for event handling, scheduling, and inter-addon + communication. +Dependencies: AceLibrary, AceOO-2.0 +]] + +local MAJOR_VERSION = "AceEvent-2.0" +local MINOR_VERSION = "$Revision: 17803 $" + +if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary") end +if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end + +if loadstring("return function(...) return ... end") and AceLibrary:HasInstance(MAJOR_VERSION) then return end -- lua51 check +if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end + +local AceOO = AceLibrary:GetInstance("AceOO-2.0") +local Mixin = AceOO.Mixin +local AceEvent = Mixin { + "RegisterEvent", + "RegisterAllEvents", + "UnregisterEvent", + "UnregisterAllEvents", + "TriggerEvent", + "ScheduleEvent", + "ScheduleRepeatingEvent", + "CancelScheduledEvent", + "CancelAllScheduledEvents", + "IsEventRegistered", + "IsEventScheduled", + "RegisterBucketEvent", + "UnregisterBucketEvent", + "UnregisterAllBucketEvents", + "IsBucketEventRegistered", + } + +local table_setn +do + local version = GetBuildInfo() + if string.find(version, "^2%.") then + -- 2.0.0 + table_setn = function() end + else + table_setn = table.setn + end +end + +local weakKey = {__mode="k"} +local new, del +do + local list = setmetatable({}, weakKey) + function new() + local t = next(list) + if t then + list[t] = nil + return t + else + return {} + end + end + + function del(t) + setmetatable(t, nil) + for k in pairs(t) do + t[k] = nil + end + list[t] = true + end +end + +local FAKE_NIL +local RATE + +local eventsWhichHappenOnce = { + PLAYER_LOGIN = true, + AceEvent_FullyInitialized = true, + VARIABLES_LOADED = true, + PLAYER_LOGOUT = true, +} + +local registeringFromAceEvent +function AceEvent:RegisterEvent(event, method, once) + AceEvent:argCheck(event, 2, "string") + if self == AceEvent and not registeringFromAceEvent then + AceEvent:argCheck(method, 3, "function") + self = method + else + AceEvent:argCheck(method, 3, "string", "function", "nil", "boolean", "number") + if type(method) == "boolean" or type(method) == "number" then + AceEvent:argCheck(once, 4, "nil") + once, method = method, event + end + end + AceEvent:argCheck(once, 4, "number", "boolean", "nil") + if eventsWhichHappenOnce[event] then + once = true + end + local throttleRate + if type(once) == "number" then + throttleRate, once = once + end + if not method then + method = event + end + if type(method) == "string" and type(self[method]) ~= "function" then + AceEvent:error("Cannot register event %q to method %q, it does not exist", event, method) + else + assert(type(method) == "function" or type(method) == "string") + end + + local AceEvent_registry = AceEvent.registry + if not AceEvent_registry[event] then + AceEvent_registry[event] = new() + AceEvent.frame:RegisterEvent(event) + end + + local remember = true + if AceEvent_registry[event][self] then + remember = false + end + AceEvent_registry[event][self] = method + + local AceEvent_onceRegistry = AceEvent.onceRegistry + if once then + if not AceEvent_onceRegistry then + AceEvent.onceRegistry = new() + AceEvent_onceRegistry = AceEvent.onceRegistry + end + if not AceEvent_onceRegistry[event] then + AceEvent_onceRegistry[event] = new() + end + AceEvent_onceRegistry[event][self] = true + else + if AceEvent_onceRegistry and AceEvent_onceRegistry[event] then + AceEvent_onceRegistry[event][self] = nil + if not next(AceEvent_onceRegistry[event]) then + AceEvent_onceRegistry[event] = del(AceEvent_onceRegistry[event]) + end + end + end + + local AceEvent_throttleRegistry = AceEvent.throttleRegistry + if throttleRate then + if not AceEvent_throttleRegistry then + AceEvent.throttleRegistry = new() + AceEvent_throttleRegistry = AceEvent.throttleRegistry + end + if not AceEvent_throttleRegistry[event] then + AceEvent_throttleRegistry[event] = new() + end + if AceEvent_throttleRegistry[event][self] then + AceEvent_throttleRegistry[event][self] = del(AceEvent_throttleRegistry[event][self]) + end + AceEvent_throttleRegistry[event][self] = setmetatable(new(), weakKey) + local t = AceEvent_throttleRegistry[event][self] + t[RATE] = throttleRate + else + if AceEvent_throttleRegistry and AceEvent_throttleRegistry[event] then + if AceEvent_throttleRegistry[event][self] then + AceEvent_throttleRegistry[event][self] = del(AceEvent_throttleRegistry[event][self]) + end + if not next(AceEvent_throttleRegistry[event]) then + AceEvent_throttleRegistry[event] = del(AceEvent_throttleRegistry[event]) + end + end + end + + if remember then + AceEvent:TriggerEvent("AceEvent_EventRegistered", self, event) + end +end + +local ALL_EVENTS + +function AceEvent:RegisterAllEvents(method) + if self == AceEvent then + AceEvent:argCheck(method, 1, "function") + self = method + else + AceEvent:argCheck(method, 1, "string", "function") + if type(method) == "string" and type(self[method]) ~= "function" then + AceEvent:error("Cannot register all events to method %q, it does not exist", method) + end + end + + local AceEvent_registry = AceEvent.registry + if not AceEvent_registry[ALL_EVENTS] then + AceEvent_registry[ALL_EVENTS] = new() + AceEvent.frame:RegisterAllEvents() + end + + AceEvent_registry[ALL_EVENTS][self] = method +end + +local _G = getfenv(0) +local memstack, timestack = {}, {} +local memdiff, timediff +function AceEvent:TriggerEvent(event, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + AceEvent:argCheck(event, 2, "string") + local AceEvent_registry = AceEvent.registry + if (not AceEvent_registry[event] or not next(AceEvent_registry[event])) and (not AceEvent_registry[ALL_EVENTS] or not next(AceEvent_registry[ALL_EVENTS])) then + return + end + local _G_event = _G.event + _G.event = event + local lastEvent = AceEvent.currentEvent + AceEvent.currentEvent = event + + local AceEvent_onceRegistry = AceEvent.onceRegistry + local AceEvent_debugTable = AceEvent.debugTable + if AceEvent_onceRegistry and AceEvent_onceRegistry[event] then + local tmp = new() + for obj, method in pairs(AceEvent_onceRegistry[event]) do + tmp[obj] = AceEvent_registry[event] and AceEvent_registry[event][obj] or nil + end + local obj = next(tmp) + while obj do + local mem, time + if AceEvent_debugTable then + if not AceEvent_debugTable[event] then + AceEvent_debugTable[event] = new() + end + if not AceEvent_debugTable[event][obj] then + AceEvent_debugTable[event][obj] = new() + AceEvent_debugTable[event][obj].mem = 0 + AceEvent_debugTable[event][obj].time = 0 + AceEvent_debugTable[event][obj].count = 0 + end + if memdiff then + table.insert(memstack, memdiff) + table.insert(timestack, timediff) + end + memdiff, timediff = 0, 0 + mem, time = gcinfo(), GetTime() + end + local method = tmp[obj] + AceEvent.UnregisterEvent(obj, event) + if type(method) == "string" then + local obj_method = obj[method] + if obj_method then + obj_method(obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + end + elseif method then -- function + method(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + end + if AceEvent_debugTable then + local dmem, dtime = memdiff, timediff + mem, time = gcinfo() - mem - memdiff, GetTime() - time - timediff + AceEvent_debugTable[event][obj].mem = AceEvent_debugTable[event][obj].mem + mem + AceEvent_debugTable[event][obj].time = AceEvent_debugTable[event][obj].time + time + AceEvent_debugTable[event][obj].count = AceEvent_debugTable[event][obj].count + 1 + + memdiff, timediff = table.remove(memstack), table.remove(timestack) + if memdiff then + memdiff = memdiff + mem + dmem + timediff = timediff + time + dtime + end + end + tmp[obj] = nil + obj = next(tmp) + end + del(tmp) + end + + local AceEvent_throttleRegistry = AceEvent.throttleRegistry + local throttleTable = AceEvent_throttleRegistry and AceEvent_throttleRegistry[event] + if AceEvent_registry[event] then + local tmp = new() + for obj, method in pairs(AceEvent_registry[event]) do + tmp[obj] = method + end + local obj = next(tmp) + while obj do + local method = tmp[obj] + local continue = false + if throttleTable and throttleTable[obj] then + local a1 = a1 + if a1 == nil then + a1 = FAKE_NIL + end + if not throttleTable[obj][a1] or GetTime() - throttleTable[obj][a1] >= throttleTable[obj][RATE] then + throttleTable[obj][a1] = GetTime() + else + continue = true + end + end + if not continue then + local mem, time + if AceEvent_debugTable then + if not AceEvent_debugTable[event] then + AceEvent_debugTable[event] = new() + end + if not AceEvent_debugTable[event][obj] then + AceEvent_debugTable[event][obj] = new() + AceEvent_debugTable[event][obj].mem = 0 + AceEvent_debugTable[event][obj].time = 0 + AceEvent_debugTable[event][obj].count = 0 + end + if memdiff then + table.insert(memstack, memdiff) + table.insert(timestack, timediff) + end + memdiff, timediff = 0, 0 + mem, time = gcinfo(), GetTime() + end + if type(method) == "string" then + local obj_method = obj[method] + if obj_method then + obj_method(obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + end + elseif method then -- function + method(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + end + if AceEvent_debugTable then + local dmem, dtime = memdiff, timediff + mem, time = gcinfo() - mem - memdiff, GetTime() - time - timediff + AceEvent_debugTable[event][obj].mem = AceEvent_debugTable[event][obj].mem + mem + AceEvent_debugTable[event][obj].time = AceEvent_debugTable[event][obj].time + time + AceEvent_debugTable[event][obj].count = AceEvent_debugTable[event][obj].count + 1 + + memdiff, timediff = table.remove(memstack), table.remove(timestack) + if memdiff then + memdiff = memdiff + mem + dmem + timediff = timediff + time + dtime + end + end + end + tmp[obj] = nil + obj = next(tmp) + end + del(tmp) + end + if AceEvent_registry[ALL_EVENTS] then + local tmp = new() + for obj, method in pairs(AceEvent_registry[ALL_EVENTS]) do + tmp[obj] = method + end + local obj = next(tmp) + while obj do + local method = tmp[obj] + local mem, time + if AceEvent_debugTable then + if not AceEvent_debugTable[event] then + AceEvent_debugTable[event] = new() + end + if not AceEvent_debugTable[event][obj] then + AceEvent_debugTable[event][obj] = new() + AceEvent_debugTable[event][obj].mem = 0 + AceEvent_debugTable[event][obj].time = 0 + AceEvent_debugTable[event][obj].count = 0 + end + if memdiff then + table.insert(memstack, memdiff) + table.insert(timestack, timediff) + end + memdiff, timediff = 0, 0 + mem, time = gcinfo(), GetTime() + end + if type(method) == "string" then + local obj_method = obj[method] + if obj_method then + obj_method(obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + end + elseif method then -- function + method(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + end + if AceEvent_debugTable then + local dmem, dtime = memdiff, timediff + mem, time = gcinfo() - mem - memdiff, GetTime() - time - timediff + AceEvent_debugTable[event][obj].mem = AceEvent_debugTable[event][obj].mem + mem + AceEvent_debugTable[event][obj].time = AceEvent_debugTable[event][obj].time + time + AceEvent_debugTable[event][obj].count = AceEvent_debugTable[event][obj].count + 1 + + memdiff, timediff = table.remove(memstack), table.remove(timestack) + if memdiff then + memdiff = memdiff + mem + dmem + timediff = timediff + time + dtime + end + end + tmp[obj] = nil + obj = next(tmp) + end + del(tmp) + end + _G.event = _G_event + AceEvent.currentEvent = lastEvent +end + +-- local accessors +local getn = table.getn +local tinsert = table.insert +local tremove = table.remove +local floor = math.floor +local GetTime = GetTime +local next = next +local pairs = pairs +local unpack = unpack + +local delayRegistry +local tmp = {} +local function OnUpdate() + local t = GetTime() + for k,v in pairs(delayRegistry) do + tmp[k] = true + end + for k in pairs(tmp) do + local v = delayRegistry[k] + if v then + local v_time = v.time + if not v_time then + delayRegistry[k] = del(v) + elseif v_time <= t then + local v_repeatDelay = v.repeatDelay + if v_repeatDelay then + -- use the event time, not the current time, else timing inaccuracies add up over time + v.time = v_time + v_repeatDelay + end + local event = v.event + local mem, time + if AceEvent_debugTable then + mem, time = gcinfo(), GetTime() + end + if type(event) == "function" then + event(unpack(v)) + else + AceEvent:TriggerEvent(event, unpack(v)) + end + if AceEvent_debugTable then + mem, time = gcinfo() - mem, GetTime() - time + v.mem = v.mem + mem + v.timeSpent = v.timeSpent + time + v.count = v.count + 1 + end + if not v_repeatDelay then + local x = delayRegistry[k] + if x and x.time == v_time then -- check if it was manually reset + delayRegistry[k] = del(v) + end + end + end + end + end + for k in pairs(tmp) do + tmp[k] = nil + end + if not next(delayRegistry) then + AceEvent.frame:Hide() + end +end + +local function ScheduleEvent(self, repeating, event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + local id + if type(event) == "string" or type(event) == "table" then + if type(event) == "table" then + if not delayRegistry or not delayRegistry[event] then + AceEvent:error("Bad argument #2 to `ScheduleEvent'. Improper id table fed in.") + end + end + if type(delay) ~= "number" then + id, event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 = event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 + AceEvent:argCheck(event, 3, "string", "function", --[[ so message is right ]] "number") + AceEvent:argCheck(delay, 4, "number") + self:CancelScheduledEvent(id) + end + else + AceEvent:argCheck(event, 2, "string", "function") + AceEvent:argCheck(delay, 3, "number") + end + + if not delayRegistry then + AceEvent.delayRegistry = new() + delayRegistry = AceEvent.delayRegistry + AceEvent.frame:SetScript("OnUpdate", OnUpdate) + end + local t + if type(id) == "table" then + for k in pairs(id) do + id[k] = nil + end + t = id + else + t = new() + end + t[1] = a1 + t[2] = a2 + t[3] = a3 + t[4] = a4 + t[5] = a5 + t[6] = a6 + t[7] = a7 + t[8] = a8 + t[9] = a9 + t[10] = a10 + t[11] = a11 + t[12] = a12 + t[13] = a13 + t[14] = a14 + t[15] = a15 + t[16] = a16 + t[17] = a17 + t[18] = a18 + t[19] = a19 + t[20] = a20 + table_setn(t, 20) + t.event = event + t.time = GetTime() + delay + t.self = self + t.id = id or t + t.repeatDelay = repeating and delay + if AceEvent_debugTable then + t.mem = 0 + t.count = 0 + t.timeSpent = 0 + end + delayRegistry[t.id] = t + AceEvent.frame:Show() + return t.id +end + +function AceEvent:ScheduleEvent(event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + if type(event) == "string" or type(event) == "table" then + if type(event) == "table" then + if not delayRegistry or not delayRegistry[event] then + AceEvent:error("Bad argument #2 to `ScheduleEvent'. Improper id table fed in.") + end + end + if type(delay) ~= "number" then + AceEvent:argCheck(delay, 3, "string", "function", --[[ so message is right ]] "number") + AceEvent:argCheck(a1, 4, "number") + end + else + AceEvent:argCheck(event, 2, "string", "function") + AceEvent:argCheck(delay, 3, "number") + end + + return ScheduleEvent(self, false, event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) +end + +function AceEvent:ScheduleRepeatingEvent(event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + if type(event) == "string" or type(event) == "table" then + if type(event) == "table" then + if not delayRegistry or not delayRegistry[event] then + AceEvent:error("Bad argument #2 to `ScheduleEvent'. Improper id table fed in.") + end + end + if type(delay) ~= "number" then + AceEvent:argCheck(delay, 3, "string", "function", --[[ so message is right ]] "number") + AceEvent:argCheck(a1, 4, "number") + end + else + AceEvent:argCheck(event, 2, "string", "function") + AceEvent:argCheck(delay, 3, "number") + end + + return ScheduleEvent(self, true, event, delay, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) +end + +function AceEvent:CancelScheduledEvent(t) + AceEvent:argCheck(t, 2, "string", "table") + if delayRegistry then + local v = delayRegistry[t] + if v then + delayRegistry[t] = del(v) + if not next(delayRegistry) then + AceEvent.frame:Hide() + end + return true + end + end + return false +end + +function AceEvent:IsEventScheduled(t) + AceEvent:argCheck(t, 2, "string", "table") + if delayRegistry then + local v = delayRegistry[t] + if v then + return true, v.time - GetTime() + end + end + return false, nil +end + +function AceEvent:UnregisterEvent(event) + AceEvent:argCheck(event, 2, "string") + local AceEvent_registry = AceEvent.registry + if AceEvent_registry[event] and AceEvent_registry[event][self] then + AceEvent_registry[event][self] = nil + local AceEvent_onceRegistry = AceEvent.onceRegistry + if AceEvent_onceRegistry and AceEvent_onceRegistry[event] and AceEvent_onceRegistry[event][self] then + AceEvent_onceRegistry[event][self] = nil + if not next(AceEvent_onceRegistry[event]) then + AceEvent_onceRegistry[event] = del(AceEvent_onceRegistry[event]) + end + end + local AceEvent_throttleRegistry = AceEvent.throttleRegistry + if AceEvent_throttleRegistry and AceEvent_throttleRegistry[event] and AceEvent_throttleRegistry[event][self] then + AceEvent_throttleRegistry[event][self] = del(AceEvent_throttleRegistry[event][self]) + if not next(AceEvent_throttleRegistry[event]) then + AceEvent_throttleRegistry[event] = del(AceEvent_throttleRegistry[event]) + end + end + if not next(AceEvent_registry[event]) then + AceEvent_registry[event] = del(AceEvent_registry[event]) + if not AceEvent_registry[ALL_EVENTS] or not next(AceEvent_registry[ALL_EVENTS]) then + AceEvent.frame:UnregisterEvent(event) + end + end + else + if self == AceEvent then + error(string.format("Cannot unregister event %q. Improperly unregistering from AceEvent-2.0.", event), 2) + else + AceEvent:error("Cannot unregister event %q. %q is not registered with it.", event, self) + end + end + AceEvent:TriggerEvent("AceEvent_EventUnregistered", self, event) +end + +function AceEvent:UnregisterAllEvents() + local AceEvent_registry = AceEvent.registry + if AceEvent_registry[ALL_EVENTS] and AceEvent_registry[ALL_EVENTS][self] then + AceEvent_registry[ALL_EVENTS][self] = nil + if not next(AceEvent_registry[ALL_EVENTS]) then + del(AceEvent_registry[ALL_EVENTS]) + AceEvent.frame:UnregisterAllEvents() + for k,v in pairs(AceEvent_registry) do + if k ~= ALL_EVENTS then + AceEvent.frame:RegisterEvent(k) + end + end + AceEvent_registry[event] = nil + end + end + local first = true + for event, data in pairs(AceEvent_registry) do + if first then + if AceEvent_registry.AceEvent_EventUnregistered then + event = "AceEvent_EventUnregistered" + else + first = false + end + end + local x = data[self] + data[self] = nil + if x and event ~= ALL_EVENTS then + if not next(data) then + del(data) + if not AceEvent_registry[ALL_EVENTS] or not next(AceEvent_registry[ALL_EVENTS]) then + AceEvent.frame:UnregisterEvent(event) + end + AceEvent_registry[event] = nil + end + AceEvent:TriggerEvent("AceEvent_EventUnregistered", self, event) + end + if first then + event = nil + end + end + if AceEvent.onceRegistry then + for event, data in pairs(AceEvent.onceRegistry) do + data[self] = nil + end + end +end + +function AceEvent:CancelAllScheduledEvents() + if delayRegistry then + for k,v in pairs(delayRegistry) do + if v.self == self then + delayRegistry[k] = del(v) + end + end + if not next(delayRegistry) then + AceEvent.frame:Hide() + end + end +end + +function AceEvent:IsEventRegistered(event) + AceEvent:argCheck(event, 2, "string") + local AceEvent_registry = AceEvent.registry + if self == AceEvent then + return AceEvent_registry[event] and next(AceEvent_registry[event]) and true or false + end + if AceEvent_registry[event] and AceEvent_registry[event][self] then + return true, AceEvent_registry[event][self] + end + return false, nil +end + +local bucketfunc +function AceEvent:RegisterBucketEvent(event, delay, method) + AceEvent:argCheck(event, 2, "string", "table") + if type(event) == "table" then + for k,v in pairs(event) do + if type(k) ~= "number" then + AceEvent:error("All keys to argument #2 to `RegisterBucketEvent' must be numbers.") + elseif type(v) ~= "string" then + AceEvent:error("All values to argument #2 to `RegisterBucketEvent' must be strings.") + end + end + end + AceEvent:argCheck(delay, 3, "number") + if AceEvent == self then + AceEvent:argCheck(method, 4, "function") + self = method + else + if type(event) == "string" then + AceEvent:argCheck(method, 4, "string", "function", "nil") + if not method then + method = event + end + else + AceEvent:argCheck(method, 4, "string", "function") + end + + if type(method) == "string" and type(self[method]) ~= "function" then + AceEvent:error("Cannot register event %q to method %q, it does not exist", event, method) + end + end + if not AceEvent.buckets then + AceEvent.buckets = new() + end + if not AceEvent.buckets[event] then + AceEvent.buckets[event] = new() + end + if not AceEvent.buckets[event][self] then + AceEvent.buckets[event][self] = new() + AceEvent.buckets[event][self].current = new() + AceEvent.buckets[event][self].self = self + else + AceEvent.CancelScheduledEvent(self, AceEvent.buckets[event][self].id) + end + local bucket = AceEvent.buckets[event][self] + bucket.method = method + + local func = function(arg1) + bucket.run = true + if arg1 then + bucket.current[arg1] = true + end + end + AceEvent.buckets[event][self].func = func + if type(event) == "string" then + AceEvent.RegisterEvent(self, event, func) + else + for _,v in ipairs(event) do + AceEvent.RegisterEvent(self, v, func) + end + end + if not bucketfunc then + bucketfunc = function(bucket) + local current = bucket.current + local method = bucket.method + local self = bucket.self + if bucket.run then + if type(method) == "string" then + self[method](self, current) + elseif method then -- function + method(current) + end + for k in pairs(current) do + current[k] = nil + k = nil + end + bucket.run = false + end + end + end + bucket.id = AceEvent.ScheduleRepeatingEvent(self, bucketfunc, delay, bucket) +end + +function AceEvent:IsBucketEventRegistered(event) + AceEvent:argCheck(event, 2, "string", "table") + return AceEvent.buckets and AceEvent.buckets[event] and AceEvent.buckets[event][self] +end + +function AceEvent:UnregisterBucketEvent(event) + AceEvent:argCheck(event, 2, "string", "table") + if not AceEvent.buckets or not AceEvent.buckets[event] or not AceEvent.buckets[event][self] then + AceEvent:error("Cannot unregister bucket event %q. %q is not registered with it.", event, self) + end + + local bucket = AceEvent.buckets[event][self] + + if type(event) == "string" then + AceEvent.UnregisterEvent(self, event) + else + for _,v in ipairs(event) do + AceEvent.UnregisterEvent(self, v) + end + end + AceEvent:CancelScheduledEvent(bucket.id) + + del(bucket.current) + AceEvent.buckets[event][self] = del(AceEvent.buckets[event][self]) + if not next(AceEvent.buckets[event]) then + AceEvent.buckets[event] = del(AceEvent.buckets[event]) + end +end + +function AceEvent:UnregisterAllBucketEvents() + if not AceEvent.buckets or not next(AceEvent.buckets) then + return + end + for k,v in pairs(AceEvent.buckets) do + if v == self then + AceEvent.UnregisterBucketEvent(self, k) + k = nil + end + end +end + +function AceEvent:OnEmbedDisable(target) + self.UnregisterAllEvents(target) + + self.CancelAllScheduledEvents(target) + + self.UnregisterAllBucketEvents(target) +end + +function AceEvent:EnableDebugging() + if not self.debugTable then + self.debugTable = new() + + if delayRegistry then + for k,v in pairs(self.delayRegistry) do + if not v.mem then + v.mem = 0 + v.count = 0 + v.timeSpent = 0 + end + end + end + end +end + +function AceEvent:IsFullyInitialized() + return self.postInit or false +end + +function AceEvent:IsPostPlayerLogin() + return self.playerLogin or false +end + +function AceEvent:activate(oldLib, oldDeactivate) + AceEvent = self + + if oldLib then + self.onceRegistry = oldLib.onceRegistry + self.throttleRegistry = oldLib.throttleRegistry + self.delayRegistry = oldLib.delayRegistry + self.buckets = oldLib.buckets + self.registry = oldLib.registry + self.frame = oldLib.frame + self.debugTable = oldLib.debugTable + self.playerLogin = oldLib.pew or DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.defaultLanguage and true + self.postInit = oldLib.postInit or self.playerLogin and ChatTypeInfo and ChatTypeInfo.WHISPER and ChatTypeInfo.WHISPER.r and true + self.ALL_EVENTS = oldLib.ALL_EVENTS + self.FAKE_NIL = oldLib.FAKE_NIL + self.RATE = oldLib.RATE + end + if not self.registry then + self.registry = {} + end + if not self.frame then + self.frame = CreateFrame("Frame", "AceEvent20Frame") + end + if not self.ALL_EVENTS then + self.ALL_EVENTS = {} + end + if not self.FAKE_NIL then + self.FAKE_NIL = {} + end + if not self.RATE then + self.RATE = {} + end + ALL_EVENTS = self.ALL_EVENTS + FAKE_NIL = self.FAKE_NIL + RATE = self.RATE + local inPlw = false + local blacklist = { + UNIT_INVENTORY_CHANGED = true, + BAG_UPDATE = true, + ITEM_LOCK_CHANGED = true, + ACTIONBAR_SLOT_CHANGED = true, + } + self.frame:SetScript("OnEvent", function() + local event = event + if event == "PLAYER_ENTERING_WORLD" then + inPlw = false + elseif event == "PLAYER_LEAVING_WORLD" then + inPlw = true + end + if event and (not inPlw or not blacklist[event]) then + self:TriggerEvent(event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) + end + end) + if self.delayRegistry then + delayRegistry = self.delayRegistry + self.frame:SetScript("OnUpdate", OnUpdate) + end + + self:UnregisterAllEvents() + self:CancelAllScheduledEvents() + + registeringFromAceEvent = true + self:RegisterEvent("LOOT_OPENED", function() + SendAddonMessage("LOOT_OPENED", "", "RAID") + end) + registeringFromAceEvent = nil + + if not self.playerLogin then + registeringFromAceEvent = true + self:RegisterEvent("PLAYER_LOGIN", function() + self.playerLogin = true + end, true) + registeringFromAceEvent = nil + end + + if not self.postInit then + local isReload = true + local function func() + self.postInit = true + self:TriggerEvent("AceEvent_FullyInitialized") + if self.registry["CHAT_MSG_CHANNEL_NOTICE"] and self.registry["CHAT_MSG_CHANNEL_NOTICE"][self] then + self:UnregisterEvent("CHAT_MSG_CHANNEL_NOTICE") + end + if self.registry["MEETINGSTONE_CHANGED"] and self.registry["MEETINGSTONE_CHANGED"][self] then + self:UnregisterEvent("MEETINGSTONE_CHANGED") + end + if self.registry["MINIMAP_ZONE_CHANGED"] and self.registry["MINIMAP_ZONE_CHANGED"][self] then + self:UnregisterEvent("MINIMAP_ZONE_CHANGED") + end + if self.registry["LANGUAGE_LIST_CHANGED"] and self.registry["LANGUAGE_LIST_CHANGED"][self] then + self:UnregisterEvent("LANGUAGE_LIST_CHANGED") + end + end + registeringFromAceEvent = true + local f = function() + self.playerLogin = true + self:ScheduleEvent("AceEvent_FullyInitialized", func, 1) + end + self:RegisterEvent("MEETINGSTONE_CHANGED", f, true) + self:RegisterEvent("CHAT_MSG_CHANNEL_NOTICE", function() + self:ScheduleEvent("AceEvent_FullyInitialized", func, 0.05) + end) + self:RegisterEvent("LANGUAGE_LIST_CHANGED", function() + if self.registry["MEETINGSTONE_CHANGED"] and self.registry["MEETINGSTONE_CHANGED"][self] then + registeringFromAceEvent = true + self:UnregisterEvent("MEETINGSTONE_CHANGED") + self:RegisterEvent("MINIMAP_ZONE_CHANGED", f, true) + registeringFromAceEvent = nil + end + end) + self:ScheduleEvent("AceEvent_FullyInitialized", func, 10) + registeringFromAceEvent = nil + end + + self.super.activate(self, oldLib, oldDeactivate) + if oldLib then + oldDeactivate(oldLib) + end +end + +AceLibrary:Register(AceEvent, MAJOR_VERSION, MINOR_VERSION, AceEvent.activate) +AceEvent = AceLibrary(MAJOR_VERSION) diff --git a/Libs/AceLibrary/AceLibrary.lua b/Libs/AceLibrary/AceLibrary.lua new file mode 100644 index 0000000..0f2b237 --- /dev/null +++ b/Libs/AceLibrary/AceLibrary.lua @@ -0,0 +1,757 @@ +--[[ +Name: AceLibrary +Revision: $Rev: 17722 $ +Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team) +Inspired By: Iriel (iriel@vigilance-committee.org) + Tekkub (tekkub@gmail.com) + Revision: $Rev: 17722 $ +Website: http://www.wowace.com/ +Documentation: http://www.wowace.com/index.php/AceLibrary +SVN: http://svn.wowace.com/root/trunk/Ace2/AceLibrary +Description: Versioning library to handle other library instances, upgrading, + and proper access. + It also provides a base for libraries to work off of, providing + proper error tools. It is handy because all the errors occur in the + file that called it, not in the library file itself. +Dependencies: None +]] + +local ACELIBRARY_MAJOR = "AceLibrary" +local ACELIBRARY_MINOR = "$Revision: 17722 $" + +if loadstring("return function(...) return ... end") and AceLibrary and AceLibrary:HasInstance(ACELIBRARY_MAJOR) then return end -- lua51 check +local table_setn +do + local version = GetBuildInfo() + if string.find(version, "^2%.") then + -- 2.0.0 + table_setn = function() end + else + table_setn = table.setn + end +end + +local string_gfind = string.gmatch or string.gfind + +local _G = getfenv(0) +local previous = _G[ACELIBRARY_MAJOR] +if previous and not previous:IsNewVersion(ACELIBRARY_MAJOR, ACELIBRARY_MINOR) then return end + +local function safecall(func,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10) + local success, err = pcall(func,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10) + if not success then geterrorhandler()(err) end +end + +-- @table AceLibrary +-- @brief System to handle all versioning of libraries. +local AceLibrary = {} +local AceLibrary_mt = {} +setmetatable(AceLibrary, AceLibrary_mt) + +local tmp +local function error(self, message, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + if type(self) ~= "table" then + _G.error(string.format("Bad argument #1 to `error' (table expected, got %s)", type(self)), 2) + end + if not tmp then + tmp = {} + else + for k in pairs(tmp) do tmp[k] = nil end + table_setn(tmp, 0) + end + + table.insert(tmp, a1) + table.insert(tmp, a2) + table.insert(tmp, a3) + table.insert(tmp, a4) + table.insert(tmp, a5) + table.insert(tmp, a6) + table.insert(tmp, a7) + table.insert(tmp, a8) + table.insert(tmp, a9) + table.insert(tmp, a10) + table.insert(tmp, a11) + table.insert(tmp, a12) + table.insert(tmp, a13) + table.insert(tmp, a14) + table.insert(tmp, a15) + table.insert(tmp, a16) + table.insert(tmp, a17) + table.insert(tmp, a18) + table.insert(tmp, a19) + table.insert(tmp, a20) + + local stack = debugstack() + if not message then + local _,_,second = string.find(stack, "\n(.-)\n") + message = "error raised! " .. second + else + for i = 1,table.getn(tmp) do + tmp[i] = tostring(tmp[i]) + end + for i = 1,10 do + table.insert(tmp, "nil") + end + message = string.format(message, unpack(tmp)) + end + + if getmetatable(self) and getmetatable(self).__tostring then + message = string.format("%s: %s", tostring(self), message) + elseif type(rawget(self, 'GetLibraryVersion')) == "function" and AceLibrary:HasInstance(self:GetLibraryVersion()) then + message = string.format("%s: %s", self:GetLibraryVersion(), message) + elseif type(rawget(self, 'class')) == "table" and type(rawget(self.class, 'GetLibraryVersion')) == "function" and AceLibrary:HasInstance(self.class:GetLibraryVersion()) then + message = string.format("%s: %s", self.class:GetLibraryVersion(), message) + end + + local first = string.gsub(stack, "\n.*", "") + local file = string.gsub(first, ".*\\(.*).lua:%d+: .*", "%1") + file = string.gsub(file, "([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1") + + local i = 0 + for s in string_gfind(stack, "\n([^\n]*)") do + i = i + 1 + if not string.find(s, file .. "%.lua:%d+:") then + file = string.gsub(s, "^.*\\(.*).lua:%d+: .*", "%1") + file = string.gsub(file, "([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1") + break + end + end + local j = 0 + for s in string_gfind(stack, "\n([^\n]*)") do + j = j + 1 + if j > i and not string.find(s, file .. "%.lua:%d+:") then + _G.error(message, j + 1) + return + end + end + _G.error(message, 2) + return +end + +local function assert(self, condition, message, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + if not condition then + if not message then + local stack = debugstack() + local _,_,second = string.find(stack, "\n(.-)\n") + message = "assertion failed! " .. second + end + error(self, message, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + return + end + return condition +end + +local function argCheck(self, arg, num, kind, kind2, kind3, kind4, kind5) + if type(num) ~= "number" then + error(self, "Bad argument #3 to `argCheck' (number expected, got %s)", type(num)) + elseif type(kind) ~= "string" then + error(self, "Bad argument #4 to `argCheck' (string expected, got %s)", type(kind)) + end + local errored = false + arg = type(arg) + if arg ~= kind and arg ~= kind2 and arg ~= kind3 and arg ~= kind4 and arg ~= kind5 then + local _,_,func = string.find(debugstack(), "`argCheck'.-([`<].-['>])") + if not func then + _,_,func = string.find(debugstack(), "([`<].-['>])") + end + if kind5 then + error(self, "Bad argument #%s to %s (%s, %s, %s, %s, or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, kind3, kind4, kind5, arg) + elseif kind4 then + error(self, "Bad argument #%s to %s (%s, %s, %s, or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, kind3, kind4, arg) + elseif kind3 then + error(self, "Bad argument #%s to %s (%s, %s, or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, kind3, arg) + elseif kind2 then + error(self, "Bad argument #%s to %s (%s or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, arg) + else + error(self, "Bad argument #%s to %s (%s expected, got %s)", tonumber(num) or 0/0, func, kind, arg) + end + end +end + +local function pcall(self, func, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 = _G.pcall(func, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + if not a1 then + error(self, string.gsub(a2, ".-%.lua:%d-: ", "")) + else + return a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 + end +end + +local recurse = {} +local function addToPositions(t, major) + if not AceLibrary.positions[t] or AceLibrary.positions[t] == major then + rawset(t, recurse, true) + AceLibrary.positions[t] = major + for k,v in pairs(t) do + if type(v) == "table" and not rawget(v, recurse) then + addToPositions(v, major) + end + if type(k) == "table" and not rawget(k, recurse) then + addToPositions(k, major) + end + end + local mt = getmetatable(t) + if mt and not rawget(mt, recurse) then + addToPositions(mt, major) + end + rawset(t, recurse, nil) + end +end + +local function svnRevisionToNumber(text) + if type(text) == "string" then + if string.find(text, "^%$Revision: (%d+) %$$") then + return tonumber((string.gsub(text, "^%$Revision: (%d+) %$$", "%1"))) + elseif string.find(text, "^%$Rev: (%d+) %$$") then + return tonumber((string.gsub(text, "^%$Rev: (%d+) %$$", "%1"))) + elseif string.find(text, "^%$LastChangedRevision: (%d+) %$$") then + return tonumber((string.gsub(text, "^%$LastChangedRevision: (%d+) %$$", "%1"))) + end + elseif type(text) == "number" then + return text + end + return nil +end + +local crawlReplace +do + local recurse = {} + local function func(t, to, from) + if recurse[t] then + return + end + recurse[t] = true + local mt = getmetatable(t) + setmetatable(t, nil) + rawset(t, to, rawget(t, from)) + rawset(t, from, nil) + for k,v in pairs(t) do + if v == from then + t[k] = to + elseif type(v) == "table" then + if not recurse[v] then + func(v, to, from) + end + end + + if type(k) == "table" then + if not recurse[k] then + func(k, to, from) + end + end + end + setmetatable(t, mt) + if mt then + if mt == from then + setmetatable(t, to) + elseif not recurse[mt] then + func(mt, to, from) + end + end + end + function crawlReplace(t, to, from) + func(t, to, from) + for k in pairs(recurse) do + recurse[k] = nil + end + end +end + +-- @function destroyTable +-- @brief remove all the contents of a table +-- @param t table to destroy +local function destroyTable(t) + setmetatable(t, nil) + for k,v in pairs(t) do t[k] = nil end + table_setn(t, 0) +end + +local function isFrame(frame) + return type(frame) == "table" and type(rawget(frame, 0)) == "userdata" and type(rawget(frame, 'IsFrameType')) == "function" and getmetatable(frame) and type(rawget(getmetatable(frame), '__index')) == "function" +end + +local new, del +do + local tables = setmetatable({}, {__mode = "k"}) + + function new() + local t = next(tables) + if t then + tables[t] = nil + return t + else + return {} + end + end + + function del(t, depth) + if depth and depth > 0 then + for k,v in pairs(t) do + if type(v) == "table" and not isFrame(v) then + del(v, depth - 1) + end + end + end + destroyTable(t) + tables[t] = true + end +end + +-- @function copyTable +-- @brief Create a shallow copy of a table and return it. +-- @param from The table to copy from +-- @return A shallow copy of the table +local function copyTable(from) + local to = new() + for k,v in pairs(from) do to[k] = v end + table_setn(to, table.getn(from)) + setmetatable(to, getmetatable(from)) + return to +end + +-- @function deepTransfer +-- @brief Fully transfer all data, keeping proper previous table +-- backreferences stable. +-- @param to The table with which data is to be injected into +-- @param from The table whose data will be injected into the first +-- @param saveFields If available, a shallow copy of the basic data is saved +-- in here. +-- @param list The account of table references +-- @param list2 The current status on which tables have been traversed. +local deepTransfer +do + -- @function examine + -- @brief Take account of all the table references to be shared + -- between the to and from tables. + -- @param to The table with which data is to be injected into + -- @param from The table whose data will be injected into the first + -- @param list An account of the table references + local function examine(to, from, list, major) + list[from] = to + for k,v in pairs(from) do + if rawget(to, k) and type(from[k]) == "table" and type(to[k]) == "table" and not list[from[k]] then + if from[k] == to[k] then + list[from[k]] = to[k] + elseif AceLibrary.positions[from[v]] ~= major and AceLibrary.positions[from[v]] then + list[from[k]] = from[k] + elseif not list[from[k]] then + examine(to[k], from[k], list, major) + end + end + end + return list + end + + function deepTransfer(to, from, saveFields, major, list, list2) + setmetatable(to, nil) + local createdList + if not list then + createdList = true + list = new() + list2 = new() + examine(to, from, list, major) + end + list2[to] = to + for k,v in pairs(to) do + if type(rawget(from, k)) ~= "table" or type(v) ~= "table" or isFrame(v) then + if saveFields then + saveFields[k] = v + end + to[k] = nil + elseif v ~= _G then + if saveFields then + saveFields[k] = copyTable(v) + end + end + end + for k in pairs(from) do + if rawget(to, k) and to[k] ~= from[k] and AceLibrary.positions[to[k]] == major and from[k] ~= _G then + if not list2[to[k]] then + deepTransfer(to[k], from[k], nil, major, list, list2) + end + to[k] = list[to[k]] or list2[to[k]] + else + rawset(to, k, from[k]) + end + end + table_setn(to, table.getn(from)) + setmetatable(to, getmetatable(from)) + local mt = getmetatable(to) + if mt then + if list[mt] then + setmetatable(to, list[mt]) + elseif mt.__index and list[mt.__index] then + mt.__index = list[mt.__index] + end + end + destroyTable(from) + if createdList then + del(list) + del(list2) + end + end +end + +-- @method TryToLoadStandalone +-- @brief Attempt to find and load a standalone version of the requested library +-- @param major A string representing the major version +-- @return If library is found, return values from the call to LoadAddOn are returned +-- If the library has been requested previously, nil is returned. +local function TryToLoadStandalone(major) + if not AceLibrary.scannedlibs then AceLibrary.scannedlibs = {} end + if AceLibrary.scannedlibs[major] then return end + + AceLibrary.scannedlibs[major] = true + + local name, _, _, enabled, loadable = GetAddOnInfo(major) + if loadable then + return LoadAddOn(name) + end + + for i=1,GetNumAddOns() do + if GetAddOnMetadata(i, "X-AceLibrary-"..major) then + local name, _, _, enabled, loadable = GetAddOnInfo(i) + if loadable then + return LoadAddOn(name) + end + end + end +end + +-- @method IsNewVersion +-- @brief Obtain whether the supplied version would be an upgrade to the +-- current version. This allows for bypass code in library +-- declaration. +-- @param major A string representing the major version +-- @param minor An integer or an svn revision string representing the minor version +-- @return whether the supplied version would be newer than what is +-- currently available. +function AceLibrary:IsNewVersion(major, minor) + argCheck(self, major, 2, "string") + TryToLoadStandalone(major) + + if type(minor) == "string" then + local m = svnRevisionToNumber(minor) + if m then + minor = m + else + _G.error(string.format("Bad argument #3 to `IsNewVersion'. Must be a number or SVN revision string. %q is not appropriate", minor), 2) + end + end + argCheck(self, minor, 3, "number") + local data = self.libs[major] + if not data then + return true + end + return data.minor < minor +end + +-- @method HasInstance +-- @brief Returns whether an instance exists. This allows for optional support of a library. +-- @param major A string representing the major version. +-- @param minor (optional) An integer or an svn revision string representing the minor version. +-- @return Whether an instance exists. +function AceLibrary:HasInstance(major, minor) + argCheck(self, major, 2, "string") + TryToLoadStandalone(major) + + if minor then + if type(minor) == "string" then + local m = svnRevisionToNumber(minor) + if m then + minor = m + else + _G.error(string.format("Bad argument #3 to `HasInstance'. Must be a number or SVN revision string. %q is not appropriate", minor), 2) + end + end + argCheck(self, minor, 3, "number") + if not self.libs[major] then + return + end + return self.libs[major].minor == minor + end + return self.libs[major] and true +end + +-- @method GetInstance +-- @brief Returns the library with the given major/minor version. +-- @param major A string representing the major version. +-- @param minor (optional) An integer or an svn revision string representing the minor version. +-- @return The library with the given major/minor version. +function AceLibrary:GetInstance(major, minor) + argCheck(self, major, 2, "string") + TryToLoadStandalone(major) + + local data = self.libs[major] + if not data then + _G.error(string.format("Cannot find a library instance of %s.", major), 2) + return + end + if minor then + if type(minor) == "string" then + local m = svnRevisionToNumber(minor) + if m then + minor = m + else + _G.error(string.format("Bad argument #3 to `GetInstance'. Must be a number or SVN revision string. %q is not appropriate", minor), 2) + end + end + argCheck(self, minor, 2, "number") + if data.minor ~= minor then + _G.error(string.format("Cannot find a library instance of %s, minor version %d.", major, minor), 2) + return + end + end + return data.instance +end + +-- Syntax sugar. AceLibrary("FooBar-1.0") +AceLibrary_mt.__call = AceLibrary.GetInstance + +local donothing + +local AceEvent + +-- @method Register +-- @brief Registers a new version of a given library. +-- @param newInstance the library to register +-- @param major the major version of the library +-- @param minor the minor version of the library +-- @param activateFunc (optional) A function to be called when the library is +-- fully activated. Takes the arguments +-- (newInstance [, oldInstance, oldDeactivateFunc]). If +-- oldInstance is given, you should probably call +-- oldDeactivateFunc(oldInstance). +-- @param deactivateFunc (optional) A function to be called by a newer library's +-- activateFunc. +-- @param externalFunc (optional) A function to be called whenever a new +-- library is registered. +function AceLibrary:Register(newInstance, major, minor, activateFunc, deactivateFunc, externalFunc) + argCheck(self, newInstance, 2, "table") + argCheck(self, major, 3, "string") + if type(minor) == "string" then + local m = svnRevisionToNumber(minor) + if m then + minor = m + else + _G.error(string.format("Bad argument #4 to `Register'. Must be a number or SVN revision string. %q is not appropriate", minor), 2) + end + end + argCheck(self, minor, 4, "number") + if math.floor(minor) ~= minor or minor < 0 then + error(self, "Bad argument #4 to `Register' (integer >= 0 expected, got %s)", minor) + end + argCheck(self, activateFunc, 5, "function", "nil") + argCheck(self, deactivateFunc, 6, "function", "nil") + argCheck(self, externalFunc, 7, "function", "nil") + if not deactivateFunc then + if not donothing then + donothing = function() end + end + deactivateFunc = donothing + end + local data = self.libs[major] + if not data then + -- This is new + local instance = copyTable(newInstance) + crawlReplace(instance, instance, newInstance) + destroyTable(newInstance) + if AceLibrary == newInstance then + self = instance + AceLibrary = instance + end + self.libs[major] = { + instance = instance, + minor = minor, + deactivateFunc = deactivateFunc, + externalFunc = externalFunc, + } + rawset(instance, 'GetLibraryVersion', function(self) + return major, minor + end) + if not rawget(instance, 'error') then + rawset(instance, 'error', error) + end + if not rawget(instance, 'assert') then + rawset(instance, 'assert', assert) + end + if not rawget(instance, 'argCheck') then + rawset(instance, 'argCheck', argCheck) + end + if not rawget(instance, 'pcall') then + rawset(instance, 'pcall', pcall) + end + addToPositions(instance, major) + if activateFunc then + safecall(activateFunc, instance, nil, nil) -- no old version, so explicit nil + end + + if externalFunc then + for k,data in pairs(self.libs) do + if k ~= major then + safecall(externalFunc, instance, k, data.instance) + end + end + end + + for k,data in pairs(self.libs) do + if k ~= major and data.externalFunc then + safecall(data.externalFunc, data.instance, major, instance) + end + end + if major == "AceEvent-2.0" then + AceEvent = instance + end + if AceEvent then + AceEvent.TriggerEvent(self, "AceLibrary_Register", major, instance) + end + + return instance + end + local instance = data.instance + if minor <= data.minor then + -- This one is already obsolete, raise an error. + _G.error(string.format("Obsolete library registered. %s is already registered at version %d. You are trying to register version %d. Hint: if not AceLibrary:IsNewVersion(%q, %d) then return end", major, data.minor, minor, major, minor), 2) + return + end + -- This is an update + local oldInstance = new() + + addToPositions(newInstance, major) + local isAceLibrary = (AceLibrary == newInstance) + local old_error, old_assert, old_argCheck, old_pcall + if isAceLibrary then + self = instance + AceLibrary = instance + + old_error = instance.error + old_assert = instance.assert + old_argCheck = instance.argCheck + old_pcall = instance.pcall + + self.error = error + self.assert = assert + self.argCheck = argCheck + self.pcall = pcall + end + deepTransfer(instance, newInstance, oldInstance, major) + crawlReplace(instance, instance, newInstance) + local oldDeactivateFunc = data.deactivateFunc + data.minor = minor + data.deactivateFunc = deactivateFunc + data.externalFunc = externalFunc + rawset(instance, 'GetLibraryVersion', function(self) + return major, minor + end) + if not rawget(instance, 'error') then + rawset(instance, 'error', error) + end + if not rawget(instance, 'assert') then + rawset(instance, 'assert', assert) + end + if not rawget(instance, 'argCheck') then + rawset(instance, 'argCheck', argCheck) + end + if not rawget(instance, 'pcall') then + rawset(instance, 'pcall', pcall) + end + if isAceLibrary then + for _,v in pairs(self.libs) do + local i = type(v) == "table" and v.instance + if type(i) == "table" then + if not rawget(i, 'error') or i.error == old_error then + rawset(i, 'error', error) + end + if not rawget(i, 'assert') or i.assert == old_assert then + rawset(i, 'assert', assert) + end + if not rawget(i, 'argCheck') or i.argCheck == old_argCheck then + rawset(i, 'argCheck', argCheck) + end + if not rawget(i, 'pcall') or i.pcall == old_pcall then + rawset(i, 'pcall', pcall) + end + end + end + end + if activateFunc then + safecall(activateFunc, instance, oldInstance, oldDeactivateFunc) + else + safecall(oldDeactivateFunc, oldInstance) + end + del(oldInstance) + + if externalFunc then + for k,data in pairs(self.libs) do + if k ~= major then + safecall(externalFunc, instance, k, data.instance) + end + end + end + + return instance +end + +local iter +function AceLibrary:IterateLibraries() + if not iter then + local function iter(t, k) + k = next(t, k) + if not k then + return nil + else + return k, t[k].instance + end + end + end + return iter, self.libs, nil +end + +-- @function Activate +-- @brief The activateFunc for AceLibrary itself. Called when +-- AceLibrary properly registers. +-- @param self Reference to AceLibrary +-- @param oldLib (optional) Reference to an old version of AceLibrary +-- @param oldDeactivate (optional) Function to deactivate the old lib +local function activate(self, oldLib, oldDeactivate) + if not self.libs then + if oldLib then + self.libs = oldLib.libs + self.scannedlibs = oldLib.scannedlibs + end + if not self.libs then + self.libs = {} + end + if not self.scannedlibs then + self.scannedlibs = {} + end + end + if not self.positions then + if oldLib then + self.positions = oldLib.positions + end + if not self.positions then + self.positions = setmetatable({}, { __mode = "k" }) + end + end + + -- Expose the library in the global environment + _G[ACELIBRARY_MAJOR] = self + + if oldDeactivate then + oldDeactivate(oldLib) + end +end + +if not previous then + previous = AceLibrary +end +if not previous.libs then + previous.libs = {} +end +AceLibrary.libs = previous.libs +if not previous.positions then + previous.positions = setmetatable({}, { __mode = "k" }) +end +AceLibrary.positions = previous.positions +AceLibrary:Register(AceLibrary, ACELIBRARY_MAJOR, ACELIBRARY_MINOR, activate) diff --git a/Libs/AceOO-2.0/AceOO-2.0.lua b/Libs/AceOO-2.0/AceOO-2.0.lua new file mode 100644 index 0000000..1bbf52c --- /dev/null +++ b/Libs/AceOO-2.0/AceOO-2.0.lua @@ -0,0 +1,1006 @@ +--[[ +Name: AceOO-2.0 +Revision: $Rev: 17638 $ +Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team) +Inspired By: Ace 1.x by Turan (turan@gryphon.com) +Website: http://www.wowace.com/ +Documentation: http://www.wowace.com/index.php/AceOO-2.0 +SVN: http://svn.wowace.com/root/trunk/Ace2/AceOO-2.0 +Description: Library to provide an object-orientation framework. +Dependencies: AceLibrary +]] + +local MAJOR_VERSION = "AceOO-2.0" +local MINOR_VERSION = "$Revision: 17639 $" + +-- This ensures the code is only executed if the libary doesn't already exist, or is a newer version +if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end +if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end + +if loadstring("return function(...) return ... end") and AceLibrary:HasInstance(MAJOR_VERSION) then return end -- lua51 check +local table_setn +do + local version = GetBuildInfo() + if string.find(version, "^2%.") then + -- 2.0.0 + table_setn = function() end + else + table_setn = table.setn + end +end + +local AceOO = { + error = AceLibrary.error, + argCheck = AceLibrary.argCheck +} + +-- @function getuid +-- @brief Obtain a unique string identifier for the object in question. +-- @param t The object to obtain the uid for. +-- @return The uid string. +local function pad(cap) + return string.rep('0', 8 - string.len(cap)) .. cap +end +local function getuid(t) + local mt = getmetatable(t) + setmetatable(t, nil) + local str = tostring(t) + setmetatable(t, mt) + local _,_,cap = string.find(str, '[^:]*: 0x(.*)$') + if cap then return pad(cap) end + _,_,cap = string.find(str, '[^:]*: (.*)$') + if cap then return pad(cap) end +end + +local function getlibrary(o) + if type(o) == "table" then + return o + elseif type(o) == "string" then + if not AceLibrary:HasInstance(o) then + AceOO:error("Library %q does not exist.", o) + end + return AceLibrary(o) + end +end + +-- @function Factory +-- @brief Construct a factory for the creation of objects. +-- @param obj The object whose init method will be called on the new factory +-- object. +-- @param newobj The object whose init method will be called on the new +-- objects that the Factory creates, to initialize them. +-- @param (a1..a20) Arguments which will be passed to obj.init() in addition +-- to the Factory object. +-- @return The new factory which creates a newobj when its new method is called, +-- or when it is called directly (__call metamethod). +local Factory +do + local function new(obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + a13, a14, a15, a16, a17, a18, a19, a20) + local t = {} + local uid = getuid(t) + local l = getlibrary + obj:init(t, l(a1), l(a2), l(a3), l(a4), l(a5), l(a6), l(a7), + l(a8), l(a9), l(a10), l(a11), l(a12), l(a13), + l(a14), l(a15), l(a16), l(a17), l(a18), l(a19), + l(a20)) + t.uid = uid + return t + end + + local function createnew(self, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, + a11, a12, a13, a14, a15, a16, a17, a18, + a19, a20) + local o = self.prototype + local l = getlibrary + return new(o, l(a1), l(a2), l(a3), l(a4), l(a5), l(a6), l(a7), + l(a8), l(a9), l(a10), l(a11), l(a12), l(a13), + l(a14), l(a15), l(a16), l(a17), l(a18), l(a19), + l(a20)) + end + + function Factory(obj, newobj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, + a11, a12, a13, a14, a15, a16, a17, a18, + a19, a20) + local t = new(obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + a13, a14, a15, a16, a17, a18, a19, a20) + t.prototype = newobj + t.new = createnew + getmetatable(t).__call = t.new + return t + end +end + + +local function objtostring(self) + if self.ToString then + return self:ToString() + elseif self.GetLibraryVersion then + return (self:GetLibraryVersion()) + elseif self.super then + local s = "Sub-" .. tostring(self.super) + local first = true + if self.interfaces then + for interface in pairs(self.interfaces) do + if first then + s = s .. "(" .. tostring(interface) + first = false + else + s = s .. ", " .. tostring(interface) + end + end + end + if self.mixins then + for mixin in pairs(self.mixins) do + if first then + s = s .. tostring(mixin) + first = false + else + s = s .. ", " .. tostring(mixin) + end + end + end + if first then + if self.uid then + return s .. ":" .. self.uid + else + return s + end + else + return s .. ")" + end + else + return self.uid and 'Subclass:' .. self.uid or 'Subclass' + end +end + +-- @table Object +-- @brief Base of all objects, including Class. +-- +-- @method init +-- @brief Initialize a new object. +-- @param newobject The object to initialize +-- @param class The class to make newobject inherit from +local Object +do + Object = {} + function Object:init(newobject, class) + local parent = class or self + if not rawget(newobject, 'uid') then + newobject.uid = getuid(newobject) + end + local mt = { + __index = parent, + __tostring = objtostring, + } + setmetatable(newobject, mt) + end + Object.uid = getuid(Object) + setmetatable(Object, { __tostring = function() return 'Object' end }) +end + +local Interface + +local function validateInterface(object, interface) + if not object.class and object.prototype then + object = object.prototype + end + for k,v in pairs(interface.interface) do + if tostring(type(object[k])) ~= v then + return false + end + end + if interface.superinterfaces then + for superinterface in pairs(interface.superinterfaces) do + if not validateInterface(object, superinterface) then + return false + end + end + end + if type(object.class) == "table" and rawequal(object.class.prototype, object) then + if not object.class.interfaces then + rawset(object.class, 'interfaces', {}) + end + object.class.interfaces[interface] = true + elseif type(object.class) == "table" and type(object.class.prototype) == "table" then + validateInterface(object.class.prototype, interface) + -- check if class is proper, thus preventing future checks. + end + return true +end + +-- @function inherits +-- @brief Return whether an Object or Class inherits from a given +-- parent. +-- @param object Object or Class to check +-- @param parent Parent to test inheritance from +-- @return whether an Object or Class inherits from a given +-- parent. +local function inherits(object, parent) + object = getlibrary(object) + if type(parent) == "string" then + if not AceLibrary:HasInstance(parent) then + return false + else + parent = AceLibrary(parent) + end + end + AceOO:argCheck(parent, 2, "table") + if type(object) ~= "table" then + return false + end + local current + if object.class then + current = object.class + else + current = object + end + if type(current) ~= "table" then + return false + end + if rawequal(current, parent) then + return true + end + if parent.class then + while true do + if rawequal(current, Object) then + break + end + if current.mixins then + for mixin in pairs(current.mixins) do + if rawequal(mixin, parent) then + return true + end + end + end + if current.interfaces then + for interface in pairs(current.interfaces) do + if rawequal(interface, parent) then + return true + end + end + end + current = current.super + if type(current) ~= "table" then + break + end + end + + local isInterface = false + local curr = parent.class + while true do + if rawequal(curr, Object) then + break + elseif rawequal(curr, Interface) then + isInterface = true + break + end + curr = curr.super + if type(curr) ~= "table" then + break + end + end + return isInterface and validateInterface(object, parent) + else + while true do + if rawequal(current, parent) then + return true + elseif rawequal(current, Object) then + return false + end + current = current.super + if type(current) ~= "table" then + return false + end + end + end +end + +-- @table Class +-- @brief An object factory which sets up inheritence and supports +-- 'mixins'. +-- +-- @metamethod Class call +-- @brief Call ClassFactory:new() to create a new class. +-- +-- @method Class new +-- @brief Construct a new object. +-- @param (a1..a20) Arguments to pass to the object init function. +-- @return The new object. +-- +-- @method Class init +-- @brief Initialize a new class. +-- @param parent Superclass. +-- @param (a1..a20) Mixins. +-- +-- @method Class ToString +-- @return A string representing the object, in this case 'Class'. +local initStatus +local Class +local Mixin +local autoEmbed = false +local function traverseInterfaces(bit, total) + if bit.superinterfaces then + for interface in pairs(bit.superinterfaces) do + if not total[interface] then + total[interface] = true + traverseInterfaces(interface, total) + end + end + end +end +local class_new +do + Class = Factory(Object, setmetatable({}, {__index = Object}), Object) + Class.super = Object + + local function protostring(t) + return '<' .. tostring(t.class) .. ' prototype>' + end + local function classobjectstring(t) + if t.ToString then + return t:ToString() + elseif t.GetLibraryVersion then + return (t:GetLibraryVersion()) + else + return '<' .. tostring(t.class) .. ' instance>' + end + end + local function classobjectequal(self, other) + if type(self) == "table" and self.Equals then + return self:Equals(other) + elseif type(other) == "table" and other.Equals then + return other:Equals(self) + elseif type(self) == "table" and self.CompareTo then + return self:CompareTo(other) == 0 + elseif type(other) == "table" and other.CompareTo then + return other:CompareTo(self) == 0 + else + return rawequal(self, other) + end + end + local function classobjectlessthan(self, other) + if type(self) == "table" and self.IsLessThan then + return self:IsLessThan(other) + elseif type(other) == "table" and other.IsLessThanOrEqualTo then + return not other:IsLessThanOrEqualTo(self) + elseif type(self) == "table" and self.CompareTo then + return self:CompareTo(other) < 0 + elseif type(other) == "table" and other.CompareTo then + return other:CompareTo(self) > 0 + elseif type(other) == "table" and other.IsLessThan and other.Equals then + return other:Equals(self) or other:IsLessThan(self) + else + AceOO:error("cannot compare two objects") + end + end + local function classobjectlessthanequal(self, other) + if type(self) == "table" and self.IsLessThanOrEqualTo then + return self:IsLessThanOrEqualTo(other) + elseif type(other) == "table" and other.IsLessThan then + return not other:IsLessThan(self) + elseif type(self) == "table" and self.CompareTo then + return self:CompareTo(other) <= 0 + elseif type(other) == "table" and other.CompareTo then + return other:CompareTo(self) >= 0 + elseif type(self) == "table" and self.IsLessThan and self.Equals then + return self:Equals(other) or self:IsLessThan(other) + else + AceOO:error("cannot compare two incompatible objects") + end + end + local function classobjectadd(self, other) + if type(self) == "table" and self.Add then + return self:Add(other) + else + AceOO:error("cannot add two incompatible objects") + end + end + local function classobjectsub(self, other) + if type(self) == "table" and self.Subtract then + return self:Subtract(other) + else + AceOO:error("cannot subtract two incompatible objects") + end + end + local function classobjectunm(self, other) + if type(self) == "table" and self.UnaryNegation then + return self:UnaryNegation(other) + else + AceOO:error("attempt to negate an incompatible object") + end + end + local function classobjectmul(self, other) + if type(self) == "table" and self.Multiply then + return self:Multiply(other) + else + AceOO:error("cannot multiply two incompatible objects") + end + end + local function classobjectdiv(self, other) + if type(self) == "table" and self.Divide then + return self:Divide(other) + else + AceOO:error("cannot divide two incompatible objects") + end + end + local function classobjectpow(self, other) + if type(self) == "table" and self.Exponent then + return self:Exponent(other) + else + AceOO:error("cannot exponentiate two incompatible objects") + end + end + local function classobjectconcat(self, other) + if type(self) == "table" and self.Concatenate then + return self:Concatenate(other) + else + AceOO:error("cannot concatenate two incompatible objects") + end + end + function class_new(self, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + a13, a14, a15, a16, a17, a18, a19, a20) + if self.virtual then + AceOO:error("Cannot instantiate a virtual class.") + end + + local o = self.prototype + local newobj = {} + if o.class and o.class.instancemeta then + setmetatable(newobj, o.class.instancemeta) + else + Object:init(newobj, o) + end + + if self.interfaces and not self.interfacesVerified then + -- Verify the interfaces + + for interface in pairs(self.interfaces) do + for field,kind in pairs(interface.interface) do + if tostring(type(newobj[field])) ~= kind then + AceOO:error("Class did not satisfy all interfaces. %q is required to be a %s. It is a %s", field, kind, tostring(type(newobj[field]))) + end + end + end + self.interfacesVerified = true + end + local tmp = initStatus + initStatus = newobj + newobj:init(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + a13, a14, a15, a16, a17, a18, a19, a20) + if initStatus then + initStatus = tmp + AceOO:error("Initialization not completed, be sure to call the superclass's init method.") + return + end + initStatus = tmp + return newobj + end + local classmeta = { + __tostring = objtostring, + __call = function(self, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + a13, a14, a15, a16, a17, a18, a19, a20) + return self:new(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + a13, a14, a15, a16, a17, a18, a19, a20) + end, + } + function Class:init(newclass, parent, a1, a2, a3, a4, a5, a6, a7, a8, a9, + a10, a11, a12, a13, a14, a15, a16, + a17, a18, a19, a20) + parent = parent or self + + local total + + if parent.class then + total = { + parent, a1, a2, a3, a4, a5, a6, a7, a8, a9, + a10, a11, a12, a13, a14, a15, a16, + a17, a18, a19, a20 + } + parent = self + else + total = { + a1, a2, a3, a4, a5, a6, a7, a8, a9, + a10, a11, a12, a13, a14, a15, a16, + a17, a18, a19, a20 + } + end + if not inherits(parent, Class) then + AceOO:error("Classes must inherit from a proper class") + end + if parent.sealed then + AceOO:error("Cannot inherit from a sealed class") + end + for i,v in ipairs(total) do + if inherits(v, Mixin) and v.class then + if not newclass.mixins then + newclass.mixins = {} + end + if newclass.mixins[v] then + AceOO:error("Cannot explicitly inherit from the same mixin twice") + end + newclass.mixins[v] = true + elseif inherits(v, Interface) and v.class then + if not newclass.interfaces then + newclass.interfaces = {} + end + if newclass.interfaces[v] then + AceOO:error("Cannot explicitly inherit from the same interface twice") + end + newclass.interfaces[v] = true + else + AceOO:error("Classes can only inherit from one or zero classes and any number of mixins or interfaces") + end + end + if parent.interfaces then + if newclass.interfaces then + for interface in pairs(parent.interfaces) do + newclass.interfaces[interface] = true + end + else + newclass.interfaces = parent.interfaces + end + end + for k in pairs(total) do + total[k] = nil + end + table_setn(total, 0) + + newclass.super = parent + + newclass.prototype = setmetatable(total, { + __index = parent.prototype, + __tostring = protostring, + }) + total = nil + + newclass.instancemeta = { + __index = newclass.prototype, + __tostring = classobjectstring, + __eq = classobjectequal, + __lt = classobjectlessthan, + __le = classobjectlessthanequal, + __add = classobjectadd, + __sub = classobjectsub, + __unm = classobjectunm, + __mul = classobjectmul, + __div = classobjectdiv, + __pow = classobjectpow, + __concat = classobjectconcat, + } + + setmetatable(newclass, classmeta) + + newclass.new = class_new + + if newclass.mixins then + -- Fold in the mixins + local err, msg + for mixin in pairs(newclass.mixins) do + local ret + autoEmbed = true + ret, msg = pcall(mixin.embed, mixin, newclass.prototype) + autoEmbed = false + if not ret then + err = true + break + end + end + + if err then + local pt = newclass.prototype + for k,v in pairs(pt) do + pt[k] = nil + end + + -- method conflict + AceOO:error(msg) + end + end + + newclass.prototype.class = newclass + + if newclass.interfaces then + for interface in pairs(newclass.interfaces) do + traverseInterfaces(interface, newclass.interfaces) + end + end + if newclass.mixins then + for mixin in pairs(newclass.mixins) do + if mixin.interfaces then + if not newclass.interfaces then + newclass.interfaces = {} + end + for interface in pairs(mixin.interfaces) do + newclass.interfaces[interface] = true + end + end + end + end + end + function Class:ToString() + if type(self.GetLibraryVersion) == "function" then + return (self:GetLibraryVersion()) + else + return "Class" + end + end + + local tmp + function Class.prototype:init() + if rawequal(self, initStatus) then + initStatus = nil + else + AceOO:error("Improper self passed to init. You must do MyClass.super.prototype.init(self, ...)", 2) + end + self.uid = getuid(self) + local current = self.class + while true do + if current == Class then + break + end + if current.mixins then + for mixin in pairs(current.mixins) do + if type(mixin.OnInstanceInit) == "function" then + mixin:OnInstanceInit(self) + end + end + end + current = current.super + end + end +end + + +-- @object ClassFactory +-- @brief A factory for creating classes. Rarely used directly. +local ClassFactory = Factory(Object, Class, Object) + +function Class:new(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, + a12, a13, a14, a15, a16, a17, a18, a19, a20) + local x = ClassFactory:new(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, + a12, a13, a14, a15, a16, a17, a18, a19, a20) + if AceOO.classes then + AceOO.classes[x] = true + end + return x +end +getmetatable(Class).__call = Class.new + +-- @class Mixin +-- @brief A class to create mixin objects, which contain methods that get +-- "mixed in" to class prototypes. +-- +-- @object Mixin prototype +-- @brief The prototype that mixin objects inherit their methods from. +-- +-- @method Mixin prototype embed +-- @brief Mix in the methods of our object which are listed in our interface +-- to the supplied target table. +-- +-- @method Mixin prototype init +-- @brief Initialize the mixin object. +-- @param newobj The new object we're initializing. +-- @param interface The interface we implement (the list of methods our +-- prototype provides which should be mixed into the target +-- table by embed). +do + Mixin = Class() + function Mixin:ToString() + if self.GetLibraryVersion then + return (self:GetLibraryVersion()) + else + return 'Mixin' + end + end + local function _Embed(state, field, target) + field = next(state.export, field) + if field == nil then + return + end + + if rawget(target, field) or (target[field] and target[field] ~= state[field]) then + AceOO:error("Method conflict in attempt to mixin. Field %q", field) + end + + target[field] = state[field] + + local ret,msg = pcall(_Embed, state, field, target) + if not ret then + -- Mix in the next method according to the defined interface. If that + -- fails due to a conflict, re-raise to back out the previous mixed + -- methods. + + target[field] = nil + AceOO:error(msg) + end + end + function Mixin.prototype:embed(target) + local mt = getmetatable(target) + setmetatable(target, nil) + local err, msg = pcall(_Embed, self, nil, target) + if not err then + setmetatable(target, mt) + AceOO:error(msg) + return + end + if type(self.embedList) == "table" then + self.embedList[target] = true + end + if type(target.class) ~= "table" then + target[self] = true + end + if not autoEmbed and type(self.OnManualEmbed) == "function" then + if not target.modules then + self:OnManualEmbed(target) + end + end + setmetatable(target, mt) + end + + function Mixin.prototype:activate(oldLib, oldDeactivate) + if oldLib and oldLib.embedList then + for target in pairs(oldLib.embedList) do + local mt = getmetatable(target) + setmetatable(target, nil) + for field in pairs(oldLib.export) do + target[field] = nil + end + setmetatable(target, mt) + end + self.embedList = oldLib.embedList + for target in pairs(self.embedList) do + self:embed(target) + end + else + self.embedList = setmetatable({}, {__mode="k"}) + end + end + + function Mixin.prototype:init(export, a1, a2, a3, a4, a5, a6, a7, a8, a9, + a10, a11, a12, a13, a14, a15, a16, + a17, a18, a19, a20) + AceOO:argCheck(export, 2, "table") + for k,v in pairs(export) do + if type(k) ~= "number" then + AceOO:error("All keys to argument #2 must be numbers.") + elseif type(v) ~= "string" then + AceOO:error("All values to argument #2 must be strings.") + end + end + local num = table.getn(export) + for i = 1, num do + local v = export[i] + export[i] = nil + export[v] = true + end + table_setn(export, 0) + local interfaces + if a1 then + local l = getlibrary + interfaces = { l(a1), l(a2), l(a3), l(a4), l(a5), l(a6), l(a7), l(a8), + l(a9), l(a10), l(a11), l(a12), l(a13), l(a14), l(a15), l(a16), + l(a17), l(a18), l(a19), l(a20) } + for _,v in ipairs(interfaces) do + if not v.class or not inherits(v, Interface) then + AceOO:error("Mixins can inherit only from interfaces") + end + end + local num = table.getn(interfaces) + for i = 1, num do + local v = interfaces[i] + interfaces[i] = nil + interfaces[v] = true + end + table_setn(interfaces, 0) + for interface in pairs(interfaces) do + traverseInterfaces(interface, interfaces) + end + for interface in pairs(interfaces) do + for field,kind in pairs(interface.interface) do + if kind ~= "nil" then + local good = false + for bit in pairs(export) do + if bit == field then + good = true + break + end + end + if not good then + AceOO:error("Mixin does not fully accommodate field %q", field) + end + end + end + end + end + self.super = Mixin.prototype + Mixin.super.prototype.init(self) + self.export = export + self.interfaces = interfaces + end +end + +-- @class Interface +-- @brief A class to create interfaces, which contain contracts that classes +-- which inherit from this must comply with. +-- +-- @object Interface prototype +-- @brief The prototype that interface objects must adhere to. +-- +-- @method Interface prototype init +-- @brief Initialize the mixin object. +-- @param interface The interface we contract (the hash of fields forced). +-- @param (a1..a20) Superinterfaces +do + Interface = Class() + function Interface:ToString() + if self.GetLibraryVersion then + return (self:GetLibraryVersion()) + else + return 'Instance' + end + end + function Interface.prototype:init(interface, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + Interface.super.prototype.init(self) + AceOO:argCheck(interface, 2, "table") + for k,v in pairs(interface) do + if type(k) ~= "string" then + AceOO:error("All keys to argument #2 must be numbers.") + elseif type(v) ~= "string" then + AceOO:error("All values to argument #2 must be strings.") + elseif v ~= "nil" and v ~= "string" and v ~= "number" and v ~= "table" and v ~= "function" then + AceOO:error('All values to argument #2 must either be "nil", "string", "number", "table", or "function".') + end + end + local l = getlibrary + a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 = l(a1), l(a2), l(a3), l(a4), l(a5), l(a6), l(a7), l(a8), l(a9), l(a10), l(a11), l(a12), l(a13), l(a14), l(a15), l(a16), l(a17), l(a18), l(a19), l(a20) + if a1 then + self.superinterfaces = {a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20} + for k,v in ipairs(self.superinterfaces) do + if not inherits(v, Interface) or not v.class then + AceOO:error('Cannot provide a non-Interface to inherit from') + end + end + local num = table.getn(self.superinterfaces) + for i = 1, num do + local v = self.superinterfaces[i] + self.superinterfaces[i] = nil + self.superinterfaces[v] = true + end + table_setn(self.superinterfaces, 0) + end + self.interface = interface + end +end + +-- @function Classpool +-- @brief Obtain a read only class from our pool of classes, indexed by the +-- superclass and mixins. +-- @param sc The superclass of the class we want. +-- @param (m1..m20) Mixins of the class we want's objects. +-- @return A read only class from the class pool. +local Classpool +do + local pool = setmetatable({}, {__mode = 'v'}) + local function newindex(k, v) + AceOO:error('Attempt to modify a read-only class.') + end + local function protonewindex(k, v) + AceOO:error('Attempt to modify a read-only class prototype.') + end + local function ts(bit) + if type(bit) ~= "table" then + return tostring(bit) + elseif getmetatable(bit) and bit.__tostring then + return tostring(bit) + elseif type(bit.GetLibraryVersion) == "function" then + return bit:GetLibraryVersion() + else + return tostring(bit) + end + end + local t + local function getcomplexuid(sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, + m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20) + if not t then t = {} end + if sc then if sc.uid then table.insert(t, sc.uid) else AceOO:error("%s is not an appropriate class/mixin", ts(sc)) end + if m1 then if m1.uid then table.insert(t, m1.uid) else AceOO:error("%s is not an appropriate mixin", ts(m1)) end + if m2 then if m2.uid then table.insert(t, m2.uid) else AceOO:error("%s is not an appropriate mixin", ts(m2)) end + if m3 then if m3.uid then table.insert(t, m3.uid) else AceOO:error("%s is not an appropriate mixin", ts(m3)) end + if m4 then if m4.uid then table.insert(t, m4.uid) else AceOO:error("%s is not an appropriate mixin", ts(m4)) end + if m5 then if m5.uid then table.insert(t, m5.uid) else AceOO:error("%s is not an appropriate mixin", ts(m5)) end + if m6 then if m6.uid then table.insert(t, m6.uid) else AceOO:error("%s is not an appropriate mixin", ts(m6)) end + if m7 then if m7.uid then table.insert(t, m7.uid) else AceOO:error("%s is not an appropriate mixin", ts(m7)) end + if m8 then if m8.uid then table.insert(t, m8.uid) else AceOO:error("%s is not an appropriate mixin", ts(m8)) end + if m9 then if m9.uid then table.insert(t, m9.uid) else AceOO:error("%s is not an appropriate mixin", ts(m9)) end + if m10 then if m10.uid then table.insert(t, m10.uid) else AceOO:error("%s is not an appropriate mixin", ts(m10)) end + if m11 then if m11.uid then table.insert(t, m11.uid) else AceOO:error("%s is not an appropriate mixin", ts(m11)) end + if m12 then if m12.uid then table.insert(t, m12.uid) else AceOO:error("%s is not an appropriate mixin", ts(m12)) end + if m13 then if m13.uid then table.insert(t, m13.uid) else AceOO:error("%s is not an appropriate mixin", ts(m13)) end + if m14 then if m14.uid then table.insert(t, m14.uid) else AceOO:error("%s is not an appropriate mixin", ts(m14)) end + if m15 then if m15.uid then table.insert(t, m15.uid) else AceOO:error("%s is not an appropriate mixin", ts(m15)) end + if m16 then if m16.uid then table.insert(t, m16.uid) else AceOO:error("%s is not an appropriate mixin", ts(m16)) end + if m17 then if m17.uid then table.insert(t, m17.uid) else AceOO:error("%s is not an appropriate mixin", ts(m17)) end + if m18 then if m18.uid then table.insert(t, m18.uid) else AceOO:error("%s is not an appropriate mixin", ts(m18)) end + if m19 then if m19.uid then table.insert(t, m19.uid) else AceOO:error("%s is not an appropriate mixin", ts(m19)) end + if m20 then if m20.uid then table.insert(t, m20.uid) else AceOO:error("%s is not an appropriate mixin", ts(m20)) end + end end end end end end end end end end end end end end end end end end end end end + table.sort(t) + local uid = table.concat(t, '') + for k in pairs(t) do t[k] = nil end + table_setn(t, 0) + return uid + end + local classmeta + function Classpool(sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, + m10, m11, m12, m13, m14, m15, m16, + m17, m18, m19, m20) + local l = getlibrary + sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20 = l(sc), l(m1), l(m2), l(m3), l(m4), l(m5), l(m6), l(m7), l(m8), l(m9), l(m10), l(m11), l(m12), l(m13), l(m14), l(m15), l(m16), l(m17), l(m18), l(m19), l(m20) + if sc and sc.class then + sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20 = Class, sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19 + end + sc = sc or Class + local key = getcomplexuid(sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20) + if not pool[key] then + local class = Class(sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, + m10, m11, m12, m13, m14, m15, m16, m17, + m18, m19, m20) + if not classmeta then + classmeta = {} + local mt = getmetatable(class) + for k,v in pairs(mt) do + classmeta[k] = v + end + classmeta.__newindex = newindex + end + -- Prevent the user from adding methods to this class. + -- NOTE: I'm not preventing modifications of existing class members, + -- but it's likely that only a truly malicious user will be doing so. + class.sealed = true + setmetatable(class, classmeta) + getmetatable(class.prototype).__newindex = protonewindex + pool[key] = class + end + return pool[key] + end +end + +AceOO.Factory = Factory +AceOO.Object = Object +AceOO.Class = Class +AceOO.Mixin = Mixin +AceOO.Interface = Interface +AceOO.Classpool = Classpool +AceOO.inherits = inherits + +-- Library handling bits + +local function activate(self, oldLib, oldDeactivate) + AceOO = self + Factory = self.Factory + Object = self.Object + Class = self.Class + ClassFactory.prototype = Class + Mixin = self.Mixin + Interface = self.Interface + Classpool = self.Classpool + + if oldLib then + self.classes = oldLib.classes + end + if not self.classes then + self.classes = setmetatable({}, {__mode="k"}) + else + for class in pairs(self.classes) do + class.new = class_new + end + end + + if oldDeactivate then + oldDeactivate(oldLib) + end +end + +AceLibrary:Register(AceOO, MAJOR_VERSION, MINOR_VERSION, activate) +AceOO = AceLibrary(MAJOR_VERSION) diff --git a/Outfitter.lua b/Outfitter.lua new file mode 100644 index 0000000..2c3210e --- /dev/null +++ b/Outfitter.lua @@ -0,0 +1,7387 @@ +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 " .. NORMAL_FONT_COLOR_CODE .. ": Wear an outfit"); + Outfitter_NoteMessage(HIGHLIGHT_FONT_COLOR_CODE .. "/outfitter unwear " .. NORMAL_FONT_COLOR_CODE .. ": Remove an outfit"); + Outfitter_NoteMessage(HIGHLIGHT_FONT_COLOR_CODE .. "/outfitter toggle " .. NORMAL_FONT_COLOR_CODE .. ": Wears or removes an outfit"); + 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 \ No newline at end of file diff --git a/Outfitter.toc b/Outfitter.toc new file mode 100644 index 0000000..d328d12 --- /dev/null +++ b/Outfitter.toc @@ -0,0 +1,25 @@ +## Interface: 11200 +## Title: Outfitter +## Notes: Outfitter +## Author: |cff5BCEFAL|cffF5A9B8e|cffFFFFFFx|cffF5A9B8i|cff5BCEFAe|r +## Version: 1.6.0 +## OptionalDeps: TankPoints, Ace2 +## X-ClientMods: SuperWoW, ClassicAPI (optional) +## RequiredDeps: +## SavedVariablesPerCharacter: gOutfitter_Settings + +Libs\AceLibrary\AceLibrary.lua +Libs\AceOO-2.0\AceOO-2.0.lua +Libs\AceEvent-2.0\AceEvent-2.0.lua + +OutfitterStrings.lua +OutfitterStrings_de.lua +OutfitterStrings_fr.lua +OutfitterStrings_cn.lua +OutfitterStrings_tw.lua +OutfitterStrings_kr.lua + +BossTrash.lua +OutfitterClientMods.lua +Outfitter.lua +Outfitter.xml diff --git a/Outfitter.xml b/Outfitter.xml new file mode 100644 index 0000000..e69c47e --- /dev/null +++ b/Outfitter.xml @@ -0,0 +1,1774 @@ + + + + + + + OutfitterInputBox_OnLoad(0); + + + OutfitterInputBox_TabPressed(); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OutfitterScrollbarTrench_SizeChanged(this); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Outfitter_SlotEnableClicked(this, arg1); + + + Outfitter_SlotEnableClicked(this, "LeftButton", 1); + + + GameTooltip_AddNewbieTip(Outfitter_cSlotEnableTitle, 1, 1, 1, Outfitter_cSlotEnableDescription, 1); + + + GameTooltip:Hide(); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + this:RegisterForClicks("LeftButtonUp", "RightButtonUp"); + + + OutfitterItem_OnClick(this, arg1); + + + OutfitterItem_OnEnter(this); + + + OutfitterItem_OnLeave(this); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OutfitterMainFrameBackground:SetVertexColor(1, 1, 1, 0.20); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + getglobal(this:GetName().."Text"):SetText(Outfitter_cShowMinimapButton); + + + Outfitter_SetShowMinimapButton(this:GetChecked()); + + + if this:GetChecked() then + GameTooltip_AddNewbieTip(Outfitter_cShowMinimapButton, 1.0, 1.0, 1.0, Outfitter_cShowMinimapButtonOnDescription, 1); + else + GameTooltip_AddNewbieTip(Outfitter_cShowMinimapButton, 1.0, 1.0, 1.0, Outfitter_cShowMinimapButtonOffDescription, 1); + end + + + GameTooltip:Hide(); + + + + + + + + + + + + + + getglobal(this:GetName().."Text"):SetText(Outfitter_cRememberVisibility); + + + Outfitter_SetRememberVisibility(this:GetChecked()); + + + if this:GetChecked() then + GameTooltip_AddNewbieTip(Outfitter_cRememberVisibility, 1.0, 1.0, 1.0, Outfitter_cRememberVisibilityOnDescription, 1); + else + GameTooltip_AddNewbieTip(Outfitter_cRememberVisibility, 1.0, 1.0, 1.0, Outfitter_cRememberVisibilityOffDescription, 1); + end + + + GameTooltip:Hide(); + + + + + + + + + + + + + + getglobal(this:GetName().."Text"):SetText(Outfitter_cShowHotkeyMessages); + + + Outfitter_SetShowHotkeyMessages(this:GetChecked()); + + + if this:GetChecked() then + GameTooltip_AddNewbieTip(Outfitter_cShowHotkeyMessages, 1.0, 1.0, 1.0, Outfitter_cShowHotkeyMessagesOnDescription, 1); + else + GameTooltip_AddNewbieTip(Outfitter_cShowHotkeyMessages, 1.0, 1.0, 1.0, Outfitter_cShowHotkeyMessagesOffDescription, 1); + end + + + GameTooltip:Hide(); + + + + + + + + + + + + + + getglobal(this:GetName().."Text"):SetText(Outfitter_cShowCurrentOutfit); + + + Outfitter_SetShowCurrentOutfit(this:GetChecked()); + + + + + + + + + + + + + + getglobal(this:GetName().."Text"):SetText(Outfitter_cHideDisabledOutfits); + + + Outfitter_SetHideDisabledOutfits(this:GetChecked()); + + + + + + + OutfitterOptionsFrameBackground:SetVertexColor(1, 1, 1, 0.20); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Outfitter_OnLoad(); + + + Outfitter_OnShow(); + + + Outfitter_OnHide(); + + + Outfitter_OnEvent(event); + + + + + + + + + + + + + + + + + this.SlotName = "HeadSlot"; + + + + + + + + + + + + this.SlotName = "NeckSlot"; + + + + + + + + + + + + this.SlotName = "ShoulderSlot"; + + + + + + + + + + + + this.SlotName = "BackSlot"; + + + + + + + + + + + + this.SlotName = "ChestSlot"; + + + + + + + + + + + + this.SlotName = "ShirtSlot"; + + + + + + + + + + + + this.SlotName = "TabardSlot"; + + + + + + + + + + + + this.SlotName = "WristSlot"; + + + + + + + + + + + + this.SlotName = "HandsSlot"; + + + + + + + + + + + + this.SlotName = "WaistSlot"; + + + + + + + + + + + + this.SlotName = "LegsSlot"; + + + + + + + + + + + + this.SlotName = "FeetSlot"; + + + + + + + + + + + + this.SlotName = "Finger0Slot"; + + + + + + + + + + + + this.SlotName = "Finger1Slot"; + + + + + + + + + + + + this.SlotName = "Trinket0Slot"; + + + + + + + + + + + + this.SlotName = "Trinket1Slot"; + + + + + + + + + + + + this.SlotName = "MainHandSlot"; + + + + + + + + + + + + this.SlotName = "SecondaryHandSlot"; + + + + + + + + + + + + this.SlotName = "RangedSlot"; + + + + + + + + + + + + this.SlotName = "AmmoSlot"; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OutfitterNameOutfit_Update() + + + + this:ClearFocus(); + + + OutfitterNameOutfit_Done(); + + + + + + + + + + + + + + + + + + + + + + + + + + + + OutfitterStatDropdown_OnLoad(); + + + OutfitterStatDropdown_OnLoad(); + + + + + + + + + this.baseHeight = this:GetHeight(); + + + OutfitterNameOutfit_Update(); + + + + + + + + OutfitterUpdateFrame_OnUpdate(arg1); + + + + + + + + + + + + ContainerFrame_OnEvent(); + + + OutfitterQuickSlotItem_OnLoad(); + + + OutfitterQuickSlotItem_OnShow(); + + + OutfitterQuickSlotItem_OnHide(); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OutfitterQuickSlots_OnLoad(); + + + OutfitterQuickSlots_OnShow(); + + + OutfitterQuickSlots_OnHide(); + + + OutfitterQuickSlots_OnEvent(event); + + + + diff --git a/OutfitterClientMods.lua b/OutfitterClientMods.lua new file mode 100644 index 0000000..54f8cb4 --- /dev/null +++ b/OutfitterClientMods.lua @@ -0,0 +1,157 @@ +-- OutfitterClientMods.lua +-- +-- Optional integration with 1.12.1 client mods. Every helper here has a +-- plain-vanilla fallback, so Outfitter behaves exactly as before when no +-- client mod is installed. +-- +-- SuperWoW : UnitBuff() 3rd return = aura spell ID, IsMounted(), +-- IsSwimming(), UnitExists() 2nd return = GUID +-- ClassicAPI : C_UnitAuras.GetAuraDataByIndex() (spellId + name), +-- IsMounted(), IsSwimming(), UnitGUID() +-- Nampower : detected and reported only. Its item/cast/spell-queue API +-- has no task inside Outfitter that it does better. +-- UnitXP SP3 : detected and reported only. Distance, line of sight and +-- camera functions have no use in an equipment manager. +-- +-- Lua 5.0 (no select(), no #, no varargs expressions). + +Outfitter_ClientMods = { + SuperWoW = false, + ClassicAPI = false, + Nampower = false, + UnitXP = false, + Detected = false, +}; + +local OCM = Outfitter_ClientMods; + +function Outfitter_DetectClientMods() + OCM.SuperWoW = (SUPERWOW_VERSION ~= nil) or (SUPERWOW_STRING ~= nil); + + OCM.ClassicAPI = (CLASSIC_API_VERSION ~= nil) + or (type(C_UnitAuras) == "table" and type(C_UnitAuras.GetAuraDataByIndex) == "function"); + + OCM.Nampower = type(GetNampowerVersion) == "function"; + + OCM.UnitXP = false; + if type(UnitXP) == "function" then + -- UnitXP("nop", "nop") is the documented presence check; it raises if the dll is missing + local vOK = pcall(UnitXP, "nop", "nop"); + OCM.UnitXP = vOK and true or false; + end + + -- Capabilities actually used by Outfitter + + OCM.HasIsMounted = type(IsMounted) == "function" and (OCM.SuperWoW or OCM.ClassicAPI); + OCM.HasIsSwimming = type(IsSwimming) == "function" and (OCM.SuperWoW or OCM.ClassicAPI); + OCM.HasAuraSpellID = OCM.SuperWoW or OCM.ClassicAPI; + OCM.HasGUID = OCM.SuperWoW or (OCM.ClassicAPI and type(UnitGUID) == "function"); + + OCM.Detected = true; +end + +-- Returns texture, spellID[, name] for the player's buff at pIndex (1-based). +-- spellID is nil when no client mod provides it. + +function Outfitter_GetPlayerBuffInfo(pIndex) + if OCM.SuperWoW then + local vTexture, vCount, vSpellID = UnitBuff("player", pIndex); + return vTexture, vSpellID; + end + + if OCM.ClassicAPI then + local vTexture = UnitBuff("player", pIndex); + + if not vTexture then + return nil; + end + + local vOK, vData = pcall(C_UnitAuras.GetAuraDataByIndex, "player", pIndex, "HELPFUL"); + + if vOK and type(vData) == "table" then + -- name lets the caller confirm the two APIs agree on this index + return vTexture, vData.spellId, vData.name; + end + + return vTexture, nil; + end + + return UnitBuff("player", pIndex), nil; +end + +-- Returns true/false when a client mod can answer, nil when unknown + +function Outfitter_ClientIsMounted() + if not OCM.HasIsMounted then + return nil; + end + + local vOK, vResult = pcall(IsMounted); + + if not vOK then + return nil; + end + + return vResult and vResult ~= 0 and true or false; +end + +function Outfitter_ClientIsSwimming() + if not OCM.HasIsSwimming then + return nil; + end + + local vOK, vResult = pcall(IsSwimming); + + if not vOK then + return nil; + end + + return vResult and vResult ~= 0 and true or false; +end + +-- Stable identity for a unit: GUID when available, otherwise name..level + +function Outfitter_GetUnitIdentity(pUnit) + if not UnitExists(pUnit) then + return nil; + end + + if OCM.ClassicAPI and type(UnitGUID) == "function" then + local vOK, vGUID = pcall(UnitGUID, pUnit); + + if vOK and vGUID then + return vGUID; + end + end + + if OCM.SuperWoW then + local vExists, vGUID = UnitExists(pUnit); + + if vGUID then + return vGUID; + end + end + + return (UnitName(pUnit) or "?") .. ":" .. (UnitLevel(pUnit) or 0); +end + +function Outfitter_ClientModsSummary() + if not OCM.Detected then + Outfitter_DetectClientMods(); + end + + local function YesNo(pValue) + if pValue then + return GREEN_FONT_COLOR_CODE .. "yes" .. FONT_COLOR_CODE_CLOSE; + end + return GRAY_FONT_COLOR_CODE .. "no" .. FONT_COLOR_CODE_CLOSE; + end + + DEFAULT_CHAT_FRAME:AddMessage(NORMAL_FONT_COLOR_CODE .. "Outfitter client mod support:" .. FONT_COLOR_CODE_CLOSE); + DEFAULT_CHAT_FRAME:AddMessage(" SuperWoW: " .. YesNo(OCM.SuperWoW) .. " ClassicAPI: " .. YesNo(OCM.ClassicAPI) + .. " Nampower: " .. YesNo(OCM.Nampower) .. " UnitXP SP3: " .. YesNo(OCM.UnitXP)); + DEFAULT_CHAT_FRAME:AddMessage(" Mount detection: " .. (OCM.HasIsMounted and "client flag" or "buff tooltip text")); + DEFAULT_CHAT_FRAME:AddMessage(" Swimming detection: " .. (OCM.HasIsSwimming and "client flag (surface + underwater)" or "breath timer (underwater only)")); + DEFAULT_CHAT_FRAME:AddMessage(" Buff tooltip scans: " .. (OCM.HasAuraSpellID and "cached per spell ID" or "every aura change")); + DEFAULT_CHAT_FRAME:AddMessage(" Target identity: " .. (OCM.HasGUID and "GUID" or "name + level")); +end diff --git a/OutfitterStrings.lua b/OutfitterStrings.lua new file mode 100644 index 0000000..afd9347 --- /dev/null +++ b/OutfitterStrings.lua @@ -0,0 +1,411 @@ +Outfitter_cVersion = "1.6.0"; + +Outfitter_cTitle = "Outfitter"; +Outfitter_cTitleVersion = Outfitter_cTitle .. " " .. Outfitter_cVersion; + +Outfitter_cNameLabel = "Name:"; +Outfitter_cCreateUsingTitle = "Optimize for:"; +Outfitter_cUseCurrentOutfit = "Use Current Outfit"; +Outfitter_cUseEmptyOutfit = "Create Empty Outfit"; + +Outfitter_cOutfitterTabTitle = "Outfitter"; +Outfitter_cOptionsTabTitle = "Options"; +Outfitter_cAboutTabTitle = "About"; + +Outfitter_cNewOutfit = "New Outfit"; +Outfitter_cRenameOutfit = "Rename Outfit"; + +Outfitter_cCompleteOutfits = "Complete wardrobes"; +Outfitter_cPartialOutfits = "Mix-n-match"; +Outfitter_cAccessoryOutfits = "Accessories"; +Outfitter_cSpecialOutfits = "Special occassions"; +Outfitter_cOddsNEndsOutfits = "Odds 'n ends"; + +Outfitter_cNormalOutfit = "Normal"; +Outfitter_cNakedOutfit = "Birthday Suit"; + +Outfitter_cFishingOutfit = "Fishing"; +Outfitter_cHerbalismOutfit = "Herbalism"; +Outfitter_cMiningOutfit = "Mining"; +Outfitter_cSkinningOutfit = "Skinning"; +Outfitter_cFireResistOutfit = "Fire Resist"; +Outfitter_cNatureResistOutfit = "Nature Resist"; +Outfitter_cShadowResistOutfit = "Shadow Resist"; +Outfitter_cArcaneResistOutfit = "Arcane Resist"; +Outfitter_cFrostResistOutfit = "Frost Resist"; + +Outfitter_cArgentDawnOutfit = "Argent Dawn"; +Outfitter_cRidingOutfit = "Riding"; +Outfitter_cDiningOutfit = "Dining"; +Outfitter_cBattlegroundOutfit = "Battleground"; +Outfitter_cABOutfit = "Battleground: Arathi Basin"; +Outfitter_cAVOutfit = "Battleground: Alterac Valley"; +Outfitter_cWSGOutfit = "Battleground: Warsong Gulch"; +Outfitter_cBROutfit = "Battleground: Blood Ring"; +Outfitter_cSGVOutfit = "Battleground: Sunnyglade Valley"; +Outfitter_cSwimmingOutfit = "Swimming"; +Outfitter_cDiscardedStripOutfit = "Outfitter: ignored a saved change that would have unequipped all your gear (left over from a crash or disconnect)"; +Outfitter_cCityOutfit = "Around Town"; +Outfitter_cBossOutfit = "Boss Mobs"; +Outfitter_cLvl63Outfit = "Lvl63+"; +Outfitter_cTrashOutfit = "Trash Mobs"; +Outfitter_cCritterOutfit = "Critters"; + +Outfitter_cBeastTrashOutfit = "Beast Trash Mobs"; +Outfitter_cUndeadTrashOutfit = "Undead Trash Mobs"; +Outfitter_cDemonTrashOutfit = "Demon Trash Mobs"; + +Outfitter_cMountSpeedFormat = "Increases speed by (%d+)%%."; -- For detecting when mounted + +Outfitter_cBagsFullError = "Outfitter: Can't remove %s because all bags are full"; +Outfitter_cDepositBagsFullError = "Outfitter: Can't deposit %s because all bank bags are full"; +Outfitter_cWithdrawBagsFullError = "Outfitter: Can't withdraw %s because all bags are full"; +Outfitter_cItemNotFoundError = "Outfitter: Can't find item %s"; +Outfitter_cItemAlreadyUsedError = "Outfitter: Can't put %s in the %s slot because it's already being used in another slot"; +Outfitter_cAddingItem = "Outfitter: Adding %s to %s outfit"; +Outfitter_cNameAlreadyUsedError = "Error: That name is already being used"; +Outfitter_cNoItemsWithStatError = "Warning: None of your items have that attribute"; + +Outfitter_cEnableAll = "Enable all"; +Outfitter_cEnableNone = "Enable none"; + +Outfitter_cConfirmDeleteMsg = "Are you sure you want to delete the outfit %s?"; +Outfitter_cConfirmUpdateMsg = "Are you sure you want to update the outfit %s to your current outfit?"; +Outfitter_cConfirmRebuildMsg = "Are you sure you want to rebuild the outfit %s?"; +Outfitter_cRebuild = "Rebuild"; +Outfitter_cUpdate = "Update"; +Outfitter_cUpdateToCurrent = "Update to current"; + +Outfitter_cWesternPlaguelands = "Western Plaguelands"; +Outfitter_cEasternPlaguelands = "Eastern Plaguelands"; +Outfitter_cStratholme = "Stratholme"; +Outfitter_cScholomance = "Scholomance"; +Outfitter_cNaxxramas = "Naxxramas"; +Outfitter_cAlteracValley = "Alterac Valley"; +Outfitter_cArathiBasin = "Arathi Basin"; +Outfitter_cWarsongGulch = "Warsong Gulch"; +Outfitter_cBloodRing = "Blood Ring"; +Outfitter_cSunnygladeValley = "Sunnyglade Valley"; +Outfitter_cIronforge = "Ironforge"; +Outfitter_cCityOfIronforge = "City of Ironforge"; +Outfitter_cDarnassus = "Darnassus"; +Outfitter_cStormwind = "Stormwind City"; +Outfitter_cOrgrimmar = "Orgrimmar"; +Outfitter_cThunderBluff = "Thunder Bluff"; +Outfitter_cUndercity = "Undercity"; + +--Instances +Outfitter_cAQ20 = "Ruins of Ahn'Qiraj"; +Outfitter_cAQ40 = "Ahn'Qiraj"; +Outfitter_cZG = "Zul'Gurub"; +Outfitter_cES = "Emerald Sanctum"; +Outfitter_cBM = "The Black Morass"; +Outfitter_cK40 = "The Rock of Desolation"; + +Outfitter_cFishingPole = "Fishing Pole"; +Outfitter_cStrongFishingPole = "Strong Fishing Pole"; +Outfitter_cDarkwoodFishingPole = "Darkwood Fishing Pole"; +Outfitter_cBigIronFishingPole = "Big Iron Fishing Pole"; +Outfitter_cBlumpFishingPole = "Blump Family Fishing Pole"; +Outfitter_cNatPaglesFishingPole = "Nat Pagle's Extreme Angler FC-5000"; +Outfitter_cArcaniteFishingPole = "Arcanite Fishing Pole"; +Outfitter_cBarkskinFisher = "Barkskin Fisher"; + +Outfitter_cArgentDawnCommission = "Argent Dawn Commission"; +Outfitter_cSealOfTheDawn = "Seal of the Dawn"; +Outfitter_cRuneOfTheDawn = "Rune of the Dawn"; + +Outfitter_cWhipOfEncouragement = "Whip of Encouragement"; +Outfitter_cGoblinCarKey = "Goblin Car Key"; +Outfitter_cGnomeCarKey = "Gnome Car Key"; +Outfitter_cCarrotOnAStick = "Carrot on a Stick"; + +Outfitter_cItemStatFormats = { + { Format = "Stamina %+(%d+)", Types = { "Stamina" } }, + { Format = "Intellect %+(%d+)", Types = { "Intellect" } }, + { Format = "Agility %+(%d+)", Types = { "Agility" } }, + { Format = "Strength %+(%d+)", Types = { "Strength" } }, + { Format = "Spirit %+(%d+)", Types = { "Spirit" } }, + { Format = "Armor %+(%d+)", Types = { "Armor" } }, + { Format = "Defense %+(%d+)", Types = { "Defense" } }, + { Format = "Increased Defense %+(%d+)", Types = { "Defense" } }, + + { Format = "%+(%d+) Stamina", Types = { "Stamina" } }, + { Format = "%+(%d+) Intellect", Types = { "Intellect" } }, + { Format = "%+(%d+) Agility", Types = { "Agility" } }, + { Format = "%+(%d+) Strength", Types = { "Strength" } }, + { Format = "%+(%d+) Spirit", Types = { "Spirit" } }, + { Format = "(%d+) Armor", Types = { "Armor" } }, + { Format = "%+(%d+) Attack Power", Types = { "Attack" } }, + + { Format = "All Stats %+(%d+)", Types = { "Stamina", "Intellect", "Agility", "Strength", "Spirit" } }, + + { Format = "Mana %+(%d+)", Types = { "Mana" } }, + { Format = "Health %+(%d+)", Types = { "Health" } }, + + { Format = "%+(%d+) mana every 5 sec.", Types = { "ManaRegen" } }, + { Format = "Restores (%d+) mana per 5 sec.", Types = { "ManaRegen" } }, + + { Format = "%+(%d+) health every 5 sec.", Types = { "HealthRegen" } }, + { Format = "Restores (%d+) health every 5 sec.", Types = { "HealthRegen" } }, + { Format = "Restores (%d+) health per 5 sec.", Types = { "HealthRegen" } }, + + { Format = "Minor Mount Speed Increase", Value = 3, Types = { "Riding" } }, + { Format = "Mithril Spurs", Value = 3, Types = { "Riding" } }, + { Format = "Thorium Spurs", Value = 5, Types = { "Riding" } }, + + { Format = "%+(%d+) Fire Resistance", Types = { "FireResist" } }, + { Format = "%+(%d+) Nature Resistance", Types = { "NatureResist" } }, + { Format = "%+(%d+) Frost Resistance", Types = { "FrostResist" } }, + { Format = "%+(%d+) Shadow Resistance", Types = { "ShadowResist" } }, + { Format = "%+(%d+) Arcane Resistance", Types = { "ArcaneResist" } }, + { Format = "%+(%d+) All Resistances", Types = { "FireResist", "NatureResist", "FrostResist", "ShadowResist", "ArcaneResist" } }, + + { Format = "Weapon Damage %+(%d+)", Types = { "MeleeDmg" } }, + { Format = "Improves your chance to hit by (%d+)%%", Types = { "MeleeHit" } }, + { Format = "Improves your chance to get a critical strike by (%d+)%%", Types = { "MeleeCrit" } }, + { Format = "Increases your chance to dodge an attack by (%d+)%%", Types = { "Dodge" } }, + { Format = "Damage %+(%d+)", Types = { "MeleeDmg" } }, + { Format = "(%d+) Block", Types = { "Block" } }, + { Format = "Block Value %+(%d+)", Types = { "Block" } }, + { Format = "Increases the block value of your shield by (%d+)", Types = { "Block" } }, + + { Format = "Increased Fishing %+(%d+)%.", Types = { "Fishing" } }, + { Format = "Fishing %+(%d+)", Types = { "Fishing" } }, + { Format = "Herbalism %+(%d+)", Types = { "Herbalism" } }, + { Format = "Mining %+(%d+)", Types = { "Mining" } }, + { Format = "Skinning %+(%d+)", Types = { "Skinning" } }, + + { Format = "Improves your chance to get a critical strike with spells by (%d+)%%", Types = { "SpellCrit" } }, + { Format = "Improves your chance to hit with spells by (%d+)%%", Types = { "SpellHit" } }, + { Format = "Increases damage and healing done by magical spells and effects by up to (%d+)", Types = { "SpellDmg", "ShadowDmg", "FireDmg", "FrostDmg", "ArcaneDmg", "NatureDmg", "Healing" } }, + { Format = "Increases healing done by spells and effects by up to (%d+)", Types = { "Healing" } }, + { Format = "Healing Spells %+(%d+)", Types = { "Healing" } }, + { Format = "%+(%d+) Healing Spells", Types = { "Healing" } }, + + { Format = "%+(%d+) Fire Spell Damage", Types = { "FireDmg" } }, + { Format = "%+(%d+) Shadow Spell Damage", Types = { "ShadowDmg" } }, + { Format = "%+(%d+) Frost Spell Damage", Types = { "FrostDmg" } }, + { Format = "%+(%d+) Arcane Spell Damage", Types = { "ArcaneDmg" } }, + { Format = "%+(%d+) Nature Spell Damage", Types = { "NatureDmg" } }, + + { Format = "Increases damage done by Fire spells and effects by up to (%d+)", Types = { "FireDmg" } }, + { Format = "Increases damage done by Shadow spells and effects by up to (%d+)", Types = { "ShadowDmg" } }, + { Format = "Increases damage done by Frost spells and effects by up to (%d+)", Types = { "FrostDmg" } }, + { Format = "Increases damage done by Arcane spells and effects by up to (%d+)", Types = { "ArcaneDmg" } }, + { Format = "Increases damage done by Nature spells and effects by up to (%d+)", Types = { "NatureDmg" } }, +}; + +Outfitter_cAgilityStatName = "Agility"; +Outfitter_cArmorStatName = "Armor"; +Outfitter_cDefenseStatName = "Defense"; +Outfitter_cIntellectStatName = "Intellect"; +Outfitter_cSpiritStatName = "Spirit"; +Outfitter_cStaminaStatName = "Stamina"; +Outfitter_cStrengthStatName = "Strength"; + +Outfitter_cManaRegenStatName = "Mana Regeneration"; +Outfitter_cHealthRegenStatName = "Health Regeneration"; + +Outfitter_cSpellCritStatName = "Spell Critical Strike"; +Outfitter_cSpellHitStatName = "Spell Chance to Hit"; +Outfitter_cSpellDmgStatName = "Spell Damage"; +Outfitter_cFrostDmgStatName = "Frost Spell Damage"; +Outfitter_cFireDmgStatName = "Fire Spell Damage"; +Outfitter_cArcaneDmgStatName = "Arcane Spell Damage"; +Outfitter_cShadowDmgStatName = "Shadow Spell Damage"; +Outfitter_cNatureDmgStatName = "Nature Spell Damage"; +Outfitter_cHealingStatName = "Healing"; + +Outfitter_cMeleeCritStatName = "Melee Critical Strike"; +Outfitter_cMeleeHitStatName = "Melee Chance to Hit"; +Outfitter_cMeleeDmgStatName = "Melee Damage"; +Outfitter_cAttackStatName = "Attack Power"; +Outfitter_cDodgeStatName = "Dodge"; + +Outfitter_cArcaneResistStatName = "Arcane Resistance"; +Outfitter_cFireResistStatName = "Fire Resistance"; +Outfitter_cFrostResistStatName = "Frost Resistance"; +Outfitter_cNatureResistStatName = "Nature Resistance"; +Outfitter_cShadowResistStatName = "Shadow Resistance"; + +Outfitter_cFishingStatName = "Fishing"; +Outfitter_cHerbalismStatName = "Herbalism"; +Outfitter_cMiningStatName = "Mining"; +Outfitter_cSkinningStatName = "Skinning"; + +Outfitter_cOptionsTitle = "Outfitter Options"; +Outfitter_cShowMinimapButton = "Show Minimap Button"; +Outfitter_cShowMinimapButtonOnDescription = "Turn this off if you don't want the Outfitter button on your minimap cluster"; +Outfitter_cShowMinimapButtonOffDescription = "Turn this on if you want the Outfitter button on your minimap cluster"; +Outfitter_cRememberVisibility = "Remember cloak and helm settings"; +Outfitter_cRememberVisibilityOnDescription = "Turn this off if you want to use a single show/hide setting for all cloaks and helms"; +Outfitter_cRememberVisibilityOffDescription = "Turn this on if you want Outfitter to remember your preference for showing or hiding each cloak and helm individually"; +Outfitter_cShowHotkeyMessages = "Show key binding outfit changes"; +Outfitter_cShowHotkeyMessagesOnDescription = "Turn this off if you don't want to see a message when you change outfits using a key binding"; +Outfitter_cShowHotkeyMessagesOffDescription = "Turn this on if you want to see a message when you change outfits using a key binding"; +Outfitter_cShowCurrentOutfit = "Display current outfit in small window"; +Outfitter_cHideDisabledOutfits = "Hide disabled outfits from list"; + +Outfitter_cEquipOutfitMessageFormat = "Outfitter: %s equipped"; +Outfitter_cUnequipOutfitMessageFormat = "Outfitter: %s unequipped"; + +Outfitter_cAboutTitle = "About Outfitter"; +Outfitter_cAuthor = "Designed and written by John Stephen\nUpdated for WoW 1.18.1 Clients by |cff5BCEFAL|cffF5A9B8e|cffFFFFFFx|cffF5A9B8i|cff5BCEFAe|r"; +Outfitter_cTestersTitle = "Beta Testers"; +Outfitter_cTestersNames = "Airmid, Desiree, Fizzlebang, Harper, Kallah and Sumitra"; +Outfitter_cSpecialThanksTitle = "Special thanks for their support to"; +Outfitter_cSpecialThanksNames = "Brian, Dave, Glenn, Leah, Mark, The Mighty Pol, SFC and Forge"; +Outfitter_cGuildURL = ""; +Outfitter_cGuildURL2 = ""; + +Outfitter_cOpenOutfitter = "Open Outfitter"; + +Outfitter_cArgentDawnOutfitDescription = "This outfit will automatically be worn whenever you're in the Plaguelands"; +Outfitter_cRidingOutfitDescription = "This outfit will automatically be worn whenever you're mounted"; +Outfitter_cDiningOutfitDescription = "This outfit will automatically be worn whenever you're eating or drinking"; +Outfitter_cBattlegroundOutfitDescription = "This outfit will automatically be worn whenever you're in a battleground"; +Outfitter_cArathiBasinOutfitDescription = "This outfit will automatically be worn whenever you're in the Arathi Basin battleground"; +Outfitter_cAlteracValleyOutfitDescription = "This outfit will automatically be worn whenever you're in the Alterac Valley battleground"; +Outfitter_cWarsongGulchOutfitDescription = "This outfit will automatically be worn whenever you're in the Warsong Gulch battleground"; +Outfitter_cBloodRingOutfitDescription = "This outfit will automatically be worn whenever you're in the Blood Ring battleground"; +Outfitter_cSunnygladeValleyOutfitDescription = "This outfit will automatically be worn whenever you're in the Sunnyglade Valley battleground"; +Outfitter_cCityOutfitDescription = "This outfit will automatically be worn whenever you're in a friendly major city"; +Outfitter_cBossOutfitDescription = "This outfit will automatically be worn whenever you target level 63+ Boss mobs"; +Outfitter_cLvl63OutfitDescription = "This outfit will automatically be worn whenever you target level 63+ mobs"; +Outfitter_cTrashOutfitDescription = "This outfit will automatically be worn whenever you target level <63 mobs"; +Outfitter_cCritterOutfitDescription = "This outfit will automatically be worn whenever you target a critter"; +Outfitter_cBeastTrashOutfitDescription = "This outfit will automatically be worn whenever you target beasts level <63 mobs"; +Outfitter_cUndeadTrashOutfitDescription = "This outfit will automatically be worn whenever you target undead level <63 mobs"; +Outfitter_cDemonTrashOutfitDescription = "This outfit will automatically be worn whenever you target demons level <63 mobs"; +Outfitter_cSwimmingOutfitDescription = "This outfit will automatically be worn whenever you're swimming (underwater only unless SuperWoW or ClassicAPI is installed)"; + +Outfitter_cKeyBinding = "Key Binding"; + +BINDING_HEADER_OUTFITTER_TITLE = Outfitter_cTitle; + +BINDING_NAME_OUTFITTER_OUTFIT1 = "Outfit 1"; +BINDING_NAME_OUTFITTER_OUTFIT2 = "Outfit 2"; +BINDING_NAME_OUTFITTER_OUTFIT3 = "Outfit 3"; +BINDING_NAME_OUTFITTER_OUTFIT4 = "Outfit 4"; +BINDING_NAME_OUTFITTER_OUTFIT5 = "Outfit 5"; +BINDING_NAME_OUTFITTER_OUTFIT6 = "Outfit 6"; +BINDING_NAME_OUTFITTER_OUTFIT7 = "Outfit 7"; +BINDING_NAME_OUTFITTER_OUTFIT8 = "Outfit 8"; +BINDING_NAME_OUTFITTER_OUTFIT9 = "Outfit 9"; +BINDING_NAME_OUTFITTER_OUTFIT10 = "Outfit 10"; + +Outfitter_cDisableOutfit = "Disable Outfit"; +Outfitter_cDisableOutfitInBG = "Disable Outfit When in Battlegrounds"; +Outfitter_cDisableOutfitInInstance = "Disable Outfit When in Instances"; +Outfitter_cDisabledOutfitName = "%s (Disabled)"; + +Outfitter_cMinimapButtonTitle = "Outfitter Minimap Button"; +Outfitter_cMinimapButtonDescription = "Click to select a different outfit or drag to re-position this button."; + +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"; + +Outfitter_cBattleStance = "Battle Stance"; +Outfitter_cDefensiveStance = "Defensive Stance"; +Outfitter_cBerserkerStance = "Berserker Stance"; + +Outfitter_cWarriorBattleStance = "Warrior: Battle Stance"; +Outfitter_cWarriorDefensiveStance = "Warrior: Defensive Stance"; +Outfitter_cWarriorBerserkerStance = "Warrior: Berserker Stance"; + +Outfitter_cBearForm = "Bear Form"; +Outfitter_cCatForm = "Cat Form"; +Outfitter_cAquaticForm = "Aquatic Form"; +Outfitter_cTravelForm = "Travel Form"; +Outfitter_cDireBearForm = "Dire Bear Form"; +Outfitter_cMoonkinForm = "Moonkin Form"; + +Outfitter_cGhostWolfForm = "Ghost Wolf"; + +Outfitter_cStealth = "Stealth"; + +Outfitter_cDruidBearForm = "Druid: Bear Form"; +Outfitter_cDruidCatForm = "Druid: Cat Form"; +Outfitter_cDruidAquaticForm = "Druid: Aquatic Form"; +Outfitter_cDruidTravelForm = "Druid: Travel Form"; +Outfitter_cDruidMoonkinForm = "Druid: Moonkin Form"; + +Outfitter_cPriestShadowform = "Priest: Shadowform"; + +Outfitter_cRogueStealth = "Rogue: Stealth"; + +Outfitter_cShamanGhostWolf = "Shaman: Ghost Wolf"; + +Outfitter_cHunterMonkey = "Hunter: Monkey"; +Outfitter_cHunterHawk = "Hunter: Hawk"; +Outfitter_cHunterCheetah = "Hunter: Cheetah"; +Outfitter_cHunterPack = "Hunter: Pack"; +Outfitter_cHunterBeast = "Hunter: Beast"; +Outfitter_cHunterWild = "Hunter: Wild"; + +Outfitter_cMageEvocate = "Mage: Evocate"; + +Outfitter_cAspectOfTheCheetah = "Aspect of the Cheetah"; +Outfitter_cAspectOfThePack = "Aspect of the Pack"; +Outfitter_cAspectOfTheBeast = "Aspect of the Beast"; +Outfitter_cAspectOfTheWild = "Aspect of the Wild"; +Outfitter_cEvocate = "Evocation"; + +Outfitter_cCritter = "Critter"; +Outfitter_cCreatureBeast = "Beast"; +Outfitter_cCreatureUndead = "Undead"; +Outfitter_cCreatureDemon = "Demon"; + +Outfitter_cCompleteCategoryDescripton = "Complete outfits have items specified for every slot and will replace all other outfits when worn."; +Outfitter_cPartialCategoryDescription = "Mix-n-match outfits have only some slots specified, but not all. When equipped, they are added on top of your selcted Complete outfit, replacing any other Mix-n-match or Accessory outfits selected."; +Outfitter_cAccessoryCategoryDescription = "Accessory outfits have only some slots specified, but not all. Unlike mix-n-match, you can select as many Accessory outfits as you like and they will all be combined together and worn on top of your selected Complete and Mix-n-match outfits."; +Outfitter_cSpecialCategoryDescription = "Special Occassion outfits are automatically worn whenever the occassion warrants it. They are worn over all other selected outfits."; +Outfitter_cOddsNEndsCategoryDescription = "Odds 'n ends is a list of items which you haven't used in any of your outfits. This may be useful in ensuring that you're using all of your items or that you're not carrying around excess baggage."; + +Outfitter_cRebuildOutfitFormat = "Rebuild for %s"; + +Outfitter_cTranslationCredit = " "; + +Outfitter_cSlotEnableTitle = "Slot Enable"; +Outfitter_cSlotEnableDescription = "Select this if you want the item in this slot to be equipped when changing to the selected outfit. If not selected then this slot will not be modified when changing to the selected outfit."; + +Outfitter_cFinger0SlotName = "First Finger"; +Outfitter_cFinger1SlotName = "Second Finger"; + +Outfitter_cTrinket0SlotName = "First Trinket"; +Outfitter_cTrinket1SlotName = "Second Trinket"; + +Outfitter_cOutfitCategoryTitle = "Category"; +Outfitter_cBankCategoryTitle = "Bank"; +Outfitter_cDepositToBank = "Deposit all items to bank"; +Outfitter_cDepositUniqueToBank = "Deposit unique items to bank"; +Outfitter_cWithdrawFromBank = "Withdraw items from bank"; + +Outfitter_cMissingItemsLabel = "Missing items: "; +Outfitter_cBankedItemsLabel = "Banked items: "; + +Outfitter_cRepairAllBags = "Outfitter: Repair All Bagged Items"; + +Outfitter_cStatsCategory = "Stats"; +Outfitter_cMeleeCategory = "Melee"; +Outfitter_cSpellsCategory = "Healing and Spells"; +Outfitter_cRegenCategory = "Regeneration"; +Outfitter_cResistCategory = "Resistances"; +Outfitter_cTradeCategory = "Trade Skills"; + +Outfitter_cTankPoints = "TankPoints"; +Outfitter_cCustom = "Custom"; + +Outfitter_cRequiresHerbalism = "Requires Herbalism"; +Outfitter_cRequiresSkinning = "Requires Skinning"; +Outfitter_cRequiresMining = "Requires Mining"; +Outfitter_cAutoEquip = "Auto equip"; +Outfitter_cAutoUnEquip = "Auto unequip"; \ No newline at end of file diff --git a/OutfitterStrings_cn.lua b/OutfitterStrings_cn.lua new file mode 100644 index 0000000..e69de29 diff --git a/OutfitterStrings_de.lua b/OutfitterStrings_de.lua new file mode 100644 index 0000000..69cf689 --- /dev/null +++ b/OutfitterStrings_de.lua @@ -0,0 +1,339 @@ +if GetLocale() == "deDE" then + Outfitter_cTitle = "Outfitter"; + Outfitter_cTitleVersion = Outfitter_cTitle .. " " .. Outfitter_cVersion; + + Outfitter_cNameLabel = "Name:"; + Outfitter_cCreateUsingTitle = "Optimieren f\195\188r:"; + Outfitter_cUseCurrentOutfit = "Benutze derzeitiges Outfit"; + Outfitter_cUseEmptyOutfit = "Erstelle neues Outfit"; + + Outfitter_cOutfitterTabTitle = "Outfitter"; + Outfitter_cOptionsTabTitle = "Einstellungen"; + Outfitter_cAboutTabTitle = "\195\188ber"; + + Outfitter_cNewOutfit = "Neues Outfit"; + Outfitter_cRenameOutfit = "Outfit umbenennen"; + + Outfitter_cCompleteOutfits = "Vollst\195\164ndige Ausr\195\188stungen"; + Outfitter_cPartialOutfits = "Mix-n-match"; + Outfitter_cAccessoryOutfits = "Zusatzgegenst\195\164nde"; + Outfitter_cSpecialOutfits = "Besondere Gelegenheiten"; + + Outfitter_cNormalOutfit = "Normal"; + Outfitter_cNakedOutfit = "Ausgehroben"; + + Outfitter_cFishingOutfit = "Angeln"; + Outfitter_cHerbalismOutfit = "Kr\195\164uterkunde"; + Outfitter_cMiningOutfit = "Bergbau"; + Outfitter_cSkinningOutfit = "K\195\188rschnerei"; + Outfitter_cFireResistOutfit = "Feuerwiderstand"; + Outfitter_cNatureResistOutfit = "Naturwiderstand"; + Outfitter_cShadowResistOutfit = "Schattenwiderstand"; + Outfitter_cArcaneResistOutfit = "Arkanwiderstand"; + Outfitter_cFrostResistOutfit = "Frostwiderstand"; + + Outfitter_cArgentDawnOutfit = "Argentumd\195\164mmerung"; + Outfitter_cRidingOutfit = "Reiten"; + Outfitter_cDiningOutfit = "Ausruhen"; + Outfitter_cBattlegroundOutfit = "Schlachtfeld"; + Outfitter_cBossOutfit = "Boss Mobs"; + Outfitter_cLvl63Outfit = "Lvl63+"; + Outfitter_cTrashOutfit = "Trash Mobs"; + Outfitter_cBeastTrashOutfit = "Beast Trash Mobs"; + Outfitter_cUndeadTrashOutfit = "Undead Trash Mobs"; + Outfitter_cDemonTrashOutfit = "Demon Trash Mobs"; + + Outfitter_cABOutfit = "Schlachtfeld: Arathibecken"; + Outfitter_cAVOutfit = "Schlachtfeld: Alteractal"; + Outfitter_cWSGOutfit = "Schlachtfeld: Warsongschlucht"; + Outfitter_cCityOutfit = "Stadt"; + + Outfitter_cMountSpeedFormat = "Erh\195\182ht Tempo um (%d+)%%."; -- For detecting when mounted + + Outfitter_cBagsFullError = "Outfitter: %s kann nicht enfernt werden da alle Taschen voll sind."; + Outfitter_cItemNotFoundError = "Outfitter: Kann %s nicht finden."; + Outfitter_cAddingItem = "Outfitter: F\195\188ge %s zum %s Outfit."; + Outfitter_cNameAlreadyUsedError = "Fehler: Dieser Name ist bereits vergeben."; + Outfitter_cNoItemsWithStatError = "Warnung: Keines deiner Gegenst\195\164nde hat dieses Attribut."; + + Outfitter_cEnableAll = "Alle aktivieren"; + Outfitter_cEnableNone = "Alle deaktivieren"; + + Outfitter_cConfirmDeleteMsg = "Bist du sicher dass du das %s Outfit l\195\182schen willst?"; + Outfitter_cConfirmRebuildMsg = "Bist du sicher dass du das %s Outfit ver\195\164ndern willst?"; + Outfitter_cRebuild = "Ver\195\164ndern"; + + Outfitter_cWesternPlaguelands = "Westliche Pestl\195\164nder"; + Outfitter_cEasternPlaguelands = "\195\150stliche Pestl\195\164nder"; + Outfitter_cStratholme = "Stratholme"; + Outfitter_cScholomance = "Scholomance"; + Outfitter_cNaxxramas = "Naxxramas"; + Outfitter_cAlteracValley = "Alteractal"; + Outfitter_cArathiBasin = "Arathibecken"; + Outfitter_cWarsongGulch = "Warsongschlucht"; + Outfitter_cIronforge = "Ironforge"; + Outfitter_cDarnassus = "Darnassus"; + Outfitter_cStormwind = "Stormwind"; + Outfitter_cOrgrimmar = "Orgrimmar"; + Outfitter_cThunderBluff = "Thunder Bluff"; + Outfitter_cUndercity = "Undercity"; + + Outfitter_cFishingPole = "Angel"; + Outfitter_cStrongFishingPole = "Starke Angel"; + Outfitter_cBigIronFishingPole = "Gro\195\159e Eisenangel"; + Outfitter_cBlumpFishingPole = "Angel der Familie Blump"; + Outfitter_cNatPaglesFishingPole = "Nat Pagles Extremangler FC-5000"; + Outfitter_cArcaniteFishingPole = "Arkanitangel"; + + Outfitter_cArgentDawnCommission = "Anstecknadel der Argentumd\195\164mmerung"; + Outfitter_cSealOfTheDawn = "Siegel der D\195\164mmerung"; + Outfitter_cRuneOfTheDawn = "Rune der D\195\164mmerung"; + + Outfitter_cCarrotOnAStick = "Karotte am Stiel"; + + Outfitter_cItemStatFormats = { + { Format = "Ausdauer %+(%d+)", Types = { "Stamina" } }, + { Format = "Intelligenz %+(%d+)", Types = { "Intellect" } }, + { Format = "Beweglichkeit %+(%d+)", Types = { "Agility" } }, + { Format = "St\195\164rke %+(%d+)", Types = { "Strength" } }, + { Format = "Willenskraft %+(%d+)", Types = { "Spirit" } }, + { Format = "R\195\188stung %+(%d+)", Types = { "Armor" } }, + { Format = "Verteidigung %+(%d+)", Types = { "Defense" } }, + { Format = "Anlegen: Verteidigung %+(%d+)", Types = { "Defense" } }, + + { Format = "%+(%d+) Ausdauer", Types = { "Stamina" } }, + { Format = "%+(%d+) Intelligenz", Types = { "Intellect" } }, + { Format = "%+(%d+) Beweglichkeit", Types = { "Agility" } }, + { Format = "%+(%d+) St\195\164rke", Types = { "Strength" } }, + { Format = "%+(%d+) Willenskraft", Types = { "Spirit" } }, + { Format = "(%d+) R\195\188stung", Types = { "Armor" } }, + { Format = "%+(%d+) Angriffskraft", Types = { "Attack" } }, + + { Format = "Mana %+(%d+)", Types = { "Mana" } }, + { Format = "Gesundheit %+(%d+)", Types = { "Health" } }, + + { Format = "%+(%d+) Mana alle 5 Sek.", Types = { "ManaRegen" } }, + { Format = "Stellt alle 5 Sek. (%d+) Punkt(e) Mana wieder her.", Types = { "ManaRegen" } }, + + { Format = "%+(%d+) Gesundheit alle 5 Sek.", Types = { "HealthRegen" } }, + { Format = "Stellt alle 5 Sek. (%d+) Punkt(e) Gesundheit wieder her.", Types = { "HealthRegen" } }, + + { Format = "Schwache Reittemposteigerung", Value = 3, Types = { "Riding" } }, + { Format = "Mithrilsporen", Value = 3, Types = { "Riding" } }, + + { Format = "%+(%d+) Feuerwiderstand", Types = { "FireResist" } }, + { Format = "%+(%d+) Naturwiderstand", Types = { "NatureResist" } }, + { Format = "%+(%d+) Frostwiderstand", Types = { "FrostResist" } }, + { Format = "%+(%d+) Schattenwiderstand", Types = { "ShadowResist" } }, + { Format = "%+(%d+) Arkanwiderstand", Types = { "ArcaneResist" } }, + { Format = "%+(%d+) Alle Widerst\195\164nde", Types = { "FireResist", "NatureResist", "FrostResist", "ShadowResist", "ArcaneResist" } }, + + { Format = "Waffenschaden %+(%d+)", Types = { "MeleeDmg" } }, + { Format = "Erh\195\182ht Eure Trefferchance um (%d+)%%", Types = { "MeleeHit" } }, + { Format = "Erh\195\182ht Eure Chance, einen kritischen Schlag zu erzielen, um (%d+)%%", Types = { "MeleeCrit" } }, + { Format = "Erh\195\182ht Eure Chance, einem Angriff auszuweichen, um (%d+)%%", Types = { "Dodge" } }, + { Format = "Schaden %+(%d+)", Types = { "MeleeDmg" } }, + { Format = "(%d+) Blocken", Types = { "BlockAmount" } }, + + { Format = "Erh\195\182ht Angeln um %+(%d+)%.", Types = { "Fishing" } }, + { Format = "Angeln %+(%d+)", Types = { "Fishing" } }, + { Format = "Kr\195\164uterkunde %+(%d+)", Types = { "Herbalism" } }, + { Format = "Bergbau %+(%d+)", Types = { "Mining" } }, + { Format = "K\195\188rschnerei %+(%d+)", Types = { "Skinning" } }, + + { Format = "Erh\195\182ht Eure Chance, einen kritischen Treffer durch Zauber zu erzielen, um (%d+)%%", Types = { "SpellCrit" } }, + { Format = "Improves your chance to hit with spells by (%d+)%%", Types = { "SpellHit" } }, + { Format = "Erh\195\182ht durch Zauber und magische Effekte zugef\195\188gten Schaden und Heilung um bis zu (%d+)", Types = { "SpellDmg", "ShadowDmg", "FireDmg", "FrostDmg", "ArcaneDmg", "NatureDmg", "Healing" } }, + { Format = "Erh\195\182ht durch Zauber und Effekte verursachte Heilung um bis zu (%d+)", Types = { "Healing" } }, + { Format = "Heilzauber %+(%d+)", Types = { "Healing" } }, + { Format = "%+(%d+) Heilzauber", Types = { "Healing" } }, + + { Format = "%+(%d+) Feuerzauberschaden", Types = { "FireDmg" } }, + { Format = "%+(%d+) Schattenzauberschaden", Types = { "ShadowDmg" } }, + { Format = "%+(%d+) Frostzauberschaden", Types = { "FrostDmg" } }, + { Format = "%+(%d+) Arkanzauberschaden", Types = { "ArcaneDmg" } }, + { Format = "%+(%d+) Naturzauberschaden", Types = { "NatureDmg" } }, + + { Format = "Increases damage done by Fire spells and effects by up to (%d+)", Types = { "FireDmg" } }, + { Format = "Increases damage done by Shadow spells and effects by up to (%d+)", Types = { "ShadowDmg" } }, + { Format = "Increases damage done by Frost spells and effects by up to (%d+)", Types = { "FrostDmg" } }, + { Format = "Increases damage done by Arcane spells and effects by up to (%d+)", Types = { "ArcaneDmg" } }, + { Format = "Increases damage done by Nature spells and effects by up to (%d+)", Types = { "NatureDmg" } }, + }; + + Outfitter_cAgilityStatName = "Beweglichkeit"; + Outfitter_cArmorStatName = "R\195\188stung"; + Outfitter_cDefenseStatName = "Verteidigung"; + Outfitter_cIntellectStatName = "Intelligenz"; + Outfitter_cSpiritStatName = "Willenskraft"; + Outfitter_cStaminaStatName = "Ausdauer"; + Outfitter_cStrengthStatName = "St\195\164rke"; + + Outfitter_cManaRegenStatName = "Manaregeneration"; + Outfitter_cHealthRegenStatName = "Gesundheitsregeneration"; + + Outfitter_cSpellCritStatName = "Zauberkritisch"; + Outfitter_cSpellHitStatName = "Spell Chance to Hit"; + Outfitter_cSpellDmgStatName = "Zauberschaden"; + Outfitter_cFrostDmgStatName = "Frostzauberschaden"; + Outfitter_cFireDmgStatName = "Feuerzauberschaden"; + Outfitter_cArcaneDmgStatName = "Arkanzauberschaden"; + Outfitter_cShadowDmgStatName = "Schattenzauberschaden"; + Outfitter_cNatureDmgStatName = "Naturzauberschaden"; + Outfitter_cHealingStatName = "Heilung"; + + Outfitter_cMeleeCritStatName = "Kampfkritisch"; + Outfitter_cMeleeHitStatName = "Kampftrefferchance"; + Outfitter_cMeleeDmgStatName = "Kampfschaden"; + Outfitter_cAttackStatName = "Angriffskraft"; + Outfitter_cDodgeStatName = "Ausweichen"; + + Outfitter_cArcaneResistStatName = "Arkanwiderstand"; + Outfitter_cFireResistStatName = "Feuerwiderstand"; + Outfitter_cFrostResistStatName = "Frostwiderstand"; + Outfitter_cNatureResistStatName = "Naturwiderstand"; + Outfitter_cShadowResistStatName = "Schattenwiderstand"; + + Outfitter_cFishingStatName = "Angeln"; + Outfitter_cHerbalismStatName = "Kr\195\164uterkunde"; + Outfitter_cMiningStatName = "Bergbau"; + Outfitter_cSkinningStatName = "K\195\188rschnerei"; + + Outfitter_cOptionsTitle = "Outfitter Einstellungen"; + Outfitter_cShowMinimapButton = "Zeige Minimapbutton"; + Outfitter_cShowMinimapButtonOnDescription = "Deaktivieren, um den Minimapbutton von Outfitter zu verstecken."; + Outfitter_cShowMinimapButtonOffDescription = "Aktivieren, um den Minimapbutton von Outfitter zu zeigen."; + Outfitter_cRememberVisibility = "Remember cloak and helm settings"; + Outfitter_cRememberVisibilityOnDescription = "Turn this off if you want to use a single show/hide setting for all cloaks and helms"; + Outfitter_cRememberVisibilityOffDescription = "Turn this on if you want Outfitter to remember your preference for showing or hiding each cloak and helm individually"; + + Outfitter_cAboutTitle = "\195\188ber Outfitter"; + Outfitter_cAuthor = "Gestaltet und geschrieben von John Stephen"; + Outfitter_cTestersTitle = "Beta Tester"; + Outfitter_cTestersNames = "Airmid, Desiree, Fizzlebang, Harper, Kallah und Sumitra"; + Outfitter_cSpecialThanksTitle = "Besonderen Dank f\195\188r ihre Unterst\195\188tzung geht an"; + Outfitter_cSpecialThanksNames = "Brian, Dave, Glenn, Leah, Mark, The Mighty Pol, SFC und Forge"; + Outfitter_cGuildURL = "http://www.starfleetclan.com"; + Outfitter_cGuildURL2 = "http://www.forgeguild.com"; + + Outfitter_cOpenOutfitter = "Outfitter \195\182ffnen"; + + Outfitter_cArgentDawnOutfitDescription = "Dieses Outfit wird automatisch beim Betreten der Pestl\195\164nder angelegt."; + Outfitter_cRidingOutfitDescription = "Dieses Outfit wird automatisch beim Reiten angelegt."; + Outfitter_cDiningOutfitDescription = "Dieses Outfit wird automatisch beim Essen und/oder Trinken angelegt."; + Outfitter_cBattlegroundOutfitDescription = "Dieses Outfit wird automatisch beim Betreten eines Schlachtfeldes angelegt."; + Outfitter_cArathiBasinOutfitDescription = "This outfit will automatically be worn whenever you're in the Arathi Basin battleground"; + Outfitter_cAlteracValleyOutfitDescription = "This outfit will automatically be worn whenever you're in the Alterac Valley battleground"; + Outfitter_cWarsongGulchOutfitDescription = "This outfit will automatically be worn whenever you're in the Warsong Gulch battleground"; + Outfitter_cCityOutfitDescription = "This outfit will automatically be worn whenever you're in a friendly major city"; + Outfitter_cBossOutfitDescription = "This outfit will automatically be worn whenever you target level 63+ Boss mobs"; + Outfitter_cLvl63OutfitDescription = "This outfit will automatically be worn whenever you target level 63+ mobs"; + Outfitter_cTrashOutfitDescription = "This outfit will automatically be worn whenever you target level <63 mobs"; + Outfitter_cBeastTrashOutfitDescription = "This outfit will automatically be worn whenever you target beasts level <63 mobs"; + Outfitter_cUndeadTrashOutfitDescription = "This outfit will automatically be worn whenever you target undead level <63 mobs"; + Outfitter_cDemonTrashOutfitDescription = "This outfit will automatically be worn whenever you target demons level <63 mobs"; + + Outfitter_cKeyBinding = "Tastaturbelegung"; + + BINDING_HEADER_OUTFITTER_TITLE = Outfitter_cTitle; + + BINDING_NAME_OUTFITTER_OUTFIT1 = "Outfit 1"; + BINDING_NAME_OUTFITTER_OUTFIT2 = "Outfit 2"; + BINDING_NAME_OUTFITTER_OUTFIT3 = "Outfit 3"; + BINDING_NAME_OUTFITTER_OUTFIT4 = "Outfit 4"; + BINDING_NAME_OUTFITTER_OUTFIT5 = "Outfit 5"; + BINDING_NAME_OUTFITTER_OUTFIT6 = "Outfit 6"; + BINDING_NAME_OUTFITTER_OUTFIT7 = "Outfit 7"; + BINDING_NAME_OUTFITTER_OUTFIT8 = "Outfit 8"; + BINDING_NAME_OUTFITTER_OUTFIT9 = "Outfit 9"; + BINDING_NAME_OUTFITTER_OUTFIT10 = "Outfit 10"; + + Outfitter_cDisableOutfit = "Outfit abschalten"; + Outfitter_cDisableOutfitInBG = "Outfit beim Betreten eines Schlachtfeldes abschalten."; + Outfitter_cDisabledOutfitName = "%s (Abgeschalten)"; + + Outfitter_cMinimapButtonTitle = "Outfitter Minimapbutton"; + Outfitter_cMinimapButtonDescription = "Klicken f\195\188r eine Auswahl an Outfits oder gedr\195\188ckthalten zum bewegen des Buttons."; + + Outfitter_cDruidClassName = "Druide"; + Outfitter_cHunterClassName = "J\195\164ger"; + Outfitter_cMageClassName = "Magier"; + Outfitter_cPaladinClassName = "Paladin"; + Outfitter_cPriestClassName = "Priester"; + Outfitter_cRogueClassName = "Schurke"; + Outfitter_cShamanClassName = "Schamane"; + Outfitter_cWarlockClassName = "Hexenmeister"; + Outfitter_cWarriorClassName = "Krieger"; + + Outfitter_cBattleStance = "Kampfhaltung"; + Outfitter_cDefensiveStance = "Verteidigungshaltung"; + Outfitter_cBerserkerStance = "Berserkerhaltung"; + + Outfitter_cWarriorBattleStance = "Krieger: Kampfhaltung"; + Outfitter_cWarriorDefensiveStance = "Krieger: Verteidigungshaltung"; + Outfitter_cWarriorBerserkerStance = "Krieger: Berserkerhaltung"; + + Outfitter_cBearForm = "B\195\164rengestalt"; + Outfitter_cCatForm = "Katzengestalt"; + Outfitter_cAquaticForm = "Wassergestalt"; + Outfitter_cTravelForm = "Reisegestalt"; + Outfitter_cDireBearForm = "Terrorb\195\164rengestalt"; + Outfitter_cMoonkinForm = "Moonkin Form"; + + Outfitter_cGhostWolfForm = "Geisterwolf"; + + Outfitter_cStealth = "Verstohlenheit"; + + Outfitter_cDruidBearForm = "Druide: B\195\164rengestalt"; + Outfitter_cDruidCatForm = "Druide: Katzengestalt"; + Outfitter_cDruidAquaticForm = "Druide: Wassergestalt"; + Outfitter_cDruidTravelForm = "Druide: Reisegestalt"; + Outfitter_cDruidMoonkinForm = "Druide: Moonkin Form"; + + Outfitter_cPriestShadowform = "Priester: Schattengestalt"; + + Outfitter_cRogueStealth = "Schurke: Verstohlenheit"; + + Outfitter_cShamanGhostWolf = "Schamane: Geisterwolf"; + + Outfitter_cCompleteCategoryDescripton = "Vollst\195\164ndige Outfits haben f\195\188r jeden Inventarslot festgelegte Gegenst\195\164nde, die alles andere ersetzen wenn sie getragen werden."; + Outfitter_cPartialCategoryDescription = "Bei Mix-n-match Outfits sind nur einige Gegenst\195\164nde festgelegt, jedoch nicht alle. Werden diese Outfits ausgew\195\164hlt bleibt das vorherige Outfit erhalten, nur die neuen Gegenst\195\164nde werden ge\195\164ndert."; + Outfitter_cAccessoryCategoryDescription = "Zusatzgegenst\195\164nde-Outfits haben nur einige festgelegte Inventarslots. Anders als beim Mix-n-match kannst du so viele Zusatzgegenst\195\164nde-Outfits erstellen und tragen, sie werden alle miteinander verbunden und \195\188ber bestehenden Outfits getragen."; + Outfitter_cSpecialCategoryDescription = "Besondere Gelegenheit-Outfits werden automatisch angelegt wenn die Situation es verlangt. Sie werden \195\188ber allen anderen Outfits getragen."; + Outfitter_cOddsNEndsCategoryDescription = "Odds 'n ends ist eine Auflistung der Gegenst\195\164nde, die keinem Outfit zugewiesen sind. Mit dieser Funktion kannst du sicherstellen, dass alle Gegenst\195\164nde ihren Platz haben oder dass du keine unn\195\182tigen Gegenst\195\164nde mit dir herumtr\195\164gst."; + + Outfitter_cRebuildOutfitFormat = "%s ge\195\164ndert."; + + Outfitter_cTranslationCredit = "Deutsche \195\156bersetzung: Ani"; + + Outfitter_cSlotEnableTitle = "Slot aktivieren"; + Outfitter_cSlotEnableDescription = "Aktiviere diese Option, damit der Gegenstand in diesem Slot automatisch angelegt wird wenn du zu diesem Outfit wechselst. Ist dieser Slot nicht aktiviert, wird er beim Anlegen eines anderen Outfits nicht ver\195\164ndert."; + + Outfitter_cFinger0SlotName = "Erster Finger"; + Outfitter_cFinger1SlotName = "Zweiter Finger"; + + Outfitter_cTrinket0SlotName = "Erstes Schmuckst\195\188ck"; + Outfitter_cTrinket1SlotName = "Zweites Schmuckst\195\188ck"; + + Outfitter_cOutfitCategoryTitle = "Kategorie"; + Outfitter_cBankCategoryTitle = "Bank"; + Outfitter_cDepositToBank = "Gegenst\195\164nde im Bankfach ablegen"; + Outfitter_cDepositUniqueToBank = "Deposit unique items to bank"; + Outfitter_cWithdrawFromBank = "Gegenst\195\164nde vom Bankfach aufnehmen"; + + Outfitter_cMissingItemsLabel = "Fehlende Gegenst\195\164nde: "; + Outfitter_cBankedItemsLabel = "Gegenst\195\164nde auf der Bank: "; + + Outfitter_cStatsCategory = "Stats"; + Outfitter_cMeleeCategory = "Melee"; + Outfitter_cSpellsCategory = "Healing and Spells"; + Outfitter_cRegenCategory = "Regeneration"; + Outfitter_cResistCategory = "Resistances"; + Outfitter_cTradeCategory = "Trade Skills"; + + Outfitter_cCritter = "Kleintier"; + Outfitter_cCreatureBeast = "Wildtier"; + Outfitter_cCreatureUndead = "Untoter"; + Outfitter_cCreatureDemon = "Dämon"; + Outfitter_cSwimmingOutfit = "Schwimmen"; +end diff --git a/OutfitterStrings_fr.lua b/OutfitterStrings_fr.lua new file mode 100644 index 0000000..056d1aa --- /dev/null +++ b/OutfitterStrings_fr.lua @@ -0,0 +1,349 @@ +if GetLocale() == "frFR" then + Outfitter_cTitle = "Outfitter"; + Outfitter_cTitleVersion = Outfitter_cTitle .. " " .. Outfitter_cVersion; + + Outfitter_cNameLabel = "Nom:"; + Outfitter_cCreateUsingTitle = "Optimiser pour:"; + Outfitter_cUseCurrentOutfit = "Utiliser la tenue actuelle"; + Outfitter_cUseEmptyOutfit = "Cr\195\169er une tenue vide"; + + Outfitter_cOutfitterTabTitle = "Outfitter"; + Outfitter_cOptionsTabTitle = "Options"; + Outfitter_cAboutTabTitle = "A propos"; + + Outfitter_cNewOutfit = "Nouvelle tenue"; + Outfitter_cRenameOutfit = "Renommer la tenue"; + + Outfitter_cCompleteOutfits = "Tenues compl\195\168tes"; + Outfitter_cPartialOutfits = "Mix-n-match"; + Outfitter_cAccessoryOutfits = "Accessoires"; + Outfitter_cSpecialOutfits = "Occasions sp\195\169ciales"; + Outfitter_cOddsNEndsOutfits = "Odds \226\128\153n ends"; + + Outfitter_cNormalOutfit = "Normale"; + Outfitter_cNakedOutfit = "Nu(e)"; + + Outfitter_cFishingOutfit = "P\195\170che"; + Outfitter_cHerbalismOutfit = "Herborisme"; + Outfitter_cMiningOutfit = "Minage"; + Outfitter_cSkinningOutfit = "D\195\169pecage"; + Outfitter_cFireResistOutfit = "R\195\169sistance au feu"; + Outfitter_cNatureResistOutfit = "R\195\169sistance \195\160 la nature"; + Outfitter_cShadowResistOutfit = "R\195\169sistance \195\160 l\226\128\153ombre"; + Outfitter_cArcaneResistOutfit = "R\195\169sistance aux arcanes"; + Outfitter_cFrostResistOutfit = "R\195\169sistance au givre"; + + Outfitter_cArgentDawnOutfit = "L\226\128\153aube d\226\128\153argent"; + Outfitter_cRidingOutfit = "Monture"; + Outfitter_cDiningOutfit = "Diner"; + Outfitter_cBattlegroundOutfit = "Champs de Bataille"; + Outfitter_cABOutfit = "Battleground: Arathi Basin"; + Outfitter_cAVOutfit = "Battleground: Alterac Valley"; + Outfitter_cWSGOutfit = "Battleground: Warsong Gulch"; + Outfitter_cCityOutfit = "Around Town"; + Outfitter_cBossOutfit = "Boss Mobs"; + Outfitter_cLvl63Outfit = "Lvl63+"; + Outfitter_cTrashOutfit = "Trash Mobs"; + Outfitter_cBeastTrashOutfit = "Beast Trash Mobs"; + Outfitter_cUndeadTrashOutfit = "Undead Trash Mobs"; + Outfitter_cDemonTrashOutfit = "Demon Trash Mobs"; + + Outfitter_cMountSpeedFormat = "Augmente la vitesse de (%d+)%%."; -- For detecting when mounted + + Outfitter_cBagsFullError = "Outfitter: Impossible d\226\128\153enlever %s, tous les sacs sont pleins"; + Outfitter_cDepositBagsFullError = "Outfitter: Impossible de d\195\169poser %s. Tous les sacs de la banque sont pleins."; + Outfitter_cWithdrawBagsFullError = "Outfitter: Impossible de r\195\169cup\195\169rer %s. Tous vos sacs sont pleins."; + Outfitter_cItemNotFoundError = "Outfitter: %s est introuvable"; + Outfitter_cItemAlreadyUsedError = "Outfitter: Impossible d\226\128\153 \195\169quiper %s \195\160 la place \226\128\153%s\226\128\153 car il est d\195\169j\195\160 utilis\195\169 \195\160 un autre endroit."; + Outfitter_cAddingItem = "Outfitter: %s ajout\195\169 \195\160 la tenue : %s"; + Outfitter_cNameAlreadyUsedError = "Erreur: Nom existant"; + Outfitter_cNoItemsWithStatError = "Attention: Aucuns de vos objets n\226\128\153a cet attribut"; + + Outfitter_cEnableAll = "Tout activer"; + Outfitter_cEnableNone = "Tout d\195\169sactiver"; + + Outfitter_cConfirmDeleteMsg = "Etes vous s\195\187r de vouloir supprimer la tenue : %s?"; + Outfitter_cConfirmRebuildMsg = "Etes vous s\195\187r de vouloir reconstruire la tenue : %s?"; + Outfitter_cRebuild = "Reconstruire"; + + Outfitter_cWesternPlaguelands = "Maleterres de l\226\128\153ouest"; + Outfitter_cEasternPlaguelands = "Maleterres de l\226\128\153est"; + Outfitter_cStratholme = "Stratholme"; + Outfitter_cScholomance = "Scholomance"; + Outfitter_cNaxxramas = "Naxxramas"; + Outfitter_cAlteracValley = "Vall\195\169e d\226\128\153alterac"; + Outfitter_cArathiBasin = "Bassin d\226\128\153arathi"; + Outfitter_cWarsongGulch = "Goulet des warsong"; + Outfitter_cIronforge = "Ironforge"; + Outfitter_cDarnassus = "Darnassus"; + Outfitter_cStormwind = "Stormwind City"; + Outfitter_cOrgrimmar = "Orgrimmar"; + Outfitter_cThunderBluff = "Thunder Bluff"; + Outfitter_cUndercity = "Undercity"; + + Outfitter_cFishingPole = "Canne \195\160 p\195\170che"; + Outfitter_cStrongFishingPole = "Solide canne \195\160 p\195\170che"; + Outfitter_cBigIronFishingPole = "Grande canne \195\160 p\195\170che en fer"; + Outfitter_cBlumpFishingPole = "Canne \195\160 p\195\170che de la famille Blump"; + Outfitter_cNatPaglesFishingPole = "FC-5000 de Nat Pagle, p\195\170cheur de l\226\128\153extr\195\170me"; + Outfitter_cArcaniteFishingPole = "Canne \195\160 p\195\170che en arcanite"; + + Outfitter_cArgentDawnCommission = "Brevet de l\226\128\153aube d\226\128\153argent"; + Outfitter_cSealOfTheDawn = "Sceau de l\226\128\153aube"; + Outfitter_cRuneOfTheDawn = "Rune de l\226\128\153aube"; + + Outfitter_cCarrotOnAStick = "Carotte et b\195\162ton"; + + Outfitter_cItemStatFormats = { + { Format = "Endurance %+(%d+)", Types = { "Stamina" } }, + { Format = "Intelligence %+(%d+)", Types = { "Intellect" } }, + { Format = "Agilit\195\169 %+(%d+)", Types = { "Agility" } }, + { Format = "Force %+(%d+)", Types = { "Strength" } }, + { Format = "Esprit %+(%d+)", Types = { "Spirit" } }, + { Format = "Armure %+(%d+)", Types = { "Armor" } }, + { Format = "D\195\169fense %+(%d+)", Types = { "Defense" } }, + { Format = "D\195\169fense augment\195\169e de %+(%d+)", Types = { "Defense" } }, + + { Format = "%+(%d+) Endurance", Types = { "Stamina" } }, + { Format = "%+(%d+) Intelligence", Types = { "Intellect" } }, + { Format = "%+(%d+) Agilit\195\169", Types = { "Agility" } }, + { Format = "%+(%d+) Force", Types = { "Strength" } }, + { Format = "%+(%d+) Esprit", Types = { "Spirit" } }, + { Format = "%+(%d+) Armure", Types = { "Armor" } }, + { Format = "%+(%d+) \195\160 la puissance d\226\128\153attaque", Types = { "Attack" } }, + + { Format = "%+(%d+) \195\160 toutes les caract\195\169ristiques", Types = { "Stamina", "Intellect", "Agility", "Strength", "Spirit" } }, + + { Format = "Mana %+(%d+)", Types = { "Mana" } }, + { Format = "Vie %+(%d+)", Types = { "Health" } }, + + { Format = "%+(%d+) points de mana toutes les 5 secondes.", Types = { "ManaRegen" } }, + { Format = "Rends (%d+) points de mana toutes les 5 secondes.", Types = { "ManaRegen" } }, + + { Format = "%+(%d+) points de vie toutes les 5 secondes.", Types = { "HealthRegen" } }, + { Format = "Rends (%d+) points de vie toutes les 5 secondes.", Types = { "HealthRegen" } }, + + { Format = "Augmentation mineure de la vitesse de monture", Value = 3, Types = { "Riding" } }, + { Format = "Eperons en mithril", Value = 3, Types = { "Riding" } }, + + { Format = "%+(%d+) R\195\169sistance Feu", Types = { "FireResist" } }, + { Format = "%+(%d+) \195\160 la r\195\169sistance Feu", Types = { "FireResist" } }, + { Format = "%+(%d+) R\195\169sistance Nature", Types = { "NatureResist" } }, + { Format = "%+(%d+) \195\160 la r\195\169sistance Nature", Types = { "NatureResist" } }, + { Format = "%+(%d+) R\195\169sistance Givre", Types = { "FrostResist" } }, + { Format = "%+(%d+) \195\160 la r\195\169sistance Givre", Types = { "FrostResist" } }, + { Format = "%+(%d+) R\195\169sistance Ombre", Types = { "ShadowResist" } }, + { Format = "%+(%d+) \195\160 la r\195\169sistance Ombre", Types = { "ShadowResist" } }, + { Format = "%+(%d+) R\195\169sistance Arcanes", Types = { "ArcaneResist" } }, + { Format = "%+(%d+) \195\160 la r\195\169sistance Arcanes", Types = { "ArcaneResist" } }, + { Format = "%+(%d+) \195\160 toutes les r\195\169sistances", Types = { "FireResist", "NatureResist", "FrostResist", "ShadowResist", "ArcaneResist" } }, + + { Format = "D\195\169g\195\162ts de l\226\128\153arme %+(%d+)", Types = { "MeleeDmg" } }, + { Format = "Augmente vos chances de toucher de (%d+)%%", Types = { "MeleeHit" } }, + { Format = "Augmente vos chance de faire un coup critique de (%d+)%%", Types = { "MeleeCrit" } }, + { Format = "Augmente vos chances d\226\128\153esquiver une attaque de (%d+)%%", Types = { "Dodge" } }, + { Format = "D\195\169g\195\162ts %+(%d+)", Types = { "MeleeDmg" } }, + { Format = "(%d+) Block", Types = { "BlockAmount" } }, + + { Format = "Augmente la p\195\168che de %+(%d+)%.", Types = { "Fishing" } }, + { Format = "P\195\168che %+(%d+)", Types = { "Fishing" } }, + { Format = "Herborisme %+(%d+)", Types = { "Herbalism" } }, + { Format = "Minage %+(%d+)", Types = { "Mining" } }, + { Format = "D\195\169pecage %+(%d+)", Types = { "Skinning" } }, + + { Format = "Augmente vos chances d\226\128\153infliger des coups critiques avec vos sorts de (%d+)%%", Types = { "SpellCrit" } }, + { Format = "Improves your chance to hit with spells by (%d+)%%", Types = { "SpellHit" } }, + { Format = "Augmente les d\195\169g\195\162ts et les soins produits par les sorts et effets magiques de (%d+) au maximum", Types = { "SpellDmg", "ShadowDmg", "FireDmg", "FrostDmg", "ArcaneDmg", "NatureDmg", "Healing" } }, + { Format = "Augmente les soins prodigu\195\169s par les sorts et effets de (%d+) au maximum", Types = { "Healing" } }, + { Format = "Sorts de soins %+(%d+)", Types = { "Healing" } }, + { Format = "%+(%d+) Sorts de soins", Types = { "Healing" } }, + + { Format = "%+(%d+) aux d\195\169g\195\162ts des sorts de feu", Types = { "FireDmg" } }, + { Format = "%+(%d+) aux d\195\169g\195\162ts des sorts d\226\128\153ombre", Types = { "ShadowDmg" } }, + { Format = "%+(%d+) aux d\195\169g\195\162ts des sorts de givre", Types = { "FrostDmg" } }, + { Format = "%+(%d+) aux d\195\169g\195\162ts des sorts d\226\128\153arcane", Types = { "ArcaneDmg" } }, + { Format = "%+(%d+) aux d\195\169g\195\162ts des sorts de nature", Types = { "NatureDmg" } }, + + { Format = "Augmente les points de d\195\169g\195\162ts inflig\195\169 par les sorts et effets de feu de (%d+) au maximum", Types = { "FireDmg" } }, + { Format = "Augmente les points de d\195\169g\195\162ts inflig\195\169 par les sorts et effets d\226\128\153ombre de (%d+) au maximum", Types = { "ShadowDmg" } }, + { Format = "Augmente les points de d\195\169g\195\162ts inflig\195\169 par les sorts et effets de givre de (%d+) au maximum", Types = { "FrostDmg" } }, + { Format = "Augmente les points de d\195\169g\195\162ts inflig\195\169 par les sorts et effets d\226\128\153arcane de (%d+) au maximum", Types = { "ArcaneDmg" } }, + { Format = "Augmente les points de d\195\169g\195\162ts inflig\195\169 par les sorts et effets de nature de (%d+) au maximum", Types = { "NatureDmg" } }, + }; + + Outfitter_cAgilityStatName = "Agilit\195\169"; + Outfitter_cArmorStatName = "Armure"; + Outfitter_cDefenseStatName = "D\195\169fense"; + Outfitter_cIntellectStatName = "Intelligence"; + Outfitter_cSpiritStatName = "Esprit"; + Outfitter_cStaminaStatName = "Endurance"; + Outfitter_cStrengthStatName = "Force"; + + Outfitter_cManaRegenStatName = "R\195\169g\195\169n\195\169ration de mana"; + Outfitter_cHealthRegenStatName = "R\195\169g\195\169n\195\169ration de vie"; + + Outfitter_cSpellCritStatName = "Coups critiques des sorts"; + Outfitter_cSpellHitStatName = "Spell Chance to Hit"; + Outfitter_cSpellDmgStatName = "D\195\169g\195\162ts des sorts"; + Outfitter_cFrostDmgStatName = "D\195\169g\195\162ts des sorts de givre"; + Outfitter_cFireDmgStatName = "D\195\169g\195\162ts des sorts de feu"; + Outfitter_cArcaneDmgStatName = "D\195\169g\195\162ts des sorts d\226\128\153arcane"; + Outfitter_cShadowDmgStatName = "D\195\169g\195\162ts des sorts d\226\128\153ombre"; + Outfitter_cNatureDmgStatName = "D\195\169g\195\162ts des sorts de nature"; + Outfitter_cHealingStatName = "Soins"; + + Outfitter_cMeleeCritStatName = "Coups critiques"; + Outfitter_cMeleeHitStatName = "Chance de toucher"; + Outfitter_cMeleeDmgStatName = "D\195\169g\195\162ts"; + Outfitter_cAttackStatName = "Puissance d\226\128\153attaque"; + Outfitter_cDodgeStatName = "Esquive"; + + Outfitter_cArcaneResistStatName = "R\195\169sistance aux arcanes"; + Outfitter_cFireResistStatName = "R\195\169sistance au feu"; + Outfitter_cFrostResistStatName = "R\195\169sistance au givre"; + Outfitter_cNatureResistStatName = "R\195\169sistance \195\160 la nature"; + Outfitter_cShadowResistStatName = "R\195\169sistance \195\160 l\226\128\153ombre"; + + Outfitter_cFishingStatName = "P\195\170che"; + Outfitter_cHerbalismStatName = "Herborisme"; + Outfitter_cMiningStatName = "Minage"; + Outfitter_cSkinningStatName = "D\195\169pecage"; + + Outfitter_cOptionsTitle = "Options de Outfitter"; + Outfitter_cShowMinimapButton = "Bouton Minimap"; + Outfitter_cShowMinimapButtonOnDescription = "D\195\169sactivez si vous ne voulez pas le bouton d\226\128\153Outfitter sur votre minimap"; + Outfitter_cShowMinimapButtonOffDescription = "Activez pour afficher le bouton d\226\128\153Outfitter sur la minimap"; + Outfitter_cRememberVisibility = "Remember cloak and helm settings"; + Outfitter_cRememberVisibilityOnDescription = "Turn this off if you want to use a single show/hide setting for all cloaks and helms"; + Outfitter_cRememberVisibilityOffDescription = "Turn this on if you want Outfitter to remember your preference for showing or hiding each cloak and helm individually"; + + Outfitter_cAboutTitle = "A propos d\226\128\153Outfitter"; + Outfitter_cAuthor = "Designed and written by Baylord of Thunderlord"; + Outfitter_cTestersTitle = "Beta Testers"; + Outfitter_cTestersNames = "Airmid, Desiree, Fizzlebang, Harper, Kallah and Sumitra"; + Outfitter_cSpecialThanksTitle = "Special thanks for their support to"; + Outfitter_cSpecialThanksNames = "Brian, Dave, Glenn, Leah, Mark, The Mighty Pol, SFC and Forge"; + Outfitter_cGuildURL = "http://www.starfleetclan.com"; + Outfitter_cGuildURL2 = "http://www.forgeguild.com"; + + Outfitter_cOpenOutfitter = "Ouvrir Outfitter"; + + Outfitter_cArgentDawnOutfitDescription = "Cette tenue sera automatiquement \195\169quip\195\169e quand vous serez dans les Maleterres"; + Outfitter_cRidingOutfitDescription = "Cette tenue sera automatiquement \195\169quip\195\169e quand vous serez sur votre monture"; + Outfitter_cDiningOutfitDescription = "Cette tenue sera automatiquement \195\169quip\195\169e quand vous mangerez ou boirez"; + Outfitter_cBattlegroundOutfitDescription = "Cette tenue sera automatiquement \195\169quip\195\169e quand vous serez dans un Champ de BAtaille"; + Outfitter_cArathiBasinOutfitDescription = "This outfit will automatically be worn whenever you're in the Arathi Basin battleground"; + Outfitter_cAlteracValleyOutfitDescription = "This outfit will automatically be worn whenever you're in the Alterac Valley battleground"; + Outfitter_cWarsongGulchOutfitDescription = "This outfit will automatically be worn whenever you're in the Warsong Gulch battleground"; + Outfitter_cCityOutfitDescription = "This outfit will automatically be worn whenever you're in a friendly major city"; + Outfitter_cBossOutfitDescription = "This outfit will automatically be worn whenever you target level 63+ Boss mobs"; + Outfitter_cLvl63OutfitDescription = "This outfit will automatically be worn whenever you target level 63+ mobs"; + Outfitter_cTrashOutfitDescription = "This outfit will automatically be worn whenever you target level <63 mobs"; + Outfitter_cBeastTrashOutfitDescription = "This outfit will automatically be worn whenever you target beasts level <63 mobs"; + Outfitter_cUndeadTrashOutfitDescription = "This outfit will automatically be worn whenever you target undead level <63 mobs"; + Outfitter_cDemonTrashOutfitDescription = "This outfit will automatically be worn whenever you target demons level <63 mobs"; + + Outfitter_cKeyBinding = "Raccourcis"; + + BINDING_HEADER_OUTFITTER_TITLE = Outfitter_cTitle; + + BINDING_NAME_OUTFITTER_OUTFIT1 = "Tenue 1"; + BINDING_NAME_OUTFITTER_OUTFIT2 = "Tenue 2"; + BINDING_NAME_OUTFITTER_OUTFIT3 = "Tenue 3"; + BINDING_NAME_OUTFITTER_OUTFIT4 = "Tenue 4"; + BINDING_NAME_OUTFITTER_OUTFIT5 = "Tenue 5"; + BINDING_NAME_OUTFITTER_OUTFIT6 = "Tenue 6"; + BINDING_NAME_OUTFITTER_OUTFIT7 = "Tenue 7"; + BINDING_NAME_OUTFITTER_OUTFIT8 = "Tenue 8"; + BINDING_NAME_OUTFITTER_OUTFIT9 = "Tenue 9"; + BINDING_NAME_OUTFITTER_OUTFIT10 = "Tenue 10"; + + Outfitter_cDisableOutfit = "D\195\169sactiver la tenue"; + Outfitter_cDisableOutfitInBG = "D\195\169sactiver la tenue dans les Champs de Bataille"; + Outfitter_cDisabledOutfitName = "%s (D\195\169sactiv\195\169)"; + + Outfitter_cMinimapButtonTitle = "Outfitter"; + Outfitter_cMinimapButtonDescription = "Cliquez pour s\195\169l\195\169ctionner une autre tenue ou d\195\169placez pour changer la position du bouton."; + + Outfitter_cDruidClassName = "Druide"; + Outfitter_cHunterClassName = "Chasseur"; + Outfitter_cMageClassName = "Mage"; + Outfitter_cPaladinClassName = "Paladin"; + Outfitter_cPriestClassName = "Pr\195\170tre"; + Outfitter_cRogueClassName = "Voleur"; + Outfitter_cShamanClassName = "Chaman"; + Outfitter_cWarlockClassName = "D\195\169moniste"; + Outfitter_cWarriorClassName = "Guerrier"; + + Outfitter_cBattleStance = "Posture de combat"; + Outfitter_cDefensiveStance = "Posture d\195\169fensive"; + Outfitter_cBerserkerStance = "Posture berzeker"; + + Outfitter_cWarriorBattleStance = "Guerrier: Posture de combat"; + Outfitter_cWarriorDefensiveStance = "Guerrier: Posture d\195\169fensive"; + Outfitter_cWarriorBerserkerStance = "Guerrier: Posture berzeker"; + + Outfitter_cBearForm = "Forme d\226\128\153ours"; + Outfitter_cCatForm = "Forme de f\195\169lin"; + Outfitter_cAquaticForm = "Forme aquatique"; + Outfitter_cTravelForm = "Forme de voyage"; + Outfitter_cDireBearForm = "Forme d\226\128\153ours redoutable"; + Outfitter_cMoonkinForm = "Fome de s\195\169l\195\169nien"; + + Outfitter_cGhostWolfForm = "Loup fant\195\180me"; + + Outfitter_cStealth = "Camouflage"; + + Outfitter_cDruidBearForm = "Druide: Forme d\226\128\153ours"; + Outfitter_cDruidCatForm = "Druide: Forme de f\195\169lin"; + Outfitter_cDruidAquaticForm = "Druide: Forme aquatique"; + Outfitter_cDruidTravelForm = "Druide: Forme de voyage"; + Outfitter_cDruidMoonkinForm = "Druid: Forme de s\195\169l\195\169nien"; + + Outfitter_cPriestShadowform = "Pr\195\170tre: Forme d\226\128\153ombre"; + + Outfitter_cRogueStealth = "Voleur: Camouflage"; + + Outfitter_cShamanGhostWolf = "Chaman: Loup fant\195\180me"; + + Outfitter_cCompleteCategoryDescripton = "Les tenues compl\195\168tes ont un objet sp\195\169cifi\195\169 pour chaque emplacement et remplaceront toutes les autres tenues quand elles sont activ\195\169es"; + Outfitter_cPartialCategoryDescription = "Les tenues \"Mix-n-match\" ont quelques emplacements sp\195\169cifi\195\169s. Quand on les \195\169quipe, elles vont par-dessus la tenue compl\195\168te s\195\169l\195\169ctionn\195\169e et remplace toutes les tenues \"Mix-n-match\" et Accessoires"; + Outfitter_cAccessoryCategoryDescription = "Les tenues d\226\128\153accessoires ont quelques emplacements sp\195\169cifi\195\169s. Contrairement aux \"Mix-n-Match\", Contrairement aux \"Mix-n-Match\", vous pouvez s\195\169l\195\169ctionner autant d\226\128\153accessoires que vous voulez et ils seront combin\195\169s par-dessus votre tenue compl\195\168te et \"Mix-n-Match\"."; + Outfitter_cSpecialCategoryDescription = "Les tenues sp\195\169ciales sont automatiquement \195\169quip\195\169es dans certaines conditions. Elles remplacent les tenues \195\169quip\195\169es avant."; + Outfitter_cOddsNEndsCategoryDescription = "Les Odds \226\128\153n ends sont des objets que vous n\226\128\153utilisez dans aucune tenue. Ceci peut \195\170tre utile pour v\195\169rifier que vous utilisez tous les objets que vous avez dans l\226\128\153inventaire."; + + Outfitter_cRebuildOutfitFormat = "Reconstruire pour %s"; + + Outfitter_cTranslationCredit = "Traduction fran\195\167aise: Jullye de Kael\226\128\153Thas"; + + Outfitter_cSlotEnableTitle = "Activer l\226\128\153emplacement"; + Outfitter_cSlotEnableDescription = "Cochez la case si vous voulez que l\226\128\153objet de cet emplacement soit automatiquement \195\169quip\195\169 lors du changement de tenue. Si la case n\226\128\153est pas coch\195\169e, l\226\128\153objet ne sera pas modifi\195\169 lors du changement de tenue."; + + Outfitter_cFinger0SlotName = "Premier doigt"; + Outfitter_cFinger1SlotName = "Second doigt"; + + Outfitter_cTrinket0SlotName = "Premier bijou"; + Outfitter_cTrinket1SlotName = "Second bijou"; + + Outfitter_cOutfitCategoryTitle = "Cat\195\169gorie"; + Outfitter_cBankCategoryTitle = "Banque"; + Outfitter_cDepositToBank = "D\195\169poser les objets \195\160 la banque."; + Outfitter_cDepositUniqueToBank = "Deposit unique items to bank"; + Outfitter_cWithdrawFromBank = "R\195\169cup\195\169rer les objets de la banque."; + + Outfitter_cMissingItemsLabel = "Objets manquants: "; + Outfitter_cBankedItemsLabel = "Objets \195\160 la banque: "; + + Outfitter_cStatsCategory = "Stats"; + Outfitter_cMeleeCategory = "Melee"; + Outfitter_cSpellsCategory = "Healing and Spells"; + Outfitter_cRegenCategory = "Regeneration"; + Outfitter_cResistCategory = "Resistances"; + Outfitter_cTradeCategory = "Trade Skills"; + + Outfitter_cCritter = "Bestiole"; + Outfitter_cCreatureBeast = "Bête"; + Outfitter_cCreatureUndead = "Mort-vivant"; + Outfitter_cCreatureDemon = "Démon"; + Outfitter_cSwimmingOutfit = "Natation"; +end diff --git a/OutfitterStrings_kr.lua b/OutfitterStrings_kr.lua new file mode 100644 index 0000000..dbf3ef9 --- /dev/null +++ b/OutfitterStrings_kr.lua @@ -0,0 +1,368 @@ +if GetLocale() == "koKR" then + Outfitter_cTitle = "Outfitter"; + Outfitter_cTitleVersion = Outfitter_cTitle .. " " .. Outfitter_cVersion; + + Outfitter_cNameLabel = "이름:"; + Outfitter_cCreateUsingTitle = "최적화:"; + Outfitter_cUseCurrentOutfit = "현재 장비 세트 사용"; + Outfitter_cUseEmptyOutfit = "빈 장비 세트 만듬"; + + Outfitter_cOutfitterTabTitle = "Outfitter"; + Outfitter_cOptionsTabTitle = "옵션"; + Outfitter_cAboutTabTitle = "정보"; + + Outfitter_cNewOutfit = "신규 장비 세트"; + Outfitter_cRenameOutfit = "장비 세트 이름 변경"; + + Outfitter_cCompleteOutfits = "완비 세트"; + Outfitter_cPartialOutfits = "Mix-n-match"; + Outfitter_cAccessoryOutfits = "보조 장비"; + Outfitter_cSpecialOutfits = "특수 조건"; + Outfitter_cOddsNEndsOutfits = "나머지 장비들"; + + Outfitter_cNormalOutfit = "평상시"; + Outfitter_cNakedOutfit = "벗기"; + + Outfitter_cFishingOutfit = "낚시"; + Outfitter_cHerbalismOutfit = "약초 채집"; + Outfitter_cMiningOutfit = "채광"; + Outfitter_cSkinningOutfit = "무두질"; + Outfitter_cFireResistOutfit = "화염 저항"; + Outfitter_cNatureResistOutfit = "자연 저항"; + Outfitter_cShadowResistOutfit = "암흑 저항"; + Outfitter_cArcaneResistOutfit = "비전 저항"; + Outfitter_cFrostResistOutfit = "냉기 저항"; + + Outfitter_cArgentDawnOutfit = "은빛 여명회"; + Outfitter_cRidingOutfit = "말타기"; + Outfitter_cDiningOutfit = "음식 먹기"; + Outfitter_cBattlegroundOutfit = "전장"; + Outfitter_cABOutfit = "전장: 아라시 분지"; + Outfitter_cAVOutfit = "전장: 알터랙 계곡"; + Outfitter_cWSGOutfit = "전장: 전쟁노래 협곡"; + Outfitter_cCityOutfit = "마을 주변"; + Outfitter_cBossOutfit = "Boss Mobs"; + Outfitter_cLvl63Outfit = "Lvl63+"; + Outfitter_cTrashOutfit = "Trash Mobs"; + Outfitter_cBeastTrashOutfit = "Beast Trash Mobs"; + Outfitter_cUndeadTrashOutfit = "Undead Trash Mobs"; + Outfitter_cDemonTrashOutfit = "Demon Trash Mobs"; + + Outfitter_cMountSpeedFormat = "이동 속도 (%d+)%%만큼 증가"; -- For detecting when mounted + + Outfitter_cBagsFullError = "Outfitter: 가방이 가득 차서 %s|1을;를; 벗을 수 없습니다."; + Outfitter_cDepositBagsFullError = "Outfitter: 가방이 가득 차서 %s|1을;를; 벗을 수 없습니다."; + Outfitter_cWithdrawBagsFullError = "Outfitter: 가방이 가득 차서 %s|1을;를; 벗을 수 없습니다."; + Outfitter_cItemNotFoundError = "Outfitter: %s 아이템을 찾을 수 없습니다."; + Outfitter_cItemAlreadyUsedError = "Outfitter: %s|1은;는; 이미 다른 슬롯에서 사용중이므로 %s 슬롯에 착용할 수 없습니다."; + Outfitter_cAddingItem = "Outfitter: %s|1을;를 %s 세트에 추가합니다."; + Outfitter_cNameAlreadyUsedError = "에러: 사용중인 이름입니다."; + Outfitter_cNoItemsWithStatError = "경고: 해당 능력을 가진 아이템이 없습니다."; + + Outfitter_cEnableAll = "모두 활성화"; + Outfitter_cEnableNone = "모두 비활성화"; + + Outfitter_cConfirmDeleteMsg = "%s 세트를 삭제 하시겠습니까?"; + Outfitter_cConfirmRebuildMsg = "%s 세트를 재구성 하시겠습니까?"; + Outfitter_cRebuild = "Rebuild"; + + Outfitter_cWesternPlaguelands = "서부 역병지대"; + Outfitter_cEasternPlaguelands = "동부 역병지대"; + Outfitter_cStratholme = "스트라솔룸"; + Outfitter_cScholomance = "스칼로맨스"; + Outfitter_cNaxxramas = "낙스라마스"; + Outfitter_cAlteracValley = "알터랙 계곡"; + Outfitter_cArathiBasin = "아라시 분지"; + Outfitter_cWarsongGulch = "전쟁노래 협곡"; + Outfitter_cIronforge = "아이언포지"; + Outfitter_cCityOfIronforge = "아이언포지"; + Outfitter_cDarnassus = "다르나서스"; + Outfitter_cStormwind = "스톰윈드"; + Outfitter_cOrgrimmar = "오그리마"; + Outfitter_cThunderBluff = "썬더 블러프"; + Outfitter_cUndercity = "언더시티"; + + Outfitter_cFishingPole = "낚싯대"; + Outfitter_cStrongFishingPole = "특튼한 낚싯대"; + Outfitter_cBigIronFishingPole = "큰 철재 낚싯대"; + Outfitter_cBlumpFishingPole = "블럼프가의 낚싯대"; + Outfitter_cNatPaglesFishingPole = "네트 페이글의 다낚아 FC-5000"; + Outfitter_cArcaniteFishingPole = "아케이나이트 낚싯대"; + + Outfitter_cArgentDawnCommission = "은빛 여명회 위임봉"; + Outfitter_cSealOfTheDawn = "여명의 문장"; + Outfitter_cRuneOfTheDawn = "여명의 룬"; + + Outfitter_cCarrotOnAStick = "당근 달린 지팡이"; + + Outfitter_cItemStatFormats = { + { Format = "체력 %+(%d+)", Types = { "Stamina" } }, + { Format = "지능 %+(%d+)", Types = { "Intellect" } }, + { Format = "민첩 %+(%d+)", Types = { "Agility" } }, + { Format = "힘 %+(%d+)", Types = { "Strength" } }, + { Format = "정신력 %+(%d+)", Types = { "Spirit" } }, + { Format = "방어도 %+(%d+)", Types = { "Armor" } }, + { Format = "방어 숙련도 %+(%d+)", Types = { "Defense" } }, + { Format = "Increased Defense %+(%d+)", Types = { "Defense" } }, + + { Format = "%+(%d+) 체력", Types = { "Stamina" } }, + { Format = "%+(%d+) 지능", Types = { "Intellect" } }, + { Format = "%+(%d+) 민첩", Types = { "Agility" } }, + { Format = "%+(%d+) 힘", Types = { "Strength" } }, + { Format = "%+(%d+) 정신력", Types = { "Spirit" } }, + { Format = "(%d+) 방어도", Types = { "Armor" } }, + { Format = "%+(%d+) 전투력", Types = { "Attack" } }, + + { Format = "모든 능력치 %+(%d+)", Types = { "Stamina", "Intellect", "Agility", "Strength", "Spirit" } }, + + { Format = "마나 %+(%d+)", Types = { "Mana" } }, + { Format = "생명력 %+(%d+)", Types = { "Health" } }, + + { Format = "매 5초마다 %+(%d+)의 마나", Types = { "ManaRegen" } }, + { Format = "매 5초마다 (%d+)의 마나가 회복됩니다.", Types = { "ManaRegen" } }, + + { Format = "매 5초마다 %+(%d+)의 생명력", Types = { "HealthRegen" } }, + { Format = "매 5초마다 (%d+)의 생명력이 회복됩니다.", Types = { "HealthRegen" } }, + { Format = "Restores (%d+) health per 5 sec.", Types = { "HealthRegen" } }, + + { Format = "최하급 탈것 속도 증가", Value = 3, Types = { "Riding" } }, + { Format = "미스릴 박차", Value = 3, Types = { "Riding" } }, + + { Format = "화염 저항력 %+(%d+)", Types = { "FireResist" } }, + { Format = "자연 저항력 %+(%d+)", Types = { "NatureResist" } }, + { Format = "냉기 저항력 %+(%d+)", Types = { "FrostResist" } }, + { Format = "암흑 저항력 %+(%d+)", Types = { "ShadowResist" } }, + { Format = "비전 저항력 %+(%d+)", Types = { "ArcaneResist" } }, + { Format = "모든 저항력 %+(%d+)", Types = { "FireResist", "NatureResist", "FrostResist", "ShadowResist", "ArcaneResist" } }, + + { Format = "무기 공격력 %+(%d+)", Types = { "MeleeDmg" } }, + { Format = "무기의 적중률이 (%d+)%%만큼 증가합니다.", Types = { "MeleeHit" } }, + { Format = "치명타를 적중시킬 확률이 (%d+)%%만큼 증가합니다.", Types = { "MeleeCrit" } }, + { Format = "공격을 회피할 확률이 (%d+)%%만큼 증가합니다.", Types = { "Dodge" } }, + { Format = "공격력 %+(%d+)", Types = { "MeleeDmg" } }, + { Format = "(%d+)의 피해 방어", Types = { "Block" } }, + { Format = "방어도 %+(%d+)", Types = { "Block" } }, + { Format = "방패의 피해 방어량이 (%d+)만큼 증가합니다.", Types = { "Block" } }, + + { Format = "낚시 숙련도 %+(%d+)%.", Types = { "Fishing" } }, + { Format = "낚시 %+(%d+)", Types = { "Fishing" } }, + { Format = "약초 채집 %+(%d+)", Types = { "Herbalism" } }, + { Format = "채광 %+(%d+)", Types = { "Mining" } }, + { Format = "무두질 %+(%d+)", Types = { "Skinning" } }, + + { Format = "주문이 극대화 효과를 낼 확률이(%d+)%%만큼 증가합니다.", Types = { "SpellCrit" } }, + { Format = "주문의 적중률이 (%d+)%%만큼 증가합니다.", Types = { "SpellHit" } }, + { Format = "모든 주문 및 효과에 의한 피해와 치유량이 최대 (%d+)만큼 증가합니다.", Types = { "SpellDmg", "ShadowDmg", "FireDmg", "FrostDmg", "ArcaneDmg", "NatureDmg", "Healing" } }, + { Format = "모든 주문 및 효과에 의한 치유량이 최대 (%d+)만큼 증가합니다.", Types = { "Healing" } }, + { Format = "치유 주문 %+(%d+)", Types = { "Healing" } }, + { Format = "%+(%d+) 치유 주문", Types = { "Healing" } }, + + { Format = "%+(%d+) 화염 주문 공격력", Types = { "FireDmg" } }, + { Format = "%+(%d+) 암흑 주문 공격력", Types = { "ShadowDmg" } }, + { Format = "%+(%d+) 냉기 주문 공격력", Types = { "FrostDmg" } }, + { Format = "%+(%d+) 비전 주문 공격력", Types = { "ArcaneDmg" } }, + { Format = "%+(%d+) 자연 주문 공격력", Types = { "NatureDmg" } }, + + { Format = "화염 계열의 주문과 효과의 공격력이 최대 (%d+)만큼 증가합니다.", Types = { "FireDmg" } }, + { Format = "암흑 계열의 주문과 효과의 공격력이 최대 (%d+)만큼 증가합니다.", Types = { "ShadowDmg" } }, + { Format = "냉기 계열의 주문과 효과의 공격력이 최대 (%d+)만큼 증가합니다.", Types = { "FrostDmg" } }, + { Format = "비전 계열의 주문과 효과의 공격력이 최대 (%d+)만큼 증가합니다.", Types = { "ArcaneDmg" } }, + { Format = "자연 계열의 주문과 효과의 공격력이 최대 (%d+)만큼 증가합니다.", Types = { "NatureDmg" } }, + }; + + Outfitter_cAgilityStatName = "민첩"; + Outfitter_cArmorStatName = "방어도"; + Outfitter_cDefenseStatName = "방어 숙련도"; + Outfitter_cIntellectStatName = "지능"; + Outfitter_cSpiritStatName = "정신력"; + Outfitter_cStaminaStatName = "체력"; + Outfitter_cStrengthStatName = "힘"; + + Outfitter_cManaRegenStatName = "마나 회복"; + Outfitter_cHealthRegenStatName = "생명력 회복"; + + Outfitter_cSpellCritStatName = "주문 치명타"; + Outfitter_cSpellHitStatName = "주문 적중률"; + Outfitter_cSpellDmgStatName = "주문 공격력"; + Outfitter_cFrostDmgStatName = "냉기 주문 공격력"; + Outfitter_cFireDmgStatName = "화염 주문 공격력"; + Outfitter_cArcaneDmgStatName = "비전 주문 공격력"; + Outfitter_cShadowDmgStatName = "암흑 주문 공격력"; + Outfitter_cNatureDmgStatName = "자연 주문 공격력"; + Outfitter_cHealingStatName = "치유량"; + + Outfitter_cMeleeCritStatName = "근접 치명타"; + Outfitter_cMeleeHitStatName = "근접 적중률"; + Outfitter_cMeleeDmgStatName = "근접 공격력"; + Outfitter_cAttackStatName = "전투력"; + Outfitter_cDodgeStatName = "회피"; + + Outfitter_cArcaneResistStatName = "비전 저항력"; + Outfitter_cFireResistStatName = "화염 저항력"; + Outfitter_cFrostResistStatName = "냉기 저항력"; + Outfitter_cNatureResistStatName = "자연 저항력"; + Outfitter_cShadowResistStatName = "암흑 저항력"; + + Outfitter_cFishingStatName = "낚시"; + Outfitter_cHerbalismStatName = "약초 채집"; + Outfitter_cMiningStatName = "채광"; + Outfitter_cSkinningStatName = "무두질"; + + Outfitter_cOptionsTitle = "Outfitter Options"; + Outfitter_cShowMinimapButton = "미니맵 버튼 표시"; + Outfitter_cShowMinimapButtonOnDescription = "미니맵 버튼을 사용하지 않으려면 끕니다."; + Outfitter_cShowMinimapButtonOffDescription = "미니맵 버튼을 사용하려면 켭니다."; + Outfitter_cRememberVisibility = "망토와 투구 설정 기억"; + Outfitter_cRememberVisibilityOnDescription = "모든 망토와 투구에 대해서 동일한 설정을 하려면 끕니다."; + Outfitter_cRememberVisibilityOffDescription = "각각의 망토와 투구에 대한 설정을 기억하려면 켭니다."; + Outfitter_cShowHotkeyMessages = "단축키로 변경할때 보여주기"; + Outfitter_cShowHotkeyMessagesOnDescription = "단축키로 세트를 변경할때 메시지를 보지 않으려면 끕니다."; + Outfitter_cShowHotkeyMessagesOffDescription = "단축키로 세트를 변경할때 메시지를 보려면 켭니다."; + + Outfitter_cEquipOutfitMessageFormat = "Outfitter: %s 장비됨"; + Outfitter_cUnequipOutfitMessageFormat = "Outfitter: %s 해제됨"; + + Outfitter_cAboutTitle = "Outfitter 정보"; + Outfitter_cAuthor = "Designed and written by John Stephen"; + Outfitter_cTestersTitle = "Beta Testers"; + Outfitter_cTestersNames = "Airmid, Desiree, Fizzlebang, Harper, Kallah and Sumitra"; + Outfitter_cSpecialThanksTitle = "Special thanks for their support to"; + Outfitter_cSpecialThanksNames = "Brian, Dave, Glenn, Leah, Mark, The Mighty Pol, SFC and Forge"; + Outfitter_cGuildURL = "http://www.starfleetclan.com"; + Outfitter_cGuildURL2 = "http://www.forgeguild.com"; + + Outfitter_cOpenOutfitter = "Outfitter 열기"; + + Outfitter_cArgentDawnOutfitDescription = "이 세트는 역병지대에 있을 때 자동으로 착용 됩니다."; + Outfitter_cRidingOutfitDescription = "이 세트는 탈것을 탈 때 자동으로 착용 됩니다."; + Outfitter_cDiningOutfitDescription = "이 세트는 음식을 먹거나 음료를 마실 때 자동으로 착용 됩니다."; + Outfitter_cBattlegroundOutfitDescription = "이 세트는 전장에 있을 때 자동으로 착용 됩니다."; + Outfitter_cArathiBasinOutfitDescription = "이 세트는 아라시 분지에 있을 때 자동으로 착용 됩니다."; + Outfitter_cAlteracValleyOutfitDescription = "이 세트는 알터랙 계곡에 있을 때 자동으로 착용 됩니다."; + Outfitter_cWarsongGulchOutfitDescription = "이 세트는 전쟁노래 협곡에 있을 때 자동으로 착용 됩니다."; + Outfitter_cCityOutfitDescription = "이 세트는 우호적인 대도시에 있을 때 자동으로 착용 됩니다."; + Outfitter_cBossOutfitDescription = "This outfit will automatically be worn whenever you target level 63+ Boss mobs"; + Outfitter_cLvl63OutfitDescription = "This outfit will automatically be worn whenever you target level 63+ mobs"; + Outfitter_cTrashOutfitDescription = "This outfit will automatically be worn whenever you target level <63 mobs"; + Outfitter_cBeastTrashOutfitDescription = "This outfit will automatically be worn whenever you target beasts level <63 mobs"; + Outfitter_cUndeadTrashOutfitDescription = "This outfit will automatically be worn whenever you target undead level <63 mobs"; + Outfitter_cDemonTrashOutfitDescription = "This outfit will automatically be worn whenever you target demons level <63 mobs"; + + Outfitter_cKeyBinding = "단축키"; + + BINDING_HEADER_OUTFITTER_TITLE = Outfitter_cTitle; + + BINDING_NAME_OUTFITTER_OUTFIT1 = "세트 1"; + BINDING_NAME_OUTFITTER_OUTFIT2 = "세트 2"; + BINDING_NAME_OUTFITTER_OUTFIT3 = "세트 3"; + BINDING_NAME_OUTFITTER_OUTFIT4 = "세트 4"; + BINDING_NAME_OUTFITTER_OUTFIT5 = "세트 5"; + BINDING_NAME_OUTFITTER_OUTFIT6 = "세트 6"; + BINDING_NAME_OUTFITTER_OUTFIT7 = "세트 7"; + BINDING_NAME_OUTFITTER_OUTFIT8 = "세트 8"; + BINDING_NAME_OUTFITTER_OUTFIT9 = "세트 9"; + BINDING_NAME_OUTFITTER_OUTFIT10 = "세트 10"; + + Outfitter_cDisableOutfit = "세트 사용 안함"; + Outfitter_cDisableOutfitInBG = "전장에서 사용 안함"; + Outfitter_cDisabledOutfitName = "%s (사용 안함)"; + + Outfitter_cMinimapButtonTitle = "미니맵 버튼"; + Outfitter_cMinimapButtonDescription = "클릭 : 세트 선택, 드래그 : 미니맵 버튼 이동"; + + Outfitter_cDruidClassName = "드루이드"; + Outfitter_cHunterClassName = "사냥꾼"; + Outfitter_cMageClassName = "마법사"; + Outfitter_cPaladinClassName = "성기사"; + Outfitter_cPriestClassName = "사제"; + Outfitter_cRogueClassName = "도적"; + Outfitter_cShamanClassName = "주술사"; + Outfitter_cWarlockClassName = "흑마법사"; + Outfitter_cWarriorClassName = "전자"; + + Outfitter_cBattleStance = "전투 태세"; + Outfitter_cDefensiveStance = "방어 태세"; + Outfitter_cBerserkerStance = "광폭 태세"; + + Outfitter_cWarriorBattleStance = "전사: 전투 태세"; + Outfitter_cWarriorDefensiveStance = "전사: 방어 태세"; + Outfitter_cWarriorBerserkerStance = "전사: 광폭 태세"; + + Outfitter_cBearForm = "곰 변신"; + Outfitter_cCatForm = "표범 변신표범 변신"; + Outfitter_cAquaticForm = "바다표범 변신"; + Outfitter_cTravelForm = "치타 변신"; + Outfitter_cDireBearForm = "광포한 곰 변신"; + Outfitter_cMoonkinForm = "달빛야수 변신"; + + Outfitter_cGhostWolfForm = "늑대 정령"; + + Outfitter_cStealth = "은신"; + + Outfitter_cDruidBearForm = "드루이드: 곰 변신"; + Outfitter_cDruidCatForm = "드루이드: 표범 변신"; + Outfitter_cDruidAquaticForm = "드루이드: 바다표범 변신"; + Outfitter_cDruidTravelForm = "드루이드: 치타 변신"; + Outfitter_cDruidMoonkinForm = "드루이드: 달빛야수 변신"; + + Outfitter_cPriestShadowform = "사제: 어둠의 형상"; + + Outfitter_cRogueStealth = "도적: 은신"; + + Outfitter_cShamanGhostWolf = "주술사: 늑대 정령"; + + Outfitter_cHunterMonkey = "사냥꾼: 원숭이의 상"; + Outfitter_cHunterHawk = "사냥꾼: 매의 상"; + Outfitter_cHunterCheetah = "사냥꾼: 치타의 상"; + Outfitter_cHunterPack = "사냥꾼: 치타 무리의 상"; + Outfitter_cHunterBeast = "사냥꾼: 야수의 상"; + Outfitter_cHunterWild = "사냥꾼: 야생의 상"; + + Outfitter_cMageEvocate = "마법사: 환기"; + + Outfitter_cAspectOfTheCheetah = "치타의 상"; + Outfitter_cAspectOfThePack = "치타 무리의 상"; + Outfitter_cAspectOfTheBeast = "야수의 상"; + Outfitter_cAspectOfTheWild = "야생의 상"; + Outfitter_cEvocate = "환기"; + + Outfitter_cCompleteCategoryDescripton = "모든 슬롯의 아이템에 관한 설정이 들어있는 완전한 장비 세트입니다."; + Outfitter_cPartialCategoryDescription = "Mix-n-match의 장비 세트는 전부가 아닌 일부 슬롯만 가집니다. 장비 세트가 선택되면 이전에 선택되었던 보조 장비 세트 또는 Mix-n-match 세트를 대체하면서, 완비 세트에서 해당 아이템만을 교체합니다."; + Outfitter_cAccessoryCategoryDescription = "보조 장비의 장비 세트는 전부가 아닌 일부 슬롯만 가집니다. Mix-n-match와는 다르게 이전에 선택되었던 보조 장비 세트 또는 Mix-n-match 세트를 대체하지 않고 추가로 완비 세트에서 해당 아이템을 교체합니다."; + Outfitter_cSpecialCategoryDescription = "특수 조건의 장비 세트는 해당하는 조건을 충족시킬 경우 자동으로 착용됩니다."; + Outfitter_cOddsNEndsCategoryDescription = "나머지 장비들의 아이템들은 장비 세트에 한번도 사용되지 않은 아이템들입니다."; + + Outfitter_cRebuildOutfitFormat = "%s 재구성"; + + Outfitter_cTranslationCredit = " "; + + Outfitter_cSlotEnableTitle = "슬롯 활성화"; + Outfitter_cSlotEnableDescription = "슬롯을 활성화 하면 해당 장비 세트를 사용할때 같이 변경됩니다."; + + Outfitter_cFinger0SlotName = "첫번째 손가락"; + Outfitter_cFinger1SlotName = "두번째 손가락"; + + Outfitter_cTrinket0SlotName = "첫번째 장신구"; + Outfitter_cTrinket1SlotName = "두번째 장신구"; + + Outfitter_cOutfitCategoryTitle = "카테고리"; + Outfitter_cBankCategoryTitle = "은행"; + Outfitter_cDepositToBank = "모든 아이템을 은행으로"; + Outfitter_cDepositUniqueToBank = "특정 아이템을 은행으로"; + Outfitter_cWithdrawFromBank = "은행으로부터 아이템 회수"; + + Outfitter_cMissingItemsLabel = "찾을 수 없는 아이템: "; + Outfitter_cBankedItemsLabel = "은행에 있는 아이템: "; + + Outfitter_cRepairAllBags = "Outfitter: 모든 아이템 수리"; + + Outfitter_cStatsCategory = "능력치"; + Outfitter_cMeleeCategory = "근접"; + Outfitter_cSpellsCategory = "주문과 치유"; + Outfitter_cRegenCategory = "회복"; + Outfitter_cResistCategory = "저항"; + Outfitter_cTradeCategory = "전문기술"; + + Outfitter_cTankPoints = "탱크 포인트"; + Outfitter_cCustom = "사용자 설정"; +end diff --git a/OutfitterStrings_tw.lua b/OutfitterStrings_tw.lua new file mode 100644 index 0000000..e69de29 diff --git a/Textures/ButtonBarBackground.blp b/Textures/ButtonBarBackground.blp new file mode 100644 index 0000000..b83f919 Binary files /dev/null and b/Textures/ButtonBarBackground.blp differ diff --git a/Textures/CheckboxUnknown.blp b/Textures/CheckboxUnknown.blp new file mode 100644 index 0000000..b1efc0b Binary files /dev/null and b/Textures/CheckboxUnknown.blp differ diff --git a/Textures/DialogBox-Background.blp b/Textures/DialogBox-Background.blp new file mode 100644 index 0000000..d131b30 Binary files /dev/null and b/Textures/DialogBox-Background.blp differ diff --git a/Textures/GuildLogo.blp b/Textures/GuildLogo.blp new file mode 100644 index 0000000..649d24a Binary files /dev/null and b/Textures/GuildLogo.blp differ diff --git a/Textures/GuildLogo2.blp b/Textures/GuildLogo2.blp new file mode 100644 index 0000000..8671014 Binary files /dev/null and b/Textures/GuildLogo2.blp differ diff --git a/Textures/Icon.blp b/Textures/Icon.blp new file mode 100644 index 0000000..85591c8 Binary files /dev/null and b/Textures/Icon.blp differ diff --git a/Textures/MinimapButton.blp b/Textures/MinimapButton.blp new file mode 100644 index 0000000..442693c Binary files /dev/null and b/Textures/MinimapButton.blp differ diff --git a/Textures/Outfitter-Button-pfUI.blp b/Textures/Outfitter-Button-pfUI.blp new file mode 100644 index 0000000..b937e63 Binary files /dev/null and b/Textures/Outfitter-Button-pfUI.blp differ diff --git a/Textures/Outfitter-Button.blp b/Textures/Outfitter-Button.blp new file mode 100644 index 0000000..d4453cc Binary files /dev/null and b/Textures/Outfitter-Button.blp differ diff --git a/Textures/OutfitterFrame-Bottom.blp b/Textures/OutfitterFrame-Bottom.blp new file mode 100644 index 0000000..62715ab Binary files /dev/null and b/Textures/OutfitterFrame-Bottom.blp differ diff --git a/Textures/OutfitterFrame-Top.blp b/Textures/OutfitterFrame-Top.blp new file mode 100644 index 0000000..8319329 Binary files /dev/null and b/Textures/OutfitterFrame-Top.blp differ diff --git a/Textures/QuickSlotsBackground.blp b/Textures/QuickSlotsBackground.blp new file mode 100644 index 0000000..31e0152 Binary files /dev/null and b/Textures/QuickSlotsBackground.blp differ diff --git a/Textures/RepairBags.blp b/Textures/RepairBags.blp new file mode 100644 index 0000000..f3a7607 Binary files /dev/null and b/Textures/RepairBags.blp differ diff --git a/Textures/ScrollbarTrench.blp b/Textures/ScrollbarTrench.blp new file mode 100644 index 0000000..9eaeedd Binary files /dev/null and b/Textures/ScrollbarTrench.blp differ diff --git a/UI.xsd b/UI.xsd new file mode 100644 index 0000000..30b730c --- /dev/null +++ b/UI.xsd @@ -0,0 +1,663 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +